Halaman ini diterjemahkan oleh Cloud Translation API.
Switch to English

Maju otomatis Diferensiasi

Lihat di TensorFlow.org Jalankan di Google CoLab Lihat sumber di GitHub notebook Download

The panduan diferensiasi otomatis meliputi segala sesuatu yang diperlukan untuk gradien menghitung. Panduan ini berfokus pada lebih dalam, fitur kurang umum dari tf.GradientTape api.

Mempersiapkan

 import tensorflow as tf

import matplotlib as mpl
import matplotlib.pyplot as plt

mpl.rcParams['figure.figsize'] = (8, 6)
 

Mengontrol rekaman gradien

Dalam panduan diferensiasi otomatis Anda melihat bagaimana untuk mengontrol variabel dan tensor ditonton oleh rekaman itu sambil membangun perhitungan gradien.

Rekaman itu juga memiliki metode untuk memanipulasi rekaman.

Jika Anda ingin berhenti merekam gradien, Anda dapat menggunakan GradientTape.stop_recording() untuk menghentikan sementara perekaman.

Ini mungkin berguna untuk mengurangi overhead jika Anda tidak ingin membedakan operasi yang rumit di tengah-tengah model Anda. Hal ini dapat mencakup menghitung metrik atau hasil tengah:

 x = tf.Variable(2.0)
y = tf.Variable(3.0)

with tf.GradientTape() as t:
  x_sq = x * x
  with t.stop_recording():
    y_sq = y * y
  z = x_sq + y_sq

grad = t.gradient(z, {'x': x, 'y': y})

print('dz/dx:', grad['x'])  # 2*x => 4
print('dz/dy:', grad['y'])
 
dz/dx: tf.Tensor(4.0, shape=(), dtype=float32)
dz/dy: None

Jika Anda ingin memulai sepenuhnya, menggunakan reset() . Cukup keluar blok pita gradien dan restart biasanya lebih mudah dibaca, tetapi Anda dapat menggunakan reset ketika keluar blok pita sulit atau tidak mungkin.

 x = tf.Variable(2.0)
y = tf.Variable(3.0)
reset = True

with tf.GradientTape() as t:
  y_sq = y * y
  if reset:
    # Throw out all the tape recorded so far
    t.reset()
  z = x * x + y_sq

grad = t.gradient(z, {'x': x, 'y': y})

print('dz/dx:', grad['x'])  # 2*x => 4
print('dz/dy:', grad['y'])
 
dz/dx: tf.Tensor(4.0, shape=(), dtype=float32)
dz/dy: None

berhenti gradien

Berbeda dengan kontrol pita global di atas, yang tf.stop_gradient fungsi jauh lebih tepat. Hal ini dapat digunakan untuk menghentikan gradien mengalir sepanjang jalur tertentu, tanpa perlu akses ke tape itu sendiri:

 x = tf.Variable(2.0)
y = tf.Variable(3.0)

with tf.GradientTape() as t:
  y_sq = y**2
  z = x**2 + tf.stop_gradient(y_sq)

grad = t.gradient(z, {'x': x, 'y': y})

print('dz/dx:', grad['x'])  # 2*x => 4
print('dz/dy:', grad['y'])
 
dz/dx: tf.Tensor(4.0, shape=(), dtype=float32)
dz/dy: None

gradien kustom

Dalam beberapa kasus, Anda mungkin ingin mengontrol persis bagaimana gradien dihitung daripada menggunakan default. Situasi ini termasuk:

  • Tidak ada didefinisikan gradien untuk op baru Anda menulis.
  • Perhitungan default numerik tidak stabil.
  • Anda ingin men-cache suatu perhitungan mahal dari depan lulus.
  • Anda ingin memodifikasi nilai (misalnya menggunakan: tf.clip_by_value , tf.math.round ) tanpa memodifikasi gradien.

Untuk menulis op baru, Anda dapat menggunakan tf.RegisterGradient untuk mengatur Anda sendiri. Melihat bahwa halaman untuk detail. (Perhatikan bahwa registri gradien adalah global, sehingga mengubahnya dengan hati-hati.)

Selama tiga yang terakhir kasus, Anda dapat menggunakan tf.custom_gradient .

Berikut adalah contoh yang berlaku tf.clip_by_norm untuk gradien menengah.

 # Establish an identity operation, but clip during the gradient pass
@tf.custom_gradient
def clip_gradients(y):
  def backward(dy):
    return tf.clip_by_norm(dy, 0.5)
  return y, backward

