最適化アルゴリズム比較 — SGD・Momentum・Adam・AdamW

勾配降下法を使ってモデルを訓練するとき、「どの最適化アルゴリズムを使えばよいか」は常に悩ましい問題です。SGDだけでも学習は進みますが、損失関数の地形が複雑な場合、収束が遅かったり局所解にトラップされたりします。

たとえば、細長い谷のような損失関数を想像してみてください。SGDでは谷の壁を左右に振動しながら少しずつ谷底に進みますが、モメンタムを加えると過去の勾配の方向を「記憶」して振動を抑えつつ谷に沿って加速できます。さらにAdamは各パラメータごとに学習率を適応的に調整し、方向によって異なるスケールの問題にも対応します。

最適化アルゴリズムの選択を理解すると、以下のような場面で的確な判断ができるようになります。

  • 画像認識モデル: SGD + Momentum が依然として高い汎化性能を示す理由
  • 自然言語処理: なぜTransformerではAdamW + warmupが標準なのか
  • 学習が停滞したとき: 最適化アルゴリズムの変更で打開できるか
  • ハイパーパラメータチューニング: 各手法の調整すべきパラメータの優先度

本記事の内容

  • SGDの限界とモメンタムの導入
  • RMSPropの適応的学習率
  • Adamの理論と更新則
  • AdamWの重み減衰修正
  • 各手法の収束特性の可視化と比較

前提知識

この記事を読む前に、以下の記事を読んでおくと理解が深まります。

SGDの限界

振動問題

前の記事で見たように、SGDは損失関数の等高線が細長い楕円(条件数が大きい)の場合、狭い方向に振動しながらゆっくりと最適解に向かいます。

この振動はパラメータ更新の効率を著しく低下させます。勾配の方向が最適解に向かう成分よりも、振動する成分の方が大きくなり、学習率を上げれば発散し、下げれば収束が遅くなるというジレンマに陥ります。

この問題に対する直感的な解決策は、過去の勾配の情報を利用して振動を抑制し、一貫した方向への移動を加速することです。これがモメンタムのアイデアです。

SGD + Momentum

物理的な直感

ボールが斜面を転がり落ちる様子をイメージしてください。ボールは現在の斜面の傾き(勾配)だけでなく、これまで獲得した運動量(momentum)の影響も受けます。急な坂では加速し、凸凹した地面では慣性によって小さな起伏を乗り越えます。

更新則

$$ \begin{align} \bm{v}_t &= \mu \bm{v}_{t-1} + \nabla J(\bm{w}_t) \\ \bm{w}_{t+1} &= \bm{w}_t – \eta \bm{v}_t \end{align} $$

ここで $\bm{v}_t$ は速度ベクトル(velocity)、$\mu \in [0, 1)$ はモメンタム係数です。典型的には $\mu = 0.9$ が使われます。

$\bm{v}_t$ を展開すると

$$ \bm{v}_t = \nabla J(\bm{w}_t) + \mu \nabla J(\bm{w}_{t-1}) + \mu^2 \nabla J(\bm{w}_{t-2}) + \cdots $$

過去の勾配の指数移動平均(Exponential Moving Average, EMA)になっています。一貫した方向の勾配は蓄積されて加速し、振動する成分は正負が相殺されて減衰します。

Nesterov Momentum

Nesterov Accelerated Gradient(NAG)は、モメンタムの改良版です。

$$ \begin{align} \bm{v}_t &= \mu \bm{v}_{t-1} + \nabla J(\bm{w}_t – \eta \mu \bm{v}_{t-1}) \\ \bm{w}_{t+1} &= \bm{w}_t – \eta \bm{v}_t \end{align} $$

通常のモメンタムが「現在位置の勾配」を使うのに対し、Nesterovは「モメンタムで進んだ先の位置の勾配」を使います。これにより「先読み」の効果があり、最小点を通り過ぎそうなときに早めにブレーキがかかります。凸最適化での収束率が $O(1/t)$ から $O(1/t^2)$ に改善されることが理論的に示されています。

ここまでモメンタムによる加速を見てきましたが、パラメータごとに勾配のスケールが大きく異なる問題もあります。次のRMSPropは、この問題にアプローチします。

RMSProp

動機

