JavaScriptを有効にしてください

Chainerの自動微分で勾配を求める(補足)

 ·   3 min read

※記事内に商品プロモーションを含むことがあります。

はじめに

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

以下では、各ライブラリを以下のようにインポートしていることを前提とする。

1
2
3
import numpy as np
import chainer
from chainer import Variable

backwardメソッドの注意点

chainer.Variablebackwardを使うと自動微分が計算される。ただし、Chainerの仕様上、backwardを実行するたびに、変数の勾配が加算されてしまう。そのため、2回以上backwardを実行すると、望ましい結果が得られなくなる。


$y = 2x$の勾配を計算する。$x=1$に対する勾配は2であるが、2度backwardを実行すると、誤った勾配が出力される。

1
2
3
4
5
6
7
8
9
>>> 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で勾配を削除してやる必要がある。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
>>> 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変数間の勾配を計算できる。主な引数を以下に示す。

1
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では、全て以下の変数を使用している。

1
2
3
4
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の勾配を計算する。

1
2
3
4
5
6
>>> 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の勾配を計算する。

1
2
>>> chainer.grad([z], [x0, x1])
[variable([4., 4.]), variable([6., 6.])]

実行結果3: yに対するx0の勾配を計算する。

1
2
>>> chainer.grad([y], [x0])
[variable([2., 2.])]

実行結果4: gradに勾配を残す

1
2
3
4
>>> chainer.grad([y], [x0], set_grad=True)
[variable([4., 4.])]
>>> print(x0.grad)
array([4., 4.], dtype=float32)

実行結果5: 中間変数yに勾配を残す

1
2
3
4
5
6
>>> chainer.grad([y], [x0], retain_grad=True)
[variable([4., 4.])]
>>> print(y.grad) # 中間変数
array([2., 2.], dtype=float32)
>>> print(x0.grad) # 入力変数には勾配は残らない
None
シェアする

Helve
WRITTEN BY
Helve
関西在住、電機メーカ勤務のエンジニア。X(旧Twitter)で新着記事を配信中です

サイト内検索