深層学習／最適化手法

#1.はじめに
　今回は、最適化手法についてまとめます。

#2.SGD
　更新する重みパラメータをW, Wに関する損失関数を$\frac{\partial L}{\partial W}$とし、学習率をηとすると、
![スクリーンショット 2020-03-30 16.52.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209705/e69104cb-7870-3e74-7132-293904f87249.png)

　もう少し厳密な書き方。抽出されたm個の訓練集合{${x^{(1)}, ... , x^{(m)}}$}とそれぞれに対応する目標$y^{(i)}$を元にパラメータθによって構成される推論を行うための関数fの勾配を推定し、誤差Lが小さくなる様にパラメータθの更新を繰り返し行う。
![スクリーンショット 2020-03-30 16.53.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209705/16f98a0c-4a44-822f-8932-a55a17ff5304.png)

```python
calss StocasticGradient:

   def __init__(self, lr=0.01):
       self.lr = lr

   def update_params(self, params, grads):
       for key in params.keys():
           params[key] -= self.lr * grads[key]
```
　ここで、引数 params, grads はそれぞれ更新対象のパラメータ、誤差関数の各パラメータに対する勾配を格納する辞書とします。

#3.Momentum
　SGDは、単に勾配方向へ進むだけで、勾配が急であると行ったり来たりでジグザクな動きになるし、勾配が緩慢な場合は中々最適化が進まないという問題点があります。これを改善しようと提案されたのが、Momentumです。

　Momentumでは、vという変数を加えて最初はゆっくりした速度で動き、勾配が同じ方向ならば徐々に加速させる工夫をしています。
![スクリーンショット 2020-03-30 16.54.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209705/8a5ffa1f-40bd-9c72-1c44-6704066dc831.png)


　当初は、αvがブレーキの役目を果たし(α=0.9などの値を設定)、徐々にこのブレーキが緩んで行くイメージです。

　先程同様、もう少し厳密な書き方をすると、
![スクリーンショット 2020-03-30 16.55.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209705/e6a36c39-92a8-8b8d-8c0c-7db0158cd78d.png)

```python
class Momentum:

   def __init__(self, lr=0.01, momentum=0.9):
       self.lr = lr
       self.momentum = momentum
       self.v = None

   def update_params(self, 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]
```
　momentum = α　として、先程同様コードを書くとこんな形です。


#4.ネステロフのMomentum
　モメンタムには、ロスの小さな点を行き過ぎてしまうオーバーシュートが起こる問題があります。そこで、現在の速度を使ってさらに一歩更新した点から求めた勾配を元に更新することで、このオーバーシュートを軽減しています。

![スクリーンショット 2020-04-14 14.18.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209705/bec10ac6-fba3-b247-7a8f-3ec56b41b366.png)

#5.AdaGrad
　パラメータ毎に最適な学習率が存在するという考え方で、学習過程で個別に最適化を図ることを適用的学習率を持つと言います。これ以降は、**適応的学習率を持つアルゴリズム**を説明します。
　　
　Adagradは、パラメータ毎に異なる学習率を設定しますると共に、全てのパラメータの学習率の補正として、過去の学習率の二乗和の平方根に反比例させます。

![スクリーンショット 2020-04-13 15.36.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209705/f2c781dd-3eab-3409-8f26-024b31affecd.png)


```python
class AdaGrad:

    def __init__(self, lr=0.01):
        self.lr = lr
        self.h = None
        
    def update(self, 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)
```

#6.RMSProp
　AdaGradは、パラメータの勾配の二乗和を使いますが、これだと学習率の減衰が過剰気味になってしまう場合があります。RMSpropは、勾配の累計を指数関数的な重みを付けた移動平均に変更することで、非凸の条件下でAdaGradの性能を改善しています。
![スクリーンショット 2020-04-13 15.36.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209705/a803c172-9550-044f-8ea4-50a25ee9b25d.png)


```python
class RMSprop:

    def __init__(self, lr=0.01, decay_rate = 0.99):
        self.lr = lr
        self.decay_rate = decay_rate
        self.h = None
        
    def update(self, 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] *= self.decay_rate
            self.h[key] += (1 - self.decay_rate) * grads[key] * grads[key]
            params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)
```


#7.Adam
　基本的には、RMSpropとMomentumの組み合わせとみることができますが、2つの違いがあります。1つ目は、モメンタムは勾配の（指数関数的な重み付けのある）一次モーメントの推定として直接導入される点。2つ目は、一次モーメント（モメンタム項）と二次モーメント（中心化されていない）両方の推定へのバイアス補正が含まれていて、原点の初期化が考慮されている点です。

![スクリーンショット 2020-04-13 15.32.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209705/a174c97c-75ab-c3c4-f975-e340d2707226.png)



```python
class Adam:

    def __init__(self, lr=0.001, beta1=0.9, beta2=0.999):
        self.lr = lr
        self.beta1 = beta1
        self.beta2 = beta2
        self.iter = 0
        self.m = None
        self.v = None
        
    def update(self, params, grads):
        if self.m is None:
            self.m, self.v = {}, {}
            for key, val in params.items():
                self.m[key] = np.zeros_like(val)
                self.v[key] = np.zeros_like(val)
        
        self.iter += 1
        lr_t  = self.lr * np.sqrt(1.0 - self.beta2**self.iter) / (1.0 - self.beta1**self.iter)         
        
        for key in params.keys():
            self.m[key] += (1 - self.beta1) * (grads[key] - self.m[key])
            self.v[key] += (1 - self.beta2) * (grads[key]**2 - self.v[key])
            
            params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) + 1e-7)            
```
コードが少しわかり難いので、self.iter +=1以降の更新式を補足します。
![スクリーンショット 2020-04-14 11.12.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209705/80ed1cb7-eec9-b877-cb31-8c4805e1e10b.png)
これは、`self.m[key] += (1 - self.beta1) * (grads[key] - self.m[key])`に相当します。

![スクリーンショット 2020-04-14 11.14.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209705/24dc311a-4fd0-f8cc-0be4-4ff27da60ba0.png)
これは、`self.v[key] += (1 - self.beta2) * (grads[key]**2 - self.v[key])`に相当します。

![スクリーンショット 2020-04-14 10.53.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209705/c8c668cc-ad07-baca-3701-486eecbe0f20.png)
赤枠の左側が、`lr_t  = self.lr * np.sqrt(1.0 - self.beta2**self.iter) / (1.0 - self.beta1**self.iter)`に相当します。

赤枠の右側が、`params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) + 1e-7)`に相当します。