パラメータによって勾配のスケールが大きく異なる場合、一つの学習率 $\eta$ では全てのパラメータに適切な更新量を与えられません。頻繁に大きな勾配を受けるパラメータには小さな学習率を、滅多に勾配が来ないパラメータには大きな学習率を割り当てたいのです。

更新則

Hintonが講義で提案したRMSProp(Root Mean Square Propagation)は、勾配の二乗の指数移動平均で学習率を適応的にスケーリングします。

$$ \begin{align} \bm{s}_t &= \beta \bm{s}_{t-1} + (1 – \beta) \nabla J(\bm{w}_t) \odot \nabla J(\bm{w}_t) \\ \bm{w}_{t+1} &= \bm{w}_t – \frac{\eta}{\sqrt{\bm{s}_t} + \epsilon} \odot \nabla J(\bm{w}_t) \end{align} $$

$\bm{s}_t$ は勾配の二乗のEMA(各要素ごと)で、$\beta$ は典型的に0.999、$\epsilon = 10^{-8}$ はゼロ除算防止の小さな定数、$\odot$ は要素ごとの積です。

$\sqrt{\bm{s}_t}$ は勾配のRMS(Root Mean Square)の近似であり、大きな勾配を持つパラメータの実効学習率は $\eta/\sqrt{s_t}$ と自動的に小さくなり、小さな勾配のパラメータの学習率は相対的に大きくなります。

RMSPropはモメンタムとは独立に適応的学習率を実現しますが、Adamはモメンタムと適応的学習率を統合します。

Adam(Adaptive Moment Estimation)

更新則の導出

Adam(Kingma & Ba, 2015)は、モメンタム(1次モーメントのEMA)とRMSProp(2次モーメントのEMA)を統合した手法です。

ステップ1: 1次モーメント(平均)と2次モーメント(分散)の推定

$$ \begin{align} \bm{m}_t &= \beta_1 \bm{m}_{t-1} + (1 – \beta_1) \bm{g}_t \quad \text{(勾配の1次モーメント)} \\ \bm{v}_t &= \beta_2 \bm{v}_{t-1} + (1 – \beta_2) \bm{g}_t \odot \bm{g}_t \quad \text{(勾配の2次モーメント)} \end{align} $$

ここで $\bm{g}_t = \nabla J(\bm{w}_t)$ は現在の勾配です。

ステップ2: バイアス補正

$\bm{m}_t$ と $\bm{v}_t$ はゼロ初期化されるため、特に初期のステップでは0に偏った推定値になります。これを補正します。

$\bm{m}_0 = \bm{0}$ として $\bm{m}_t$ を展開すると

$$ \bm{m}_t = (1 – \beta_1) \sum_{i=1}^t \beta_1^{t-i} \bm{g}_i $$

期待値をとると

$$ E[\bm{m}_t] = E[\bm{g}_t] \cdot (1 – \beta_1^t) $$

したがって $E[\bm{m}_t] / (1 – \beta_1^t) = E[\bm{g}_t]$ となり、バイアス補正された推定量は

$$ \begin{align} \hat{\bm{m}}_t &= \frac{\bm{m}_t}{1 – \beta_1^t} \\ \hat{\bm{v}}_t &= \frac{\bm{v}_t}{1 – \beta_2^t} \end{align} $$

ステップ3: パラメータ更新

$$ \begin{equation} \bm{w}_{t+1} = \bm{w}_t – \frac{\eta}{\sqrt{\hat{\bm{v}}_t} + \epsilon} \odot \hat{\bm{m}}_t \end{equation} $$

デフォルトのハイパーパラメータ

Adamのデフォルト設定は

  • $\eta = 0.001$(学習率)
  • $\beta_1 = 0.9$(1次モーメントの減衰率)
  • $\beta_2 = 0.999$(2次モーメントの減衰率)
  • $\epsilon = 10^{-8}$(数値安定性)

これらの値は多くの問題で良い結果を出すことが実験的に確認されており、チューニングの手間が少ないことがAdamの大きな利点です。

Adamの直感的理解

Adamの更新量を直感的に分解すると

$$ \Delta \bm{w}_t = -\frac{\eta}{\sqrt{\hat{\bm{v}}_t} + \epsilon} \odot \hat{\bm{m}}_t $$

  • $\hat{\bm{m}}_t$: 勾配の方向を決める(モメンタムの効果)
  • $\sqrt{\hat{\bm{v}}_t}$: 各パラメータの学習率をスケーリングする(適応学習率の効果)
  • $\hat{\bm{m}}_t / \sqrt{\hat{\bm{v}}_t}$: 勾配の「信号対ノイズ比」(SNR)に近い量

