深度学习优化器
本章问题: 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 | 仍需调 lr | RNN |
| Adam | 收敛快, 默认参数好用 | 泛化可能比 SGD 差 | Transformer, RNN, GAN, 通用首选 |
| AdamW | Adam + 正确权重衰减 | 同 Adam | BERT/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 | 自适应 lr | NLP 稀疏特征 |
| RMSProp | EMA 二阶矩 | RNN |
| Adam | Momentum + RMSProp | 通用首选 |
| AdamW | Adam + 正确权重衰减 | Transformer 标配 |
🎯 一句话总结: Adam 是"省心"优化器 (默认参数就能用), SGD+Momentum 是"精调"优化器 (调好了泛化更好), AdamW 是 Transformer 训练的事实标准。
练习
- 复现: 在 MNIST 上对比 SGD / SGD+M / Adam, 画出 loss 曲线。
- 思考: 为什么 Adam 在 GAN 训练里特别常用? (提示: GAN 的 loss 震荡剧烈)
- 实验: ResNet 训练 CIFAR-10, 分别用 SGD 和 Adam, 看哪个 test accuracy 高。
- 查论文: 找一个用 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 节 (李沐等): 本章主要参考
章末小测验
检验你对《深度学习优化器》的掌握程度。
optimizers 的核心概念是?
还有疑问? 问问 AI (v19.5)
基于全站 19 门课 68 章内容检索 + LLM 总结, 会引用具体章节作为出处