v = tf.Variable(2.0)
with tf.GradientTape() as t:
  output = clip_gradients(v * v)
print(t.gradient(output, v))  # calls "backward", which clips 4 to 2

 
tf.Tensor(2.0, shape=(), dtype=float32)

Lihat tf.custom_gradient dekorator untuk lebih jelasnya.

beberapa kaset

Beberapa kaset berinteraksi mulus. Sebagai contoh, di sini masing-masing pita jam tangan yang berbeda dari tensor:

 x0 = tf.constant(0.0)
x1 = tf.constant(0.0)

with tf.GradientTape() as tape0, tf.GradientTape() as tape1:
  tape0.watch(x0)
  tape1.watch(x1)

  y0 = tf.math.sin(x0)
  y1 = tf.nn.sigmoid(x1)

  y = y0 + y1

  ys = tf.reduce_sum(y)
 
 tape0.gradient(ys, x0).numpy()   # cos(x) => 1.0
 
1.0
 tape1.gradient(ys, x1).numpy()   # sigmoid(x1)*(1-sigmoid(x1)) => 0.25
 
0.25

Tingkat tinggi gradien

Operasi dalam dari GradientTape manajer konteks dicatat untuk diferensiasi otomatis. Jika gradien dihitung dalam konteks itu, maka perhitungan gradien dicatat juga. Akibatnya, API yang sama persis bekerja untuk tingkat tinggi gradien juga. Sebagai contoh:

 x = tf.Variable(1.0)  # Create a Tensorflow variable initialized to 1.0

with tf.GradientTape() as t2:
  with tf.GradientTape() as t1:
    y = x * x * x

  # Compute the gradient inside the outer `t2` context manager
  # which means the gradient computation is differentiable as well.
  dy_dx = t1.gradient(y, x)
d2y_dx2 = t2.gradient(dy_dx, x)

print('dy_dx:', dy_dx.numpy())  # 3 * x**2 => 3.0
print('d2y_dx2:', d2y_dx2.numpy())  # 6 * x => 6.0
 
dy_dx: 3.0
d2y_dx2: 6.0

Sementara yang tidak memberikan turunan kedua dari fungsi skalar, pola ini tidak menggeneralisasi untuk menghasilkan matriks Hessian, karena GradientTape.gradient hanya menghitung gradien skalar. Untuk membangun sebuah Hessian, lihat contoh Hessian di bawah bagian Jacobian .

"Digabung panggilan ke GradientTape.gradient " adalah pola yang baik ketika Anda menghitung skalar dari gradien, dan kemudian skalar yang dihasilkan bertindak sebagai sumber untuk perhitungan gradien kedua, seperti dalam contoh berikut.

Contoh: Masukan gradien regularisasi

Banyak model yang rentan terhadap "contoh permusuhan". Koleksi ini teknik memodifikasi masukan model membingungkan output model. The pelaksanaan sederhana mengambil satu langkah di sepanjang gradien dari output terhadap input; "masukan gradien".

Salah satu teknik untuk peningkatan ketahanan untuk contoh permusuhan adalah masukan gradien regularisasi , yang upaya untuk meminimalkan besarnya gradien masukan. Jika gradien input kecil, maka perubahan output harus kecil juga.

Di bawah ini adalah implementasi naif masukan gradien regularisasi. Implementasi adalah:

  1. Hitung gradien dari output terhadap input menggunakan pita batin.
  2. Menghitung besarnya bahwa masukan gradien.
  3. Hitung gradien besarnya yang berkenaan dengan model.
 x = tf.random.normal([7, 5])

layer = tf.keras.layers.Dense(10, activation=tf.nn.relu)
 
 with tf.GradientTape() as t2:
  # The inner tape only takes the gradient with respect to the input,
  # not the variables.
  with tf.GradientTape(watch_accessed_variables=False) as t1:
    t1.watch(x)
    y = layer(x)
    out = tf.reduce_sum(layer(x)**2)
  # 1. Calculate the input gradient.
  g1 = t1.gradient(out, x)
  # 2. Calculate the magnitude of the input gradient.
  g1_mag = tf.norm(g1)

# 3. Calculate the gradient of the magnitude with respect to the model.
dg1_mag = t2.gradient(g1_mag, layer.trainable_variables)
 
 [var.shape for var in dg1_mag]
 
[TensorShape([5, 10]), TensorShape([10])]

Jacobian

Semua contoh sebelumnya mengambil gradien dari target skalar sehubungan dengan beberapa sumber tensor (s).

