Gelişmiş otomatik farklılaşma

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

Gradyanlar ve otomatik farklılaşma giriş kılavuzunda TensorFlow içinde hesapla geçişlerini için gerekli her şeyi içerir. Bu kılavuz derin, daha az yaygın özelliklere odaklanan tf.GradientTape API.

Kurulum

pip uninstall tensorflow keras -y
pip install tf-nightly
import tensorflow as tf

import matplotlib as mpl
import matplotlib.pyplot as plt

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

Degrade kaydını kontrol etme

In otomatik farklılaşma rehber size degrade hesaplama kurarken bant tarafından izlenen hangi değişkenler ve tansörler nasıl kontrol gördük.

Kaset ayrıca kaydı manipüle etme yöntemlerine de sahiptir.

Kaydetmeyi bırak

Eğer geçişlerini Kaydı durdurmak isterseniz, kullanabileceğiniz tf.GradientTape.stop_recording Kaydı geçici askıya almak.

Bu, modelinizin ortasındaki karmaşık bir işlemi ayırt etmek istemiyorsanız, ek yükü azaltmak için yararlı olabilir. Bu, bir metrik veya bir ara sonucun hesaplanmasını içerebilir:

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
2021-07-01 01:22:12.311927: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-07-01 01:22:12.319895: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-07-01 01:22:12.320536: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-07-01 01:22:12.322087: I tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations:  AVX2 AVX512F FMA
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
2021-07-01 01:22:12.322666: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-07-01 01:22:12.323332: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-07-01 01:22:12.323939: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-07-01 01:22:12.907440: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-07-01 01:22:12.908098: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-07-01 01:22:12.908676: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-07-01 01:22:12.909259: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1510] Created device /job:localhost/replica:0/task:0/device:GPU:0 with 14646 MB memory:  -> device: 0, name: NVIDIA Tesla V100-SXM2-16GB, pci bus id: 0000:00:05.0, compute capability: 7.0

Sıfırla/kaydı sıfırdan başlat

Tamamen baştan başlamak isterseniz, kullanmak tf.GradientTape.reset . Basitçe degrade bant blok çıkmadan ve yeniden başlatılıyor genellikle daha kolay okunmasını, ancak kullanabilirsiniz reset bant blok zor veya imkansız çıkarken yöntemi.

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

Gradyan akışını hassas bir şekilde durdurun

Yukarıdaki küresel teyp kontrolleri aksine, tf.stop_gradient fonksiyonu çok daha hassas. Bantın kendisine erişmeye gerek kalmadan belirli bir yol boyunca gradyanların akmasını durdurmak için kullanılabilir:

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

Özel degradeler

Bazı durumlarda, varsayılanı kullanmak yerine degradelerin tam olarak nasıl hesaplandığını kontrol etmek isteyebilirsiniz. Bu durumlar şunları içerir:

  1. Yazmakta olduğunuz yeni bir operasyon için tanımlanmış bir gradyan yok.
  2. Varsayılan hesaplamalar sayısal olarak kararsızdır.
  3. İleri geçişten pahalı bir hesaplamayı önbelleğe almak istiyorsunuz.
  4. Eğer (kullanarak, örneğin, bir değerini değiştirmek isteyen tf.clip_by_value veya tf.math.round gradyanı değiştirmeden).

İlk durumda, kullanabileceğiniz yeni bir operasyon yazmak için tf.RegisterGradient kendi kurmak için (ayrıntılar için API belgelerine bakın). (Degrade kayıt defterinin genel olduğunu unutmayın, bu nedenle dikkatli bir şekilde değiştirin.)

İkincisi üç durumlar için kullanabileceğiniz tf.custom_gradient .

Burada geçerli bir örnek tf.clip_by_norm ara gradyanı:

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

Bakın tf.custom_gradient Daha fazla detay için dekoratör API docs.

SavedModel'de özel degradeler

Özel gradyanları seçeneğini kullanarak SavedModel kaydedilebilir tf.saved_model.SaveOptions(experimental_custom_gradients=True) .

SavedModel içine kaydedilecek için degrade fonksiyonu (kontrol, daha fazla bilgi edinmek için izlenebilir olmalıdır tf.function ile daha iyi performans kılavuzu).

