Wikipediaにある実装を参考にしてニューラルネットワーク(NN)による関数の学習(フィッティング)を行う。 ネットワークとしては2層からなる1入力1出力のものを考える。
layer 0 | layer 1 | layer 2 | ||
---|---|---|---|---|
input | Affine | activation | Affine | output + loss |
$x$ | $a_1=w_1x+b_1$ | $z_1=f(a_1)$ | $a_2=w_2z_1+b_2$ | $y=a_2, L(y)$ |
※ネットワークのパラメータの初期値は[-1,1]の一様分布から生成した疑似乱数に重みをかけた値になっている。
※sigmoidやtanhは原点付近に非線形性がないので、大きめの重み(3ぐらい)を設定して入力値を非線形領域に対応させる必要がある。
入力値と教師ラベルの揃った訓練データがあれば、これを基準に学習できる。 今の場合は目的の関数を$y(x)=2x^2-1$として、少ないデータ点からこれを補間することが目標である。
NNの学習は損失関数と呼ばれる性能の悪さを表す1実数量を最小化する最適化問題となる。 フィッティング(回帰問題)の場合、損失関数として次の二乗誤差がよく用いられる。
\[ L=\sum \frac{1}{2}(y^i-t^i)^2 = \frac{1}{2}(y^i-t^i)(y_i-t_i). \]ここで$y$はNNの出力値、$t$は教師ラベルの値(真の値)である。 この計算を行う層についてはloss-layerなどと呼ばれる。
最適化問題の手法には色々あるが、 シンプルなものとしてはパラメータ空間における損失関数の勾配(gradient)から最小値をとるパラメータを探る勾配降下法がある。 よって次の損失関数の勾配を求めることが必要となる。
\[ \global\def\dd{\mathrm{d}} \global\def\pdv#1#2{\frac{\partial #1}{\partial #2}} \pdv{L}{W^a_{[n]}}. \](ここで$W^a_{[n]}$は全$n$層まで各層のweightとbiasパラメータ全部ひっくるめた量として書いた。)
勾配は偏微分で与えられているので、各レイヤーについてchain rule(連鎖律)を使って遡っていくことができる。
二乗誤差の全微分を取ると、
ここでloss layerの勾配への寄与は$(y_k-t_k)$であることがわかる。 このように各レイヤーを遡って勾配を求める方法を誤差逆伝搬法(error backward propagation)という。
今の場合、出力レイヤーは$y=w_2 z_1 + b_2$で計算されるので (添字が煩雑になるので$w_2=\bar{w},w_1=w$のように表す。)
\[ \begin{aligned} \dd y^k =& \pdv{\bar{w}^k_l}{\bar{w}^i_j} z^l \dd \bar{w}^i_j + \pdv{\bar{b}^k}{\bar{b}_i} \dd \bar{b}^i + \bar{w}^k_l\dd z^l\\ =& z^l \dd \bar{w}^k_l + \dd \bar{b}^k + \bar{w}^k_l\dd z^l. \end{aligned} \]または行列的に書くと
\[ \global\def\bvec#1{\mathbf{#1}} \dd\bvec{y} = (\dd w_2)\bvec{z}_1 + \dd\bvec{b}_2 + w_2\dd\bvec{z_1}. \]更に遡ってレイヤー1の値、$z_1$の微分を求める。 $z_1=f(w_1 x+b_1)$となって今度は活性化関数がかかっていることに注意すると $a_1=w_1 x+b_1$として
\[ \dd z^l = \pdv{f}{a_l}\dd a^l,~~~ \dd\bvec{z}_1 = \pdv{f}{\bvec{a}_1} \circ\dd\bvec{a}_1. \]ここで活性化関数$f$の部分については通常の行列の演算からは外れているので、通常の行列の積ではなくアダマール積$\circ$となる。 (和を取らない。) 最後にレイヤー1のアフィンレイヤー部分は前と同様に
\[ \dd a^l = x^m \dd w^l_m + \dd b^l,~~~ \dd\bvec{a}_1 = (\dd w_1)\bvec{x} + \dd\bvec{b_1} \]これらの結果から損失関数の全微分に代入すると次のように得られる。
\[ \begin{aligned} \dd L =& (y_k-t_k)\left[ z^l \dd \bar{w}^k_l + \dd \bar{b}^k + \bar{w}^k_l\pdv{f}{a_l}\left( x^m\dd w^l_m + \dd b^l \right) \right]\\ =& (\bvec{y}-\bvec{t})\cdot\left[ (\dd w_2)\bvec{z}_1 + \dd\bvec{b}_2 + w_2 \pdv{f}{\bvec{a}_1}\circ\left( (\dd w_1)\bvec{x} + \bvec{b}_1 \right) \right]. \end{aligned} \]この式を見ると、NNの各レイヤーによる寄与がそれぞれ現れていることがわかる。
\[ \dd L = \underbrace{(\bvec{y}-\bvec{t})}_{\text{loss}}\cdot\left[ \underbrace{(\dd w_2)\bvec{z}_1 + \dd\bvec{b}_2 + w_2}_{\text{Affine 2}} \underbrace{\pdv{f}{\bvec{a}_1}}_{\text{act.}} \circ\left( \underbrace{(\dd w_1)\bvec{x} + \bvec{b}_1}_{\text{Affine 1}} \right) \right] \]ここから各パラメータについての勾配を計算する式を求めることができる。 まず出力層については
\[ \pdv{L}{\bar{b}^k} = y_k-t_k,~~~ \pdv{L}{\bvec{b}_2} = \bvec{y}-\bvec{t}, \]\[ \pdv{L}{\bar{w}^k_l} = (y_k-t_k) z^l,~~~ \pdv{L}{w_2} = (\bvec{y}-\bvec{t})\otimes\bvec{z}. \]第一層については
\[ \pdv{L}{b^l} = (y_k-t_k)\bar{w}^k_l\pdv{f}{a_l},~~~ \pdv{L}{\bvec{b}_1} = w_2^\mathrm{T} (\bvec{y}-\bvec{t}) \circ \pdv{f}{\bvec{a}_1}, \]\[ \pdv{L}{w^l_m} = (y_k-t_k)\bar{w}^k_l\pdv{f}{a_l} x^m,~~~ \pdv{L}{w_1} = w_2^\mathrm{T} (\bvec{y}-\bvec{t}) \circ \pdv{f}{\bvec{a}_1} \otimes \bvec{x}. \]math.transpose(A)
math.kron(A, B)
math.dotMultiply(A, B)