引言
训练模型本质上是「把损失函数压到最低」——而压到最低的引擎就是梯度下降。理解优化器,是理解训练一切模型的起点:为什么学习率一大就震荡、Adam 为什么总比 SGD 收敛快、为什么 2018 年后大家默认用 AdamW。本文从梯度下降的直觉出发,讲清 SGD、Momentum、AdaGrad、RMSProp、Adam、AdamW 六代优化器的演进逻辑,给出超参建议与选型矩阵,并附 PyTorch 实战对照。
前置:/ml-neural-networks-basics/(张量与自动求导)、/ml-model-evaluation/(验证集与调参)、/ml-deep-learning-advanced/(训练调参全景)。
目录
- 1. 梯度下降:把损失压到最低的引擎
- 2. 学习率:最敏感的超参
- 3. 批大小与随机性:全量、随机与小批量
- 4. Momentum:让下坡更稳
- 5. AdaGrad 与 RMSProp:逐参数自适应
- 6. Adam:一阶动量二阶动量结合
- 7. AdamW:解耦权重衰减
- 8. 学习率调度:从常数到 warmup
- 9. 优化器选择与超参矩阵
- 10. 速查表与一句话记忆
- 延伸阅读
1. 梯度下降:把损失压到最低的引擎
1.1 直觉:下坡找最低点
想象把损失函数想成一片起伏的山地,参数是位置,目标是走到谷底。梯度就是当前点「最陡的上坡方向」,梯度下降则沿相反方向(最陡的下坡)迈步:
# 更新规则:θ ← θ - lr · ∇L(θ)
theta = theta - lr * grad
其中 lr(学习率)是步长,grad 是损失对参数的梯度。
1.2 一个超简线性回归对照
import numpy as np
# y = 2x + 1 + noise,用梯度下降拟合
X = np.random.rand(100, 1) * 10
y = 2 * X + 1 + np.random.randn(100, 1) * 0.5
w, b, lr = 0.0, 0.0, 0.01
for _ in range(500):
pred = w * X + b
loss = ((pred - y) ** 2).mean()
dw = (2 * (pred - y) * X).mean() # ∂L/∂w
db = (2 * (pred - y)).mean() # ∂L/∂b
w -= lr * dw
b -= lr * db
print(w, b) # ≈ 2.0, 1.0
记忆:梯度下降 = 沿最陡下坡方向迈步;θ ← θ - lr·∇L;学习率是步长,梯度是方向。
2. 学习率:最敏感的超参
2.1 学习率三态
| 学习率 | 表现 | 诊断 |
|---|---|---|
| 太小 | 收敛极慢,loss 平缓下降 | 训练半天 loss 才动 |
| 合适 | loss 平滑下降、收敛到好解 | 理想状态 |
| 太大 | loss 震荡甚至发散 | 训练曲线锯齿状 |
2.2 实践建议
# 从默认值开始:SGD 0.01 / Adam 0.001
# 观察前几百步:loss 若震荡 → 减半;若几乎不动 → 翻倍
# 用对数尺度搜:1e-1 / 1e-2 / 1e-3 / 1e-4,各跑短训练比较
记忆:学习率三态「太小慢 / 合适稳 / 太大炸」;从默认值起步、按对数尺度搜索、看前几百步 loss 反应。
3. 批大小与随机性:全量、随机与小批量
3.1 三种梯度
| 模式 | 每次用多少样本 | 特点 |
|---|---|---|
| 全量(Batch GD) | 全部 | 方向准但慢、易陷入局部最优 |
| 随机(SGD) | 1 个 | 更新快、噪声大、loss 抖动 |
| 小批量(Mini-batch) | 如 32/64 | 折中、GPU 并行友好 |
3.2 为什么噪声反而是好事
小批量引入的随机噪声让参数「能跳出」尖锐的局部极小值——这是 SGD 家族相对全量梯度下降的隐性优势。实践中 Mini-batch + 合适学习率是默认选择,batch size 常用 16/32/64,随 GPU 显存调大。
记忆:默认用小批量(32/64)——既有并行效率又有跳出局部极小的噪声;batch 太大要相应调大学习率。
4. Momentum:让下坡更稳
4.1 问题:SGD 在山谷里震荡
损失曲面常像狭长山谷:垂直于谷底的方向陡、沿谷底的方向缓。SGD 会沿陡的方向来回震荡,沿缓的方向爬得慢。
4.2 Momentum 的解法:累计方向
Momentum 用「指数加权移动平均」累计历史梯度,让陡方向的震荡相互抵消、缓方向持续加速:
v = 0.9 * v - lr * grad # v 是速度,0.9 是动量系数
theta += v
- 震荡方向:正负梯度平均 → 趋于 0 → 不来回跳
- 一致方向:连续同向 → 加速 → 更快到达谷底
4.3 参数
动量系数 momentum 常用 0.9(PyTorch 的 SGD(momentum=0.9)),0.99 更稳但更慢。
记忆:Momentum 用历史梯度的加权平均当速度——抵消震荡、加速一致方向;动量系数默认 0.9。
5. AdaGrad 与 RMSProp:逐参数自适应
5.1 问题:所有参数该用同一个学习率吗
稀疏特征(如「用户 ID」独热)梯度稀疏但偶尔很大,密集特征梯度频繁但较小——用同一学习率很难兼顾。
5.2 AdaGrad:按历史平方根缩放
AdaGrad 给每个参数累计「历史梯度平方」,学习率除以它的平方根——被更新多的参数步长自动变小:
G += grad ** 2
theta -= lr * grad / (sqrt(G) + 1e-8)
问题:G 只增不减,学习率会单调衰减到零——后期几乎停学。
5.3 RMSProp:衰减的历史平方
RMSProp 给 G 加指数衰减(类似 Momentum 的加权平均),让学习率能「恢复」:
G = 0.9 * G + 0.1 * grad ** 2
theta -= lr * grad / (sqrt(G) + 1e-8)
记忆:自适应优化器按「该参数历史梯度」逐参数缩放学习率——AdaGrad 让学习率单调衰减到停学,RMSProp 加指数衰减让它可恢复。
6. Adam:一阶动量二阶动量结合
6.1 Adam = Momentum + RMSProp
Adam 同时维护一阶动量(方向,像 Momentum)与二阶动量(自适应缩放,像 RMSProp),并加偏差校正抵消初始阶段动量偏小:
# Adam 更新核心(PyTorch 内部)
m = 0.9 * m + 0.1 * grad # 一阶动量(带偏差校正)
v = 0.999 * v + 0.001 * grad**2 # 二阶动量(带偏差校正)
theta -= lr * m_hat / (sqrt(v_hat) + 1e-8)
6.2 为什么 Adam 好用
- 对学习率不敏感:默认
lr=0.001在绝大多数任务都能跑出不错结果 - 快速起步:自适应步长让它在早期收敛很快
- 适合稀疏/非平稳梯度:NLP、Embedding、GAN 等场景首选
6.3 使用姿势
import torch.optim as optim
opt = optim.Adam(model.parameters(), lr=1e-3) # 默认即可
opt = optim.Adam(model.parameters(), lr=1e-3, betas=(0.9, 0.999), weight_decay=1e-5)
记忆:Adam = Momentum(一阶动量)+ RMSProp(二阶动量)+ 偏差校正——对学习率不敏感、起步快,是「不知道选什么时」的默认解。
7. AdamW:解耦权重衰减
7.1 L2 正则与权重衰减的微妙差异
传统实现里「权重衰减」被混进梯度(等价于 L2 正则),但 Adam 的自适应步长会扭曲这种等价关系——L2 大项被小步长吸收,衰减失效。
7.2 AdamW:衰减直接在参数上做
AdamW 把权重衰减与梯度更新解耦,衰减项绕过二阶动量直接作用在参数上:
# 经典 Adam 中:grad += weight_decay * theta(进自适应)
# AdamW 中:theta -= lr * weight_decay * theta(独立、不解耦自适应)
实践表明 AdamW 在 Transformer/预训练模型上泛化更好——PyTorch 的 optim.AdamW 已是事实标准。
记忆:AdamW 把权重衰减从自适应梯度里解耦出来直接作用于参数——Transformer 时代的事实标准,泛化更好。
8. 学习率调度:从常数到 warmup
8.1 调度的两种动机
- 前期想快:大步走快速到达好区域
- 后期想稳:小步走精修到谷底
8.2 常用调度器
| 调度器 | 行为 | 适用 |
|---|---|---|
StepLR | 每 N 步乘 γ | 简单、经典 |
CosineAnnealing | 余弦从高到 0 | 训练后期平滑收敛 |
ReduceLROnPlateau | loss 平台期才降 | 自动、省心 |
| Warmup + Decay | 先线性升再降 | Transformer/大模型标配 |
8.3 warmup 为什么必要
大学习率 + 大模型早期梯度不可靠(参数随机、统计不稳),先小步预热几百步再放开,能避免早期发散。PyTorch 用 torch.optim.lr_scheduler 组合实现:
sched = optim.lr_scheduler.CosineAnnealingLR(opt, T_max=50)
for epoch in range(50):
train_one_epoch()
sched.step()
记忆:学习率调度 = 前期大步、后期小步;CosineAnnealing 平滑收敛、ReduceLROnPlateau 平台期自动降;大模型标配 warmup 防早期发散。
9. 优化器选择与超参矩阵
9.1 选型矩阵
| 场景 | 优化器 | 理由 |
|---|---|---|
| 简单回归/CNN 入门 | SGD + Momentum | 收敛解质量高、可解释 |
| 一般深度学习 | Adam / AdamW | 默认、省心、对学习率不敏感 |
| Transformer/NLP | AdamW + warmup | 泛化最好、事实标准 |
| 稀疏特征(推荐/Embedding) | Adam | 二阶动量适配稀疏梯度 |
| 小数据要稳 | SGD + 小学习率 | 噪声下更稳 |
9.2 超参默认值与调节
# Adam: lr=1e-3, betas=(0.9, 0.999), eps=1e-8
# AdamW: lr=1e-3~3e-4(越大模型越小), weight_decay=0.01
# SGD+Momentum: lr=0.01~0.1, momentum=0.9
# 学习率调度:Cosine 或 ReduceLROnPlateau,大模型加 warmup
# 排查顺序:先固定优化器,只调 lr;再试调度器;最后调 weight_decay
记忆:选型看场景——一般任务 AdamW、NLP 必 AdamW+warmup、追求可解释与高泛化用 SGD+Momentum;超参先锁优化器只动学习率。
10. 速查表与一句话记忆
| 优化器 | 核心机制 | 一句话 |
|---|---|---|
| SGD | 直接沿梯度 | 简单、解质量高、要自己调 lr |
| SGD+Momentum | 累计方向 | 抵消震荡、加速一致方向 |
| AdaGrad | 平方根缩放 | 学习率单调衰减到停学 |
| RMSProp | 衰减平方根 | 学习率可恢复 |
| Adam | 一阶+二阶动量 | 默认解、对 lr 不敏感 |
| AdamW | 解耦权重衰减 | Transformer 标配、泛化好 |
一句话记忆:优化器演进是「补丁」的叠加——Momentum 补方向震荡、RMSProp 补逐参数步长、Adam 两者合并、AdamW 再解耦权重衰减;工程上一般任务默认 AdamW(lr=1e-3、weight_decay=0.01),追求高泛化/可解释用 SGD+Momentum(lr=0.01~0.1、momentum=0.9),Transformer 必加 warmup + Cosine 调度——「先锁优化器、只调学习率、最后动衰减」是调参的稳妥顺序。
延伸阅读
- /ml-neural-networks-basics/ — 张量、自动求导与训练循环
- /ml-deep-learning-advanced/ — 优化器演进、Dropout 与训练调参
- /ml-model-evaluation/ — 验证集、学习曲线与过拟合
- /ml-automl-hpo/ — 学习率等超参的自动搜索
- [[ai-ml]] — 训练稳定性与大规模分布式训练
- PyTorch Optimizers 文档
- AdamW 原论文
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。