The matriks Jacobian merupakan gradien dari fungsi vektor bernilai. Setiap baris berisi gradien salah satu elemen vektor.

The GradientTape.jacobian metode memungkinkan Anda untuk secara efisien menghitung matriks Jacobian.

Perhatikan bahwa:

  • Seperti gradient : The sources Argumen bisa menjadi tensor atau wadah tensor.
  • Tidak seperti gradient : The target tensor harus tensor tunggal.

sumber skalar

Sebagai contoh pertama, di sini adalah Jacobian dari vektor-sasaran sehubungan dengan skalar-sumber.

 x = tf.linspace(-10.0, 10.0, 200+1)
delta = tf.Variable(0.0)

with tf.GradientTape() as tape:
  y = tf.nn.sigmoid(x+delta)

dy_dx = tape.jacobian(y, delta)
 

Ketika Anda mengambil Jacobian sehubungan dengan skalar hasilnya memiliki bentuk target, dan memberikan gradien dari setiap elemen sehubungan dengan sumber:

 print(y.shape)
print(dy_dx.shape)
 
(201,)
(201,)

 plt.plot(x.numpy(), y, label='y')
plt.plot(x.numpy(), dy_dx, label='dy/dx')
plt.legend()
_ = plt.xlabel('x')
 

png

sumber tensor

Apakah input adalah skalar atau tensor, GradientTape.jacobian efisien menghitung gradien setiap elemen dari sumber sehubungan dengan setiap elemen dari target (s).

Sebagai contoh, output dari lapisan ini memiliki bentuk (10, 7) :

 x = tf.random.normal([7, 5])
layer = tf.keras.layers.Dense(10, activation=tf.nn.relu)

with tf.GradientTape(persistent=True) as tape:
  y = layer(x)

y.shape
 
TensorShape([7, 10])

Dan bentuk layer kernel adalah (5, 10) :

 layer.kernel.shape
 
TensorShape([5, 10])

Bentuk Jacobian output sehubungan dengan kernel adalah dua bentuk concatenated bersama-sama:

 j = tape.jacobian(y, layer.kernel)
j.shape
 
TensorShape([7, 10, 5, 10])

Jika Anda jumlah lebih dimensi target, kau pergi dengan gradien dari jumlah yang seharusnya dihitung oleh GradientTape.gradient :

 g = tape.gradient(y, layer.kernel)
print('g.shape:', g.shape)

j_sum = tf.reduce_sum(j, axis=[0, 1])
delta = tf.reduce_max(abs(g - j_sum)).numpy()
assert delta < 1e-3
print('delta:', delta)
 
g.shape: (5, 10)
delta: 4.7683716e-07

Contoh: Hessian

Sementara tf.GradientTape tidak memberikan metode eksplisit untuk membangun sebuah matriks Hessian itu mungkin untuk membangun satu menggunakan GradientTape.jacobian metode.

 x = tf.random.normal([7, 5])
layer1 = tf.keras.layers.Dense(8, activation=tf.nn.relu)
layer2 = tf.keras.layers.Dense(6, activation=tf.nn.relu)

with tf.GradientTape() as t2:
  with tf.GradientTape() as t1:
    x = layer1(x)
    x = layer2(x)
    loss = tf.reduce_mean(x**2)

  g = t1.gradient(loss, layer1.kernel)

h = t2.jacobian(g, layer1.kernel)
 
 print(f'layer.kernel.shape: {layer1.kernel.shape}')
print(f'h.shape: {h.shape}')
 
layer.kernel.shape: (5, 8)
h.shape: (5, 8, 5, 8)

Untuk menggunakan Goni ini untuk metode Newton langkah, Anda akan datar sumbu ke dalam matriks, dan meratakan keluar gradien menjadi vektor:

 n_params = tf.reduce_prod(layer1.kernel.shape)

g_vec = tf.reshape(g, [n_params, 1])
h_mat = tf.reshape(h, [n_params, n_params])
 

Matriks Hessian harus simetris:

 def imshow_zero_center(image, **kwargs):
  lim = tf.reduce_max(abs(image))
  plt.imshow(image, vmin=-lim, vmax=lim, cmap='seismic', **kwargs)
  plt.colorbar()
 
 imshow_zero_center(h_mat)
 

png

The Newton metode update langkah ditunjukkan di bawah ini.

 eps = 1e-3
