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)
loss
2 つの変数に関するの勾配を取得するには、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.Module
layers.Layer
keras.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.Variable
trainable
False
# 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
"""
あまり目立たないいくつかの方法で勾配を切断することもできます。
- テンソルを使用して変数を置き換える
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
"""
- 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
"""
- 整数または文字列でグラデーションを取得
整数と文字列は微分できません。計算パスがこれらのデータ型を使用する場合、勾配は発生しません。
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 。