Coursera Machine Learningのまとめ(ニューラルネットワーク編)

#はじめに
[ロジスティック回帰](https://qiita.com/KugeKuge/items/c04644fca0d9f90f6edd)に引き続き、ニューラルネットワークを勉強した。(Week4 & 5)
機械学習コースの次はディープラーニングを勉強したいので、難しいけどちゃんと勉強しよう。

#ニューラルネットワークとは
人の脳は、無数の神経細胞が集まってシグナルを伝達することで、非常に高性能な判断が可能となっている。
プログラム上で神経細胞の働きを模倣することで、よりよい学習器を作れないか、という試み。

最近ハードウェアの進歩で複雑な計算ができるようになったので、ディープラーニング等が急成長しているが、ニューラルネットワークの考え方自体はかなり昔からあるみたいですね。

#ニューラルネットワークの構造
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633548/62c64bcc-c977-5f12-6625-030bf5307515.png)

他のニューロンから複数のシグナルが伝わってきて、それらのシグナルを何らかの形で加工して出力する、というニューロンの動きを図示したのがこの図。

$h_θ(x)$は活性化関数と呼ばれ、前のニューロンから入ってきた値をどうやって次のニューロンに伝えるかを制御している。

この構造を多重化したのがニューラルネットワークで、前の層からの出力が次の層のすべてのニューロンに伝えられることを繰り返し、最終的な結果が出力される。
ニューロンから次のニューロンに値が伝わる時に、それぞれ重みパラメータ$Θ$がかかる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633548/80345626-9e3d-7d9d-c930-3c3abbf0e218.png)

```math
\begin{align}
&a_i^{(j)} = j番目の層のi番目のユニットの出力 \\
&Θ^{(j)}=j番目の層からj+1番目の層への重み行列
\end{align}
```
　 
活性化関数を$g$として各層の出力を数式にすると以下のようになる。
各層に0番目の出力(バイアス)が追加されている。

```math
\begin{align}
&a_1^{(2)}=g(Θ_{10}^{(1)}x_0+Θ_{11}^{(1)}x_1+Θ_{12}^{(1)}x_2+Θ_{13}^{(1)}x_3) \\
&a_2^{(2)}=g(Θ_{20}^{(1)}x_0+Θ_{21}^{(1)}x_1+Θ_{22}^{(1)}x_2+Θ_{23}^{(1)}x_3) \\
&a_3^{(2)}=g(Θ_{30}^{(1)}x_0+Θ_{31}^{(1)}x_1+Θ_{32}^{(1)}x_2+Θ_{33}^{(1)}x_3) \\
&h_Θ(x)=a_1^{(3)}=g(Θ_{10}^{(2)}a_0^{(2)}+Θ_{11}^{(2)}a_1^{(2)}+Θ_{12}^{(2)}a_2^{(2)}+Θ_{13}^{(2)}a_3^{(2)})
\end{align}
```

まとめるとこう書ける。

```math
\begin{align}
&a^{(1)}=x　(a_0^{(1)}を追加する)\\
\\
&z^{(2)} = Θ^{(1)}a^{(1)}\\
&a^{(2)} = g(z^{(2)})　(a_0^{(2)}を追加する)\\
\\
&z^{(3)} = Θ^{(2)}a^{(2)}\\
&a^{(3)} = g(z^{(3)}) = h_Θ(x)

\end{align}
```

#多クラス分類
多クラス分類を行う場合は、出力層の数を分類したいクラスの数にして、出力の1つだけが1のベクトルになるようにする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633548/dc7db796-7f93-9852-d9e6-0a223e081f7f.png)

出力層が4つの場合は以下のようになるので、それぞれにラベルを割り当てて分類する。

```math
h_Θ(x)\approx\begin{bmatrix}1\\ 0 \\ 0 \\ 0\end{bmatrix}
　h_Θ(x)\approx\begin{bmatrix}0\\ 1 \\ 0 \\ 0\end{bmatrix}
　h_Θ(x)\approx\begin{bmatrix}0\\ 0 \\ 1 \\ 0\end{bmatrix}
　h_Θ(x)\approx\begin{bmatrix}0\\ 0 \\ 0 \\ 1\end{bmatrix}
```

#コスト関数
ニューラルネットワークで活性化関数にシグモイド関数を使った場合のコスト関数を考える。
ロジスティック回帰の場合のコスト関数は以下だった。

```math
J(θ)=-\frac{1}{m}\sum_{i=1}^{m}\big[y^{(i)}log(h_θ(x^{(i)})) + (1-y^{(i)})log(1-h_θ(x^{(i)})) \big] + \frac{λ}{2m}\sum_{j=1}^{n}θ_j^2
```

ニューラルネットワークの場合はこれを行列Θに拡張したもので、さらにややこしい見た目になる。