class MyModule(tf.Module):

  @tf.function(input_signature=[tf.TensorSpec(None)])
  def call_custom_grad(self, x):
    return clip_gradients(x)

model = MyModule()
tf.saved_model.save(
    model,
    'saved_model',
    options=tf.saved_model.SaveOptions(experimental_custom_gradients=True))

# The loaded gradients will be the same as the above example.
v = tf.Variable(2.0)
loaded = tf.saved_model.load('saved_model')
with tf.GradientTape() as t:
  output = loaded.call_custom_grad(v * v)
print(t.gradient(output, v))
INFO:tensorflow:Assets written to: saved_model/assets
tf.Tensor(2.0, shape=(), dtype=float32)
2021-07-01 01:22:13.395687: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:185] None of the MLIR Optimization Passes are enabled (registered 2)

Yukarıdaki örnekte ilgili bir not: Eğer yukarıdaki kod yerine çalışırsanız tf.saved_model.SaveOptions(experimental_custom_gradients=False) , gradyan hala yükleme üzerinde aynı sonucu üretecektir. Nedeni gradyan kayıt hala işlev kullanılan özel gradyan içermesidir call_custom_op . Eğer altında yüklü modelini çalışan, özel gradyanlar olmadan kaydettikten sonra çalışma zamanını yeniden Ancak, tf.GradientTape : hata atmak olacaktır LookupError: No gradient defined for operation 'IdentityN' (op type: IdentityN) .

Çoklu bantlar

Birden çok bant sorunsuz bir şekilde etkileşime girer.

Örneğin, burada her bant farklı bir tensör setini izler:

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

Daha yüksek dereceli gradyanlar

İçinde işlemler tf.GradientTape bağlam yöneticisi otomatik farklılaşması için kaydedilmektedir. Gradyanlar bu bağlamda hesaplanırsa, gradyan hesaplaması da kaydedilir. Sonuç olarak, aynı API daha yüksek dereceli gradyanlar için de çalışır.

Örneğin:

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

Bu seni skaler fonksiyonun ikinci türevi vermek yok iken, bu desen beri, bir Hessian matrisi üretmek için genelleme değil tf.GradientTape.gradient sadece skaler eğimi hesaplar. Bir oluşturmak için Hessian matris , gidin Hessian örnek altında Jakobyan bölümü .

"İç içe çağrılar tf.GradientTape.gradient " bir gradyandan bir skalar hesaplanması ve daha sonra elde edilen sayısal aşağıdaki örnekte olduğu gibi, ikinci bir gradyan hesaplanması için bir kaynak olarak işlev görür iyi desenidir.

Örnek: Girdi gradyan düzenlemesi

Birçok model, "karşıt örneklere" duyarlıdır. Bu teknikler koleksiyonu, modelin çıktısını karıştırmak için modelin girdisini değiştirir. Gibi en basit uygulaması-böyle Metot saldırı Signed Hızlı gradyanı kullanılarak olarak rakip örnek girişine göre çıkış gradyanı boyunca tek bir adım -takes; "giriş gradyanı".

Çekişmeli örneklere sağlamlığını arttırmak için bir teknik, bir giriş gradyan düzenlileştirme girişimleri giriş gradyanı büyüklüğünü en aza indirmek için (Finlay ve Oberman 2019). Giriş gradyanı küçükse, çıkıştaki değişiklik de küçük olmalıdır.

Aşağıda, giriş gradyan düzenlemesinin saf bir uygulaması bulunmaktadır. Uygulama:

  1. Bir iç bant kullanarak girişe göre çıkışın gradyanını hesaplayın.
  2. Bu giriş gradyanının büyüklüğünü hesaplayın.
  3. Modele göre bu büyüklüğün gradyanını hesaplayın.
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])]

Jacobianlar

Önceki tüm örnekler, bazı kaynak tensör(ler)e göre bir skaler hedefin gradyanlarını aldı.

Jakobiyen matris için bir vektör değerli fonksiyon geçişlerini temsil etmektedir. Her satır, vektörün öğelerinden birinin gradyanını içerir.

