ML Topluluk Günü 9 Kasım! TensorFlow, JAX güncellemeler için bize katılın ve daha fazla bilgi edinin

Gradyanlara ve otomatik farklılaşmaya giriş

TensorFlow.org'da görüntüleyin Google Colab'da çalıştırın Kaynağı GitHub'da görüntüleyin Not defterini indir

Otomatik Farklılaşma ve Gradyanlar

Otomatik farklılaşma gibi makineyi algoritmaları öğrenme uygulamak için yararlıdır geri yayılım sinir ağları eğitimi için.

Bu kılavuzda, özellikle de, TensorFlow geçişinde hesaplamak için yollar keşfedecek istekli yürütme .

Kurmak

import numpy as np
import matplotlib.pyplot as plt

import tensorflow as tf

gradyanları hesaplama

Otomatik ayırt etmek için, TensorFlow operasyonları ileriye geçiş sırasında hangi sırayla gerçekleşmesi hatırlamak gerekiyor. Daha sonra, geriye doğru geçiş sırasında, TensorFlow işlem gradientinden tersten operasyonların bu listeyi erişir.

Degrade bantlar

TensorFlow içerir tf.GradientTape otomatik farklılaşması için API; Bazı genellikle girdileri ile ilgili olarak bir hesaplama gradyanı işlem, bir tf.Variable s. TensorFlow "kayıtları" ilgili işlemler bir bağlamında iç infaz tf.GradientTape bir "kaset" üzerine. TensorFlow sonra kullanarak bir "kaydedildi" hesaplama gradyanlarını hesaplamak için kaseti kullanan ters modu farklılaşmasını .

İşte basit bir örnek:

x = tf.Variable(3.0)

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

Bazı işlemleri kaydedildi sonra, kullanmak GradientTape.gradient(target, sources) bazı kaynağına (genellikle modelin değişkenleri) göre bazı hedefin (genellikle bir zarar) eğimi hesaplamak için:

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

Yukarıdaki örnek skalarlar kullanır, ancak tf.GradientTape herhangi tensör üzerinde kolayca çalışır:

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)

Gradyeni elde etmek için loss her iki değişken ile ilgili olarak, için kaynakları olarak geçebilir gradient yöntemi. Bant kaynakları geçirilir hakkında esnektir ve listeleri veya sözlükleri herhangi bir iç içe kombinasyonu kabul etmek ve (bakınız aynı şekilde yapılandırılmış gradyanı döndürür tf.nest ).

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

Her kaynağa göre gradyan, kaynağın şekline sahiptir:

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

İşte yine gradyan hesaplaması, bu sefer bir değişkenler sözlüğünü geçerek:

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

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

Bir modele göre gradyanlar

Toplamak için ortak tf.Variables bir içine tf.Module ya da alt sınıflarından (biri layers.Layer , keras.Model için) checkpointing ve ihracat .

Çoğu durumda, bir modelin eğitilebilir değişkenlerine göre gradyanları hesaplamak isteyeceksiniz. Tüm alt sınıfları yana tf.Module kendi değişkenleri bir araya Module.trainable_variables özelliği, bir kod birkaç satır bu geçişlerini hesaplayabilirsiniz:

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,)

Kasetin ne izlediğini kontrol etme

Varsayılan davranış eğitilebilir bir eriştikten sonra tüm işlemleri kayıt altına alınmasıdır tf.Variable . Bunun nedenleri:

  • Teybin, geri geçişteki gradyanları hesaplamak için ileri geçişte hangi işlemlerin kaydedileceğini bilmesi gerekir.
  • Bant, ara çıkışlara referanslar içerir, böylece gereksiz işlemleri kaydetmek istemezsiniz.
  • En yaygın kullanım durumu, bir modelin tüm eğitilebilir değişkenlerine göre bir kaybın gradyanını hesaplamayı içerir.

Örneğin, aşağıdaki çünkü bir eğim hesaplamak için başarısız tf.Tensor "izlenen" varsayılan olarak ve edilmez tf.Variable eğitilebilir değildir:

# 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

