はじめに #
ChainerのVariableクラスを使った自動微分に関する記事である。前回記事の補足として、backwardメソッドを使用するときの注意点と、chainer.grad
関数を使った自動微分の計算について述べる。
backwardメソッドを使用する度に勾配が加算されるため、2回以上使用するときは、勾配を除去する必要がある。また、chainer.grad
関数を使うと、任意のVariable変数間の勾配を計算できる。
前回記事:Chainerの自動微分で勾配を求める
環境 #
ソフトウェア | バージョン |
---|---|
Spyder | 3.3.3 |
Python | 3.7.3 |
NumPy | 1.16.2 |
Chainer | 6.3.0 |
以下では、各ライブラリを以下のようにインポートしていることを前提とする。
import numpy as np
import chainer
from chainer import Variable
backwardメソッドの注意点 #
chainer.Variable
のbackward
を使うと自動微分が計算される。ただし、Chainerの仕様上、backward
を実行するたびに、変数の勾配が加算されてしまう。そのため、2回以上backward
を実行すると、望ましい結果が得られなくなる。
例:
\(y = 2x\)の勾配を計算する。\(x=1\)に対する勾配は2であるが、2度backward
を実行すると、誤った勾配が出力される。
>>> x = Variable(np.array([1], dtype=np.float32))
>>> y = 2*x
>>>
>>> y.backward() # 1回目の実行
>>> print(x.grad) # 正しい勾配
[2.]
>>> y.backward() # 2回目の実行
>>> print(x.grad) # 誤った勾配
[4.]
さらにy.backward()
を実行するたびにx
の勾配は2ずつ増えていく。すなわち、勾配が蓄積され続けている。
2回以上backward
を実行しても正しい勾配を得るためには、以下のようにbackward
を実行する度にcleargrad
で勾配を削除してやる必要がある。
>>> x = Variable(np.array([1], dtype=np.float32))
>>> y = 2*x
>>>
>>> y.backward() # 1回目の実行
>>> print(x.grad) # 正しい勾配
[2.]
>>> x.cleargrad() # xの勾配を削除
>>> y.backward() # 2回目の実行
>>> print(x.grad) # 正しい勾配
[2.]
chainer.gradによる勾配計算 #
chainer.grad
関数を使うと、任意のVariable変数間の勾配を計算できる。主な引数を以下に示す。
chainer.grad(outputs, inputs, set_grad=False, retain_grad=False)
引数の説明は以下の通り。
outputs
(tuple or list of Variable): 逆誤差伝搬の起点となる、出力の変数。inputs
(tuple or list of Variable): 勾配を計算する入力側の変数。set_grad
(bool):True
の場合、inputs
の変数のgrad
に勾配が格納される。デフォルトはFalse
。retain_grad
(bool):True
の場合、中間変数のgrad
に勾配が格納される。デフォルトはFalse
。
chainer.grad
関数では、必要最小限の計算パスのみを対象として勾配が計算される。
なお、backward
と異なり、chainer.grad
を何度実行しても勾配は蓄積されず、同じ結果が得られる。
例 #
以下の計算グラフを考える。 実行結果1~5では、全て以下の変数を使用している。
x0 = Variable(np.array([0,1], dtype=np.float32))
x1 = Variable(np.array([2,3], dtype=np.float32))
y = 2*x0 + 3*x1
z = 2*y
実行結果1 #
zに対するx0の勾配を計算する。
>>> chainer.grad([z], [x0])
[variable([4., 4.])]
>>> print(x0.grad) # set_grad=Falseの時、勾配は残らない
None
>>> chainer.grad([z], [x0]) # cleargradしなくても同じ結果になる
[variable([4., 4.])]
実行結果2 #
zに対するx0, x1の勾配を計算する。
>>> chainer.grad([z], [x0, x1])
[variable([4., 4.]), variable([6., 6.])]
実行結果3 #
yに対するx0の勾配を計算する。
>>> chainer.grad([y], [x0])
[variable([2., 2.])]
実行結果4 #
gradに勾配を残す
>>> chainer.grad([y], [x0], set_grad=True)
[variable([4., 4.])]
>>> print(x0.grad)
array([4., 4.], dtype=float32)
実行結果5 #
中間変数yに勾配を残す
>>> chainer.grad([y], [x0], retain_grad=True)
[variable([4., 4.])]
>>> print(y.grad) # 中間変数
array([2., 2.], dtype=float32)
>>> print(x0.grad) # 入力変数には勾配は残らない
None