13 Deep Learning 学習アルゴリズムの実装

ニューラルネットワークの学習は以下の4つの手順で行う。

ステップ1 ミニバッチ

訓練データの中からランダムに一部のデータを選びだす。その選ばれたデータをミニバッチと言い、ここでは、このミニバッチの損失関数の値を減らすことを目的とする。

ステップ2 勾配の算出

ミニバッチの損失関数を減らすために、各重みパラメータの勾配をもとめる。勾配は、損失関数の値を最も減らす方向を示す。

ステップ3  パラメータの更新

重みパラメータを勾配方向に微小量だけ更新する。

繰り返す

ステップ1,2,3を繰り返す。

ここで使用するデータはミニバッチとして無作為に選ばれたデータを使用していることから、確率的勾配降下法(stochastic gradient descent)と呼ばれる。
省略してSGDと呼ばれることもある。

13.1  2層ニューラルネットワークのクラス

2層(隠れ層が1層)のネットワークを一つのクラスとして実装する。

実装は以下。

13.2 ミニバッチ学習の実装

下図のグラフが出力される。

12.2 ニューラルネットワークに対する勾配

今更ながら、外部ファイルの関数を使用する方法を見つけたので記しておく。

他のファイルの中で定義された関数は、import文で読み込んでから使います。たとえば、「myhelper.py」というPythonコードの中で定義されたmyfunc1という関数を使いたい場合は

と書くことで使うことができます。構文は「from [ファイル名] import [関数名]」です。

他のコードの中で定義された関数をまとめてimportしたい場合は

とすることで一括でimportできます。ただ、このように何をimportするのかを明示しない場合には名前が衝突してしまう可能性もあるので注意が必要です。

ファイル全体をimportする場合には、fromは使わず、importの対象に直接ファイル名を指定する次のような使い方も可能です。

引用元:LIFE WITH PYTHON Python関数の使い方

ニューラルネットワークの学習においても、勾配を求める必要がある。ここで言う勾配は、重みパラメータに関する損失関数の勾配である。たとえば、形状が 2 × 3 (2行3列) の重みWだけを持つニューラルネットワークがあり、損失関数をLで表す場合を考えると、この場合、勾配はδL/δW と表すことができる。実際に数式で表すと次のようになる。

\begin{equation}\begin{array}{c} W = \left( \begin{array}{ccc} w_{11} & w_{21} & w_{31} \\ w_{12} & w_{22} & w_{32} \end{array} \right) \\
\frac{\partial L}{\partial W} = \left( \begin{array}{ccc} \frac{\partial L}{\partial w_{11}} & \frac{\partial L}{\partial w_{21}} & \frac{\partial L}{\partial w_{31}}\\
\frac{\partial L}{\partial w_{12}} & \frac{\partial L}{\partial w_{22}} & \frac{\partial L}{\partial w_{32}} \end{array} \right) \tag{12.2} \end{array}
\end{equation}

δL/δWの各要素は、それぞれの要素に関する偏微分から構成される。
まず、重みのWを初期化し、入力xと重みWの内積predict()の出力から損失関数の値を求める。

実装すると以下のようになり、重みの勾配dWは次のように出力される。

12.1 Deep Learning 勾配降下法

12.1 勾配法

機械学習の問題の多くは、学習の最適なパラメータを探索する。ニューラルネットワークも同様に最適なパラメータ(重みとバイアス)を学習時に見つけなければならない。ここで、最適なパラメータというのは、損失関数が最小値をとるときのパラメータの値である。損失関数は複雑なので、勾配をうまく利用して、関数の最小値を探そうというのが勾配法である。注意点は勾配が指す先が本当に関数の最小値なのかどうか、実際、複雑な関数においては、最小値ではない場合がほとんどである。

勾配法は現在の場所から勾配方向に、一定の距離だけ移動し、さらに勾配を求め、勾配方向へ一定の距離を進むことを繰り返す。このようにして、関数の値を減らすのが勾配法(gradient method)である。一般的にニューラルネットワークでは勾配降下法(gradient descent method)と呼ばれる。(ディープラーニングにおいては、勾配降下法と誤差逆伝播法(後述)の理解が必須であるため、章をたくさん使って説明している。)

勾配法を数式で表すと、次の式(12.1)のように書くことができる。
\begin{equation} \begin{array}{cc} x_0 = x_0 – \eta \frac{\partial f}{\partial x_0} \\ x_1 = x_1 – \eta \frac{\partial f}{\partial x_1} \tag{12.1} \end{array} \end{equation}

