ML 学习站
跳到正文

深度学习优化器

Momentum / AdaGrad / RMSProp / Adam / AdamW 原理 + 怎么选。

40 分钟2 / 32,925
加载中...

深度学习优化器

本章问题: SGD 收敛慢, 还容易震荡。能不能用之前的梯度信息加速收敛? Adam 真的永远比 SGD 好吗? 学完这一章, 你能根据任务选对优化器。

1. 为什么需要更好的优化器?

SGD 的两个问题:

# SGD 在"病态" (ill-conditioned) 问题上很慢
# 想象损失函数是细长的椭圆 (长轴方向梯度小, 短轴方向梯度大)
import numpy as np
import matplotlib.pyplot as plt

# 病态二次函数: f(x, y) = 0.1*x^2 + 10*y^2
def f(x, y): return 0.1*x**2 + 10*y**2
def grad(x, y): return np.array([0.2*x, 20*y])

# 画等高线
x = np.linspace(-10, 10, 200); y = np.linspace(-1, 1, 200)
X, Y = np.meshgrid(x, y)
fig, ax = plt.subplots(figsize=(10, 5))
cs = ax.contour(X, Y, f(X, Y), levels=20)
ax.clabel(cs, inline=1, fontsize=8)
ax.set_xlabel('x (平缓方向)'); ax.set_ylabel('y (陡峭方向)')
ax.set_title('病态损失函数: 细长椭圆谷底')
plt.show()

SGD 在这种函数上:

  • y 方向梯度太大 → 震荡
  • x 方向梯度太小 → 走不动

→ 路径"之"字形, 收敛慢。

💡 核心观察: 不同方向需要不同的学习率。所有优化器都在试图解决这件事。

2. 动量法 (Momentum)

2.1 物理直觉

想象小球从山上滚下来:

  • 当前梯度 = 重力 (即时加速)
  • 之前速度 = 惯性 (持续运动)

两者合起来更新参数。

2.2 算法

# 伪代码
v = 0
for t in range(num_steps):
    g = compute_gradient(x)
    v = momentum * v + lr * g      # 累积动量
    x = x - v
# PyTorch 实现
import torch
optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)

2.3 数学解释

动量法相当于指数加权移动平均 (EMA):

有效窗口大小 ≈ 1/(1-β)。β=0.9 → 平均最近 10 步梯度。

# 动量法对比 SGD
import numpy as np
import matplotlib.pyplot as plt

def f(x, y): return 0.1*x**2 + 10*y**2
def grad(x, y): return np.array([0.2*x, 20*y])

# SGD
np.random.seed(42)
x = np.array([10.0, 0.5]); traj_sgd = [x.copy()]
for _ in range(40):
    x = x - 0.05 * grad(*x)
    traj_sgd.append(x.copy())

# Momentum (β=0.9)
x = np.array([10.0, 0.5]); traj_mom = [x.copy()]
v = np.zeros(2); beta = 0.9
for _ in range(40):
    v = beta * v + 0.05 * grad(*x)
    x = x - v
    traj_mom.append(x.copy())

traj_sgd = np.array(traj_sgd); traj_mom = np.array(traj_mom)
fig, ax = plt.subplots(figsize=(8, 8))
ax.contour(X, Y, f(X, Y), levels=20, alpha=0.5)
ax.plot(traj_sgd[:, 0], traj_sgd[:, 1], 'b-o', label='SGD', ms=4)
ax.plot(traj_mom[:, 0], traj_mom[:, 1], 'r-o', label='Momentum', ms=4)
ax.legend(); ax.set_xlabel('x'); ax.set_ylabel('y')
ax.set_title('动量法收敛更快 (病态函数)')
plt.show()

🎯 β 怎么选: β=0.9 是工业默认。β=0.99 更平滑但慢, β=0.5 接近 SGD。

3. AdaGrad

3.1 核心思想

自适应学习率: 频繁更新的参数, 学习率变小; 稀疏更新的参数, 学习率变大。

3.2 算法

# 伪代码
s = 0
for t in range(num_steps):
    g = compute_gradient(x)
    s = s + g**2                       # 累积梯度平方
    x = x - lr / (sqrt(s) + eps) * g   # 自适应学习率
