【用語】Pathological Curvature、Momentum、Nesterov Accelerated Gradient

E資格用語の備忘録
シラバス2024 3.深層学習 (2)深層モデルのための最適化 i.基本的なアルゴリズム　モメンタム

# Pathological Curvature
関数の形状が渓谷のようになっており、勾配降下法などの最適化アルゴリズムに影響を与える問題。
この形状で勾配降下法は、一方向に進むスピードが速く、他の方向に進むスピードが遅い場合、オーバーシュート（一度の更新が大きすぎること）を引き起こし、振動が発生し、最小値に到達するのが遅くなる。
解決策として、モメンタムやNesterov Accelerated Gradientなどの手法が提案されている。
```python
import numpy as np
# 最適化手法 : SGD : 確率的勾配降下法
class SGD:
    # 学習率を0.01で初期化
    def __init__(self, lr=0.01):
        self.lr = lr
    # パラメータと勾配を受け取り、パラメータの更新
    def update(self, params, grads):
        # パラメータの各キーを順に取得、.keys()は辞書のすべてのキーを返すメソッド
        for key in params.keys():
            # 各パラメータを各勾配で更新
            params[key] -= self.lr * grads[key]
```
https://github.com/kabasunny/E_practice/tree/develop

# Momentum
最適化アルゴリズムの一つで、パラメータの更新に前回の更新方向を考慮に入れた手法。
通常の勾配降下法(SGD)に比べ最適化の過程での振動が抑制され、よりスムーズな収束が可能になる。
```python
import numpy as np


# 最適化手法 : モーメンタム
class Momentum:
    # 学習率を0.01、モーメンタム項を0.9で初期化
    def __init__(self, lr=0.01, momentum=0.9):
        self.lr = lr
        self.momentum = momentum
        self.v = None  # ベロシティ用のインスタンス変数の初期化

    # パラメータの更新、パラメータと勾配を受け取る
    def update(self, params, grads):
        # 初回ベロシティが無い状態
        if self.v is None:
            # 空の辞書を代入
            self.v = {}
            # 各パラメータのキーと値を順に取得、.items()は辞書のすべてのキーと値のペアを返すメソッド
            for key, val in params.items():
                # valと同じ形状のゼロ配列を生成
                self.v[key] = np.zeros_like(val)
        # パラメータの各キーを順に取得、.keys()は辞書のすべてのキーを返すメソッド
        for key in params.keys():
            # ベロシティを各勾配で更新
            self.v[key] = self.momentum * self.v[key] - self.lr * grads[key]
            # 各パラメータを各勾配で更新
            params[key] += self.v[key]
```

# Nesterov Accelerated Gradient
ネステロフのモーメンタムやNAGとも言う。
最適化アルゴリズムの一つで、モメンタムを改良したもので、モメンタムが次の位置を予測し、その予測位置での勾配を計算する。
モメンタムSGDが引き起こすオーバーシュ
ートを抑制し、最適化の速度を向上させる。
実装においては理論上の更新式に等価な式を使用する。
```python
import numpy as np


# 最適化手法 : ネステロフのモーメンタム : NAG
class Nesterov:
    # 学習率を0.01、モーメンタム項を0.9で初期化
    def __init__(self, lr=0.01, momentum=0.9):
        self.lr = lr
        self.momentum = momentum
        self.v = None  # ベロシティ用のインスタンス変数の初期化

    # パラメータの更新、パラメータと勾配を受け取る
    def update(self, params, grads):
        # 初回ベロシティが無い状態
        if self.v is None:
            # 空の辞書を代入
            self.v = {}
            # 各パラメータのキーと値を順に取得、.items()は辞書のすべてのキーと値のペアを返すメソッド
            for key, val in params.items():
                # valと同じ形状のゼロ配列を生成
                self.v[key] = np.zeros_like(val)
        # パラメータの各キーを順に取得、.keys()は辞書のすべてのキーを返すメソッド
        for key in params.keys():
            # 各パラメータを各勾配で更新
            params[key] += self.momentum * self.momentum * self.v[key]
            params[key] -= (1 + self.momentum) * self.lr * grads[key]
            # params[key] = params[key] + self.momentum * self.momentum * self.v[key] - (1 + self.momentum) * self.lr * grads[key]

            # 次回のベロシティを各勾配で更新
            self.v[key] *= self.momentum
            self.v[key] -= self.lr * grads[key]
            # self.v[key] = self.momentum * self.v[key] - self.lr * grads[key]
        # ネステロフのモーメンタムでは、「先読み」を行い、現在のベロシティ方向に少し「先読み」した位置での勾配を計算し、その情報を用いてパラメータを更新する。
        # 上記の実装では、次回のベロシティを今回の最後に更新する。初回の更新については、ネステロフのモーメンタムも通常のモーメンタムも同じ。

```