勾配が一貫して大きいパラメータは $\hat{\bm{m}}_t \approx \sqrt{\hat{\bm{v}}_t}$ となり、実効的な更新量は約 $\eta$ と制限されます。勾配の方向が頻繁に変わるパラメータは $\hat{\bm{m}}_t \ll \sqrt{\hat{\bm{v}}_t}$ となり、更新量が小さくなります。

Adamは多くの問題で高速に収束しますが、汎化性能の面でSGD+Momentumに劣る場合があることが報告されています。その原因の一つがL2正則化との相互作用であり、AdamWがこれを修正します。

AdamW(Weight Decay の修正)

L2正則化と重み減衰の違い

L2正則化は損失関数に $\lambda\|\bm{w}\|^2$ を加えることで、勾配に $2\lambda\bm{w}$ が加算されます。

SGDの場合、L2正則化の勾配を加えた更新は

$$ \bm{w}_{t+1} = \bm{w}_t – \eta(\nabla J + 2\lambda\bm{w}_t) = (1 – 2\eta\lambda)\bm{w}_t – \eta\nabla J $$

一方、重み減衰(weight decay)は直接パラメータを減衰させます。

$$ \bm{w}_{t+1} = (1 – \lambda’)\bm{w}_t – \eta\nabla J $$

SGDでは $\lambda’ = 2\eta\lambda$ とすれば両者は等価です。しかし、Adamではこの等価性が成り立ちません

Adamでは適応的な学習率 $\eta/\sqrt{\hat{\bm{v}}_t}$ によって勾配がスケーリングされるため、L2正則化の項 $2\lambda\bm{w}$ も同じスケーリングを受けてしまいます。つまり、大きな勾配を持つパラメータは正則化の効果が薄められ、小さな勾配のパラメータは過度に正則化されます。

AdamWの更新則

Loshchilov & Hutter (2019) が提案したAdamWは、重み減衰をAdam の勾配ベースの更新とは分離(decouple)します。

$$ \begin{align} \bm{m}_t &= \beta_1 \bm{m}_{t-1} + (1 – \beta_1) \nabla J(\bm{w}_t) \\ \bm{v}_t &= \beta_2 \bm{v}_{t-1} + (1 – \beta_2) \nabla J(\bm{w}_t) \odot \nabla J(\bm{w}_t) \\ \hat{\bm{m}}_t &= \bm{m}_t / (1 – \beta_1^t) \\ \hat{\bm{v}}_t &= \bm{v}_t / (1 – \beta_2^t) \\ \bm{w}_{t+1} &= (1 – \eta\lambda)\bm{w}_t – \frac{\eta}{\sqrt{\hat{\bm{v}}_t} + \epsilon} \odot \hat{\bm{m}}_t \end{align} $$

重要な違いは最後の行です。重み減衰 $(1 – \eta\lambda)\bm{w}_t$ がAdamのスケーリングとは独立に適用されています。これにより、全てのパラメータに均等な正則化効果が得られます。

AdamWはTransformerの訓練やBERT/GPTの事前学習で標準的に使われています。

Pythonでのスクラッチ実装

import numpy as np
import matplotlib.pyplot as plt

class Optimizer:
    """最適化アルゴリズムの基底クラス"""
    def step(self, w, grad):
        raise NotImplementedError

class SGD(Optimizer):
    def __init__(self, lr=0.01):
        self.lr = lr

    def step(self, w, grad):
        return w - self.lr * grad

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

    def step(self, w, grad):
        if self.v is None:
            self.v = np.zeros_like(w)
        self.v = self.momentum * self.v + grad
        return w - self.lr * self.v

class RMSProp(Optimizer):
    def __init__(self, lr=0.01, beta=0.999, eps=1e-8):
        self.lr = lr
        self.beta = beta
        self.eps = eps
        self.s = None

    def step(self, w, grad):
        if self.s is None:
            self.s = np.zeros_like(w)
        self.s = self.beta * self.s + (1 - self.beta) * grad ** 2
        return w - self.lr * grad / (np.sqrt(self.s) + self.eps)