# PyTorch
optimizer = torch.optim.Adagrad(model.parameters(), lr=0.01)

3.3 优缺点

优点缺点
稀疏特征也能学好 (NLP word2vec 经典)学习率单调递减, 后期太小 → 停训
不用手动调学习率凸问题上收敛好, 深度学习上常被 RMSProp/Adam 超越

⚠️ AdaGrad 的致命问题: 累积 s 单调增, η_t → 0, 模型可能学不到

4. RMSProp

4.1 修 AdaGrad 的 bug

不用累积所有梯度平方, 而是用指数加权移动平均——最近的梯度权重高。

# 伪代码
s = 0
for t in range(num_steps):
    g = compute_gradient(x)
    s = gamma * s + (1 - gamma) * g**2     # EMA
    x = x - lr / (sqrt(s) + eps) * g
# PyTorch
optimizer = torch.optim.RMSprop(model.parameters(), lr=0.001, alpha=0.9)

γ=0.9 是默认。

4.2 为什么有效

RMSProp 同时有:

  • 梯度动量 (类似 SGD+Momentum)
  • 自适应学习率 (类似 AdaGrad)

但不会像 AdaGrad 那样学习率 → 0。

5. AdaDelta

AdaDelta 是 RMSProp 的进一步改进: 不需要学习率参数。

# PyTorch
optimizer = torch.optim.Adadelta(model.parameters(), lr=1.0)

工业界用得不多 (被 Adam 替代), 但 Hinton 原作者坚持认为它是更好的设计。

6. Adam (Adaptive Moment Estimation)

6.1 公式

Adam = Momentum + RMSProp:

# 伪代码
m = 0  # 一阶矩 (梯度均值)
v = 0  # 二阶矩 (梯度方差)
for t in range(1, num_steps+1):
    g = compute_gradient(x)
    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 / (sqrt(v_hat) + eps)
# PyTorch (最常用)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

默认参数: lr=1e-3, β1=0.9, β2=0.999, eps=1e-8。

6.2 偏差修正的必要性

m, v 初始化为 0, 在前几步会偏向 0。修正后才是真实的均值。

# 第 1 步: m = (1-β1) * g, 不修正的话偏小
# 修正后 m_hat = m / (1 - β1) = g ✓

6.3 AdamW (Adam + 权重衰减解耦)

AdamW 把权重衰减从梯度里拿出去, 不加到 loss 里:

# PyTorch
optimizer = torch.optim.AdamW(model.parameters(), lr=0.001, weight_decay=0.01)

AdamW 是当前 Transformer 训练的事实标准 (BERT/GPT/T5 都用 AdamW)。

# 训练 Transformer 标准配方
model = torch.nn.Transformer(...)
optimizer = torch.optim.AdamW(
    model.parameters(),
    lr=3e-4,              # 比 Adam 小一点
    betas=(0.9, 0.95),    # β2 调小, 适应大 batch
    weight_decay=0.1      # 强正则化
)

7. 5 种优化器对比

优化器优点缺点适用场景
SGD简单, 泛化好慢, 调学习率难CNN 图像分类
SGD+Momentum比 SGD 快, 泛化好还是要调 lr图像分类首选
AdaGrad自适应 lr, 稀疏特征友好后期学不动NLP 稀疏 embedding
RMSProp修 AdaGrad bug仍需调 lrRNN
Adam收敛快, 默认参数好用泛化可能比 SGD 差Transformer, RNN, GAN, 通用首选
AdamWAdam + 正确权重衰减同 AdamBERT/GPT 等 Transformer 训练
# 在 MNIST 上 5 种优化器对比
import torch
import torch.nn as nn
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import time

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# 简单 MLP
model = nn.Sequential(nn.Flatten(), nn.Linear(784, 256), nn.ReLU(),
                      nn.Linear(256, 10)).to(device)

