はじめに #
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