RSVP untuk acara TensorFlow Everywhere lokal Anda hari ini!
Halaman ini diterjemahkan oleh Cloud Translation API.
Switch to English

Pengantar gradien dan diferensiasi otomatis

Lihat di TensorFlow.org Jalankan di Google Colab Lihat sumber di GitHub Unduh buku catatan

Diferensiasi dan Gradien Otomatis

Diferensiasi otomatis berguna untuk mengimplementasikan algoritme pembelajaran mesin seperti propagasi mundur untuk melatih jaringan saraf.

Dalam panduan ini, Anda akan mempelajari cara menghitung gradien dengan TensorFlow, terutama dalam eksekusi cepat .

Mempersiapkan

import numpy as np
import matplotlib.pyplot as plt

import tensorflow as tf

Menghitung gradien

Untuk membedakan secara otomatis, TensorFlow perlu mengingat operasi yang terjadi dalam urutan apa selama forward pass. Kemudian, selama backward pass , TensorFlow melintasi daftar operasi ini dalam urutan terbalik untuk menghitung gradien.

Pita gradien

TensorFlow menyediakantf.GradientTape API untuk diferensiasi otomatis; yaitu, menghitung gradien komputasi sehubungan dengan beberapa input, biasanya tf.Variable s. TensorFlow "merekam" operasi relevan yang dijalankan dalam kontekstf.GradientTape ke "tape". TensorFlow kemudian menggunakan pita tersebut untuk menghitung gradien dari komputasi yang "direkam" menggunakan diferensiasi mode terbalik .

Berikut ini contoh sederhananya:

x = tf.Variable(3.0)

with tf.GradientTape() as tape:
  y = x**2

Setelah Anda merekam beberapa operasi, gunakan GradientTape.gradient(target, sources) untuk menghitung gradien beberapa target (seringkali kerugian) relatif terhadap beberapa sumber (sering kali variabel model):

# dy = 2x * dx
dy_dx = tape.gradient(y, x)
dy_dx.numpy()
6.0

Contoh di atas menggunakan skalar, tetapitf.GradientTape bekerja semudah itu pada tensor apa pun:

w = tf.Variable(tf.random.normal((3, 2)), name='w')
b = tf.Variable(tf.zeros(2, dtype=tf.float32), name='b')
x = [[1., 2., 3.]]

with tf.GradientTape(persistent=True) as tape:
  y = x @ w + b
  loss = tf.reduce_mean(y**2)

Untuk mendapatkan gradien y dengan kedua variabel, Anda dapat meneruskan keduanya sebagai sumber ke metode gradient . Rekaman itu fleksibel tentang bagaimana sumber diteruskan dan akan menerima kombinasi daftar atau kamus bersarang dan mengembalikan gradien yang terstruktur dengan cara yang sama (lihat tf.nest ).

[dl_dw, dl_db] = tape.gradient(loss, [w, b])

Gradien yang terkait dengan setiap sumber memiliki bentuk sumber:

print(w.shape)
print(dl_dw.shape)
(3, 2)
(3, 2)

Ini adalah kalkulasi gradien lagi, kali ini melalui kamus variabel:

my_vars = {
    'w': w,
    'b': b
}

grad = tape.gradient(loss, my_vars)
grad['b']
<tf.Tensor: shape=(2,), dtype=float32, numpy=array([-8.143469 ,  3.9130046], dtype=float32)>

Gradien sehubungan dengan model

Merupakan hal yang umum untuk mengumpulkan tf.Variables ke dalam tf.Module atau salah satu subkelasnya ( layers.Layer , keras.Model ) untuk checkpointing dan mengekspor .

Biasanya, Anda ingin menghitung gradien yang terkait dengan variabel model yang dapat dilatih. Karena semua subclass dari tf.Module menggabungkan variabelnya di properti Module.trainable_variables , Anda dapat menghitung gradien ini dalam beberapa baris kode:

layer = tf.keras.layers.Dense(2, activation='relu')
x = tf.constant([[1., 2., 3.]])

with tf.GradientTape() as tape:
  # Forward pass
  y = layer(x)
  loss = tf.reduce_mean(y**2)

# Calculate gradients with respect to every trainable variable
grad = tape.gradient(loss, layer.trainable_variables)
for var, g in zip(layer.trainable_variables, grad):
  print(f'{var.name}, shape: {g.shape}')
dense/kernel:0, shape: (3, 2)
dense/bias:0, shape: (2,)

Mengontrol apa yang ditonton kaset itu

