はじめに
深層学習モデルの学習は、損失関数を最小化するパラメータを見つける最適化問題です。この最適化の中心にあるのが**勾配降下法(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}\)
比較表
| 手法 | 適応的学習率 | モーメンタム | 主な特徴 |
|---|---|---|---|
| SGD | No | No | シンプル、汎化性能が良い場合あり |
| Momentum | No | Yes | 振動抑制、収束加速 |
| NAG | No | Yes | 先読みによるオーバーシュート軽減 |
| AdaGrad | Yes | No | スパースデータに有効、学習率減衰問題 |
| RMSProp | Yes | No | AdaGradの減衰問題を解決 |
| Adam | Yes | Yes | 最も広く使用、ロバスト |
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(適応的学習率により頑健)
関連記事
- クロスエントロピー法:モンテカルロ最適化の実践的手法 - 勾配を使わないブラックボックス最適化手法との比較が有益です。
- ベイズ最適化の基礎とPython実装 - ハイパーパラメータ(学習率等)の自動チューニングに使えるベイズ最適化を解説しています。
- ベイズ線形回帰の基礎 - 勾配降下法を使わないベイズ的なパラメータ推定を解説しています。
参考文献
- 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.