TensorFlow の基本 (3) 勾配と自動微分


import numpy as np
import matplotlib.pyplot as plt

import tensorflow as tf

勾配の計算

自動微分を実現するために、TensorFlowはフォワード パス (フォワード パス)中にどの操作がどの順序で発生したかを記憶あります。次に、バックワードパス中に、TensorFlow はこの op リストを逆の順序でトラバースして勾配を計算します。


グラデーションテープ

TensorFlow は、自動微分のための API を提供します。つまりtf.GradientTape、ある入力 (通常は ) に対するある量tf.Variableの勾配を計算します。TensorFlow は、「ストライプ」tf.GradientTape内。これは、リバース モード微分によって勾配を計算するために使用されます。

「録音」プロセス:

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

いくつかの操作を記録した後、ソース (通常はモデル パラメーター) に対するターゲット (通常は loss ) の勾配をGradientTape.gradient(target, sources)計算するためます。

dy_dx = tape.gradient(y, x)
dy_dx.numpy()
"""
6.0
"""

上記の例はスカラーを使用しており、tf.GradientTape 任意の tensor で動作します:

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)

loss2 つの変数に関するの勾配を取得するには、gradient両方の変数をソースとしてメソッドに渡します。GradientStrap は、ソースがどのように渡されるかについて非常に柔軟であり、リストまたは辞書のネストされた組み合わせを受け入れ、同じ方法で勾配構造を返します。

各ソースに関する勾配は、ソースの形状を持っています。

[dl_dw, dl_db] = tape.gradient(loss, [w, b])
print(w.shape)
print(dl_dw.shape)
"""
(3, 2)
(3, 2)
"""

ソースは変数辞書を渡すこともできます:

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

grad = tape.gradient(loss, source)
grad['b']
"""
<tf.Tensor: shape=(2,), dtype=float32, numpy=array([-0.85382605, -4.2623644 ], dtype=float32)>
"""

モデルに関する勾配

通常、チェックポイントまたはエクスポートのために、またはそのサブクラスの 1 つ ( 、 など)tf.Variables収集されます。tf.Modulelayers.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:
    y = layer(x)
    loss = tf.reduce_mean(y**2)

grad = tape.gradient(loss, layer.trainable_variables)
for var, gra in zip(layer.trainable_variables, grad):
    print(f'{
      
      var.name}, shape: {
      
      gra.shape}')
"""
dense/kernel:0, shape: (3, 2)
dense/bias:0, shape: (2,)
"""

テープが何を監視するかを制御する

デフォルトでは、TensorFlow は trainable にアクセスしたtf.Variable後に。tf.Tensor次の例では、 が既定監視さtf.GradientTapeれていないか、 のプロパティが に設定されているため、勾配を計算できませんtf.VariabletrainableFalse

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

に関する勾配を記録tf.Tensorする、 を呼び出す必要がありGradientTape.watch(x)ます。

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

dy_dx = tape.gradient(y, x)
dy_dx.numpy()

中間結果

で計算された中間値の勾配tf.GradientTape取得ます。

x = tf.constant(3.0)

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

# dz_dy = 2 * y and y = x ** 2 = 9
print(tape.gradient(z, y).numpy())

デフォルトGradientTape.gradientでは、メソッドが呼び出されるたびに、GradientTapeによって保持されているリソースが解放されます。同じ計算で複数の勾配を計算するには、 を設定できます persistent=Trueこれにより、グラデーション ストリップ オブジェクトがガベージ コレクションされるときにリソースが解放されるときに、グラデーション メソッドを複数回呼び出すことができます。例えば:

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.]
"""

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

勾配は基本的にスカラーに対する操作です。複数のターゲットの勾配を計算するために、次の例では、各ターゲットの勾配の合計を計算します。

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

ここに画像の説明を挿入


グラデーションが None を返すケース

ターゲットがソースに接続されていない場合、以下gradientが返されNoneます。

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

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

あまり目立たないいくつかの方法で勾配を切断することもできます。

  1. テンソルを使用して変数を置き換える
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
"""
  1. 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
"""
  1. 整数または文字列でグラデーションを取得

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

x = tf.constant(10)

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

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

参考文献

TensorFlow 公式ウェブサイト、https: //tensorflow.google.cn/guide/autodiff 。

おすすめ

転載: blog.csdn.net/myDarling_/article/details/128674925