はじめに
深層学習モデルの学習は、損失関数を最小化するパラメータを見つける最適化問題です。この最適化の中心にあるのが**勾配降下法(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)が大きいほど過去の勾配の影響が強くなります。谷状の損失地形で振動を抑制し、収束を加速する効果があります。
Momentumの導出:指数移動平均としての速度項
式\((3)\) がなぜ「振動抑制・加速」に効くのかを、指数移動平均(EMA)としての導出と2次形式の解析から確認します。速度項を
\[ v_t = \beta v_{t-1} + (1-\beta) \nabla L_{\mathcal{B}}(\theta_t) \]と定義すると(\(v_0 = 0\) )、再帰的に展開して
\[ v_t = (1-\beta) \sum_{i=1}^{t} \beta^{\,t-i} \nabla L_{\mathcal{B}}(\theta_i) \]が得られます。重み \(\beta^{t-i}\) は \(i\) が現在に近いほど大きく、指数的に過去を忘却する加重平均になっています。
本記事の式\((3)\) (\((1-\beta)\) を掛けない慣習)は、このEMA形と本質的に同じです。\(v'_t := (1-\beta)v_t\) とおくと
\[ v'_t = (1-\beta)\bigl(\beta v_{t-1} + \nabla L_{\mathcal{B}}(\theta_t)\bigr) = \beta v'_{t-1} + (1-\beta)\nabla L_{\mathcal{B}}(\theta_t) \]となりEMA形に一致するため、式\((4)\)
の更新 \(\theta_{t+1} = \theta_t - \eta v_t\)
は、実効学習率 \(\eta' = \eta/(1-\beta)\)
を使ったEMA形の更新 \(\theta_{t+1} = \theta_t - \eta' v'_t\)
と等価です。PyTorchのmomentum引数は前者(\((1-\beta)\)
なし)の慣習を採用しているため、momentum=0.9は実効的に学習率を\(1/(1-0.9)=10\)
倍しているのと同じ効果を持つ点に注意が必要です。
物理的な類推: 式\((3)\) -\((4)\) は摩擦のある質点の運動方程式の離散化とみなせます。\(v_t\) を速度、\(\nabla L\) を力、\(\beta\) を\((1-\) 摩擦係数\()\) に対応させると、力を受け続ける方向(谷に沿った浅い方向)には速度が蓄積して加速し、勾配の符号が反転する方向(谷を横切る急峻な方向)には速度が打ち消し合って振動が減衰します。
2次形式による収束速度の解析: Momentumが谷状の損失で有効な理由を定量的に見るため、2次形式の損失 \(L(\theta)=\frac{1}{2}\theta^\top A\theta\) (\(A\) を対角化した固有座標系、固有値 \(\lambda_i\) )を考えます。各固有方向は独立に振る舞い、方向 \(i\) での更新(式\((3)\) -\((4)\) の慣習)は
\[ \theta_{t+1}^{(i)} = (1+\beta-\eta\lambda_i)\,\theta_t^{(i)} - \beta\,\theta_{t-1}^{(i)} \]という2階線形漸化式になります。特性方程式 \(r^2-(1+\beta-\eta\lambda_i)r+\beta=0\) の根を \(r_1,r_2\) とすると、根と係数の関係から \(r_1 r_2=\beta\) が常に成り立ちます。判別式が負で根が複素数になる範囲では \(|r_1|=|r_2|=\sqrt{\beta}\) となり、収束レートが \(\eta\) や \(\lambda_i\) に依存せず一定になります。
これに対しモーメンタムなしの勾配降下法(\(\beta=0\) )では方向 \(i\) のレートは単に \(|1-\eta\lambda_i|\) です。条件数 \(\kappa=\lambda_{\max}/\lambda_{\min}\) の下で安定性条件 \(\eta<2/\lambda_{\max}\) を満たしつつ全方向で共通の \(\eta\) を使う必要があるため、最適化してもGDのレートは \((\kappa-1)/(\kappa+1)\) までしか下げられません。一方 Polyak (1964) の重い球(heavy-ball)法の解析により、\(\eta,\beta\) を
\[ \eta^\ast = \frac{4}{(\sqrt{\lambda_{\min}}+\sqrt{\lambda_{\max}})^2}, \qquad \beta^\ast = \left(\frac{\sqrt{\kappa}-1}{\sqrt{\kappa}+1}\right)^2 \]と最適に選ぶと、レートは \((\sqrt{\kappa}-1)/(\sqrt{\kappa}+1)\) まで改善されます。
実際に \(\kappa=25\) (\(\lambda_{\min}=1,\ \lambda_{\max}=25\) )で計算すると、GDの最適レートは \((25-1)/(25+1)=0.9231\) 、Momentumの最適レートは \((\sqrt{25}-1)/(\sqrt{25}+1)=4/6=0.6667\) となります。100ステップ後の誤差は理論上それぞれ \(0.9231^{100}\approx3.34\times10^{-4}\) 、\(0.6667^{100}\approx2.46\times10^{-18}\) で、Momentumは同じステップ数で14桁以上速く収束する計算になります。実際に上記の漸化式をPythonでシミュレーションして確かめると、収束後の実測レートはGDが\(0.9231\) (理論値と完全一致、実根のため単純な等比減衰)、Momentumが\(\approx0.674\) (理論値\(0.6667\) に近い、複素根のため振動を伴う減衰)となり、誤差が\(10^{-3}\) 以下に達するまでのステップ数はGDが87、Momentumが23と、およそ4倍の高速化を実測できました(検証コードは後述のPython実装の節を参照)。
なお、この \(\eta^\ast,\beta^\ast\) は \(\lambda_{\max}\) 方向をちょうど臨界減衰(重根)に置く選択のため、実際にシミュレーションすると当該方向で一時的に大きく行き過ぎてから収束する現象が見られます(後述のPython実装の節で具体的に確認します)。漸近的な収束レートは最良でも、過渡的な挙動は必ずしも単調減少ではない点は実務上の注意点です。
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)により古い勾配情報を忘却し、学習率の過度な減衰を防ぎます。
なぜ移動平均が学習率の過度な減衰を防ぐか
勾配の二乗がおおむね定常(\(\mathbb{E}[(\nabla L_{\mathcal{B}}(\theta_t))^2] = \sigma^2\) で一定)とみなせる状況を考えます。式\((9)\) の両辺の期待値を取ると
\[ \mathbb{E}[s_t] = \rho\,\mathbb{E}[s_{t-1}] + (1-\rho)\sigma^2 \]という線形漸化式になり、不動点 \(s^\star=\sigma^2\) を持ちます。\(\mathbb{E}[s_t]-\sigma^2=\rho\bigl(\mathbb{E}[s_{t-1}]-\sigma^2\bigr)\) を繰り返し使うと \(\mathbb{E}[s_t]-\sigma^2=\rho^t\bigl(\mathbb{E}[s_0]-\sigma^2\bigr)\to0\) となるので、\(s_t\) は指数的に \(\sigma^2\) へ収束し、学習率 \(\eta/\sqrt{s_t+\varepsilon}\) は \(t\to\infty\) でも \(\eta/\sigma\) 程度の一定値にとどまります。
一方AdaGradの \(G_t=\sum_{i=1}^{t} g_i^2\) は、同じ定常性の仮定の下で \(\mathbb{E}[G_t]=t\sigma^2\) と線形に増大し続けるため、学習率 \(\eta/\sqrt{G_t+\varepsilon}\sim\eta/(\sigma\sqrt{t})\to0\) は際限なく減衰します。これがAdaGradが学習の後半で更新がほぼ止まってしまう理由であり、RMSPropが指数移動平均(=忘却)を導入して解決した点です。
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}\]バイアス補正の必要性(導出)
式\((11)\) を \(m_0=0\) から再帰的に展開すると
\[ m_t = (1-\beta_1)\sum_{i=1}^{t}\beta_1^{\,t-i}\,\nabla L_{\mathcal{B}}(\theta_i) \]が得られます。勾配の真の値が区間内でほぼ一定 \(\mathbb{E}[\nabla L_{\mathcal{B}}(\theta_i)]\approx\mathbb{E}[g]\) とみなせるとき、期待値を取ると
\[ \mathbb{E}[m_t] = (1-\beta_1)\,\mathbb{E}[g]\sum_{i=1}^{t}\beta_1^{\,t-i} = (1-\beta_1)\,\mathbb{E}[g]\cdot\frac{1-\beta_1^{\,t}}{1-\beta_1} = (1-\beta_1^{\,t})\,\mathbb{E}[g] \]となります。すなわち \(m_t\) は真の勾配 \(\mathbb{E}[g]\) の \((1-\beta_1^t)\) 倍にしか達しない、系統的な過小評価バイアスを持ちます。\(\beta_1=0.9\) の場合、\(t=1\) では \(1-\beta_1^1=0.1\) しかなく、\(m_1\) は真の勾配のわずか10%しか表しません。\(v_t\) についても \(\beta_2\) を用いて全く同様の議論が成り立ちます。式\((13)\) の補正 \(\hat m_t=m_t/(1-\beta_1^t)\) 、\(\hat v_t=v_t/(1-\beta_2^t)\) はこの系統誤差を正確に打ち消し、\(\mathbb{E}[\hat m_t]=\mathbb{E}[g]\) を回復します。この効果は後述のPython実装の節で実際に数値検証します。
\[\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}\)
収束解析:Adamは本当に「収束」するのか
凸最適化の理論では、反復回数 \(T\) に対する収束レートで最適化手法を評価します。標準的なオンライン勾配降下法(SGD)は、勾配のノルムが有界な凸関数に対して、目的関数値の平均が最適値に \(O(1/\sqrt{T})\) のレートで近づくことが知られています(Zinkevich, 2003; Nemirovski et al., 2009)。Adamの原論文(Kingma & Ba, 2015)も同様の \(O(1/\sqrt{T})\) のオンライン後悔(regret)上界を主張していました。
しかしReddi, Kale & Kumar (2018) は、この証明に誤りがあることを指摘し、さらにAdamが凸関数に対してすら収束しない具体的な反例を構成しました。次のオンライン凸最適化の問題を考えます。
\[ f_t(x) = \begin{cases} Cx & (t \bmod 3 = 1) \\ -x & (\text{それ以外}) \end{cases}, \qquad x\in[-1,1],\ C>2 \]3ステップごとの区間で勾配の総和は \(C+(-1)+(-1)=C-2>0\) なので、真の最適解は \(x^\ast=-1\) です。しかし論文は、ある範囲の \((\beta_1,\beta_2)\) (\(\beta_1<\sqrt{\beta_2}\) を満たす場合を含む)に対して、Adamの反復 \(x_t\) が最適解 \(-1\) ではなく最悪点 \(+1\) に張り付いてしまう(平均後悔が0に収束しない)ケースが存在することを示しました。直感的には、大きな勾配 \(C\) を観測した直後は \(v_t\) (分母)が大きいままなので有効ステップ幅が抑えられる一方、その後2ステップ続く小さな勾配 \(-1\) の間に \(v_t\) が指数移動平均で減衰し、有効ステップ幅が相対的に増大してしまう非対称性が原因です。この反例への修正案がAMSGradで、\(\hat v_t\) を単調非減少にする(\(\hat v_t \leftarrow \max(\hat v_t, \hat v_{t-1})\) )ことで理論上の収束保証を回復します。
後述のPython実装の節で、この反例を実際に実装し、Adamが最悪点 \(+1\) 付近に収束し、AMSGradのみが正しく \(-1\) に収束することを数値的に確認します。
実務上の位置づけとしては、この反例は敵対的に構成された特殊な問題設定であり、実際の深層学習の損失関数(非凸かつ勾配統計が反例のように極端に偏らない)でAdamが同様の破綻を頻繁に起こすわけではありません。AMSGradはこの理論的欠陥を修正しますが、実践では素のAdamと比べて明確な性能向上が観測されないことも多く報告されており、多くのライブラリ・実務コードでは今なお素のAdam(またはその重み減衰版であるAdamW)がデフォルトとして使われ続けています。理論的な収束保証と実務上の性能は必ずしも一致しないことを示す好例といえます。
比較表
| 手法 | 適応的学習率 | モーメンタム | 主な特徴 |
|---|---|---|---|
| SGD | No | No | シンプル、汎化性能が良い場合あり |
| Momentum | No | Yes | 振動抑制、収束加速 |
| NAG | No | Yes | 先読みによるオーバーシュート軽減 |
| AdaGrad | Yes | No | スパースデータに有効、学習率減衰問題 |
| RMSProp | Yes | No | AdaGradの減衰問題を解決 |
| Adam | Yes | Yes | 最も広く使用、ロバスト |
Python実装:数値検証
ここまでの導出を、実際にPythonを実行して数値で確認します。
バイアス補正の効果を検証
一定の勾配 \(g=1\) が観測され続ける単純なケースで、バイアス補正の有無による \(m_t\) の違いを見ます。
beta1 = 0.9
g = 1.0 # 一定の勾配
m = 0.0
for t in range(1, 11):
m = beta1 * m + (1 - beta1) * g
m_hat = m / (1 - beta1**t)
print(t, round(m, 6), round(m_hat, 6))
実行結果は次の通りです。
| \(t\) | \(m_t\) (補正前) | \(1-\beta_1^t\) (理論値) | \(\hat m_t\) (補正後) |
|---|---|---|---|
| 1 | 0.100000 | 0.100000 | 1.000000 |
| 2 | 0.190000 | 0.190000 | 1.000000 |
| 3 | 0.271000 | 0.271000 | 1.000000 |
| 5 | 0.409510 | 0.409510 | 1.000000 |
| 10 | 0.651322 | 0.651322 | 1.000000 |
真の勾配は常に\(1.0\) であるのに対し、補正前の\(m_1\) はわずか\(0.1\) (90%の過小評価)で、\(t=10\) まで経過しても\(m_{10}=0.6513\) と真値の65%程度にしか達しません。理論値\(1-\beta_1^t\) と実測値は完全に一致しており、導出した式\(\mathbb{E}[m_t]=(1-\beta_1^t)\mathbb{E}[g]\) を裏付けます。一方、補正後の\(\hat m_t\) はどのステップでも厳密に\(1.0\) となり、バイアス補正が系統誤差を正確に打ち消していることが確認できます。
Rosenbrock関数での軌跡比較
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()

