ディープラーニングを基本から学ぶ　Part4 学習に関するテクニック

◆前章：[Part3 誤差逆伝播法でニューラルネットワークの学習を高速化する](http://qiita.com/nvtomo1029/items/95b82c2ab4fc0a1b5aad)
◆次章：[Part5 畳み込みニューラルネットワーク](http://qiita.com/nvtomo1029/items/601af18f82d8ffab551e)

---
##**■4.学習に関するテクニック**

###**▼4.1.パラメータの更新に関するテクニック**
ニューラルネットワークにおける学習の目的は、損失関数の値をできる限り小さくするようパラメータを最適化することである。
この最適化は非常に難しい問題で、様々な手法が出てきている。
どのようなニューラルネットワークにも対応できる優れた手法というのは今のところなく、構築したニューラルネットワークに最適な更新手法は自分で探す必要がある。

####**▽4.1.1.SGD**
これまでの章で用いた手法。
SGDは、確率的勾配降下法(Stochastic Gradient Descent)の略。

```math
W ← W - \eta{\frac{{\partial}L}{{\partial}W}}
```
※$W:更新する重みパラメータ、{\frac{{\partial}L}{{\partial}W}}:Wに関する損失関数の勾配、\eta:学習係数$

```py3
# coding: utf-8
import numpy as np

# 確率的勾配降下法(Stochastic Gradient Descent)
class SGD:
    # 初期化
    def __init__(self, lr=0.01): # lr:学習係数
        self.lr = lr
        
    # 更新
    def update(self, params, grads): # params:重みパラメータ grads:勾配
        for key in params.keys():
            params[key] -= self.lr * grads[key] 
```

SGDは仕組みが単純で理解しやすいが欠点も多く指摘されている

・学習係数を指定する必要があり、その値によっては非効率になる場合がある
・関数の形状が等方的でない(※)場合に非効率な経路で探索することになる

```math
※等方的でない関数例\\
f(x, y) = {\frac{1}{20}}x^2 + y^2
```

####**▽4.1.2.Momentum**

モーメンタムと読み、機械学習でMomentumと言うと、Momentum SGD(Stochastic Gradient Descent)のことを指す。

```math
v ← \alpha v - \eta{\frac{{\partial}L}{{\partial}W}}
\\
W ← W + v
```
※$W:更新する重みパラメータ、{\frac{{\partial}L}{{\partial}W}}:Wに関する損失関数の勾配、\eta:学習係数、\alpha:モーメンタム係数$

この中で、$\alpha:モーメンタム係数$というのがイマイチ理解できてないが、重みパラメータの揺れ(振動)を緩やかにするための係数で、これを用いることで重みパラメータの更新経路が緩やかになるらしい。

```py3
# coding: utf-8
import numpy as np

# Momentum SGD
class Momentum:
    # 初期化
    def __init__(self, lr=0.01, momentum=0.9): # lr:学習係数 momentum:モーメンタム係数
        self.lr = lr
        self.momentum = momentum
        self.v = None
     
    # 更新   
    def update(self, params, grads): # params:重みパラメータ grads:勾配
        if self.v is None:
            self.v = {}
            for key, val in params.items():                                
                self.v[key] = np.zeros_like(val)
                
        for key in params.keys():
            self.v[key] = self.momentum*self.v[key] - self.lr*grads[key] 
            params[key] += self.v[key]
```


####**▽4.1.3.AdaGrad**

アダグラッドって読むんかな...
パラメータの要素ごとに適応的に学習係数を調整しながら学習を行う手法。
ニューラルネットワークの学習では、学習係数の大小が学習時間や学習精度に大きな影響を与えるが、これを解決する有効なテクニックである。


```math
h ← h + {\frac{{\partial}L}{{\partial}W}} \times {\frac{{\partial}L}{{\partial}W}}
\\
W ← W - \eta{\frac{1}{\sqrt{h}}}{\frac{{\partial}L}{{\partial}W}}
```
※$W:更新する重みパラメータ、{\frac{{\partial}L}{{\partial}W}}:Wに関する損失関数の勾配、\eta:学習係数$

上の式の×は、行列の要素ごとの掛け算を意味する。
hで、これまでの勾配の二乗和を保持し、重みパラメータの更新時に${\frac{1}{\sqrt{h}}}$を乗算することで学習のスケールを調整する。


```py3
# coding: utf-8
import numpy as np

# AdaGrad
class AdaGrad:
    # 初期化
    def __init__(self, lr=0.01): # lr:学習係数
        self.lr = lr
        self.h = None

    # 更新        
    def update(self, params, grads): # params:重みパラメータ grads:勾配
        if self.h is None:
            self.h = {}
            for key, val in params.items():
                self.h[key] = np.zeros_like(val)
            
        for key in params.keys():
            self.h[key] += grads[key] * grads[key]
            params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7) # 微小な値:1e-7を加算することで0で除算されるのを防ぐ
```

###**▼4.2.重みパラメータの初期値をどう設定するか？**

設定した値により、ニューラルネットワークの学習の成否が左右される。

####**▽4.2.1.活性化関数に応じて設定する**

**Xavierの初期値**
Sigmoid関数のような線形な活性化関数に適した手法で、前層のノードの個数を n とした場合に、${\frac{1}{\sqrt{n}}}$ の標準偏差を持つ分布を使うものである。

**Heの初期値**
ReLUに適した手法で、前層のノードの個数を n とした場合に、${\sqrt{\frac{2}{n}}}$ の標準偏差を持つ分布を使うものである。

####**▽4.2.2.初期値に依存しない仕組みにする**

重みパラメータの初期値を適切に設定することで、各層のアクティベーション(活性化関数適用後の出力データ)の分布に適度な広がりを持たせることができ、それにより学習がスムーズに行えるようになる。
一方、各層で適度な広がりを持つように、”強制的”にアクティベーションの分布を調整することで、初期値に依存しないアプローチも存在する。
それが、**Batch Normalization** である。
Batch Normalizationは、登場したのが2015年とまだ新しい手法であるが、すでに広く使われている手法らしい。
Batch Normalizationでは、
　・学習を行う際のミニバッチごとに
　・データの分布の平均が0、分散が1になるように
正規化を行う。

Batch Normalizationの利点としては以下があげられる。
　・学習を速く進行させることができる
　・初期値にそれほど依存しない
　・過学習を抑制できる

###**▼4.3.過学習を抑制する**
機械学習では、訓練データに含まれないまだ見ぬデータであっても正しく識別できる汎化性能の高いモデルが望まれる。
過学習は、機械学習において優先度の高い課題である。

####**▽4.3.1.Weight decay(荷重減衰)**
大きな重みを持つことに対してペナルティを課すことで過学習を抑制しようという手法。
過学習抑制に昔からよく用いられている手法である。

Weight decayでは、すべての重みパラメータに対して、損失関数に${\frac{1}{2}} \lambda W^2$ を加算する。
また、それにより重みパラメータの勾配を求める計算では $\lambda W$ を加算する。
ここで、$\lambda$ : Weight decayの強さをコントロールするハイパーパラメータ、W : 重みパラメータである。

####**▽4.3.2.Dropout**
ニューロンをランダムに消去しながら学習する手法。
ニューラルネットワークのモデルが複雑になってきて、Weight decayだけでは対応が困難になってきた場合に用いられる。

```py3
# coding: utf-8
import numpy as np

class Dropout:
    # 初期化
    def __init__(self, dropout_ratio=0.5): # dropout_ratio:消去するニューロンのしきい値
        self.dropout_ratio = dropout_ratio
        self.mask = None

    # 順伝播
    def forward(self, x, train_flg=True): # train_flg:訓練時にtrue、テスト時にfalse
        # 訓練時のみニューロンを消去する
        if train_flg:
            # xと同じ形状の配列(値は0～1)をランダムに生成し、dropout_ratioの値の大小でtrue(1), false(0)をセット
            self.mask = np.random.rand(*x.shape) > self.dropout_ratio
            # xに前の行でセットしたマスクを掛けて返す
            return x * self.mask
        # テスト時
        else:
            return x * (1.0 - self.dropout_ratio)

    # 逆伝播
    def backward(self, dout):
        return dout * self.mask

```

###**▼4.4.ハイパーパラメータの最適化**
ハイパーパラメータとは、重みやバイアス以外のパラメータのことであり、例えば各層のニューロンの数、バッチサイズ、パラメータ更新の際の学習係数などのことを言う。
ハイパーパラメータの最適化については、これまであげたパラメータのように洗練された手法はなさそう。

①最初に大まかに範囲を設定
②設定した範囲内からランダムに値をサンプリング
③サンプリングした値で認識精度を評価
④②、③を繰り返し、認識精度の結果を元に範囲を狭める
⑤ハイパーパラメータの値が決められるまで①から繰り返す

と言った地道な手法が現時点では一番の近道であるよう。

##**■参考文献**
[ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装](https://www.amazon.co.jp/%E3%82%BC%E3%83%AD%E3%81%8B%E3%82%89%E4%BD%9C%E3%82%8BDeep-Learning-%E2%80%95Python%E3%81%A7%E5%AD%A6%E3%81%B6%E3%83%87%E3%82%A3%E3%83%BC%E3%83%97%E3%83%A9%E3%83%BC%E3%83%8B%E3%83%B3%E3%82%B0%E3%81%AE%E7%90%86%E8%AB%96%E3%81%A8%E5%AE%9F%E8%A3%85-%E6%96%8E%E8%97%A4-%E5%BA%B7%E6%AF%85/dp/4873117585/ref=cm_cr_arp_d_product_top?ie=UTF8)