JDLA_E資格の深層学習[勾配降下法]

#1. 深層学習における学習

深層学習における学習とは、最初に決めたモデルの適当なパラメータを、結果から間違い度合いを学習してパラメータを更新することを何度も続けて、間違い度合いの少ないパラメータを求めることである。

#2. 勾配降下法

勾配降下法は、数式で表すと以下のようになる。学習率$\varepsilon$の値が適切でないと、いつまでも収束しない。大きすぎると発散（解が求まらない）するし、小さすぎると時間がかかる。また、ちいさすぎる場合、谷が複数ある場合、間違った解（局所最適解）を求めてしまうリスクがある。

~~~math
w^{(t+1)} = w^{(t)}  - \varepsilon \nabla E
~~~

![勾配降下法.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1436941/b93fd23c-1f21-cd8a-437a-057a7c4fb4e2.png)

#3. 勾配降下法のバリエーション

勾配降下法には、確率的勾配降下法（SGD）と、ミニバッチ勾配降下法という派生がある。

|--|勾配降下法（バッチ）|確率的勾配降下法|ミニバッチ勾配降下法|
|:--|:--|:--|:--|
|使用データ|全データの平均誤差|ランダムな一部のデータの誤差|ランダムに分割した一部のデータの誤差|
|計算コスト|大|中|小|
|計算並列化|×|×|〇：ミニバッチを並列実行可能|
|最適解への収束|難|リスク少|リスク少|
|オンライン学習|×：最初に全データ必要|〇：一部のデータがそろったら学習開始可能|〇：一部のデータがそろったら学習開始可能|
|必要メモリ量|大|小|小|

オンライン学習ができないと、最初に全部のデータが揃えないといけないので、結構実務的に大変。
また、データが多すぎると、計算のためのメモリが足りない問題が発生する。
実務的にはオンライン学習が出来ないと実行出来ない。
学習に使うjpg画像とかを、学習用のフォーマットに変換すると、ファイルサイズが数十倍になる。

また、ミニバッチ勾配降下法の一個一個のバッチの計算の時には、パラメータ更新されず、全部のバッチが終わってから更新される。このため、CPUのスレッド並列化、GPUでSIMD命令で並列実行が可能である。

深層学習をする際には、ミニバッチ勾配降下法が一般的になっている。

#3. 誤差勾配の計算

勾配降下法を使う場合、以下の式が基本であることが分かった。
では具体的に $\nabla E$の部分（誤差Eの勾配）をどうやって計算すればよいか。

~~~math
w^{(t+1)} = w^{(t)}  - \varepsilon \nabla E \\

\nabla E = \frac{\partial E}{\partial w} =[\frac{\partial E}{\partial w_1} \cdots \frac{\partial E}{\partial w_m}] 
~~~

##3.1 数値微分での誤差勾配の求め方

実装的には、wに対して微小な数値hを生成して、傾きを計算してやることができる。

~~~math
\frac{\partial E}{\partial w_m} 
\approx 
\frac
{E(w_m +h) - E(w_m - h)}
{2h} 
~~~

ところが、これは計算負荷が高い。

##3.2 誤差逆伝播法での誤差勾配の求め方

前節の通り、数値微分の利用は現実的でないため、代わりに誤差逆伝播法というものが使われている。
これは、ニューラルネットワークの後半の方で求めた微分値を、その前方の微分値を計算する際に使う。ということを繰り返して、ニューラルネットワークの入力側の方まで順番に求めていこうという手法である。
ようは、直接$\frac{\partial E}{\partial w}$を求めるのが難しいので、間接的に求めてやろうということである。

~~~math
\frac{\partial E}{\partial w} = 
\frac{\partial E}{\partial y}
\frac{\partial y}{\partial u}
\frac{\partial u}{\partial w}
~~~


![誤差逆伝搬.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1436941/398a2a6c-2117-6f5f-997f-0a65469585f9.png)


**伝搬のさせかた**
❶ 最終の誤差Eに着目。Eはyとdで決まるが、dは固定なのでこれは、yの関数である。ので、yでの微分値$\frac{\partial E}{\partial y}$を求める
❷ 出力層のyに着目。これは、u2の関数である。$\frac{\partial y}{\partial u}$を求めて、さらに、前に求めた$\frac{\partial E}{\partial y}$をかけると、Eに関するuの微分値が出る。$\frac{\partial E}{\partial y}\frac{\partial y}{\partial u}$
❸ 中間層のu2に着目。これは、w2と、zとbで決まる関数である。ここでは、w2を更新したいので、w2に関して微分する。$\frac{\partial u}{\partial w}$。そして、前段までの微分結果をかけてやると、yに対するw2の微分値を求める事が出来る。$\frac{\partial E}{\partial y}\frac{\partial y}{\partial u}\frac{\partial u}{\partial w}$