Sen değişkenler kullanılarak bantla izlenen listeleyebilirsiniz GradientTape.watched_variables yöntemi:

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

tf.GradientTape veya izlemedim ne üzerinde kullanıcı kontrolü sağlamak kancaları sağlar.

Bir bakımından rekor gradyanlar için tf.Tensor , aramak gerekir 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

Tersine, tüm izlerken varsayılan davranışını devre dışı bırakmak için tf.Variables set watch_accessed_variables=False gradyan bandı oluştururken. Bu hesaplama iki değişken kullanır, ancak yalnızca değişkenlerden birinin gradyanı bağlar:

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)

Yana GradientTape.watch çağrıda değildi x0 , hiçbir gradyan buna göre hesaplanır:

# 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

ara sonuçlar

Ayrıca, içinde hesaplanmış ara değerlere göre çıkış gradyanlar isteyebilir tf.GradientTape bağlamında.

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_dy = 2 * y and y = x ** 2 = 9
print(tape.gradient(z, y).numpy())
18.0

Varsayılan olarak, bir elinde kaynaklar GradientTape en kısa sürede serbest bırakılır GradientTape.gradient yöntemi denir. Aynı hesaplama içinde birden fazla gradyanlar hesaplamak için, bir gradyan bant oluşturmak persistent=True . Bu birden fazla çağrı sağlar gradient bant nesne çöp toplama olduğunda kaynakları serbest olarak yöntem. Örneğin:

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())  # [4.0, 108.0] (4 * x**3 at x = [1.0, 3.0])
print(tape.gradient(y, x).numpy())  # [2.0, 6.0] (2 * x at x = [1.0, 3.0])
[  4. 108.]
[2. 6.]
del tape   # Drop the reference to the tape

performansla ilgili notlar

  • Bir degrade teyp bağlamında işlem yapmakla ilgili küçük bir ek yük vardır. Çoğu hevesli uygulama için bu fark edilebilir bir maliyet olmayacaktır, ancak yine de yalnızca gerekli olduğu alanlarda bant bağlamını kullanmalısınız.

  • Degrade bantlar, geri geçiş sırasında kullanılmak üzere girişler ve çıkışlar dahil olmak üzere ara sonuçları depolamak için belleği kullanır.

    Verim için (gibi bazı op ReLU ) kendi ara sonuçları tutmak gerek yoktur ve ileriye geçiş sırasında denetleniyor. Kullanmak Ancak, persistent=True senin kasette, hiçbir şey atılır ve zaman zirveye bellek kullanımı daha yüksek olacaktır.

Skaler olmayan hedeflerin gradyanları

Gradyan temelde bir skaler üzerinde bir işlemdir.

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

Bu nedenle, birden fazla hedefin derecesini sorarsanız, her bir kaynak için sonuç şöyle olur:

  • Hedeflerin toplamının gradyanı veya eşdeğeri
  • Her hedefin gradyanlarının toplamı.
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

Benzer şekilde, hedef(ler) skaler değilse toplamın gradyanı hesaplanır:

x = tf.Variable(2.)

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

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

Bu, kayıpların toplamının gradyanını veya eleman bazında kayıp hesaplamasının toplamının gradyanını almayı kolaylaştırır.

Her öğe için ayrı bir gradyan gerekiyorsa, bakın Jacobians .

Bazı durumlarda Jacobian'ı atlayabilirsiniz. Eleman bazında bir hesaplama için, toplamın gradyanı, her eleman bağımsız olduğundan, her elemanın girdi elemanına göre türevini verir:

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

Kontrol akışı

Bunlar yürütülür bir gradyan bant işlemleri kaydeden için, Python kontrol akış doğal (örneğin, işlenir if ve while tablolar).

İşte farklı bir değişken bir her dala kullanılır if . Degrade yalnızca kullanılan değişkene bağlanır:

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

Kontrol ifadelerinin kendilerinin türevlenebilir olmadığını unutmayın, bu nedenle degrade tabanlı optimize ediciler tarafından görünmezler.