式(12.1)のηはへ更新料を表し、学習率(learning rate)と呼ばれる。一回の学習で、どれだけ学習すべきか、すなわち、どれだけパラメータを更新するかを決めるのが学習率である。
式(12.1)は一回の更新式を示しており、この処理を繰り返し行う。つまり、この処理ごとに変数の値を更新していき、処理を何度か繰り返すことによって、徐々に関数の値を減らしていく。
ここでは、変数が2つの場合を示しているが、変数の数が増えても、同じような式—それぞれぞれの変数の偏微分の値—によって更新されることになる。
学習率の値は0.01や0.001など、前もって何らかの値に決める必要がある。
学習率は一般的に大きすぎても、小さすぎても「良い場所」にたどり着くことができない。
勾配降下法をPythonで実装すると、下記のようになる。

勾配を求めるnumerical_gradient()は以下のようであった。

f(x0,x1) = x0^2 + x1^2 を勾配法で求めてみる。

出力が微小な値(-6.111..e-10, 8,184e-10)に対して、真の最小値は(0,0)であるため、勾配法を実装した関数が正しく動作していることが確認できる。

勾配法による学習のプロセスは下図のように表される。

原点が最も低い場所であるが、徐々に近づいていることがわかる。

学習率は小さすぎても、大きすぎてもよい結果にならないと述べたが、具体例を下図に示す。

この結果が示す通り、学習率が大きすぎると、大きな値へと発散し、小さすぎると、ほとんど更新されずに終わる。学習率のようなパラメータはハイパーパラメータと言い、カッコいい名前に反して、人力で探す必要がある手動パラメータである。

11 Deep Learning 勾配

11.1 偏微分

続いて、変数が2つある微分を行う。次式がまずあるとする。

\[f(x0,x1) = x_0^2 + x_1^2 \tag{11.1}\]

Pythonでは次のように実装される。

 

ここでは、引数にNumPy配列が入力されることを想定する。
この関数をグラフに描画すると下図のようになる。

式(11.1)の微分を求めるとき、注意すべきは変数が2つあるということである。このため、「どの変数に対しての微分か」ということ、つまり、x0とx1のどちらの変数に対しての微分かということを区別する必要がある。ここで扱うような複数の変数からなる関数の微分を偏微分という。 偏微分を数式で表すとδf/δx0、δf/δx1のようにかける。

x0=3、x1=4のときのx0に対する偏微分δf/δx0は

つぎのようにPythonで実装される。

x0=3、x1=4のときのx1に対する偏微分δf/δx1は

つぎのようにPythonで実装される。

解析的な偏微分の解は定数項は0になるため、 δf/δx0 =2×0 = 6 δf/δx1=2×1=8
であるが 、上記の実装の出力結果とほぼ一致している。

偏微分は、1変数の微分と同じで、ある場所の傾きを求めます。ただし、偏微分の場合、複数ある変数の中でターゲットとする変数を一つに絞り、他の変数はある値に固定する。上の例の実装では、ターゲットとする変数以外を特定の値に固定するために、新しい関数を定義した。そして、その新しく定義した関数に対して、これまで使用した数値微分の関数を適用して、偏微分を求めた。

11.2  勾配

勾配は、のちに説明する、勾配降下法、誤差逆伝播法の勾配確認に必要な、ニューラルネットワークの重要な概念である。微分や偏微分の説明は過去を懐かしむためではない。

先の例ではx0とx1の偏微分の計算を変数ごとに計算したが、それではまとめて計算したいとする。(x0,x1)の両方の偏微分をまとめて、(δf/δx0, δf/δx1)として計算することを考える。この(δf/δx0, δf/δx1)のように、すべての変数の偏微分をベクトルとしてまとめたものを勾配(gradient)という。例えば、次のように実装される。

勾配が何を表すかというと、下図を参照されるとわかりやすい。

勾配は上図のように、一点に集中する場合もあれば、実際の山の起伏のようにそうならない場合もある。各地点において低くなる方向を指す。正確に言うと、勾配が示す方向(ベクトル)は、各場所において、関数の値を最も減らす方向である。

10 Deep Learning 数値微分

10.1 微分再考

微分・積分・古文・漢文は大人になっても役に立たないと言われている。
微積は数学、物理、電磁気学で、多様するし、漢文は英語と同時に学習することで、あれ?日本語の文法おかしくね?と発見することに役に立つ。
さらに、古文・漢文は様式美を学ぶ上で最適なのだが、それらすべて日常生活で役に立つことはほとんどない。