上記が大きな流れである。この流れを、w1や、b1、b2に対しても実施する。

**実際の更新対象パラメータw**

ここで注意したいのは、更新すべきパラメータwは、行列形式であるはずなので、上記の式的には、wのどの要素を更新するのか意識しておく必要がある。

![重み.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1436941/3c35383e-5964-122f-055d-725c0886d8cc.png)


~~~math
w^2 = 
\begin{pmatrix}
w_{11}^2 & w_{12}^2 \\
w_{21}^2 & w_{22}^2
\end{pmatrix}

~~~

~~~math
\frac{\partial E}{\partial w_{ji}^{2}} = 
\frac{\partial E}{\partial y}
\frac{\partial y}{\partial u^2}
\frac{\partial u^2}{\partial w_{ji}^2}
~~~


##3.3  実装

下図のようなモデル構成の場合のW2の更新を逆伝播法で考える。

![誤差逆伝搬実装例.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1436941/2de30a85-8243-8a23-66a6-900551309979.png)


パラメータ更新の基本は、

~~~math
w^{(t+1)} = w^{(t)}  - \varepsilon \nabla E \\
w^{(t+1)} = w^{(t)}  - \varepsilon \frac{\partial E}{\partial w} \\
~~~

ここで、対象を$w_{ji}^2$とする。jは出力側のニューロンの番号、iは入力側のニューロンの番号、肩字は層番号。

~~~math
w_{ji}^{(2)(t+1)} = w_{ji}^{(2)(t)}  - \varepsilon \frac{\partial E}{\partial w_{ji}^{(2)}} \\
~~~

~~~math
\frac{\partial E}{\partial w_{ji}^{(2)}} = 
\frac{\partial E}{\partial u_j^2}
\frac{\partial u_j^2}{\partial w_{ji}^2}

~~~

公式通りなら最初はyでの微分だが、y=u2なので、u2での微分。

ここで、損失関数はMSE

~~~math
E=MSE = \frac{1}{2} \sum_{j=1}^N (y_j - d_j)^2　\\

\frac{\partial E}{\partial u_j^2} = y_j - d_j
~~~

~~~py
# 出力層でのデルタ
delta2 = y-d
~~~

次にu2の層に着目

~~~math
u_j^2 = \sum w_{ji}^2 z_i^1 + b_i\\
\frac{\partial u_j^2}{\partial w_{ji}^2} = z_i^1　\\
~~~

少し補足

~~~math
\frac{\partial u_j^2}{\partial w_{ji}^2} =
\frac{\partial }{\partial w_{ji}^2} ((w^2 z1 + b)) = 
\frac{\partial }{\partial w_{ji}^2} 
\Biggl(
\begin{vmatrix}
w_{11} & w_{12} &  \cdots & w_{1i}\\
\cdots \\
w_{j1} & w_{j2} & \cdots & w_{ji} & \cdots\\
\cdots \\
\end{vmatrix}

\begin{vmatrix}
z_1\\
z_2\\
\cdots \\
z_i \\
\cdots\\

\end{vmatrix}
+
\begin{vmatrix}
b_1\\
b_2\\
\cdots \\
b_i \\
\cdots\\
\end{vmatrix}
\Biggr)

=
\begin{vmatrix}
0\\
0\\
\cdots \\
z_i \\
\cdots\\
\end{vmatrix}

~~~

上記の式の右辺において、$w_{ji}$ での微分となると、ほとんどの項が定数として消えていき、最終的に$z_i$のみが残る。

中間層での傾き

~~~py

grad_w2 = (y-d)(z1) 

~~~


#4. 勾配消失問題

誤差逆伝播法を使うときの大きな問題の一つとして、勾配消失問題がある。
深層学習モデルは、誤差関数の値が小さくなるように、最適なパラメータ$w$を求める事が必要。
誤差逆伝播法とは、このパラメータ$w$を見つけるために、誤差関数の微分値（勾配）をNNの後ろから前へ伝搬させていって、最適な$w$を見つけるとうものだった。
ここで、微分値を連鎖律によって、何回も掛け算していくことになる。この微分値であるが、中間層の活性化関数にシグモイド関数とかを使っていると、微分値は０から１の間になる。つまり、０．３とか０．７とか。この１未満の微分値の掛け算を何回もすると、NNの前段にくるころには、０近くになり、パラメータ$w$は何も更新されなくなってしまう。つまりパラメータの学習が出来ない。これが勾配消失問題である。


~~~math
w^{(t+1)} = w^{(t)}  - \varepsilon \frac{\partial E}{\partial w} \\
~~~

![誤差逆伝搬.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1436941/398a2a6c-2117-6f5f-997f-0a65469585f9.png)

~~~math
\frac{\partial E}{\partial w} = 
\frac{\partial E}{\partial y}
\frac{\partial y}{\partial u}
\frac{\partial u}{\partial w}
~~~