eye_eps = tf.eye(h_mat.shape[0])*eps
 
 # X(k+1) = X(k) - (∇²f(X(k)))^-1 @ ∇f(X(k))
# h_mat = ∇²f(X(k))
# g_vec = ∇f(X(k))
update = tf.linalg.solve(h_mat + eye_eps, g_vec)

# Reshape the update and apply it to the variable.
_ = layer1.kernel.assign_sub(tf.reshape(update, layer1.kernel.shape))
 

Sementara ini relatif sederhana untuk satu tf.Variable , menerapkan ini untuk model non-sepele akan membutuhkan Rangkaian hati-hati dan mengiris untuk menghasilkan Hessian penuh di beberapa variabel.

Batch Jacobian

Dalam beberapa kasus, Anda ingin mengambil Jacobian dari masing-masing tumpukan target sehubungan dengan setumpuk sumber, di mana Jacobian untuk setiap pasangan sasaran-sumber independen.

Sebagai contoh, di sini input x berbentuk (batch, ins) dan output y berbentuk (batch, outs) .

 x = tf.random.normal([7, 5])

layer1 = tf.keras.layers.Dense(8, activation=tf.nn.elu)
layer2 = tf.keras.layers.Dense(6, activation=tf.nn.elu)

with tf.GradientTape(persistent=True, watch_accessed_variables=False) as tape:
  tape.watch(x)
  y = layer1(x)
  y = layer2(y)

y.shape
 
TensorShape([7, 6])

Jacobian penuh y terhadap x memiliki bentuk (batch, ins, batch, outs) , bahkan jika Anda hanya ingin (batch, ins, outs) .

 j = tape.jacobian(y, x)
j.shape
 
TensorShape([7, 6, 7, 5])

Jika gradien dari setiap item dalam tumpukan independen, maka setiap (batch, batch) sepotong tensor ini adalah matriks diagonal:

 imshow_zero_center(j[:, 0, :, 0])
_ = plt.title('A (batch, batch) slice')
 

png

 def plot_as_patches(j):
  # Reorder axes so the diagonals will each form a contiguous patch.
  j = tf.transpose(j, [1, 0, 3, 2])
  # Pad in between each patch.
  lim = tf.reduce_max(abs(j))
  j = tf.pad(j, [[0, 0], [1, 1], [0, 0], [1, 1]],
             constant_values=-lim)
  # Reshape to form a single image.
  s = j.shape
  j = tf.reshape(j, [s[0]*s[1], s[2]*s[3]])
  imshow_zero_center(j, extent=[-0.5, s[2]-0.5, s[0]-0.5, -0.5])

plot_as_patches(j)
_ = plt.title('All (batch, batch) slices are diagonal')
 

png

Untuk mendapatkan hasil yang diinginkan Anda dapat jumlah lebih duplikat batch dimensi, atau pilih diagonal menggunakan tf.einsum .

 j_sum = tf.reduce_sum(j, axis=2)
print(j_sum.shape)
j_select = tf.einsum('bxby->bxy', j)
print(j_select.shape)
 
(7, 6, 5)
(7, 6, 5)

Akan jauh lebih efisien untuk melakukan perhitungan tanpa dimensi ekstra di tempat pertama. The GradientTape.batch_jacobian metode tidak tepat.

 jb = tape.batch_jacobian(y, x)
jb.shape
 
TensorShape([7, 6, 5])
 error = tf.reduce_max(abs(jb - j_sum))
assert error < 1e-3
print(error.numpy())
 
0.0

 x = tf.random.normal([7, 5])

layer1 = tf.keras.layers.Dense(8, activation=tf.nn.elu)
bn = tf.keras.layers.BatchNormalization()
layer2 = tf.keras.layers.Dense(6, activation=tf.nn.elu)

with tf.GradientTape(persistent=True, watch_accessed_variables=False) as tape:
  tape.watch(x)
  y = layer1(x)
  y = bn(y, training=True)
  y = layer2(y)

j = tape.jacobian(y, x)
print(f'j.shape: {j.shape}')
 
j.shape: (7, 6, 7, 5)

 plot_as_patches(j)

_ = plt.title('These slices are not diagonal')
_ = plt.xlabel("Don't use `batch_jacobian`")
 

png

Dalam hal ini batch_jacobian masih berjalan dan kembali sesuatu dengan bentuk yang diharapkan, tapi isinya itu memiliki makna yang tidak jelas.

 jb = tape.batch_jacobian(y, x)
print(f'jb.shape: {jb.shape}')
 
jb.shape: (7, 6, 5)