前置きはさておき、マラソンのランナーがスタートから10分間で2km走ったとすると、走る速さは2/10 = 0.2 [km/分]計算できる。
ここで行った計算は10分間の「平均速度」を求めたことになる。微分とは「ある瞬間」の変化量を求めたものである。

10分間で走った距離、1分間で走った距離、1秒間で走った距離、0.1秒間で走った距離、0.01秒間で走った距離、0.001秒間で走った距離
というように、どんどん時間を小さくすることで、ある瞬間の変化の量(ある瞬間の速度)がもとめられる。

微分とはある瞬間の変化の量をもとめるものである。数式では次式で定義される。
\[\frac{df(x)}{dx} = \displaystyle \lim_{h \to 0} \frac{f(x+h)-f(x)}{h} \tag{10.1} \]

左辺のdf(x)/dxは、f(x)のxについての微分、 xに対するf(x)の変化の度合い を表す記号である。式(10.1)で表される微分は、xの「小さな変化」によって、
関数f(x)の値がどれだけ変化するか、ということを意味する。その際、「小さな変化」であるhを限りなく0に近づけるが、これはlim(h→0)で表される。

式(10.1)を素直に実装するとhを微小な値にして以下のようになる。

hには10e-50(「0.0000…1」0が50個続く数)という小さな値を用いているが、ここで丸め誤差(ronding error)が問題になってくる。丸め誤差とは、小数の小さな範囲において数値が省略されることで(例えば、小数点第8位以下が省略されるといったこと)、最終的な計算結果に誤差が生じることをいう。上の図のように1e-50(=10e-50)は0.0となって正しく表現されない。つまり、小さすぎる値はコンピュータで計算する上で問題になる。微小な値hを10^(-4)を用いることでこの問題は解決する。
さらに、関数fの差分について、上の実装では、x+hとxの間での関数fの差分を計算しているが、そもそも、この計算には誤差が生じる。下図に示すように、「真の微分」は、xの位置での関数の傾き(接線という)に対応するが、今回の実装で行っている微分は、(x+h)とxの間の傾きに対応する。

 

このため、真の微分(真の接線)と今回の実装の値は、厳密には一致しない。この差異は、hを無限に0へと近づくことができないために生じるものである。図9-1で示すように、数値微分には誤差が含まれる。この誤差を減らす工夫として、(x+h)と(x-h)での関数fの差分を計算することで、誤差を減らすことができる。この差分は、xを中心として、その前後の差分を計算することから、中心差分という。(一方、(x+h)とxの差分は前方差分と言う)2つの改善点を元に、数値微分(数値勾配)の実装を行う。

ここで行っているように、微小な差分によって微分を求めることを数値微分(numerical differentiation)という。
一方、数式的に微分を求めることは、解析的(analytic)という言葉を用いて、例えば、「解析的に解く」とか「解析的に微分を求める」などという。
たとえば y = x^2 の微分は 解析的には dy/dx =2x として解くことができる。そのため x = 2 でのyの微分は4(2x = 2 * 2 = 4)と解くことができる。
解析的な微分は、誤差が含まれない「真の微分」として求めることができる。

10.2 数値微分の例

上の改善した数値微分を使って、簡単な関数を微分してみよう。(写経)
次の数式で表される2次関数である。
\[ y = 0.01x^2 + 0.1x \tag{10.2} \]
この式(10.2)をPythonで実装すると次のようになる。

続いて、この関数を描画する。描画のためのコードと生成されるグラフは次のようになる。

図10-2 f(x) = 0.01x^2 + 0.1xのグラフ

ここで計算した微分の値は、xに対するf(x)の変化の量であり、これは関数の傾きに対応する。なお、f(x) = 0.01x^2 + 0.1xの解析的な解は df(x)/dx = 0.02x + 0.1である。
このため、x=5,10での「真の微分」は0.02*5 + 0.1 =0.2 , 0.02 *10 + 0.1 = 0.3であり、上の数値微分との結果(numerical_diff()の出力)を比べると、厳密には一致しないが、
その誤差は非常に小さいことが分かる。ほとんど同じ値と見なすことができるぐらい小さな誤差である。

これをプロットすると下図のようになる。

図10-3 x=5での接線 直線の傾きは数値微分を使用

図10-4  x=10での接線 直線の傾きは数値微分を使用