2014年,Diederik Kingma和Jimmy Ba在ICLR上发表了一篇论文,标题平淡无奇——《Adam: A Method for Stochastic Optimization》。没有人预料到,这篇论文中的算法会在接下来的十年里统治深度学习训练领域。时至今日,无论是GPT、LLaMA还是Stable Diffusion,几乎所有大模型的训练日志里都能看到"optimizer=AdamW"的字样。
但Adam真的是万能的吗?如果你翻看计算机视觉领域的经典论文,ResNet、EfficientNet的作者们几乎都在用SGD with Momentum。为什么NLP领域偏爱Adam,而CV领域却坚守SGD?更关键的问题是:当你面对一个全新的训练任务,该如何做出这个看似简单却可能决定模型命运的选择?
梯度下降:一切优化的起点
理解优化器演进的第一步,是回到问题的原点。梯度下降的核心思想出奇简单:沿着损失函数梯度的反方向移动,直到找到最低点。用数学语言描述:
$$\theta_{t+1} = \theta_t - \eta \nabla_\theta J(\theta)$$其中 $\eta$ 是学习率,$\nabla_\theta J(\theta)$ 是损失函数关于参数的梯度。这个公式看起来优雅简洁,但实际应用中却暗藏玄机。
根据每次更新使用的数据量,梯度下降分为三种变体:Batch Gradient Descent使用全部训练数据计算梯度,理论优美但计算代价高昂——对于百万级数据集,单次参数更新就需要遍历所有样本。Stochastic Gradient Descent(SGD)则走向另一个极端,每次只用一个样本更新参数,速度快但方差巨大,损失曲线像心电图一样剧烈波动。Mini-batch Gradient Descent取两者之长,每次用32到256个样本计算梯度,既保证了计算效率,又能利用GPU的并行能力。
graph LR
A[梯度下降家族] --> B[Batch GD<br/>全量数据]
A --> C[SGD<br/>单样本]
A --> D[Mini-batch GD<br/>批量数据]
B --> E[计算稳定<br/>速度慢]
C --> F[速度快<br/>方差大]
D --> G[平衡方案<br/>工程首选]
在实际工程中,“SGD"这个词往往指代Mini-batch版本,这已经成为一种约定俗成的用法。但无论哪种变体,它们都共享一组核心挑战。
深度学习优化的三大困境
学习率的两难
学习率太小,模型像蜗牛一样爬行,可能训练一周还没收敛;学习率太大,模型像脱缰的野马,损失函数剧烈震荡甚至直接发散。更棘手的是,对于稀疏特征,频繁出现的特征和不常出现的特征理应有不同的更新步长——但SGD给所有参数分配同样的学习率。
鞍点与局部最小值
深度神经网络的损失曲面高度非凸,充满鞍点和局部最小值。鞍点尤其危险:在某些方向上是极小值,在其他方向上是极大值。在极高维空间中,鞍点比局部最小值普遍得多。如果优化器在鞍点附近停滞不前,训练就会陷入漫长的平台期。
graph TD
A[损失曲面] --> B[全局最小值<br/>理想目标]
A --> C[局部最小值<br/>次优解]
A --> D[鞍点<br/>最常见障碍]
D --> E[某些方向下降<br/>某些方向上升]
D --> F[梯度接近零<br/>容易卡住]
style D fill:#ff9999
style B fill:#99ff99
UC Berkeley的研究团队在2017年发现,添加适当扰动的梯度下降可以在几乎不增加额外开销的情况下逃离鞍点。核心洞见在于:鞍点附近的"被困区域"非常薄,随机扰动大概率会落在逃离路径上。
数据稀疏性
在自然语言处理中,词表可能有数万甚至数十万词,但每个训练样本只包含其中极小一部分。对于罕见词,SGD的更新信号极其稀疏——可能出现几万次常见词更新才轮到一次罕见词,导致罕见词的参数学习严重滞后。
graph LR
A[数据稀疏性挑战] --> B[NLP场景]
B --> C[词表: 50000词]
B --> D[单样本: 约20词]
A --> E[推荐系统]
E --> F[物品: 百万级]
E --> G[用户交互: 极稀疏]
C --> H[罕见词更新少<br/>学习滞后]
D --> H
Momentum:给SGD装上"惯性”
1983年,Nesterov提出了加速梯度方法,但真正让动量思想在深度学习中流行起来的是后来的实践者。SGD with Momentum的核心思想是:不要只看当前的梯度方向,还要参考历史积累的"速度"。
$$v_t = \gamma v_{t-1} + \eta \nabla_\theta J(\theta_t)$$$$\theta_{t+1} = \theta_t - v_t$$动量系数 $\gamma$(通常设为0.9)决定了历史速度的衰减程度。想象一个球从山上滚下来:它会积累动量,在平坦区域保持速度,遇到小坑时依靠惯性冲过去。对于深度学习优化,这意味着:
- 在梯度方向一致的区域,更新加速
- 在梯度方向震荡的区域,更新被抑制
- 能够穿越浅层的局部最小值
graph TD
A[Momentum更新过程] --> B[计算当前梯度]
B --> C[累积历史速度]
C --> D[更新参数]
D --> E{继续训练?}
E -->|是| B
E -->|否| F[结束]
G[速度累积公式] --> H["v_t = γv_{t-1} + η∇J"]
I[参数更新公式] --> J["θ_{t+1} = θ_t - v_t"]
style A fill:#e1f5fe
Nesterov Accelerated Gradient(NAG)在Momentum基础上做了一个精妙的改进:先沿着历史动量方向"展望"一步,在展望位置计算梯度,再决定最终更新方向。这就像是边跑边向前看,提前感知地形变化:
$$v_t = \gamma v_{t-1} + \eta \nabla_\theta J(\theta_t - \gamma v_{t-1})$$$$\theta_{t+1} = \theta_t - v_t$$NAG的"展望"机制让它在接近极小值时自动减速,避免了Momentum可能出现的过冲问题。在实际应用中,NAG往往比普通Momentum收敛更快、更稳定。
graph LR
A[Momentum vs NAG] --> B[Momentum]
A --> C[NAG]
B --> D[先计算当前梯度]
D --> E[再应用累积速度]
C --> F[先展望未来位置]
F --> G[在展望点计算梯度]
G --> H[校正后更新]
style C fill:#c8e6c9
自适应学习率的革命
AdaGrad:为稀疏特征而生
2011年,John Duchi等人在JMLR上发表了AdaGrad算法,首次实现了参数级别的自适应学习率。核心思想是:记录每个参数的历史梯度平方和,用这个累积值归一化当前的学习率:
$$\theta_{t+1,i} = \theta_{t,i} - \frac{\eta}{\sqrt{G_{t,ii} + \epsilon}} \cdot g_{t,i}$$$G_t$ 是一个对角矩阵,对角线元素是参数 $\theta_i$ 历史梯度的平方和。对于频繁更新的参数,分母变大,有效学习率降低;对于稀疏参数,分母较小,有效学习率保持较高水平。
AdaGrad在自然语言处理和推荐系统中表现出色——罕见词的嵌入向量能够获得更大的更新步长,不会被常见词的频繁更新淹没。但AdaGrad有一个致命缺陷:$G_t$ 单调递增,学习率会持续下降,最终趋近于零,模型完全停止学习。
graph TD
A[AdaGrad机制] --> B[累积梯度平方和]
B --> C[频繁参数: 分母大]
B --> D[稀疏参数: 分母小]
C --> E[有效学习率降低]
D --> F[有效学习率保持]
A --> G[致命缺陷]
G --> H[累积单调递增]
H --> I[学习率持续下降]
I --> J[最终趋近于零]
style G fill:#ffcdd2
style I fill:#ffcdd2
RMSprop:遗忘的艺术
Geoff Hinton在他的Coursera课程中提出了RMSprop,用指数移动平均替代累积求和,解决了AdaGrad的学习率衰减问题:
$$E[g^2]_t = \gamma E[g^2]_{t-1} + (1-\gamma) g_t^2$$$$\theta_{t+1} = \theta_t - \frac{\eta}{\sqrt{E[g^2]_t + \epsilon}} \cdot g_t$$这里的 $\gamma$(通常0.9)控制历史信息的遗忘速度。RMSprop只关注"近期"的梯度,避免了无限累积导致的学习率崩塌。这个看似简单的改进,让自适应学习率方法真正具备了处理长期训练任务的能力。
Adam:集大成者
2014年,Kingma和Ba提出了Adam(Adaptive Moment Estimation),将Momentum的一阶矩估计和RMSprop的二阶矩估计结合起来:
$$m_t = \beta_1 m_{t-1} + (1-\beta_1) g_t$$$$v_t = \beta_2 v_{t-1} + (1-\beta_2) g_t^2$$$$\hat{m}_t = \frac{m_t}{1-\beta_1^t}$$$$\hat{v}_t = \frac{v_t}{1-\beta_2^t}$$$$\theta_{t+1} = \theta_t - \frac{\eta}{\sqrt{\hat{v}_t} + \epsilon} \hat{m}_t$$Adam的关键创新在于偏差校正。由于 $m_t$ 和 $v_t$ 初始化为零,在训练初期它们会偏向零值。除以 $(1-\beta^t)$ 后,这个偏差被有效修正。推荐的超参数设置是:$\beta_1=0.9$、$\beta_2=0.999$、$\eta=0.001$、$\epsilon=10^{-8}$。
graph TD
A[Adam = Momentum + RMSprop] --> B[一阶矩估计 m_t]
A --> C[二阶矩估计 v_t]
B --> D[梯度方向的指数移动平均]
C --> E[梯度平方的指数移动平均]
F[偏差校正] --> G["m̂_t = m_t / (1-β₁ᵗ)"]
F --> H["v̂_t = v_t / (1-β₂ᵗ)"]
D --> G
E --> H
G --> I[最终更新]
H --> I
style A fill:#bbdefb
Adam之所以强大,是因为它同时具备三种能力:
- 动量加速:在梯度方向一致的区域快速前进
- 自适应学习率:为每个参数定制更新步长
- 鲁棒性:对超参数选择相对不敏感,“开箱即用”
泛化困境:为什么Adam不如SGD?
然而,Adam的成功故事背后隐藏着一个尴尬的事实:在图像分类等任务上,Adam的泛化性能往往不如SGD with Momentum。
2020年,Pan Zhou等人在NeurIPS上发表了一篇重要论文,从理论上揭示了这一现象的根源。核心发现是:SGD的梯度噪声具有更重的尾部分布,而Adam的指数平滑机制削弱了这种重尾特性。
在损失曲面上,存在两类最小值:尖锐最小值(sharp minima)和平坦最小值(flat minima)。尖锐最小值周围的盆地很窄,参数稍有偏离就会大幅增加损失;平坦最小值周围的盆地很宽,模型对参数扰动更加鲁棒。研究表明,平坦最小值通常泛化性能更好。
graph TD
A[最小值类型] --> B[尖锐最小值]
A --> C[平坦最小值]
B --> D[盆地窄]
B --> E[对扰动敏感]
B --> F[泛化性能差]
C --> G[盆地宽]
C --> H[对扰动鲁棒]
C --> I[泛化性能好]
J[SGD: 重尾噪声] --> K[易跳出尖锐最小值]
K --> C
L[Adam: 平滑噪声] --> M[倾向于停留在尖锐最小值]
M --> B
style C fill:#c8e6c9
style B fill:#ffcdd2
SGD的重尾噪声让它更容易"跳出"尖锐最小值,继续寻找更平坦的区域。而Adam的自适应机制实际上在平滑梯度噪声,降低了逃离尖锐最小值的能力。这就是"收敛速度vs泛化性能"的根本权衡。
另一项研究指出,Adam在训练后期学习率可能过低,限制了最终的收敛精度。相比之下,配合精心设计的学习率调度(如余弦退火),SGD能够持续精细调整参数,达到更好的最终性能。
AdamW:一个被忽视十年的问题
2017年,Ilya Loshchilov和Frank Hutter发表了一篇可能改变你对Adam认知的论文。他们指出:Adam的权重衰减实现是错误的。
在标准SGD中,L2正则化和权重衰减是等价的。给损失函数添加 $\frac{\lambda}{2}\|\theta\|^2$ 项,相当于在参数更新时减去 $\eta\lambda\theta$。但这个等价性在Adam中不再成立。
Adam的更新公式中,梯度首先被自适应学习率缩放。如果正则化项被加入梯度,它也会被缩放——这意味着不同参数的权重衰减强度取决于它们的历史梯度统计量,而非用户设定的统一值。
graph TD
A[L2正则化 vs 权重衰减] --> B[在SGD中]
A --> C[在Adam中]
B --> D[两者等价]
D --> E["∇J + λθ 效果相同"]
C --> F[两者不等价!]
F --> G[L2正则化被自适应缩放]
F --> H[权重衰减需要解耦]
style F fill:#fff3e0
style G fill:#ffcdd2
AdamW的解决方案极其简单:将权重衰减从梯度更新中解耦出来,直接作用于参数:
$$\theta_{t+1} = \theta_t - \eta \left( \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon} + \lambda \theta_t \right)$$实验结果令人印象深刻:在ImageNet分类任务上,AdamW能够达到与SGD with Momentum相当甚至更好的泛化性能。这个改进如此重要,以至于现代深度学习框架中,AdamW 已经取代 Adam 成为默认选择。
Lion:AI设计AI的里程碑
2023年,Google研究团队用AutoML搜索发现了一个全新的优化器——Lion(EvoLved sIgn mOmeNtum)。Lion的更新规则出人意料地简洁:
$$c_t = \beta_1 m_{t-1} + (1-\beta_1) g_t$$$$\theta_{t+1} = \theta_t - \eta \cdot \text{sign}(c_t) - \eta\lambda\theta_t$$$$m_t = \beta_2 m_{t-1} + (1-\beta_2) g_t$$关键创新在于sign()函数:更新方向由动量加权梯度的符号决定,幅度恒为1。这与Adam的逐元素自适应更新形成鲜明对比。
graph TD
A[Lion优化器特点] --> B[sign函数]
A --> C[单动量缓冲]
A --> D[更新幅度恒定]
B --> E[只取方向]
E --> F[忽略梯度大小]
C --> G[比Adam节省50%内存]
D --> H[需要更小的学习率]
D --> I[需要更大的权重衰减]
style A fill:#e8f5e9
Lion的优势主要体现在:
- 内存效率:只维护一个动量缓冲区,比Adam节省50%内存
- 大批次训练:批次越大,性能优势越明显
- 扩散模型:在Stable Diffusion等模型上训练速度提升2.3倍
但Lion有其特殊要求:学习率需要比AdamW小3-10倍,权重衰减需要大3-10倍。这些超参数的差异源于sign()函数产生的更新幅度较大。
优化器选择的决策框架
面对具体的训练任务,如何选择优化器?以下是实践验证的决策指南:
按任务类型选择
| 任务类型 | 首选优化器 | 备选方案 |
|---|---|---|
| Transformer预训练 | AdamW | Lion(内存受限时) |
| 图像分类(CNN) | SGD + Momentum + Nesterov | AdamW(快速实验) |
| 目标检测 | SGD + Momentum | AdamW |
| 推荐系统(稀疏特征) | AdaGrad / FTRL | AdamW |
| 大批次训练(>8K) | LAMB | Lion |
按约束条件选择
| 约束条件 | 推荐方案 |
|---|---|
| GPU内存不足 | AdamW 8-bit 或 Lion |
| 调参时间有限 | AdamW(默认参数通常有效) |
| 追求最佳精度 | SGD + Momentum(配合精细LR调度) |
| 训练不稳定 | StableAdamW 或降低 $\beta_2$ |
graph TD
A[优化器选择决策树] --> B{任务类型?}
B -->|Transformer| C{内存受限?}
C -->|是| D[Lion]
C -->|否| E[AdamW]
B -->|CNN图像| F{有时间调参?}
F -->|是| G[SGD + Momentum]
F -->|否| E
B -->|推荐系统| H[AdaGrad/FTRL]
B -->|大批次>8K| I[LAMB]
style E fill:#c8e6c9
style G fill:#c8e6c9
学习率调度的重要性
优化器选择固然重要,但学习率调度的影响可能更大。对于Transformer类模型,线性预热加余弦退火已经成为标配:
graph LR
A[训练开始] --> B[线性预热<br/>LR从0升至峰值]
B --> C[余弦退火<br/>LR平滑下降]
C --> D[训练结束<br/>LR接近0]
style A fill:#e3f2fd
style B fill:#bbdefb
style C fill:#90caf9
style D fill:#64b5f6
预热阶段(通常占总步数的1-10%)让模型参数逐步适应训练信号,避免初期的剧烈震荡。余弦退火则提供平滑的学习率衰减,帮助模型在训练后期精细调整。
优化器演进全景图
graph TD
GD[梯度下降 GD] --> SGD[随机梯度下降 SGD]
SGD --> SGDM[SGD + Momentum]
SGDM --> NAG[Nesterov加速梯度]
SGD --> AdaGrad[AdaGrad]
AdaGrad --> RMSprop[RMSprop]
SGDM --> Adam[Adam]
RMSprop --> Adam
Adam --> AdamW[AdamW<br/>解耦权重衰减]
AdamW --> AdamW8[AdamW 8-bit<br/>量化状态]
AdamW --> Paged[Paged AdamW<br/>CPU卸载]
Adam --> LAMB[LAMB<br/>大批次]
AdamW --> Lion[Lion<br/>更少内存]
style AdamW fill:#c8e6c9
style SGDM fill:#bbdefb
style Lion fill:#fff9c4
这张图展示了优化器如何从最简单的梯度下降演变出两大分支:SGD家族(蓝色系)和自适应家族(绿色系)。两大分支最终在现代优化器中交汇,Lion可以看作是对AdamW的简化重构。
超参数调优的实用建议
SGD with Momentum
- 学习率:从0.1开始,使用学习率查找器确定最佳范围
- 动量系数:0.9是标准选择,0.99适合需要更长记忆的场景
- 权重衰减:1e-4到5e-4,与学习率协同调整
Adam / AdamW
- 学习率:1e-4到1e-3,比SGD小一个数量级
- $\beta_1$:0.9(控制一阶矩估计)
- $\beta_2$:0.999(控制二阶矩估计);大批次训练时可降至0.95
- 权重衰减:AdamW中可用0.01到0.1
Lion
- 学习率:AdamW的1/10到1/3
- 权重衰减:AdamW的3到10倍
- $\beta_1$:0.9,$\beta_2$:0.99
graph LR
A[超参数对比] --> B[SGD]
A --> C[AdamW]
A --> D[Lion]
B --> E["LR: 0.1<br/>Momentum: 0.9"]
C --> F["LR: 1e-4~1e-3<br/>β₁: 0.9, β₂: 0.999"]
D --> G["LR: AdamW的1/10~1/3<br/>WD: AdamW的3~10倍"]
常见陷阱与避坑指南
混淆Adam和AdamW的权重衰减:PyTorch早期版本的Adam中,weight_decay参数实际上实现的是L2正则化,而非真正的权重衰减。务必使用AdamW或在自定义实现中解耦权重衰减。
忽视预热阶段:Transformer类模型直接使用高学习率启动,容易导致注意力权重初期崩溃。1000步左右的线性预热几乎总是有益的。
盲目追求新优化器:每个新优化器都声称在某方面超越前作,但最终能否适应你的任务需要实测。先在验证集上对比基线,再决定是否切换。
忘记学习率调度:再好的优化器配合糟糕的学习率调度也难以发挥效果。至少应该使用余弦退火或多项式衰减。
从理论到实践
优化器的选择不是追求"最先进"的竞赛,而是在收敛速度、泛化性能、内存开销和调参成本之间寻找适合具体任务的平衡点。
AdamW提供了最好的"开箱即用"体验,适合快速迭代和大多数深度学习任务。SGD with Momentum在计算机视觉领域仍是黄金标准,尤其当你有时间精细调参时。Lion代表了优化器设计的新方向——让AI来发现AI,但它的超参数设置与传统优化器有显著差异。
记住一点:优化器决定了参数如何更新,但学习率调度决定了更新的节奏。一个配合适当学习率调度的简单优化器,往往比配置不当的复杂优化器表现更好。在追求更高性能之前,先确保你的训练流程已经正确配置——这比任何优化器技巧都重要。
参考文献
- Kingma, D. P., & Ba, J. (2014). Adam: A Method for Stochastic Optimization. ICLR 2015.
- Loshchilov, I., & Hutter, F. (2017). Decoupled Weight Decay Regularization. ICLR 2019.
- Zhou, P., et al. (2020). Towards Theoretically Understanding Why SGD Generalizes Better Than ADAM in Deep Learning. NeurIPS 2020.
- Chen, X., et al. (2023). Symbolic Discovery of Optimization Algorithms. ICLR 2023.
- Ruder, S. (2016). An overview of gradient descent optimization algorithms.
- Jin, C., et al. (2017). How to Escape Saddle Points Efficiently. ICML 2017.
- Duchi, J., Hazan, E., & Singer, Y. (2011). Adaptive Subgradient Methods for Online Learning and Stochastic Optimization. JMLR.
- Nesterov, Y. (1983). A method for unconstrained convex minimization problem with the rate of convergence O(1/k²).