Perilaku defaultnya adalah merekam semua operasi setelah mengakses tf.Variable dapat dilatih. Alasannya adalah:

  • Rekaman itu perlu mengetahui operasi mana yang akan direkam pada lintasan maju untuk menghitung gradien pada lintasan mundur.
  • Rekaman itu menyimpan referensi ke keluaran perantara, jadi Anda tidak ingin merekam operasi yang tidak perlu.
  • Kasus penggunaan yang paling umum melibatkan penghitungan gradien kerugian yang terkait dengan semua variabel yang dapat dilatih model.

Misalnya, berikut ini gagal menghitung gradien karena tf.Tensor tidak "diawasi" secara default, dan tf.Variable tidak bisa dilatih:

# A trainable variable
x0 = tf.Variable(3.0, name='x0')
# Not trainable
x1 = tf.Variable(3.0, name='x1', trainable=False)
# Not a Variable: A variable + tensor returns a tensor.
x2 = tf.Variable(2.0, name='x2') + 1.0
# Not a variable
x3 = tf.constant(3.0, name='x3')

with tf.GradientTape() as tape:
  y = (x0**2) + (x1**2) + (x2**2)

grad = tape.gradient(y, [x0, x1, x2, x3])

for g in grad:
  print(g)
tf.Tensor(6.0, shape=(), dtype=float32)
None
None
None

Anda dapat membuat daftar variabel yang sedang ditonton dengan menggunakan metode GradientTape.watched_variables :

[var.name for var in tape.watched_variables()]
['x0:0']

tf.GradientTape menyediakan hook yang memberi pengguna kendali atas apa yang ditonton atau tidak.

Untuk merekam gradien sehubungan dengan tf.Tensor , Anda perlu memanggil GradientTape.watch(x) :

x = tf.constant(3.0)
with tf.GradientTape() as tape:
  tape.watch(x)
  y = x**2

# dy = 2x * dx
dy_dx = tape.gradient(y, x)
print(dy_dx.numpy())
6.0

Sebaliknya, untuk menonaktifkan perilaku default dari melihat semua tf.Variables , setel watch_accessed_variables=False saat membuat pita gradien. Perhitungan ini menggunakan dua variabel, tetapi hanya menghubungkan gradien untuk salah satu variabel:

x0 = tf.Variable(0.0)
x1 = tf.Variable(10.0)

with tf.GradientTape(watch_accessed_variables=False) as tape:
  tape.watch(x1)
  y0 = tf.math.sin(x0)
  y1 = tf.nn.softplus(x1)
  y = y0 + y1
  ys = tf.reduce_sum(y)

Karena GradientTape.watch tidak dipanggil pada x0 , tidak ada gradien yang dihitung sehubungan dengan itu:

# dys/dx1 = exp(x1) / (1 + exp(x1)) = sigmoid(x1)
grad = tape.gradient(ys, {'x0': x0, 'x1': x1})

print('dy/dx0:', grad['x0'])
print('dy/dx1:', grad['x1'].numpy())
dy/dx0: None
dy/dx1: 0.9999546

Hasil menengah

Anda juga dapat meminta gradien keluaran sehubungan dengan nilai antara yang dihitung di dalam kontekstf.GradientTape .

x = tf.constant(3.0)

with tf.GradientTape() as tape:
  tape.watch(x)
  y = x * x
  z = y * y

# Use the tape to compute the gradient of z with respect to the
# intermediate value y.
# dz_dx = 2 * y, where y = x ** 2
print(tape.gradient(z, y).numpy())
18.0

Secara default, sumber daya yang dipegang oleh GradientTape dirilis segera setelah metode GradientTape.gradient dipanggil. Untuk menghitung beberapa gradien melalui komputasi yang sama, buat pita gradien dengan persistent=True . Hal ini memungkinkan beberapa panggilan ke metode gradient karena sumber daya dilepaskan ketika objek rekaman dikumpulkan sampah. Sebagai contoh:

x = tf.constant([1, 3.0])
with tf.GradientTape(persistent=True) as tape:
  tape.watch(x)
  y = x * x
  z = y * y

print(tape.gradient(z, x).numpy())  # 108.0 (4 * x**3 at x = 3)
print(tape.gradient(y, x).numpy())  # 6.0 (2 * x)
[  4. 108.]
[2. 6.]

del tape   # Drop the reference to the tape

Catatan tentang kinerja

  • Ada overhead kecil yang terkait dengan melakukan operasi di dalam konteks pita gradien. Untuk eksekusi yang paling bersemangat, ini bukan biaya yang terlihat, tetapi Anda masih harus menggunakan konteks pita di sekitar area hanya di tempat yang diperlukan.

  • Pita gradien menggunakan memori untuk menyimpan hasil antara, termasuk masukan dan keluaran, untuk digunakan selama lintasan mundur.

    Untuk efisiensi, beberapa operasi (seperti ReLU ) tidak perlu menyimpan hasil antara dan akan dipangkas selama forward pass. Namun, jika Anda menggunakan persistent=True pada rekaman Anda, tidak ada yang dibuang dan penggunaan memori puncak Anda akan lebih tinggi.

