ヘルプKaggleにTensorFlowグレートバリアリーフを保護チャレンジに参加

グラデーションと自動微分の概要

TensorFlow.orgで表示 GoogleColabで実行 GitHubでソースを表示 ノートブックをダウンロード

自動微分と勾配

自動微分は、のような機械学習アルゴリズムを実装するために有用であるバックプロパゲーションニューラルネットワークを訓練するため。

このガイドでは、特に中、TensorFlowで勾配を計算する方法探求する熱心な実行を

設定

import numpy as np
import matplotlib.pyplot as plt

import tensorflow as tf

勾配の計算

自動的に区別するために、TensorFlow操作は往路の間にどのような順序で起こるものを覚えておく必要があります。次に、復路時、TensorFlowは、計算勾配に逆の順序で操作のリストを横断します。

グラデーションテープ

TensorFlowが提供tf.GradientTape自動微分のためのAPIを。それは、通常、いくつかの入力に対する演算の勾配を計算し、あるtf.Variable S。 TensorFlow「記録」のコンテキスト内で実行関連事業tf.GradientTape 「テープ」へ。 TensorFlowは、使用して「記録」の計算の勾配を計算するためにそのテープを使用してリバースモードの分化を

簡単な例を次に示します。

x = tf.Variable(3.0)

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

あなたには、いくつかの操作を記録したら、使用GradientTape.gradient(target, sources) 、いくつかのソース(多くの場合、モデルの変数)に対するいくつかのターゲット(多くの場合、損失)の勾配を計算します:

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

上記の例では、スカラーを使用しますが、 tf.GradientTape任意のテンソル上のように簡単に動作します:

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)

勾配を取得するにはloss両方の変数に関して、あなたはへのソースとしての両方を渡すことができgradient法。テープは、ソースが渡され、リストや辞書の任意のネストされた組み合わせを受け入れ、同じように(参照構造化勾配を返します方法については柔軟性があるtf.nest )。

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

各ソースに関するグラデーションは、ソースの形状を持っています。

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

これが再び勾配計算です。今回は変数の辞書を渡します。

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

モデルに関する勾配

収集するために、それの普通tf.Variablestf.Moduleまたはそのサブクラス(の1 layers.Layerkeras.Model用)チェックポイントおよびエクスポートします

ほとんどの場合、モデルのトレーニング可能な変数に関して勾配を計算する必要があります。すべてのサブクラスのでtf.Moduleで自分の変数を集約Module.trainable_variablesプロパティには、数行のコードでこれらの勾配を計算することができます。

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

テープが監視するものを制御する

デフォルトの動作では、トレーニング可能なアクセスした後、すべての操作を記録することであるtf.Variable 。この理由は次のとおりです。

  • テープは、逆方向パスの勾配を計算するために、順方向パスに記録する操作を知る必要があります。
  • テープには中間出力への参照が含まれているため、不要な操作を記録する必要はありません。
  • 最も一般的な使用例は、すべてのモデルのトレーニング可能な変数に関する損失の勾配を計算することです。

たとえば、次のための勾配を計算するのに失敗したtf.Tensor 、デフォルトでは「見て」いない、とされてtf.Variableトレーニング可能ではありません。

# 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

あなたは、変数を使用してテープによって監視されて一覧表示することができGradientTape.watched_variables方法を:

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

tf.GradientTapeあるか見ていないものを超えるユーザーコントロールを与えるのフックを提供します。

レコード勾配にtf.Tensor 、あなたが呼び出す必要があり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

逆に、すべて見てのデフォルトの動作を無効にするtf.Variables 、セットwatch_accessed_variables=Falseグラデーションテープを作成するとき。この計算では2つの変数を使用しますが、変数の1つの勾配のみを接続します。

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)

以来GradientTape.watch上に呼び出されなかったx0 、全く勾配は、それに対して計算されていません。

# 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

中間結果

また、内部計算中間値に対する出力の勾配を要求することができるtf.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_dy = 2 * y and y = x ** 2 = 9
print(tape.gradient(z, y).numpy())
18.0

デフォルトでは、で開催されたリソースGradientTapeできるだけ早くとしてリリースされているGradientTape.gradientメソッドが呼び出されます。同じ計算上の複数の勾配を計算するには、との勾配テープを作成しpersistent=True 。これは、への複数の呼び出し可能にgradientテープオブジェクトがガベージコレクトされるときリソースが解放されたような方法を。例えば:

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