# 各种优化器
opts = {
    'SGD':       torch.optim.SGD(model.parameters(), lr=0.01),
    'SGD+M':     torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9),
    'RMSProp':   torch.optim.RMSprop(model.parameters(), lr=0.001),
    'Adam':      torch.optim.Adam(model.parameters(), lr=0.001),
    'AdamW':     torch.optim.AdamW(model.parameters(), lr=0.001, weight_decay=0.01),
}

# 训练 1 epoch, 记录耗时
dataset = datasets.MNIST('./data', train=True, download=True, transform=transforms.ToTensor())
loader = DataLoader(dataset, batch_size=64, shuffle=True)

for name, opt in opts.items():
    model = nn.Sequential(nn.Flatten(), nn.Linear(784, 256), nn.ReLU(),
                          nn.Linear(256, 10)).to(device)
    opt = type(opt)(model.parameters(), lr=opt.defaults['lr'],
                    **dict((k, v) for k, v in opt.defaults.items() if k != 'lr'))
    crit = nn.CrossEntropyLoss()
    t0 = time.time()
    for X, y in loader:
        X, y = X.to(device), y.to(device)
        opt.zero_grad()
        loss = crit(model(X), y)
        loss.backward()
        opt.step()
    print(f'{name}: 1 epoch 用时 {time.time()-t0:.1f}s')

8. Adam vs SGD: 真实表现

很多论文说 "Adam 训练 loss 低, 但测试 loss 不如 SGD" (generalization gap)。原因是:

  • Adam 自适应学习率, 让训练集 loss 降得快
  • 但训练集 loss 低 ≠ 泛化好 (尤其是 CNN)
  • SGD 的"统一学习率"反而强制泛化
# 经验 (来自 ResNet 论文):
# - CNN 图像分类: SGD+Momentum > Adam (ImageNet 测试集高 0.5-1%)
# - Transformer/NLP: AdamW > SGD (BERT 论文)
# - GAN: Adam (DCGAN 论文)
# - 强化学习: Adam / RMSProp (PPO 论文)

选优化器的决策树

你要训练什么?
├── CNN (图像分类)      → SGD+Momentum (lr=0.1, momentum=0.9)
├── Transformer (NLP)   → AdamW (lr=3e-4, weight_decay=0.1)
├── RNN / LSTM          → Adam 或 RMSProp (lr=1e-3)
├── GAN                 → Adam (lr=2e-4, betas=(0.5, 0.999))
├── 强化学习            → Adam 或 RMSProp
└── 不确定              → Adam / AdamW (默认参数就好, 先跑通)

9. 本章小结

优化器关键特性何时用
SGD最简单, 泛化好凸问题 / 简单模型
SGD+Momentum加惯性CNN 标配
AdaGrad自适应 lrNLP 稀疏特征
RMSPropEMA 二阶矩RNN
AdamMomentum + RMSProp通用首选
AdamWAdam + 正确权重衰减Transformer 标配

🎯 一句话总结: Adam 是"省心"优化器 (默认参数就能用), SGD+Momentum 是"精调"优化器 (调好了泛化更好), AdamW 是 Transformer 训练的事实标准。

练习

  1. 复现: 在 MNIST 上对比 SGD / SGD+M / Adam, 画出 loss 曲线。
  2. 思考: 为什么 Adam 在 GAN 训练里特别常用? (提示: GAN 的 loss 震荡剧烈)
  3. 实验: ResNet 训练 CIFAR-10, 分别用 SGD 和 Adam, 看哪个 test accuracy 高。
  4. 查论文: 找一个用 Transformer 的最新论文, 看它用什么优化器 + lr + wd。

拓展阅读

  • Kingma & Ba (2015) Adam: A Method for Stochastic Optimization (Adam 原始论文)
  • Loshchilov & Hutter (2019) Decoupled Weight Decay Regularization (AdamW)
  • D2L 第 11.6-11.10 节 (李沐等): 本章主要参考

章末小测验

检验你对《深度学习优化器》的掌握程度。

1

optimizers 的核心概念是?

学完这章?

标记完成, 进入下一章

下一章: 训练调优实战

还有疑问? 问问 AI (v19.5)

基于全站 19 门课 68 章内容检索 + LLM 总结, 会引用具体章节作为出处

讨论区(0)

加载评论中...