Gradien target non-skalar

Gradien pada dasarnya adalah operasi pada skalar.

x = tf.Variable(2.0)
with tf.GradientTape(persistent=True) as tape:
  y0 = x**2
  y1 = 1 / x

print(tape.gradient(y0, x).numpy())
print(tape.gradient(y1, x).numpy())
4.0
-0.25

Jadi, jika Anda meminta gradien beberapa target, hasil untuk setiap sumber adalah:

  • Gradien dari jumlah target, atau yang setara
  • Jumlah gradien setiap target.
x = tf.Variable(2.0)
with tf.GradientTape() as tape:
  y0 = x**2
  y1 = 1 / x

print(tape.gradient({'y0': y0, 'y1': y1}, x).numpy())
3.75

Demikian pula, jika target bukan skalar, gradien penjumlahan dihitung:

x = tf.Variable(2.)

with tf.GradientTape() as tape:
  y = x * [3., 4.]

print(tape.gradient(y, x).numpy())
7.0

Ini membuatnya mudah untuk mengambil gradien dari jumlah kumpulan kerugian, atau gradien dari jumlah perhitungan kerugian yang bijaksana.

Jika Anda membutuhkan gradien terpisah untuk setiap item, lihat Jacobian .

Dalam beberapa kasus, Anda dapat melewatkan Jacobian. Untuk kalkulasi berdasarkan elemen, gradien penjumlahan memberikan turunan dari setiap elemen sehubungan dengan elemen masukannya, karena setiap elemen tidak bergantung:

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

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

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

png

Aliran kontrol

Karena pita gradien merekam operasi saat dijalankan, aliran kontrol Python secara alami ditangani (misalnya, pernyataan if dan while ).

Di sini variabel berbeda digunakan pada setiap cabang if . Gradien hanya terhubung ke variabel yang digunakan:

x = tf.constant(1.0)

v0 = tf.Variable(2.0)
v1 = tf.Variable(2.0)

with tf.GradientTape(persistent=True) as tape:
  tape.watch(x)
  if x > 0.0:
    result = v0
  else:
    result = v1**2 

dv0, dv1 = tape.gradient(result, [v0, v1])

print(dv0)
print(dv1)
tf.Tensor(1.0, shape=(), dtype=float32)
None

Ingatlah bahwa pernyataan kontrol itu sendiri tidak dapat dibedakan, sehingga tidak terlihat oleh pengoptimal berbasis gradien.

Bergantung pada nilai x pada contoh di atas, rekaman itu merekam result = v0 atau result = v1**2 . Gradien terhadap x selalu None .

dx = tape.gradient(result, x)

print(dx)
None

Mendapatkan gradien None

Saat target tidak terhubung ke sumber, Anda akan mendapatkan gradien None .

x = tf.Variable(2.)
y = tf.Variable(3.)

with tf.GradientTape() as tape:
  z = y * y
print(tape.gradient(z, x))
None

Di sini z jelas tidak terhubung ke x , tetapi ada beberapa cara yang kurang jelas di mana gradien dapat diputuskan.

1. Mengganti variabel dengan tensor

Pada bagian "mengendalikan apa yang ditonton oleh pita itu", Anda melihat bahwa pita itu akan secara otomatis menonton tf.Variable tetapi bukan tf.Tensor .

Satu kesalahan umum adalah secara tidak sengaja mengganti tf.Variable dengan tf.Tensor , daripada menggunakan Variable.assign untuk memperbarui tf.Variable . Berikut ini contohnya:

x = tf.Variable(2.0)

for epoch in range(2):
  with tf.GradientTape() as tape:
    y = x+1

  print(type(x).__name__, ":", tape.gradient(y, x))
  x = x + 1   # This should be `x.assign_add(1)`
ResourceVariable : tf.Tensor(1.0, shape=(), dtype=float32)
EagerTensor : None

2. Melakukan penghitungan di luar TensorFlow

Rekaman tidak dapat merekam jalur gradien jika kalkulasi keluar dari TensorFlow. Sebagai contoh:

x = tf.Variable([[1.0, 2.0],
                 [3.0, 4.0]], dtype=tf.float32)

with tf.GradientTape() as tape:
  x2 = x**2

  # This step is calculated with NumPy
  y = np.mean(x2, axis=0)

  # Like most ops, reduce_mean will cast the NumPy array to a constant tensor
  # using `tf.convert_to_tensor`.
  y = tf.reduce_mean(y, axis=0)