##4.1　シグモイド関数の微分

シグモイド関数は０から１の結果を取り、微分値は最大で0.25である。

~~~math
y = \frac{1}{1+e^{(-x)}} \\
y = (1+e^{-x})^{-1}\\
y' = -1*(1+e^{-x})^{-2}*(e^{-x}) \\
y' = \frac{e^{-x}}{(1+e^{-x})^{-2}}
~~~

![sigmoid微分値.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1436941/a9147806-c524-c470-fa63-6fcabe13806f.png)


##4.2 勾配消失問題解決策

勾配消失問題の対策として3種類ある。

+ 活性化関数工夫（シグモイドは使うな）
+ 重みの初期値の工夫
+ バッチの正規化

####4.2.1 活性化関数（Relu関数）

シグモイド関数を使わずに、Relu関数を使う。
シグモイド関数の微分値は、1未満になる可能性が高いが、Relu関数の微分値は、０か１となる。このため、微分の連鎖によって勾配が消失していくことはない。
また、微分値が０の場合、その時の重みは使われないことになるので、必要な特徴だけを使うモデルになる。これを**スパース化**という。



####4.2.2　重み初期値の工夫(Xavier,He)

######4.2.2.1 シグモイド関数＋初期値（標準正規分布）の問題
そもそも、NNの重みの初期値は、乱数が使われることが多い。いろんなタイプの特徴を抽出できるように最初はランダムにしておく必要がある。
この乱数の作り方にもいろいろある。よく使われるのは、標準正規分布（重みの平均は０、分散が１）である。しかしこれだとうまく学習できないことが多い。

~~~math
W_1 = numpy.random.randn( 2,2);  2×2の標準正規分布の乱数の重み\\
~~~

![正規分布の重み.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1436941/6b5c398f-9c2e-86e0-bf77-26cd0b811854.png)

活性化関数にシグモイドを利用して、標準正規分布で初期値を与えた場合、各中間層の出力値は、０か１に偏ってしまい、まとまな性能を出すことが出来ない。


######4.2.2.2 シグモイド関数＋初期値（Xavier)

そこで、初期値をとるとき、乱数をその重みの直前の中間層のノード数の平方根で割ってやるやりかたが、Xavierの初期値である。

~~~math
XavierのW_1 = \frac{numpy.random.randn( 行列サイズ)}{ \sqrt{直前のノード数}}　　　\\
~~~

こうすること、中間層の出力値が、０から１の間でばらついて出力されて、ある程度の性能を出すことができるようになる。

######4.2.2.3 Relu関数＋初期値（He)

活性化関数に、シグモイドとかtanhとかのS字カーブのものを使わないとき。つまりRelu関数のときには、Xavierでも標準正規分布でもなく、Heの式を使うと良いといわれる。

~~~math
HeのW_1 = \frac{numpy.random.randn( 行列サイズ)}{ \sqrt{ \frac{2}{直前のノード数}}}　　　\\
~~~

####4.2.3 バッチ正規化

バッチ正規化とは、学習に使うミニバッチ単位のデータの方よりを無くしていって、学習を効率化する手法。
GPUはどうせ64枚くらいまでの画像しか一度に扱えない。
また、正規化するので、過学習が抑えられる。
正規化とは、データを一定のルールに従って形を整えることである。型にはめる。
つまり入力データの形を整えるわけである。整ったデータなので、学習効率が良いというわけである。

1. ミニバッチデータの平均取得
1. ミニバッチデータの分散取得
1. ミニバッチデータの正規化
1. ミニバッチデータの調整（NNで扱いやすいように）

~~~math
平均値：u_t = \frac{1}{N} \sum_{i=1}^{N_t} x_{ni} \\
分散値：\sigma_t^2 = \frac{1}{N} \sum_{i=1}^{N_t} (x_{ni} - u_t)^2 \\
正規化値：\hat{x_{ni}} = \frac{x_{ni} - u_t}{ \sqrt{\sigma_t^2  + \theta}}　\\
調整値：y_{ni} = \gamma \hat{x_{ni}} + \beta \\

※\gamma と、\beta　は、調整値
~~~


# 5. 誤差逆伝播の例題

誤差逆伝播の例題として、下図の赤？を求める問題がある。

~~~math
x_2 = x_1 * b_1 \\
y=x_2 * b_2 \\
L = y - t\\
~~~

![逆伝播例題.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1436941/bdd1f8ba-8385-0d9b-b90c-251682490f00.png)

~~~math
\frac{\partial L}{\partial b_1} = 
\frac{\partial L}{\partial y}
\frac{\partial y}{\partial x_2}
\frac{\partial x_2}{\partial b_1}
\\
=1 *  b_2 * x_1 \\
=1 * 8 * 5 \\
= 40 \\

~~~