tf.GradientTape.jacobian yöntem verimli bir Jakobyan matris hesaplamak için izin verir.

Dikkat:

  • Gibi gradient : sources bağımsız değişkeni bir tensör veya tensörlerinin bir kap olabilir.
  • Aksine gradient : target tensör tek tensör olmalıdır.

skaler kaynak

İlk örnek olarak, burada bir vektör hedefinin bir skaler kaynağa göre Jacobian'ı verilmiştir.

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)

Eğer bir sayısal göre Jakobien aldığımızda sonuç hedefin şekline sahiptir ve kaynağına göre her elemanın eğimi verir:

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

Tensör kaynağı

Giriş skaler veya tensör olsun, tf.GradientTape.jacobian etkili bir hedef (ler) in her bir elemanı ile ilgili olarak kaynağın her bir öğenin gradyanı hesaplar.

Örneğin, bu tabakanın çıkışı bir şekle sahiptir (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])

Ve tabakanın çekirdeğin şekli (5, 10) :

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

Çıktının çekirdeğe göre Jacobian şekli, birlikte birleştirilen bu iki şekildir:

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

Eğer hedefin boyutları üzerinde toplarsak, sen hesaplanmıştır olurdu toplamının gradyan ile sol tf.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: 2.3841858e-07

Örnek: Hessen

İken tf.GradientTape bir inşa için açık bir yöntem vermez Hessen matrisi o kullanarak bir inşa etmek mümkündür tf.GradientTape.jacobian yöntem.

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)

Bir bu Hessian kullanmak için Newton yöntemi adımı, önce bir matris içine eksenlerini doğrulmak ve bir vektör içine gradyan doğrulmak olacaktır:

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

Hessian matrisi simetrik olmalıdır:

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

Newton'un yöntem güncelleme adımı aşağıda gösterilmiştir:

eps = 1e-3
eye_eps = tf.eye(h_mat.shape[0])*eps
# X(k+1) = X(k) - (\u2207\xb2f(X(k)))^-1 @ \u2207f(X(k))
# h_mat = \u2207\xb2f(X(k))
# g_vec = \u2207f(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))
2021-07-01 01:22:14.866543: I tensorflow/core/util/cuda_solvers.cc:180] Creating CudaSolver handles for stream 0x7278d90

Bu tek için nispeten basit olmakla birlikte tf.Variable , önemsiz olmayan bir modele bu uygulamadan çok sayıda değişkeni arasında tam bir Hessian üretmek için dikkatli birleştirme ve dilimleme gerektirecektir.

Toplu Jacobian

Bazı durumlarda, her bir hedef-kaynak çifti için Jacobian'ların bağımsız olduğu bir kaynak yığınına göre bir hedef yığınının her birinin Jacobian'ını almak istersiniz.

Örneğin, burada, giriş x şekillendirilir (batch, ins) ve çıkış y şekillendirilir (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])

Tam Jakobyan y göre x bir şekle sahiptir (batch, ins, batch, outs) yalnızca istediğiniz bile, (batch, ins, outs) :

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

Yığın her öğenin gradyanlar bağımsız ise, o zaman, her (batch, batch) bu tensörünün dilim diyagonal matris:

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

İstenen sonucu elde etmek için, yinelenen üzerinde özetleyebilirim batch boyut, ya da başka kullanan diyagonallerini seçmek 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)

İlk etapta ekstra boyut olmadan hesaplama yapmak çok daha verimli olacaktır. tf.GradientTape.batch_jacobian yöntem tam olarak yapar:

jb = tape.batch_jacobian(y, x)
jb.shape
WARNING:tensorflow:5 out of the last 5 calls to <function pfor.<locals>.f at 0x7fb5e8133560> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has experimental_relax_shapes=True option that relaxes argument shapes that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for  more details.
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}')
WARNING:tensorflow:6 out of the last 6 calls to <function pfor.<locals>.f at 0x7fb5dc652830> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has experimental_relax_shapes=True option that relaxes argument shapes that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for  more details.
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

Bu durumda, batch_jacobian hala çalışır ve beklenen şekli ile döner bir şey, ama onun içeriği belirsiz anlamı vardır:

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