5000ステップ後の最適解 \((1,1)\) からの距離を比較すると、Adamが0.056で最も近く、Momentum 0.143、SGD 0.477、RMSProp 0.654(本設定の学習率では)という順になりました。Rosenbrock関数特有の細長い谷でSGDが振動・停滞しやすいのに対し、モーメンタムを持つ手法(Momentum・Adam)は谷に沿った加速で収束が明確に速いことが定量的に確認できます。
条件数の異なる2次形式での収束速度比較
前節で導出した「2次形式・固有値解析」の主張を、条件数 \(\kappa=25\) の異方性な2次形式 \(L(x,y)=\frac{1}{2}(x^2+25y^2)\) 上で直接検証します。
import numpy as np
lam_x, lam_y = 1.0, 25.0 # kappa = 25
def grad(theta):
x, y = theta
return np.array([lam_x * x, lam_y * y])
def optimize(method, theta0, lr, steps, beta=0.9, beta1=0.9, beta2=0.999, eps=1e-8):
theta = np.array(theta0, dtype=float)
traj = [theta.copy()]
m = np.zeros_like(theta)
v = np.zeros_like(theta)
for t in range(1, steps + 1):
g = grad(theta)
if method == 'sgd':
theta = theta - lr * g
elif method == 'momentum':
m = beta * m + g
theta = theta - lr * m
elif method == 'rmsprop':
v = beta2 * v + (1 - beta2) * g**2
theta = theta - 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)
theta = theta - lr * m_hat / (np.sqrt(v_hat) + eps)
traj.append(theta.copy())
return np.array(traj)
theta0 = [-4.0, 1.0]
steps = 150
configs = {
'SGD': dict(method='sgd', lr=0.04),
'Momentum': dict(method='momentum', lr=0.05, beta=0.6),
'RMSProp': dict(method='rmsprop', lr=0.3, beta2=0.99),
'Adam': dict(method='adam', lr=0.3, beta1=0.9, beta2=0.999),
}
trajs = {name: optimize(theta0=theta0, steps=steps, **cfg) for name, cfg in configs.items()}
for name, traj in trajs.items():
dist = np.linalg.norm(traj, axis=1)
print(name, f"{dist[-1]:.3e}")