class Adam(Optimizer):
    def __init__(self, lr=0.001, beta1=0.9, beta2=0.999, eps=1e-8):
        self.lr = lr
        self.beta1 = beta1
        self.beta2 = beta2
        self.eps = eps
        self.m = None
        self.v = None
        self.t = 0

    def step(self, w, grad):
        if self.m is None:
            self.m = np.zeros_like(w)
            self.v = np.zeros_like(w)
        self.t += 1
        self.m = self.beta1 * self.m + (1 - self.beta1) * grad
        self.v = self.beta2 * self.v + (1 - self.beta2) * grad ** 2
        m_hat = self.m / (1 - self.beta1 ** self.t)
        v_hat = self.v / (1 - self.beta2 ** self.t)
        return w - self.lr * m_hat / (np.sqrt(v_hat) + self.eps)

class AdamW(Optimizer):
    def __init__(self, lr=0.001, beta1=0.9, beta2=0.999, eps=1e-8, weight_decay=0.01):
        self.lr = lr
        self.beta1 = beta1
        self.beta2 = beta2
        self.eps = eps
        self.wd = weight_decay
        self.m = None
        self.v = None
        self.t = 0

    def step(self, w, grad):
        if self.m is None:
            self.m = np.zeros_like(w)
            self.v = np.zeros_like(w)
        self.t += 1
        self.m = self.beta1 * self.m + (1 - self.beta1) * grad
        self.v = self.beta2 * self.v + (1 - self.beta2) * grad ** 2
        m_hat = self.m / (1 - self.beta1 ** self.t)
        v_hat = self.v / (1 - self.beta2 ** self.t)
        # 重み減衰をAdam更新と分離
        w = (1 - self.lr * self.wd) * w
        return w - self.lr * m_hat / (np.sqrt(v_hat) + self.eps)

# テスト用の関数(Bealeの関数)
def beale(w):
    x, y = w
    return ((1.5 - x + x*y)**2 + (2.25 - x + x*y**2)**2
            + (2.625 - x + x*y**3)**2)

def beale_grad(w):
    x, y = w
    dx = (2*(1.5 - x + x*y)*(-1 + y)
          + 2*(2.25 - x + x*y**2)*(-1 + y**2)
          + 2*(2.625 - x + x*y**3)*(-1 + y**3))
    dy = (2*(1.5 - x + x*y)*x
          + 2*(2.25 - x + x*y**2)*(2*x*y)
          + 2*(2.625 - x + x*y**3)*(3*x*y**2))
    return np.array([dx, dy])

# 各最適化手法の軌跡
w0 = np.array([0.0, 0.0])
n_steps = 300

optimizers = [
    ("SGD (lr=0.0001)", SGD(lr=0.0001)),
    ("Momentum (lr=0.0001)", SGDMomentum(lr=0.0001, momentum=0.9)),
    ("RMSProp (lr=0.01)", RMSProp(lr=0.01)),
    ("Adam (lr=0.01)", Adam(lr=0.01)),
]

fig, axes = plt.subplots(1, 2, figsize=(16, 6))

# (a) 軌跡
ax = axes[0]
x_range = np.linspace(-1.5, 4.5, 200)
y_range = np.linspace(-1.5, 2.0, 200)
X_grid, Y_grid = np.meshgrid(x_range, y_range)
Z = np.zeros_like(X_grid)
for i in range(X_grid.shape[0]):
    for j in range(X_grid.shape[1]):
        Z[i, j] = beale(np.array([X_grid[i, j], Y_grid[i, j]]))

ax.contour(X_grid, Y_grid, np.log10(Z + 1), levels=30, cmap="Blues", alpha=0.7)
ax.plot(3, 0.5, "r*", markersize=15, zorder=10)

colors = ["red", "blue", "green", "purple"]
for (name, opt), color in zip(optimizers, colors):
    w = w0.copy()
    trajectory = [w.copy()]
    for _ in range(n_steps):
        grad = beale_grad(w)
        w = opt.step(w, grad)
        trajectory.append(w.copy())
    trajectory = np.array(trajectory)
    ax.plot(trajectory[:, 0], trajectory[:, 1], "-", color=color, linewidth=1.5,
            alpha=0.8, label=name)
    ax.plot(trajectory[0, 0], trajectory[0, 1], "s", color=color, markersize=6)

