確率的勾配降下法からAdamまで:勾配ベース最適化の進化

SGD、Momentum、RMSProp、Adamなど勾配ベース最適化アルゴリズムの数理と特性を比較し、PyTorchでの実装と収束挙動の可視化を紹介します。

はじめに

深層学習モデルの学習は、損失関数を最小化するパラメータを見つける最適化問題です。この最適化の中心にあるのが**勾配降下法(Gradient Descent)**とその派生手法です。

本記事では、基本的なSGDから現在最も広く使われるAdamまで、勾配ベース最適化アルゴリズムの数理的な仕組みと特性を比較し、PyTorchでの収束挙動を可視化します。

勾配降下法(GD)

パラメータ \(\theta\) を損失関数 \(L(\theta)\) の勾配方向に更新します。

\[\theta_{t+1} = \theta_t - \eta \nabla L(\theta_t) \tag{1}\]

ここで \(\eta\) は学習率です。全データを使って勾配を計算するため、1ステップのコストが高く大規模データには不向きです。

確率的勾配降下法(SGD)

ミニバッチ \(\mathcal{B}\) からの勾配推定を使って更新します。

\[\theta_{t+1} = \theta_t - \eta \nabla L_{\mathcal{B}}(\theta_t) \tag{2}\]

計算コストが低い反面、勾配の分散が大きく、収束が不安定になりやすい問題があります。

Momentum

過去の勾配の指数移動平均(速度項)を導入し、更新の方向を安定させます。

\[v_t = \beta v_{t-1} + \nabla L_{\mathcal{B}}(\theta_t) \tag{3}\]\[\theta_{t+1} = \theta_t - \eta v_t \tag{4}\]

\(\beta\)(典型的に0.9)が大きいほど過去の勾配の影響が強くなります。谷状の損失地形で振動を抑制し、収束を加速する効果があります。

Nesterov Accelerated Gradient(NAG)

Momentumの改良版で、「先読み」した位置での勾配を計算します。

\[v_t = \beta v_{t-1} + \nabla L_{\mathcal{B}}(\theta_t - \eta \beta v_{t-1}) \tag{5}\]\[\theta_{t+1} = \theta_t - \eta v_t \tag{6}\]

先読みにより、最適解の付近でのオーバーシュートを軽減します。

AdaGrad

パラメータごとに異なる学習率を適用する適応的学習率手法の先駆けです。

\[G_t = G_{t-1} + (\nabla L_{\mathcal{B}}(\theta_t))^2 \tag{7}\]\[\theta_{t+1} = \theta_t - \frac{\eta}{\sqrt{G_t + \varepsilon}} \nabla L_{\mathcal{B}}(\theta_t) \tag{8}\]

\(G_t\) は勾配の二乗の累積和です。頻繁に更新されるパラメータほど学習率が小さくなります。問題点として、\(G_t\) が単調増加するため学習率が過度に減衰し、学習が早期に停止することがあります。

RMSProp

AdaGradの問題を解決するため、勾配二乗の指数移動平均を使います。

\[s_t = \rho s_{t-1} + (1 - \rho)(\nabla L_{\mathcal{B}}(\theta_t))^2 \tag{9}\]\[\theta_{t+1} = \theta_t - \frac{\eta}{\sqrt{s_t + \varepsilon}} \nabla L_{\mathcal{B}}(\theta_t) \tag{10}\]

\(\rho\)(典型的に0.99)により古い勾配情報を忘却し、学習率の過度な減衰を防ぎます。

Adam(Adaptive Moment Estimation)

MomentumとRMSPropを統合した手法で、現在最も広く使われるオプティマイザです。

\[m_t = \beta_1 m_{t-1} + (1 - \beta_1) \nabla L_{\mathcal{B}}(\theta_t) \tag{11}\]\[v_t = \beta_2 v_{t-1} + (1 - \beta_2) (\nabla L_{\mathcal{B}}(\theta_t))^2 \tag{12}\]\[\hat{m}_t = \frac{m_t}{1 - \beta_1^t}, \quad \hat{v}_t = \frac{v_t}{1 - \beta_2^t} \tag{13}\]\[\theta_{t+1} = \theta_t - \frac{\eta}{\sqrt{\hat{v}_t} + \varepsilon} \hat{m}_t \tag{14}\]
  • \(m_t\): 勾配の1次モーメント(平均)の推定
  • \(v_t\): 勾配の2次モーメント(分散)の推定
  • 式 \((13)\) のバイアス補正は、初期ステップでの推定バイアスを修正します
  • 推奨パラメータ: \(\beta_1 = 0.9\), \(\beta_2 = 0.999\), \(\varepsilon = 10^{-8}\)