150ステップ後の最適解 \((0,0)\) からの距離は、SGDが \(8.8\times10^{-3}\) (\(10^{-2}\) を下回るのに147ステップを要した)、Momentumが \(4.7\times10^{-14}\) (\(10^{-2}\) に達するまで33ステップ)、RMSPropが \(6.2\times10^{-15}\) (同じく6ステップ)、Adamが \(4.7\times10^{-4}\) (同じく61ステップ)でした。学習率\(\eta=1/\lambda_{\max}=0.04\) のSGDは急峻な方向(\(y\) )を1ステップでほぼ潰す一方、浅い方向(\(x\) )の収束レートが理論値通り\(0.96\) に貼り付き、150ステップでも目視で分かるほど収束が遅い様子が図から確認できます。Momentumは\(\beta=0.6,\ \eta=0.05\) という控えめな設定でもSGDよりはるかに高速に収束しており、導出した「収束レートが条件数に依らずほぼ一定になる」効果を裏付けています(前節で触れた理論最適値 \(\beta^\ast=0.444,\ \eta^\ast=0.111\) を使うと、\(y\) 方向の最大到達値が\(1.93\) まで一時的に行き過ぎる大きなオーバーシュートを伴いながらも21ステップで\(10^{-2}\) 以下に収束することも別途確認済みです。漸近レートは最良でも過渡応答は暴れる、という前節の注意点の実例になっています)。RMSPropとAdamは勾配のスケールを方向ごとに正規化するため、決定論的なこの2次形式では特にRMSPropが極めて高速に収束しました。
Adamの非収束反例(AMSGrad)の実装
Reddi et al. (2018) の反例 \(f_t(x)=Cx\ (t\bmod 3=1),\ -x\ (\text{otherwise})\) 、\(x\in[-1,1]\) を実装し、Adamが最悪点 \(+1\) に収束してしまう様子と、AMSGradが正しく最適解 \(-1\) に収束する様子を比較します。
import numpy as np
def run(method, T, C, beta1, beta2, alpha, eps=1e-8, x0=0.0):
x = x0
m = 0.0
v = 0.0
v_hat_max = 1e-16
xs = np.empty(T)
for t in range(1, T + 1):
g = C if (t % 3 == 1) else -1.0
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)
if method == 'amsgrad':
v_hat_max = max(v_hat_max, v_hat)
denom = np.sqrt(v_hat_max) + eps
else:
denom = np.sqrt(v_hat) + eps
x = x - alpha * m_hat / denom
x = np.clip(x, -1.0, 1.0)
xs[t - 1] = x
return xs
C, beta1, beta2, alpha = 3.0, 0.09, 0.01, 0.01 # beta1 < sqrt(beta2) = 0.10 を満たす
T = 5000
xs_adam = run('adam', T, C, beta1, beta2, alpha)
xs_ams = run('amsgrad', T, C, beta1, beta2, alpha)
print("Adam final:", xs_adam[-1], "mean(last500):", xs_adam[-500:].mean())
print("AMSGrad final:", xs_ams[-1], "mean(last500):", xs_ams[-500:].mean())