パフォーマンスに関する注意事項

  • グラデーションテープコンテキスト内での操作の実行に関連する小さなオーバーヘッドがあります。ほとんどの熱心な実行では、これは目立ったコストにはなりませんが、それでも必要な領域でのみテープコンテキストを使用する必要があります。

  • グラデーションテープは、メモリを使用して、逆方向パス中に使用するために、入力と出力を含む中間結果を格納します。

    効率化のために、(のようないくつかのOPS ReLU )彼らの中間結果を維持する必要はありません、彼らは往路の間に剪定されています。あなたが使用している場合しかし、 persistent=Trueテープに何も破棄されていないし、あなたのピークメモリ使用量が高くなります。

非スカラーターゲットの勾配

グラデーションは基本的にスカラーの操作です。

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

したがって、複数のターゲットの勾配を要求すると、各ソースの結果は次のようになります。

  • ターゲットの合計の勾配、または同等に
  • 各ターゲットの勾配の合計。
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

同様に、ターゲットがスカラーでない場合、合計の勾配が計算されます。

x = tf.Variable(2.)

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

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

これにより、損失のコレクションの合計の勾配、または要素ごとの損失計算の合計の勾配を簡単に取得できます。

あなたが項目ごとに別々の勾配が必要な場合は、を参照してくださいヤコビアン

場合によっては、ヤコビアンをスキップできます。要素ごとの計算では、各要素が独立しているため、合計の勾配により、入力要素に関する各要素の導関数が得られます。

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

制御フロー

勾配テープ記録動作なぜなら、それらが実行されるように、Pythonの制御フローは、天然(例えば、処理されifwhile文)。

ここでは別の変数は、それぞれの枝に使用されているif 。グラデーションは、使用された変数にのみ接続します。

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

制御ステートメント自体は微分可能ではないため、グラデーションベースのオプティマイザーには表示されないことに注意してください。

値に応じてx上記の例では、テープのいずれかのレコードはresult = v0またはresult = v1**2 。勾配x 、常にではありませんNone

dx = tape.gradient(result, x)

print(dx)
None

勾配の取得None

ターゲットがソースに接続されていないときは、勾配取得しますNone

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

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

ここでz 、明らかに接続されていないxが、勾配を切断することができるいくつかのあまり明らかな方法があります。

1.変数をテンソルに置き換えました

上のセクションでは、「テープの腕時計何を制御する」あなたは、テープが自動的に見るだろうことを見tf.Variableではなくtf.Tensor

一つの一般的なエラーが誤って交換することですtf.Variableしてtf.Tensor代わりに使用するのでは、 Variable.assign更新するtf.Variable 。次に例を示します。

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の外部で計算を行いました

計算がTensorFlowを終了する場合、テープは勾配パスを記録できません。例えば:

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.整数または文字列を介してグラデーションを取得しました

整数と文字列は微分可能ではありません。計算パスがこれらのデータ型を使用する場合、勾配はありません。

誰も文字列が微分可能であることを期待していないが、それは誤って簡単に作成できますintを指定しない場合は定数または変数を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は型間で自動的にキャストされないため、実際には、グラデーションが欠落しているのではなく、型エラーが発生することがよくあります。

4.ステートフルオブジェクトを介してグラデーションを取得しました

状態は勾配を停止します。ステートフルオブジェクトから読み取る場合、テープは現在の状態のみを監視でき、それにつながる履歴は監視できません。

A tf.Tensor不変です。一度作成したテンソルを変更することはできません。それは価値、ない状態があります。これまでに説明したすべての操作には、ステートレスです:の出力tf.matmul唯一その入力に依存します。

A tf.Variable内部状態の値を有します。変数を使用すると、状態が読み取られます。変数に関して勾配を計算するのは正常ですが、変数の状態により、勾配の計算がそれ以上戻ることはできません。例えば:

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

同様に、 tf.data.Datasetイテレータとtf.queue Sは、ステートフルあり、それらを通過テンソル上のすべての勾配を停止します。

グラデーションが登録されていません

いくつかのtf.Operation Sは微分不可能であるとして登録されていると返されませんNone 。その他には勾配が登録されいません

tf.raw_ops低レベルのOPSが登録勾配を持つページショーを。

あなたは、テープが静かに戻っていないのではなく、エラーがスローされます登録された勾配がありませんフロートOPを通じて勾配を取るしようとするとNone 。このようにして、何かがうまくいかなかったことがわかります。

例えば、 tf.image.adjust_contrast機能は、ラップraw_ops.AdjustContrastv2勾配を有することができるが、勾配が実装されていません。

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

あなたはこのオペアンプによって区別する必要がある場合は、いずれかのグラデーションを実装し、それを登録(使用する必要がありますtf.RegisterGradient )、または他のOPSを使用して機能を再実装します。

なしではなくゼロ

いくつかのケースでは、の代わりに0を取得するために便利であるNone未接続のグラデーションの。あなたが使用して未接続の勾配を持ったときに返すために何を決めることができ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)