勾配降下法による学習ループを実装してみる

重みパラメータを闇雲に動かしてもゴール(損失関数の最小値)には近づかない。実際にニューラルネットワークを使う時には重みパラメータは数百、数千にもなり得るから、全ての実数に対して総当たりなど行うことはできない。ここで、損失関数の振る舞いを偏微分によって”少しだけ明らか”にし、”少しだけマシだと思われる方向”に重みパラメータを動かす。こうして少しずつ損失関数を小さくすることを繰り返す。ここで、”重みパラメータが少しだけマシだと思われる方向”のことを「勾配(gradient)」と呼び、勾配を求めて損失関数を小さくする方法を「勾配降下法(gradient descent method)」と呼ぶ。

画像①勾配を元に

損失関数には「2乗和誤差」や「交差エントロピー誤差」が使われるが、これらは関数を構成する方法であって、関数そのものではない。というより損失関数を1つの数式で表せるなら、最小値を求めるのにそもそも学習など必要はない。損失関数に関して知ることができるのは入力(重みパラメータ)と出力(損失関数の値)という、具体的な値だけだ。つまり、この正体不明の関数に対して解析的に微分を行うことはできない。(解析的にとは 、y = 3x をxで微分したら3、というように数式のみから解く方法のことだ。)

画像②数値微分

上の図のように、関数の全容はわからず真っ暗闇の中で、入力に”僅かな差h”を与えることで出力の変化率を導き局所的に微分係数を求める方法を「数値微分(numerical difference)」と呼ぶ。解析的に微分値を求める時はこの入力の”僅かな差h”を0に極限まで近づけるが、Pythonによるプログラミングではh = 0.0001が適当であるとされる。2変数関数に関する数値微分は数式、Pythonによる実装で以下のようになる。

画像③数式

このように入力xに対して前後にhを動かす方法を中心差分と呼ぶ。片方に動かすよりも誤差が少ない。

ここで引数としているfとxは、正体不明の損失関数fと、適当な重みパラメータxである。つまりforロープ1回につき2度、ニューラルネットワークによる誤差の計算が行われるということだ。適当な重みパラメータの初期値は乱数を与える。(重みパラメータの初期値の決め方についてはこの先で。)

画像④数式

ここで、数値微分の関数numerical_gradient(f, x)で求められる変数に対する勾配は(数式)で表されているが、1回の学習でこの勾配をどれだけ影響させるかは、係数となるηに依存する。このηを「学習率(Learnng rate)」と呼ぶ。ηが小さいと最適化に向かう速度が下がるが、ηが大きすぎると発散してしまう(飛びすぎる)。この値は自動的にではなく人間が設定しなければならない(ηのような手動で設定するものを「ハイパーパラメータ」と呼ぶ)。上に示す勾配法の数式からわかるように、各変数に対して各勾配が掛かるだけで相互的に干渉しないので、変数がどれだけ多くても並列にそれぞれ計算すれば良い。

画像⑤2列3次元の重みパラメータと勾配

以下に、「入力[0.6, 0.9]に対して正解ラベル[0,0,1]がわかっている」という条件(のみ)で、simpleNetという簡単なクラスを実装することでニューラルネットワークを形成し、勾配を求める。ここで、損失関数には交差エントロピー誤差を用いる。

このコードを実行すると、以下の出力が得られる。(重みパラメータの初期値は毎回乱数が与えられるので、この出力は毎回違うものになる。)

[[ 0.07399678 0.02367077 -0.09766755]
[ 0.11099516 0.03550616 -0.14650132]]

これによって示される「勾配」の意味は例えば「w11は約0.07 = w11をh増やすと損失関数が0.07h増える」ということを示している。この配列を勾配の計算式にはめれば(つまりηを掛けて元の重みパラメータから引けば)「少しだけ補正された重みパラメータ」が得られる。これを新たな重みパラメータとして入力することで「少しだけ小さくなった損失関数」が得られる。つまり、「学習」のループが完成する。

数の最小値を探すことが学習のゴールとなる。

Leave a Comment