ax.set_xlabel("$w_1$", fontsize=12)
ax.set_ylabel("$w_2$", fontsize=12)
ax.set_title("Optimizer Trajectories (Beale Function)", fontsize=13)
ax.legend(fontsize=9, loc="upper left")
ax.grid(True, alpha=0.3)

# (b) 損失曲線
ax = axes[1]
for (name, opt), color in zip(optimizers, colors):
    # optimizerをリセット
    if isinstance(opt, SGDMomentum):
        opt.v = None
    elif isinstance(opt, RMSProp):
        opt.s = None
    elif isinstance(opt, (Adam, AdamW)):
        opt.m = None; opt.v = None; opt.t = 0

    w = w0.copy()
    losses = []
    for _ in range(n_steps):
        losses.append(beale(w))
        grad = beale_grad(w)
        w = opt.step(w, grad)

    ax.plot(losses, color=color, linewidth=2, label=name)

ax.set_xlabel("Step", fontsize=12)
ax.set_ylabel("Loss (Beale function)", fontsize=12)
ax.set_title("Convergence Comparison", fontsize=13)
ax.legend(fontsize=9)
ax.grid(True, alpha=0.3)
ax.set_yscale("log")

plt.tight_layout()
plt.savefig("optimizer_comparison.png", dpi=150, bbox_inches="tight")
plt.show()

このグラフから、各最適化手法の特性が明確に読み取れます。

  1. 左図(軌跡): SGD(赤)は非常にゆっくりと最適解 $(3, 0.5)$ に向かっています。Momentum(青)は加速効果により SGDより速く移動しています。RMSProp(緑)とAdam(紫)は適応的学習率により、異方的な損失関数の地形に適応して効率的に最適解に接近しています

  2. 右図(損失曲線): Adam(紫)とRMSProp(緑)が最も速く損失を低下させ、300ステップでほぼ最適解に到達しています。Momentum(青)もSGD(赤)より明確に速い収束を示しています。SGDは同じステップ数ではまだ収束が不十分です

この結果は、適応的学習率を持つ手法(Adam、RMSProp)が収束速度の面で優れていることを示しています。しかし、汎化性能の面では必ずしもAdamが最良とは限らないことに注意が必要です。

各手法の比較まとめ

手法 更新の特徴 メリット デメリット 主な適用先
SGD 勾配のみ シンプル、汎化に優れる場合がある 収束が遅い、悪条件に弱い
SGD+Momentum 勾配のEMA 振動を抑制、加速 学習率のチューニングが必要 CNN(画像認識)
RMSProp 適応的学習率 パラメータごとにスケーリング 理論的収束保証が限定的 RNN
Adam EMA + 適応的学習率 高速収束、チューニング容易 汎化性能がSGDに劣る場合がある 一般的なDL
AdamW Adam + 分離型重み減衰 正則化が適切に機能 Transformer、NLP

使い分けの指針

画像認識(ResNet等): SGD + Momentum + ステップ学習率減衰。汎化性能が優れる傾向がある

自然言語処理(Transformer等): AdamW + warmup + コサイン減衰。Adamの高速収束と適切な重み減衰が必要

研究・プロトタイピング: まずAdamで素早く結果を得て、必要に応じてSGD+Momentumで追い込む

GAN: Adamの $\beta_1$ を0.0〜0.5に下げる(生成器と判別器の非定常性に対応)

import numpy as np
import matplotlib.pyplot as plt

# バイアス補正の効果を可視化
fig, axes = plt.subplots(1, 2, figsize=(14, 5.5))

# (a) バイアス補正の効果
ax = axes[0]
beta1 = 0.9
beta2 = 0.999
steps = np.arange(1, 51)

correction_m = 1 / (1 - beta1 ** steps)
correction_v = 1 / (1 - beta2 ** steps)

ax.plot(steps, correction_m, "b-", linewidth=2, label=f"1st moment ($\\beta_1={beta1}$)")
ax.plot(steps, correction_v, "r-", linewidth=2, label=f"2nd moment ($\\beta_2={beta2}$)")
ax.axhline(1.0, color="gray", linestyle="--", linewidth=1, alpha=0.5)

ax.set_xlabel("Step $t$", fontsize=12)
ax.set_ylabel("Bias correction factor $1/(1-\\beta^t)$", fontsize=12)
ax.set_title("Adam Bias Correction", fontsize=13)
ax.legend(fontsize=11)
ax.grid(True, alpha=0.3)