Değerine bağlı olarak x , yukarıdaki örnekte, bant ya da kayıt sayısı result = v0 veya result = v1**2 . İle ilgili olarak degrade x her zaman None .

dx = tape.gradient(result, x)

print(dx)
None

Meyili alınıyor None

Bir hedefin kaynağına bağlı değilken sen bir gradyan alacak None .

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

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

Burada z açıkça bağlı değildir x ama gradyan bağlantısı edilebileceği çok daha az bariz yolu vardır.

1. Bir değişkeni bir tensörle değiştirme

Üzerinde bölümde "bant saatler neyi kontrol" Eğer bant otomatik olarak izleyecekler gördük tf.Variable ancak bir tf.Tensor .

Bir genel hata yanlışlıkla değiştirmektir tf.Variable bir ile tf.Tensor kullanmak yerine, Variable.assign güncellemek için tf.Variable . İşte bir örnek:

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. TensorFlow dışında hesaplamalar yaptı

Hesaplama TensorFlow'dan çıkarsa bant degrade yolunu kaydedemez. Örneğin:

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. Degradeleri bir tamsayı veya dize yoluyla aldı

Tamsayılar ve dizeler türevlenebilir değildir. Bir hesaplama yolu bu veri türlerini kullanırsa gradyan olmaz.

Kimse dizeleri türevlenebilir olmasını beklediğini, ancak yanlışlıkla bir oluşturmak kolaydır int belirttiğiniz yoksa sabit veya değişken dtype .

x = tf.constant(10)

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

print(g.gradient(y, x))
WARNING:tensorflow:The dtype of the watched tensor must be floating (e.g. tf.float32), got tf.int32
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, türler arasında otomatik olarak yayın yapmaz, bu nedenle pratikte, eksik bir degrade yerine genellikle bir tür hatası alırsınız.

4. Durum bilgisi olan bir nesne aracılığıyla gradyanlar aldı

Devlet gradyanları durdurur. Durum bilgisi olan bir nesneden okuduğunuzda, bant, kendisine yol açan geçmişi değil, yalnızca mevcut durumu gözlemleyebilir.

Bir tf.Tensor sabittir. Bir tensörü oluşturulduktan sonra değiştiremezsiniz. Bu bir değer, ancak hiçbir devlet vardır. Buraya kadar ele Tüm işlemler de vatansız şunlardır: a çıktı tf.matmul sadece girişlerine bağlıdır.

Bir tf.Variable iç durumu-değeri vardır. Değişkeni kullandığınızda durum okunur. Bir değişkene göre bir gradyan hesaplamak normaldir, ancak değişkenin durumu gradyan hesaplamalarının daha geriye gitmesini engeller. Örneğin:

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

Benzer şekilde, tf.data.Dataset yineleyiciler ve tf.queue ler durum bilgisi vardır ve bunlardan geçmesine tensörlerle tüm geçişlerini duracaktır.

Gradyan kayıtlı değil

Bazı tf.Operation ler olmayan türevlenebilir olarak kayıtlıdır ve dönecektir None . Diğerleri hiçbir gradyan kayıtlı var.

tf.raw_ops düşük seviyeli op kayıtlı geçişlerini var sayfa gösterileri.

Eğer bant sessizce dönen yerine bir hata atar kayıtlı hiçbir renk geçişini içeren bir şamandıra op üzerinden bir gradyan almaya çalışırsanız None . Bu şekilde bir şeylerin yanlış gittiğini bilirsiniz.

Örneğin, tf.image.adjust_contrast fonksiyonu sarar raw_ops.AdjustContrastv2 bir eğime sahip olabilir, ama gradyan uygulanmadı:

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

Bu op aracılığıyla ayırt etmek gerekirse, ya eğimi uygulamak ve kaydetmek (kullanarak gerekir tf.RegisterGradient ) ya da diğer op kullanarak işlevini tekrar uygulamasını.

Yok yerine sıfırlar

Bazı durumlarda yerine 0 almak uygun olur None bağlantısız geçişlerini için. Sen kullanarak bağlantısız geçişlerini olduğunda dönmeye karar verebilir unconnected_gradients argüman:

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)