深度学习优化器(Optimization)
模型训练的本质是一场”寻找最低点”的旅程,而优化器就是你的向导。本文从梯度下降出发,系统讲解 SGD、Momentum、AdaGrad、RMSProp、Adam 等主流优化器的原理与适用场景,帮你在实践中做出正确的选择。
📑 目录
- 1. 为什么需要优化器
- 2. 梯度下降基础
- 3. 动量优化:SGD with Momentum
- 4. 自适应学习率:AdaGrad
- 5. RMSProp:修正 AdaGrad 的缺陷
- 6. Adam:工程上的首选
- 7. 进阶变体
- 8. 实战选择指南
- 9. 常见陷阱与最佳实践
- 📝 总结
- 🎯 自我检验清单
- 📚 参考资料
1. 为什么需要优化器
训练一个神经网络,本质上是在解一个优化问题:找到一组参数 $\theta$,使损失函数 $\mathcal{L}(\theta)$ 尽可能小。
损失函数的地形通常高度非凸——有山谷、鞍点、陡坡和平原。优化器就像登山者手里的路线图和工具,不同优化器对不同地形的适应能力截然不同。
朴素 SGD 存在三个根本痛点,正是这些痛点推动了现代优化器的演进:
- 参数一刀切:所有参数使用同一学习率 η,但不同参数的重要性和梯度尺度差异悬殊
- 稀疏梯度更新慢:词嵌入等稀疏特征偶尔才出现在 batch 中,统一学习率无法让这类参数快速追上
- 鞍点陷阱:高维空间中鞍点远比局部最优更常见,鞍点附近梯度趋近于零,训练容易停滞
好的优化器需要兼顾三个目标:
- 收敛速度:尽快找到低点
- 泛化能力:找到的解在测试集上表现好
- 稳定性:不在训练过程中发散或剧烈震荡
2. 梯度下降基础
2.1 原始梯度下降
最朴素的想法是:沿着损失函数下降最快的方向走一步。这就是梯度下降(Gradient Descent):
$$
\theta_{t+1} = \theta_t - \eta \cdot \nabla_\theta \mathcal{L}(\theta_t)
$$
其中 $\eta$ 是学习率,控制每步的步长;$\nabla_\theta \mathcal{L}$ 是损失函数对参数的梯度。
2.2 三种梯度下降变体
实际训练中,我们不一定每次都用全量数据计算梯度:
| 变体 | 每次使用数据量 | 梯度噪声 | 计算代价 |
|---|---|---|---|
| 批量梯度下降(BGD) | 全部训练集 | 低 | 极高(大数据集不可行) |
| 随机梯度下降(SGD) | 单个样本 | 高 | 极低 |
| 小批量梯度下降(Mini-batch SGD) | 一个 batch(如 32/128) | 适中 | 适中 |
💡 提示:现代深度学习几乎都用 Mini-batch SGD,”SGD” 在框架(如 PyTorch)中通常就指 Mini-batch SGD。
2.3 学习率的重要性
学习率 $\eta$ 是梯度下降中最关键的超参数:
- $\eta$ 太大 → 参数在最优点附近震荡甚至发散
- $\eta$ 太小 → 收敛极慢,容易卡在局部最优
⚠️ 注意:学习率没有”通用最优值”,需要结合模型规模、batch size、数据分布调整。常见的起始值范围是 $[10^{-4}, 10^{-1}]$。
3. 动量优化:SGD with Momentum
3.1 SGD 的问题
普通 SGD 在”沟壑型”损失函数上表现很差:在狭窄方向上(梯度大)步子迈得太大,在宽阔方向上(梯度小)步子迈得太小,结果路线在沟里来回震荡,进展缓慢。
类比:想象你在一个狭长的山谷里滚球,球会在两壁之间反弹,而不是沿着谷底直奔出口。
3.2 动量的引入
动量(Momentum)的核心思想:积累历史梯度方向,抑制振荡,加速前进。就像物理学中的动量,物体运动会有惯性,方向不会突变。
引入速度向量 $v$:
$$
v_t = \beta \cdot v_{t-1} + \nabla_\theta \mathcal{L}(\theta_t)
$$
$$
\theta_{t+1} = \theta_t - \eta \cdot v_t
$$
其中 $\beta \in [0, 1)$ 是动量系数,通常取 0.9。$v_t$ 直接累积梯度(而非做归一化加权),使得持续同向的梯度会叠加放大,这正是”动量”加速的来源。
📌 关键点:当历史梯度方向一致时,$v$ 会不断累积,步长加速;当梯度方向反复变化时,$v$ 相互抵消,抑制震荡。
3.3 PyTorch 使用示例
1 | import torch.optim as optim |
4. 自适应学习率:AdaGrad
4.1 核心动机
不同参数的梯度大小差异悬殊——某些参数(如高频特征对应的权重)梯度很大,另一些(如低频特征)梯度极小。用统一的学习率对所有参数一视同仁,显然不公平。
AdaGrad 的思路:给梯度大的参数自动缩小学习率,给梯度小的参数自动放大学习率。
4.2 更新规则
AdaGrad 累积每个参数的历史梯度平方和 $G_t$:
$$
G_t = G_{t-1} + \left(\nabla_\theta \mathcal{L}(\theta_t)\right)^2
$$
$$
\theta_{t+1} = \theta_t - \frac{\eta}{\sqrt{G_t + \epsilon}} \cdot \nabla_\theta \mathcal{L}(\theta_t)
$$
其中 $\epsilon$(通常取 $10^{-8}$)是防止除零的数值稳定项。
梯度越大的方向,$G_t$ 积累越多,有效学习率 $\frac{\eta}{\sqrt{G_t}}$ 越小——这正是”自适应”的含义。
4.3 AdaGrad 的缺陷
$G_t$ 只增不减。训练时间一长,$G_t$ 会越积越大,所有参数的学习率趋近于零,模型过早停止学习。
这使得 AdaGrad 在深层网络长时间训练中表现很差,但在稀疏数据场景(如 NLP 词向量)中仍有竞争力。
5. RMSProp:修正 AdaGrad 的缺陷
5.1 指数移动平均代替累积求和
RMSProp(Root Mean Square Propagation)对 AdaGrad 的”学习率归零”问题提出了一个简洁的修复:不累积全量历史梯度,改用指数移动平均(EMA)保留近期梯度信息。
$$
s_t = \rho \cdot s_{t-1} + (1 - \rho) \cdot \left(\nabla_\theta \mathcal{L}(\theta_t)\right)^2
$$
$$
\theta_{t+1} = \theta_t - \frac{\eta}{\sqrt{s_t + \epsilon}} \cdot \nabla_\theta \mathcal{L}(\theta_t)
$$
衰减系数 $\rho$(通常取 0.9)控制”记忆长度”——越接近 1,越看重历史;越小,越注重当前梯度。
📌 关键点:EMA 机制让 $s_t$ 保持稳定,不会无限增大,从而解决了学习率消亡的问题。
5.2 与 AdaGrad 的对比
| 特性 | AdaGrad | RMSProp |
|---|---|---|
| 历史梯度处理 | 累积求和 | 指数移动平均 |
| 学习率变化 | 单调递减趋零 | 动态调整,保持活跃 |
| 适用场景 | 稀疏特征、浅层模型 | 深层网络、RNN |
6. Adam:工程上的首选
6.1 Adam = Momentum + RMSProp
Adam(Adaptive Moment Estimation)可以理解为把 SGD with Momentum 和 RMSProp 的优点合二为一:
- 一阶矩(动量项)$m_t$:累积梯度方向信息,加速收敛
- 二阶矩(自适应项)$v_t$:累积梯度幅度信息,自适应调整学习率
6.2 完整公式
Step 1:计算一阶矩和二阶矩的 EMA
$$
m_t = \beta_1 \cdot m_{t-1} + (1 - \beta_1) \cdot \nabla_\theta \mathcal{L}(\theta_t)
$$
$$
v_t = \beta_2 \cdot v_{t-1} + (1 - \beta_2) \cdot \left(\nabla_\theta \mathcal{L}(\theta_t)\right)^2
$$
Step 2:偏差修正
初始时刻 $m_0 = v_0 = 0$,导致早期估计值偏小。偏差修正项将其校正:
$$
\hat{m}_t = \frac{m_t}{1 - \beta_1^t}, \quad \hat{v}_t = \frac{v_t}{1 - \beta_2^t}
$$
Step 3:参数更新
$$
\theta_{t+1} = \theta_t - \frac{\eta}{\sqrt{\hat{v}_t} + \epsilon} \cdot \hat{m}_t
$$
默认超参数:$\beta_1 = 0.9$,$\beta_2 = 0.999$,$\epsilon = 10^{-8}$,$\eta = 10^{-3}$。
6.3 为什么需要偏差修正
考虑初始状态:$m_0 = 0$。
第一步更新后:$m_1 = (1 - \beta_1) \cdot g_1 = 0.1 \cdot g_1$,这严重低估了实际梯度 $g_1$。
偏差修正后:$\hat{m}_1 = \frac{m_1}{1 - \beta_1^1} = \frac{0.1 \cdot g_1}{0.1} = g_1$,恢复了正确估计。
随着 $t$ 增大,$1 - \beta_1^t \to 1$,偏差修正的影响逐渐消失。
6.4 Adam 直觉:三个组件各司其职
| 组件 | 核心作用 | 超参默认值 | 直觉 |
|---|---|---|---|
| 一阶矩 $m_t$ | 平滑梯度方向,提供动量 | $\beta_1 = 0.9$ | 记忆约 10 步的梯度方向,帮助穿越鞍点 |
| 二阶矩 $v_t$ | 自适应缩放,反映梯度震荡幅度 | $\beta_2 = 0.999$ | 记忆约 1000 步的历史,频繁更新的参数步长被压缩 |
| 偏差修正 $\hat{m}, \hat{v}$ | 校正初始为零导致的低估 | — | $t \to \infty$ 时修正项趋近 1,自动消退 |
理解 Adam 最核心的直觉是:分母 $\sqrt{\hat{v}_t}$ 起到了”缩放器”的作用:
- 若某参数梯度持续偏大($\hat{v}_t$ 大),步长被压缩 → 防止震荡
- 若某参数梯度持续偏小($\hat{v}_t$ 小),步长被放大 → 加速稀疏特征的学习
这正是自适应学习率的精髓:不同参数获得各自合适的有效步长,而非一刀切。
6.5 PyTorch 使用示例
1 | import torch.optim as optim |
💡 提示:大多数情况下,Adam 的默认参数就能工作得很好,无需大量调参——这是它广受工程师喜爱的重要原因。
7. 进阶变体
7.1 AdamW:分离权重衰减
Adam 和 AdamW 的区别,是优化器发展史上最重要的洞见之一。
问题根源:L2 正则化 ≠ 权重衰减
在 SGD 中,L2 正则化与权重衰减是完全等价的。但在 Adam 里,两者行为截然不同:
❌ Adam 的 L2 实现(存在缺陷):将正则项 $\lambda\theta_t$ 加入梯度,再经过自适应缩放:
$$
\tilde{g}_t = g_t + \lambda \cdot \theta_t
$$
$$
\theta_{t+1} = \theta_t - \eta \cdot \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon}
\quad \text{(此处 } m, v \text{ 均由 } \tilde{g}_t \text{ 计算)}
$$
✅ AdamW 的权重衰减(正确实现):将衰减直接作用在参数上,绕过自适应机制:
$$
\theta_{t+1} = \theta_t - \eta \cdot \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon} - \eta\lambda\theta_t
\quad \text{(此处 } m, v \text{ 由原始梯度 } g_t \text{ 计算)}
$$
为什么 Adam 的 L2 有问题\?
自适应缩放会扭曲正则化效果:梯度小的参数($\hat{v}_t$ 小)本应被正则化更多,但分母小反而导致步长被放大,L2 的惩罚强度被意外增大,行为不可预测。AdamW 将权重衰减与梯度历史解耦,正则化强度不再受梯度幅度干扰。这在 Transformer 类模型(BERT、GPT 等)的预训练中被广泛验证为更有效的正则化手段。
1 | import torch |
⚠️ 注意:训练 LLM/Transformer 时,优先选择 AdamW 而非 Adam,weight_decay 常设为 0.01 或 0.1。
7.2 Lion:更节省显存
Lion(EvoLved Sign Momentum)由 Google Brain 2023 年通过程序化搜索发现,只使用符号梯度,更新规则更简洁:
$$
c_t = \beta_1 \cdot m_{t-1} + (1 - \beta_1) \cdot \nabla_\theta \mathcal{L}
$$
$$
\theta_{t+1} = \theta_t - \eta \cdot \text{sign}(c_t) - \lambda \cdot \theta_t
$$
$$
m_t = \beta_2 \cdot m_{t-1} + (1 - \beta_2) \cdot \nabla_\theta \mathcal{L}
$$
由于只维护一阶矩 $m$,**显存占用比 Adam 少约 33%**,在超大模型训练中有实际意义。
7.3 Muon:MLP 层专用
Muon(Momentum + Orthogonalization)是近期 LLM 训练中备受关注的优化器,对线性层的权重矩阵进行梯度正交化处理,在矩阵更新的”流形约束”上做优化,在某些场景下表现优于 AdamW。
8. 实战选择指南
8.1 优化器横向对比
| 优化器 | 动量 | 自适应学习率 | 显存开销 | 推荐场景 |
|---|---|---|---|---|
| SGD | ❌ | ❌ | 极低 | CV 微调、需要高泛化性 |
| SGD + Momentum | ✅ | ❌ | 极低 | CV 分类任务主流选择 |
| AdaGrad | ❌ | ✅ | 低 | 稀疏特征、词向量 |
| RMSProp | ✅(可选) | ✅ | 低 | RNN、非平稳目标 |
| Adam | ✅ | ✅ | 中(额外 2x,共 3x) | 通用默认选择 |
| AdamW | ✅ | ✅ | 中(额外 2x,共 3x) | Transformer、LLM 预训练 |
| Lion | ✅ | ❌ | 较低(额外 1x,共 2x) | 超大模型、显存受限 |
8.2 决策流程
graph TD
A["需要选择优化器"] --> B{"任务类型\?"}
B --> C["CV 分类(ImageNet 等)"]
B --> D["Transformer / LLM 训练"]
B --> E["RNN / 时间序列"]
B --> F["通用 / 快速实验"]
C --> G["SGD + Momentum\n(搭配 CosineAnnealing)"]
D --> H["AdamW\n(weight_decay=0.01)"]
E --> I["RMSProp 或 Adam"]
F --> J["Adam(默认参数)"]
H --> K{"显存是否紧张\?"}
K --> L["是 → 考虑 Lion"]
K --> M["否 → 坚持 AdamW"]
8.3 学习率调度(不可忽视)
优化器需要配合学习率调度器(Scheduler)才能发挥最佳效果:
1 | from torch.optim.lr_scheduler import CosineAnnealingLR, LinearLR, SequentialLR |
常用调度策略:
| 策略 | 适用场景 |
|---|---|
| CosineAnnealingLR | LLM/Transformer 预训练,现在几乎是标配 |
| StepLR | CV 分类训练,每隔固定 epoch 衰减 |
| ReduceLROnPlateau | 验证集 loss 不下降时自动降低 lr |
| LinearWarmup + 衰减 | 微调大模型,先 warmup 再衰减 |
8.4 超参数设置指南
| 超参数 | 默认值 | 含义 | 调整建议 |
|---|---|---|---|
| $\eta$(学习率) | 1e-3 | 整体步长 | 最重要的超参,通常搭配学习率调度使用 |
| $\beta_1$ | 0.9 | 一阶矩衰减 | 一般无需调整;训练不稳定时可降至 0.85 |
| $\beta_2$ | 0.999 | 二阶矩衰减 | LLM 训练常用 0.95;稀疏梯度场景保持 0.999 |
| $\epsilon$ | 1e-8 | 数值稳定项 | 梯度极小时可增大至 1e-6 |
| $\lambda$(AdamW) | 0.01~0.1 | 权重衰减强度 | 大模型常用 0.1;BERT/GPT 类用 0.01 |
9. 常见陷阱与最佳实践
9.1 训练不稳定时的排查顺序
当训练出现 loss 震荡、NaN 或收敛停滞时,按以下优先级排查:
- 降低学习率:过大的 lr 是最常见原因,先尝试缩小 10 倍
- 加梯度裁剪:防止梯度爆炸,
clip_grad_norm_的max_norm通常设 1.0 - **降低 $\beta_2$**:从 0.999 降至 0.95,让优化器对近期梯度变化更敏感
LLM / Transformer 推荐配置:
1 | import torch |
9.2 何时不选 Adam
Adam 的自适应步长有时会找到较尖锐的局部最优,使泛化性能不如 SGD + 动量。
- ✅ 追求极致泛化(如 ImageNet 刷 Top-1):SGD + Momentum 搭配 StepLR 仍有竞争力
- ✅ 超大规模模型、显存受限:考虑 Adafactor(不存储完整 $v_t$,显存大幅降低)或 8-bit Adam(对优化器状态量化,显存减少约 75%,性能几乎无损)
📌 关键点:Adam 的自适应性是双刃剑——它让训练更容易,但有时会以牺牲泛化为代价。追求极致性能时,值得对比 SGD + Momentum。
📝 总结
优化器的发展脉络可以用一句话概括:从统一步长到自适应步长,从纯梯度方向到动量加速,每一代改进都针对上一代的具体缺陷。
关键要点回顾:
- SGD:最简单,但需要精心调学习率,在 CV 任务中搭配 Momentum 和 Scheduler 仍是竞争力很强的选择
- AdaGrad → RMSProp:引入自适应学习率,RMSProp 用 EMA 解决了 AdaGrad 学习率归零的问题
- Adam:动量 + 自适应学习率,加上偏差修正,是工程上最常用的默认优化器
- AdamW:解耦权重衰减(L2 ≠ 权重衰减),Transformer 训练的最优选择
- Lion:只用符号梯度,显存更省,适合超大模型
实践建议:
- 快速实验用 Adam(lr=1e-3)
- Transformer 训练用 AdamW(lr=3e-4,betas=(0.9, 0.95),weight_decay=0.1)+ Warmup + Cosine Decay
- CV 分类刷指标用 SGD + Momentum(lr=0.1)+ StepLR
- 训练不稳定 → 先降 lr,再加梯度裁剪,详见第 9 节
🎯 自我检验清单
- 能推导梯度下降的参数更新公式,解释学习率过大/过小的后果
- 能解释 SGD Momentum 如何抑制震荡、加速收敛,并写出对应的更新公式
- 能说明 AdaGrad 为什么会导致学习率归零,RMSProp 如何用 EMA 修复这一问题
- 能写出 Adam 的完整更新步骤(包括偏差修正),并解释为什么需要偏差修正
- 能解释 Adam 中三个组件($m_t$、$v_t$、偏差修正)各自的直觉作用
- 能说明 L2 正则化在 Adam 中为何与真正的权重衰减不等价,以及 AdamW 如何修复这一问题
- 能在 PyTorch 中配置 AdamW + CosineAnnealingLR + 梯度裁剪,并在训练循环中正确调用
- 能根据任务类型(CV / LLM / RNN)给出合理的优化器和超参数选择建议
- 能列举 Adam 不适合的场景,并说明 Adafactor 或 8-bit Adam 的使用动机