※記事内に商品プロモーションを含むことがあります。
はじめに
本記事では、ChainerのVariableクラスについて簡単に解説し、1階微分、2階微分の求め方についてまとめる。
自動微分を使うと、関数の勾配ベクトルを自動的に求めることができるので、勾配を使った最適化手法を容易に行える。
Chainerと最適化
Chainerは、株式会社Preferred Networksが提供しているディープラーニング用のライブラリである。
Chainerには、ニューラルネットワークの学習を高速に行うため、自動微分(定義した関数の勾配を自動で求める)機能が実装されている(この機能を使うことで、損失関数を最小化するために、ニューラルネットワークの各ノードの重みをどの方向に更新すれば良いか分かる)。
一方、最適化問題を解くとき、最急降下法などの手法では、関数の勾配が必要となる。
関数の勾配を求めるためには、以下の方法がある。
- 関数の導関数を手計算で求める方法
- 数値微分(少しだけ変化させた入力変数を与えて出力の差から勾配を求める)
- 自動微分
問題が複雑な場合、(1)は困難である。
また、(2)は計算時間を要する問題がある。
(3)は、実装に手間が掛かるという欠点があるが、問題ごとに導関数を求める手間も不要で、計算時間も短い利点がある。
Chianerには(3)の機能がVariableというクラスで実装されているので、最適化に活用するため仕様についてまとめた。
また、最急降下法のPythonでの実装については、過去記事をご参考まで。
直線探索を使った最急降下法をPythonで実装
環境
ソフトウェア | バージョン |
---|---|
Spyder | 3.3.3 |
Python | 3.7.3 |
NumPy | 1.16.2 |
Chainer | 6.3.0 |
以下では、各ライブラリを以下のようにインポートしていることを前提とする。
|
|
Variableクラス
ChainerのVariableクラスは、数値の配列データを保持するクラスである。
NumPy配列に近い感覚で扱えるが、相違点もあるので注意。
Variableオブジェクトは、32ビット精度のNumPy配列を使って作成される。
例:
|
|
生成されたオブジェクトは、Variable型の配列である。
|
|
Variableオブジェクトでは、同じサイズの配列同士の演算ができる。
(配列のサイズが異なる場合、NumPyではブロードキャストしてくれるが、Variableオブジェクトはエラーを返す)
|
|
また、配列のインデックスを指定して、要素を取り出すことも可能である。
|
|
Variableオブジェクトのarray
属性から、NumPy配列を取得できる。
|
|
なお、同様にdata
属性からもNumPy配列を取得できるが、chainerの公式リファレンスでは「NumPy配列のdata
属性」と紛らわしいという理由で、array
属性の使用を推奨している。
Variables and Derivatives — Chainer 7.7.0 documentation
さらに、Variableオブジェクトのgrad
属性から、勾配のNumPy配列を取得できる(詳細は後述)。
1階微分の求め方
Variableオブジェクトを使った1階微分の求め方について述べる。
まず、以下の2変数関数を考える。
$$ f(\boldsymbol{x}) = 2x_0^2 + x_1^2 + 2x_0 + x_1, \boldsymbol{x}=[ x_0, x_1 ]^\mathrm{T} $$
この関数の勾配ベクトルは次式で与えられる。
$$ \nabla f(\boldsymbol{x}) = \left[ \frac{\partial f}{\partial x_0}, \frac{\partial f}{\partial x_1} \right]^\mathrm{T} = [4x_0 + 2, 2x_1 + 1 ]^\mathrm{T} $$
点$ (x_0, x_1)=(1, 2)$において、関数値と勾配ベクトルはそれぞれ以下のようになる。
$ f(\boldsymbol{x})=10 $
$ \displaystyle \nabla f(\boldsymbol{x}) = [6, 5 ]^\mathrm{T} $
上記の関数をVariableを使って記述すると以下のようになる。
|
|
関数の値(10)は既に得られている。
|
|
この段階では、勾配はまだ得られておらず、勾配を取得するためには以下を実行する。
|
|
まず、y
のgrad
に、値は何でも良いので配列を設定する。
np.ones_like
は、引数の配列と同じサイズで、要素が全て1の配列を返す関数である。
(ただし、厳密にはgrad
に初期値を与える必要があるのは、y
が2つ以上の要素を持つ配列の場合である。
今回の例のようにy
がスカラーの場合は、初期値の設定は省略できる)
次に、y.backward()
メソッドを実行すると、自動微分が実行され、x.grad
に勾配が格納される。
|
|
中間変数の勾配を求める場合
上記の方法では、以下のようにVariableオブジェクトの演算を2回以上重ねた場合に、中間の変数の勾配が保存されない。
|
|
中間の変数の勾配が欲しい場合には、backward
メソッドでretain_grad=True
とする。
|
|
2階微分の求め方
2階微分を求めるためには、以下のようにする。
|
|
目的関数y
のbackward
を実行するまでは同じであるが、
その後にx.grad_var
を取得する。
次に、cleargrad
メソッドでx
の勾配を削除したのち、
x.grad_var
を格納した変数に対して、backward
を実行すると、
x.grad
に2階微分が格納される。
参考リンク
Variables and Derivatives — Chainer 7.7.0 documentation
chainer.Variable — Chainer 7.7.0 documentation
ChainerのVariableを使って自動微分を簡単実装 | 自調自考の旅