# (b) Adam vs AdamW の正則化効果の違い
ax = axes[1]

np.random.seed(42)
n_params = 100
w_init = np.random.randn(n_params) * 2

n_steps = 200
wd = 0.01
lr = 0.01

# 勾配を生成(ランダムだが一貫した方向)
grad_mean = np.random.randn(n_params) * 0.1
grad_std = np.abs(np.random.randn(n_params)) + 0.5

# Adam + L2正則化
w_adam_l2 = w_init.copy()
m, v = np.zeros_like(w_adam_l2), np.zeros_like(w_adam_l2)
norms_adam_l2 = []
for t in range(1, n_steps + 1):
    grad = grad_mean + grad_std * np.random.randn(n_params)
    grad_with_l2 = grad + 2 * wd * w_adam_l2  # L2正則化を勾配に加える
    m = 0.9 * m + 0.1 * grad_with_l2
    v = 0.999 * v + 0.001 * grad_with_l2 ** 2
    m_hat = m / (1 - 0.9**t)
    v_hat = v / (1 - 0.999**t)
    w_adam_l2 -= lr * m_hat / (np.sqrt(v_hat) + 1e-8)
    norms_adam_l2.append(np.linalg.norm(w_adam_l2))

# AdamW
w_adamw = w_init.copy()
m, v = np.zeros_like(w_adamw), np.zeros_like(w_adamw)
norms_adamw = []
for t in range(1, n_steps + 1):
    grad = grad_mean + grad_std * np.random.randn(n_params)
    m = 0.9 * m + 0.1 * grad
    v = 0.999 * v + 0.001 * grad ** 2
    m_hat = m / (1 - 0.9**t)
    v_hat = v / (1 - 0.999**t)
    w_adamw = (1 - lr * wd) * w_adamw  # 分離された重み減衰
    w_adamw -= lr * m_hat / (np.sqrt(v_hat) + 1e-8)
    norms_adamw.append(np.linalg.norm(w_adamw))

ax.plot(norms_adam_l2, "r-", linewidth=2, label="Adam + L2 regularization")
ax.plot(norms_adamw, "b-", linewidth=2, label="AdamW (decoupled)")
ax.set_xlabel("Step", fontsize=12)
ax.set_ylabel("$\\|\\mathbf{w}\\|_2$", fontsize=12)
ax.set_title("Weight Norm: Adam+L2 vs AdamW", fontsize=13)
ax.legend(fontsize=11)
ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig("adam_details.png", dpi=150, bbox_inches="tight")
plt.show()

このグラフから、Adamの重要な詳細が読み取れます。

  1. 左図(バイアス補正): 2次モーメントの補正係数(赤)は初期ステップで非常に大きく、$\beta_2 = 0.999$ の場合、ステップ1での補正係数は約1000です。これは $v_1 = 0.001 \cdot g_1^2$ という過小推定を $v_1 / 0.001 = g_1^2$ に補正することを意味します。ステップ数が増えると補正係数は1に漸近します

  2. 右図(Adam+L2 vs AdamW): Adam+L2(赤)ではパラメータの重みノルムが十分に減衰していませんが、AdamW(青)では重みノルムが効果的に制御されています。これはAdam+L2では正則化項が適応的学習率でスケーリングされてしまうため、大きな勾配を持つパラメータの正則化が弱まることが原因です

まとめ

本記事では、主要な最適化アルゴリズムの理論と実装を比較しました。

  • SGD + Momentum: 勾配のEMAで振動を抑制し加速する。$\mu = 0.9$ が標準。Nesterov版は「先読み」により $O(1/t^2)$ の収束を達成
  • RMSProp: 勾配の二乗のEMAで各パラメータの学習率を適応的にスケーリング
  • Adam: モメンタム(1次)+ 適応学習率(2次)を統合し、バイアス補正で初期ステップの推定を改善。チューニングが容易
  • AdamW: 重み減衰をAdam更新から分離し、全パラメータに均等な正則化を実現。Transformerの訓練で標準
  • SGD系は汎化性能に優れる場合があり、Adam系は収束速度に優れる場合があるため、問題に応じた使い分けが重要

次のステップとして、以下の記事も参考にしてください。