print(tape.gradient(y, x))
None

3. Mengambil gradien melalui integer atau string

Integer dan string tidak dapat dibedakan. Jika jalur penghitungan menggunakan tipe data ini, tidak akan ada gradien.

Tidak ada yang mengharapkan string dapat terdiferensiasi, tetapi mudah untuk secara tidak sengaja membuat konstanta atau variabel int jika Anda tidak menentukan dtype .

# The x0 variable has an `int` dtype.
x = tf.Variable([[2, 2],
                 [2, 2]])

with tf.GradientTape() as tape:
  # The path to x1 is blocked by the `int` dtype here.
  y = tf.cast(x, tf.float32)
  y = tf.reduce_sum(x)

print(tape.gradient(y, x))
WARNING:tensorflow:The dtype of the target tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32
None

TensorFlow tidak otomatis mentransmisikan antar jenis, jadi, dalam praktiknya, Anda akan sering mendapatkan error jenis, bukan gradien yang hilang.

4. Mengambil gradien melalui objek stateful

Status menghentikan gradien. Ketika Anda membaca dari objek yang berstatus penuh, rekaman itu hanya dapat mengamati keadaan saat ini, bukan riwayat yang mengarah padanya.

tf.Tensor tidak dapat diubah. Anda tidak dapat mengubah tensor setelah dibuat. Itu memiliki nilai , tetapi tidak ada status . Semua operasi yang dibahas sejauh ini juga tidak memiliki kewarganegaraan: keluaran dari tf.matmul hanya bergantung pada masukannya.

Sebuah tf.Variable memiliki status internal — nilainya. Saat Anda menggunakan variabel, status dibaca. Merupakan hal yang normal untuk menghitung gradien sehubungan dengan variabel, tetapi status variabel memblokir penghitungan gradien agar tidak melangkah lebih jauh ke belakang. Sebagai contoh:

x0 = tf.Variable(3.0)
x1 = tf.Variable(0.0)

with tf.GradientTape() as tape:
  # Update x1 = x1 + x0.
  x1.assign_add(x0)
  # The tape starts recording from x1.
  y = x1**2   # y = (x1 + x0)**2

# This doesn't work.
print(tape.gradient(y, x0))   #dy/dx0 = 2*(x1 + x0)
None

Demikian pula, iteratortf.data.Dataset dan tf.queue s adalah stateful, dan akan menghentikan semua gradien pada tensor yang melewatinya.

Tidak ada gradien yang terdaftar

Beberapa tf.Operation didaftarkan sebagai non-diferensiable dan akan mengembalikan None . Lainnya tidak memiliki gradien terdaftar .

Halaman tf.raw_ops menunjukkan operasi tingkat rendah mana yang memiliki gradien yang terdaftar.

Jika Anda mencoba untuk mengambil gradien melalui operasi float yang tidak memiliki gradien terdaftar, rekaman itu akan menampilkan kesalahan alih-alih mengembalikan None diam-diam. Dengan cara ini Anda tahu ada yang tidak beres.

Misalnya, fungsi tf.image.adjust_contrast membungkus raw_ops.AdjustContrastv2 , yang bisa memiliki gradien tetapi gradien tidak diterapkan:

image = tf.Variable([[[0.5, 0.0, 0.0]]])
delta = tf.Variable(0.1)

with tf.GradientTape() as tape:
  new_image = tf.image.adjust_contrast(image, delta)

try:
  print(tape.gradient(new_image, [image, delta]))
  assert False   # This should not happen.
except LookupError as e:
  print(f'{type(e).__name__}: {e}')
LookupError: gradient registry has no entry for: AdjustContrastv2

Jika Anda perlu membedakan melalui operasi ini, Anda harus mengimplementasikan gradien dan mendaftarkannya (menggunakan tf.RegisterGradient ) atau mengimplementasikan ulang fungsi menggunakan operasi lain.

Nol, bukan Tidak Ada

Dalam beberapa kasus, akan lebih mudah untuk mendapatkan 0 daripada None untuk gradien yang tidak terhubung. Anda dapat memutuskan apa yang akan dikembalikan ketika Anda memiliki gradien yang tidak terhubung menggunakan argumen unconnected_gradients :

x = tf.Variable([2., 2.])
y = tf.Variable(3.)

with tf.GradientTape() as tape:
  z = y**2
print(tape.gradient(z, x, unconnected_gradients=tf.UnconnectedGradients.ZERO))
tf.Tensor([0. 0.], shape=(2,), dtype=float32)