\(C=3,\ \beta_1=0.09,\ \beta_2=0.01\) (\(\beta_1<\sqrt{\beta_2}=0.10\) を満たす)、学習率\(\alpha=0.01\) で5000ステップ実行した結果、Adamは最終的に\(x_T=0.998\) (最悪点\(+1\) のごく近傍)に収束し、直近500ステップの平均も\(0.996\) と、真の最適解\(-1\) から大きく外れたままでした。一方AMSGradは同じ設定で\(x_T=-0.998\) 、直近500ステップの平均\(-0.997\) と、正しく最適解\(-1\) に収束しました。反復回数を\(T=30,90,300,900\) と伸ばして追跡すると、Adamの直近30%平均は\(0.056\to0.177\to0.604\to0.996\) と単調に悪化して最悪点へ張り付いていく一方、AMSGradは\(-0.033\to-0.089\to-0.284\to-0.839\) と単調に最適解へ近づいていく対照的な挙動が確認できました。この反例は\(\beta_1,\beta_2\) を敵対的に選んだ特殊な設定であり、通常の深層学習の学習率・\(\beta\) 設定(\(\beta_2=0.999\) 等)で同じ発散が起きるわけではありませんが、Adamの収束保証には理論的な穴があるという事実を数値的に裏付けるものです。
実用的な選択指針
- まず Adam を試す: ほとんどのタスクで安定した性能
- 汎化性能が重要: SGD + Momentum + 学習率スケジューリング
- スパースデータ(NLP等): Adam または AdaGrad
- 学習率調整の手間を省きたい: Adam(適応的学習率により頑健)
PyTorch での利用
本記事でスクラッチ実装した各更新式は、PyTorch では torch.optim の各クラスに対応します。
import torch.optim as optim
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9) # SGD + Momentum
optimizer = optim.RMSprop(model.parameters(), lr=0.001, alpha=0.99) # RMSProp
optimizer = optim.Adam(model.parameters(), lr=0.001, betas=(0.9, 0.999)) # Adam
momentum・alpha・betas が本文の \(\beta\)
(指数移動平均の減衰率)に対応し、Adam の betas=(0.9, 0.999) は原論文の推奨値です。
関連記事
- 焼きなまし法(Simulated Annealing)の理論とPython実装 - 勾配を使わないメタヒューリスティクスとの比較が有益です。
- 遺伝的アルゴリズム(GA)の理論とPython実装 - 勾配を使わない進化的最適化手法を解説しています。
- ベイズ最適化の基礎とPython実装 - ハイパーパラメータ(学習率等)の自動チューニングに使えるベイズ最適化を解説しています。
- Self-Attentionの仕組みとPython実装 - Adamが標準的に使われるTransformerのSelf-Attention機構を解説しています。
- クロスエントロピー法:モンテカルロ最適化の実践的手法 - 勾配を使わないブラックボックス最適化手法との比較が有益です。
- ベイズ線形回帰の基礎 - 勾配降下法を使わないベイズ的なパラメータ推定を解説しています。
- Transformerによる時系列予測:Attention・Positional Encoding・PyTorch実装 - Adamが標準オプティマイザとして使われるTransformerの学習をPyTorchで実装しています。
- モンテカルロ最適化(CEM/SA/GA/MPPI/PSO)の基礎・比較・Python実装 - 勾配を使わない5つの最適化手法を共通骨格で比較するハブ記事。勾配法との使い分けが俯瞰できます。
参考文献
- Kingma, D. P., & Ba, J. (2015). “Adam: A Method for Stochastic Optimization”. ICLR 2015.
- Reddi, S. J., Kale, S., & Kumar, S. (2018). “On the Convergence of Adam and Beyond”. ICLR 2018.
- Polyak, B. T. (1964). “Some methods of speeding up the convergence of iteration methods”. USSR Computational Mathematics and Mathematical Physics, 4(5), 1-17.
- Zinkevich, M. (2003). “Online Convex Programming and Generalized Infinitesimal Gradient Ascent”. ICML 2003.
- 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.