比較表

手法適応的学習率モーメンタム主な特徴
SGDNoNoシンプル、汎化性能が良い場合あり
MomentumNoYes振動抑制、収束加速
NAGNoYes先読みによるオーバーシュート軽減
AdaGradYesNoスパースデータに有効、学習率減衰問題
RMSPropYesNoAdaGradの減衰問題を解決
AdamYesYes最も広く使用、ロバスト

Python実装:収束挙動の可視化

Rosenbrock関数上での各オプティマイザの軌跡を比較します。

import numpy as np
import matplotlib.pyplot as plt

def rosenbrock(x, y):
    """Rosenbrock関数: f(x,y) = (1-x)^2 + 100(y-x^2)^2"""
    return (1 - x)**2 + 100 * (y - x**2)**2

def rosenbrock_grad(x, y):
    """Rosenbrock関数の勾配"""
    dx = -2 * (1 - x) - 400 * x * (y - x**2)
    dy = 200 * (y - x**2)
    return np.array([dx, dy])

def optimize(method, x0, lr, steps, **kwargs):
    """各最適化手法でRosenbrock関数を最小化"""
    x = np.array(x0, dtype=float)
    trajectory = [x.copy()]
    m = np.zeros_like(x)  # 1st moment
    v = np.zeros_like(x)  # 2nd moment
    beta1 = kwargs.get('beta1', 0.9)
    beta2 = kwargs.get('beta2', 0.999)
    eps = 1e-8

    for t in range(1, steps + 1):
        g = rosenbrock_grad(x[0], x[1])

        if method == 'sgd':
            x = x - lr * g
        elif method == 'momentum':
            m = beta1 * m + g
            x = x - lr * m
        elif method == 'rmsprop':
            v = beta2 * v + (1 - beta2) * g**2
            x = x - lr * g / (np.sqrt(v) + eps)
        elif method == 'adam':
            m = beta1 * m + (1 - beta1) * g
            v = beta2 * v + (1 - beta2) * g**2
            m_hat = m / (1 - beta1**t)
            v_hat = v / (1 - beta2**t)
            x = x - lr * m_hat / (np.sqrt(v_hat) + eps)

        trajectory.append(x.copy())

    return np.array(trajectory)

# --- 各手法の軌跡を計算 ---
x0 = [-1.0, 1.5]
steps = 5000
trajectories = {
    'SGD': optimize('sgd', x0, lr=0.0005, steps=steps),
    'Momentum': optimize('momentum', x0, lr=0.0001, steps=steps),
    'RMSProp': optimize('rmsprop', x0, lr=0.001, steps=steps),
    'Adam': optimize('adam', x0, lr=0.005, steps=steps),
}

# --- 等高線プロット ---
fig, ax = plt.subplots(figsize=(10, 8))
X, Y = np.meshgrid(np.linspace(-2, 2, 200), np.linspace(-1, 3, 200))
Z = rosenbrock(X, Y)
ax.contour(X, Y, Z, levels=np.logspace(0, 3.5, 20), cmap='gray', alpha=0.3)

colors = {'SGD': 'blue', 'Momentum': 'green', 'RMSProp': 'orange', 'Adam': 'red'}
for name, traj in trajectories.items():
    ax.plot(traj[:, 0], traj[:, 1], '-', color=colors[name], label=name, alpha=0.7)
    ax.plot(traj[-1, 0], traj[-1, 1], 'o', color=colors[name], markersize=6)

ax.plot(1, 1, 'k*', markersize=15, label='Optimum (1,1)')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_title('Optimizer Trajectories on Rosenbrock Function')
ax.legend()
ax.set_xlim(-2, 2)
ax.set_ylim(-1, 3)
plt.tight_layout()
plt.show()

実用的な選択指針

  • まず Adam を試す: ほとんどのタスクで安定した性能
  • 汎化性能が重要: SGD + Momentum + 学習率スケジューリング
  • スパースデータ(NLP等): Adam または AdaGrad
  • 学習率調整の手間を省きたい: Adam(適応的学習率により頑健)

関連記事

参考文献

  • Kingma, D. P., & Ba, J. (2015). “Adam: A Method for Stochastic Optimization”. ICLR 2015.
  • Ruder, S. (2016). “An overview of gradient descent optimization algorithms”. arXiv:1609.04747.
  • Goodfellow, I., Bengio, Y., & Courville, A. (2016). Deep Learning. MIT Press. Chapter 8.