```math
J(Θ)=-\frac{1}{m}\sum_{i=1}^{m}\sum_{k=1}^{K}\big[y_k^{(i)}log((h_Θ(x^{(i)}))_k) + (1-y_k^{(i)})log(1-(h_Θ(x^{(i)}))_k) \big] + \frac{λ}{2m}\sum_{l=1}^{L-1}\sum_{i=1}^{s_l}\sum_{j=1}^{s_{l+1}}(Θ_{j,i}^{(l)})^2
```

#コスト関数の最小化
次に、コスト関数の最小化を考える。
これまでと同様にコスト関数の微分(勾配)を求めたい。

```math
\frac{\partial}{\partial Θ_{i,j}^{(l)}}J(Θ)
```

#バックプロパゲーション(誤差逆伝播法)
出力層から始めて、順番に入力層までさかのぼりながら誤差を計算していく方法。
誤差が出力層から逆に伝播していくから、このような名前がついている。

m組の訓練データセット $\\{(x^{(1)},y^{1})...(x^{(i)},y^{i})\\}$ が与えられているとすると、手順は以下の通り。

* Step1
$$Set \ Δ_{i,j}^{(l)} = 0 \ for \ all (l,i,j)$$

* Step2
訓練データセット$t=1 \ to\ m$ について、以下1-5を繰り返す。

1. $Set \ a^{(1)} = x^{(t)}$

2. 入力層から順伝播を計算して、 $a^{(l)} \ for \ l=2,3,...,L$ を計算する。

3. 出力層について、予測値と実測値の誤差を計算する。
   $$δ^{(L)} = a^{(L)} - y^{(t)}$$

4. 以下の数式で、各層の誤差 $δ^{(L-1)}, δ^{(L-2)}...,δ^{(2)}$ を計算する。

$$δ^{(l)} =((Θ^{(l)})^Tδ^{(l+1)}).*g(z^{(l)})$$

$$g'(z^{(l)})=a^{(l)}.*(1-a^{(l)})$$

&nbsp;5. 誤差を行列に加算する。
   $$Δ^{(l)} = Δ^{(l)} + δ^{(l+1)}(a^{(l)})^T$$

* Step3
全ての訓練データセットについて誤差を計算したの後、訓練データ数で割ったものが、$J$の$Θ$における偏微分となる。これまでと同様、$j=0$の場合は正則化項を足さない。

```math
\begin{align}
&\frac{\partial}{\partial Θ_{i,j}^{(l)}}J(Θ)=D_{i,j}^{(l)} = \frac{1}{m}(Δ_{i,j}^{(l)}+λΘ_{i,j}^{(l)})　if \ j\neq0\\
&\frac{\partial}{\partial Θ_{i,j}^{(l)}}J(Θ)=D_{i,j}^{(l)} = \frac{1}{m}Δ_{i,j}^{(l)}　　　　　　if \ j=0
\end{align}
```

#勾配チェック
バックプロパゲーションは実装がうまくいっているように見えても、よくわからないところでバグることが多い。
そのため、実装したプログラムが正しいかをチェックすることが必要になる。

勾配(微分)とはすなわち、ある点における傾きなので、$J$の微分は十分に小さい値$ε$を用いて、以下のように表せる。

```math
\frac{\partial}{\partial Θ_j}J(Θ)\approx\frac{J(Θ_1,...,Θ_j+ε,...,Θ_n)-J(Θ_1,...,Θ_j-ε,...,Θ_n)}{2ε}
```

この式で算出した値と、実装したバックプロパゲーションの結果の差が十分に小さければ、実装が間違っていないと判断できる。

#重みのランダム初期化
ニューラルネットワークの場合、各層の$Θ$を初期化する際に全て0で初期化してしまうと、すべてのノードが同じ値で更新されていまい、うまく学習できない。
そこで、以下の数式を満たすように、0付近の小さなランダム値でそれぞれの$Θ_{ij}^{(l)}$を初期化してあげる。

$$-ε \leq Θ_{ij}^{(l)} \leq ε$$

#まとめ
出力層から誤差を逆向きに計算していくことで勾配が計算できるとは、なんだか不思議な感じがする、というか正確には理解しきれていない。
プログラム課題もこれまでで一番時間がかかったし疲れた。

ただ、よくよく見てみると1つ1つの処理はシンプルで、とてもよくできた仕組みだということが分かる。
実際に使う時にはライブラリが勝手に計算してくれるけど、原理を理解していると応用が利くんじゃないかと思った。

どうやらこの方法は計算コストを劇的に減らすことができる効率の良い方法のようで、ノーベル賞級の発明だという意見も見かけたけど、いったいどんだけ頭良かったらこんなのを思いつくんだ…