いよいよ今回で機械学習入門も最終回、ニューラルネットワークモデルです。
ニューラルネットワークと聞いて難しそうだなと思うかもしれませんが今までやってきたことを少し拡張するだけです。今までは元の値(X)とそれにかける重み行列(W)と確率関数(sigmoid, softmax)などと結果(Y)だけでしたがYの前に、さらに別の重み行列をかけて活性化関数(今までは確率関数を使ってた)での処理を複数追加することで数式の表現力(複雑さ)を増やしより精緻な最適化ができるようにしようというコンセプトです。
簡単に書くと今までは
Y = f(W・X) |
例えば隠れ層が1つあると仮定すると同じようなことを2回繰り返しているだけです。(基本的に隠れ層の数を増やせば精度が上がっていくのでなるべく増やすのがいいですが計算量の費用対効果が下がっていくので計算能力と相談です。)
u = f1(W1・X) |
入力層 | x |
隠れ層 | u = f(a) |
出力層 | y = f(b) |
重み行列 | v, w 隠れ層の数だけ増える。 |
中間データ | a = x @ v b = u @ w |
活性化関数 | f(x), 隠れ層の数だけ増える。今回は同じもの。 |
入力層をx,隠れ層u、出力層yとしx => uの重み行列、活性化関数をV, f(x)、u => yはW, f(u)とする。
実際は隠れ層の数や層のつなげ方、どの活性化関数にするかなどはデータにより答えが変わるので試行錯誤してデータにあったモデルを探すしかありません。今回は適当
計算の流れはこんな感じです。
a = V・x |
いつものように残差平方和で計算していけば楽勝…ではないです。
ここで隠れ層uの誤差ベクトルudの問題です。正解ベクトルutが与えられていないため計算ができません。
そこで学習時は通常と逆方向に計算していく誤差逆伝播とよばれるやり方でやります。ここでは導出方法は割愛しますが興味のある人は最後の補足を読んでください。
ΘL/Θwij = uj * ΘL/Θbi |
この式を眺めてf'(ai) * Σ(ydl*wli)がudiだったらキレイだなぁと思いませんか。
そうなんです。いつものことですがなぜイコールなの?という疑問はナンセンスです。矛盾がなく計算しやすさで大きな恩恵があるから置き換えるだけです。このことにより層毎の計算方法が同じになりモデルをどんなに複雑に組んでもそれぞれの計算は比較的シンプルになります。
udi = f'(ai) * Σ(ydl*wli) |
なので学習時の計算の流れは以下の通りです。
あとはPythonでの実装です。
活性化関数について全く説明をしておりませんでしたがこの計算方法ではシグモイドなどを使うと大きな不都合がでてきます。出力層に一番近い誤差以外では毎回活性化関数の微分をかけることになります。シグモイドの微分は過去の記事のようにf'(x) = f(x)(1 - f(x))でf(x)の最大値は1なのでその微分のf'(x)は最大で0.5 * 0.5 = 0.25となり減少のバイアスが隠れ層が多く入力層に向かうにつれて大きくなり誤差が伝わらないため学習が意味のないものになっていってしまいます。udi = f'(ai) * Σ(ydl*wli)と定義したためです。いいことばかりじゃないですね。そこで登場したのがrelu関数というもので今回はこれを使います。relu関数とはx <= 0のとき0, x > 0のときxとなる関数でその微分は傾きなのでx <= 0のとき0, x > 0のとき1になります。
もう一つ問題があります。データと中間層が同じ次元数だと単に出力層のコピーに近いものになってしまうため表現力の向上にあまり寄与しなくなってしまいます。そこで中間層の次元数は異なるものを使ってみます。
(*現在ではアルゴリズムの進化により重みパラメータにランダム要素を加えることで誤差が伝わらない問題や次元圧縮・拡張がなくても普通に特徴を表現できるようになっています。)
relu関数は
x * (x > 0.0) |
reluの微分関数は
1.0 * (x > 0.0) |
x => a、u => bの計算は
a = x @ v |
よって誤差ベクトルydは
yd = yp - yt |
|
前の記事のように微分計算して誤差を反映します。
|
|
もう一つ忘れてはいけないことがあります。今までのように次元数が2や3と少ないときはいいのですが今回のように128などとすると重み行列の初期値を上手く設定しないと収束してくれません。reluを使うときにはHeの初期値というものを使うのが一般的です。以下のように正規分布のランダムな値を√{次元数}/2で割ります。
v = np.random.randn(D, H)/np.sqrt(D/2)
w = np.random.randn(H, C)/np.sqrt(H/2)
あとは全部まとめるだけです。
|
いかがでしたでしょうか。
機械学習がどのように発展してきたかを感じていただければ幸いです。被っているところも多く思ったよりシンプルだと感想を持たれる方も多いのではないでしょうか。
本編で使った損失関数の微分結果を導出します。
まず以下の2式は 前の記事 を参照ください。
|
次の2式がどこから来たのか考えます。
|
まずΘL/Θvij = xj * ΘL/Θaiを導出します。
ΘL/Θvij を分解してみます。
|
Θai/Θvijを考える。
Θai/Θvijのaはxベクトルとv行列の積でxを(1,2)のベクトル、vを(2,2)の行列で考えると
|
これをv11で微分するとx1そのほかも同様なので
|
代入して
|
次にΘL/Θai = f'(ai) * Σ(ydl*wli) を導出します。
|
まずΘL/Θuiを考えて
全微分という公式がありまして
例えばL(x,y,z)を全微分すると
|
それを例えばΘuとかでわると
|
ここでΘL/Θblを考えると前の記事より以下の通り。
|
またΘbl/Θuiを考えると Θai/Θvij の例と似たような感じでwliとなる。
|
Θui/Θaiはu = f(a)なので微分はもちろんf'(a)
それぞれ代入して
|