Adam — 让深度学习自己挑步长的优化器
是什么
Adam 是一种给神经网络挑”每一步走多远”的算法。日常类比:你在大雾里下山,普通办法是每步固定 1 米;Adam 的办法是——走过的方向最近一直一致就大胆迈两米,方向忽左忽右就小心半米。每个参数都有自己的”步长尺”,自动调。
写一行 PyTorch:
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)就这一行,让 2015 年到 2026 年这十年里90% 以上的深度学习训练有了默认起点。从 BERT 到 GPT、从 ResNet 到 Stable Diffusion,第一版基本都是 Adam 跑出来的。
为什么重要
不理解 Adam,下面这些事都没法解释:
- 为什么大家训练神经网络几乎不用调学习率,开
lr=1e-3就能跑——Adam 自己在每个参数上做二次调整 - 为什么 SGD 训练要精心设计 learning rate schedule,Adam 不太用——它自己有”自适应”机制
- 为什么 LLM 时代出现了 AdamW、Adafactor、Lion 这些”Adam 改”——大家都在踩 Adam 的肩膀
- 为什么这一篇 ICLR 2015 论文成了机器学习史上被引最多的论文之一(>17 万次)
核心要点
Adam 把两个老想法揉在一起,再加一个补丁:
-
动量(Momentum):记一个”梯度的滑动平均”
m_t。类比——下山时记住”最近一直在往东南走”,下一步带点惯性。这个想法 1964 年就有了。 -
RMSprop(自适应步长):记一个”梯度平方的滑动平均”
v_t。类比——某个参数最近梯度一直很大(剧烈晃动),就把它的步长缩小;某个参数梯度一直很小(平地),就放大步长。这个想法 Hinton 2012 课上提的。 -
偏差修正(bias correction):
m_t和v_t初始化为 0,前几步会”偏向 0”。Adam 用m̂_t = m_t / (1-β1^t)把它扳正。这是 Kingma & Ba 的原创补丁,让 Adam 在训练初期也稳。
最终更新公式(看不懂没关系,记结构就行):
m_t = β1 · m_{t-1} + (1-β1) · g_t # 梯度的一阶矩(方向)v_t = β2 · v_{t-1} + (1-β2) · g_t² # 梯度的二阶矩(震荡幅度)m̂_t = m_t / (1 - β1^t) # 偏差修正v̂_t = v_t / (1 - β2^t)θ_{t+1} = θ_t - α · m̂_t / (√v̂_t + ε) # 用方向除以震荡幅度,再走 α 步默认超参数:α=0.001, β1=0.9, β2=0.999, ε=1e-8。这组数字几乎不用调——这是 Adam 火爆十年的根本原因。
实践案例
案例 1:训练一个分类网络
import torchmodel = MyNet()optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)loss_fn = torch.nn.CrossEntropyLoss()
for x, y in dataloader: optimizer.zero_grad() loss = loss_fn(model(x), y) loss.backward() optimizer.step()注意:你没设置 momentum、没设置 schedule、没调 weight decay。SGD 能不能跑成?也能。但 SGD 你大概率要花 1 小时调 lr 和 momentum,Adam 跑通就开训。
案例 2:Adam 在不同参数上的”个性化”步长
设想一个网络有两个参数:
- w1:梯度一直在 [−0.01, 0.01] 抖(小且震荡)
- w2:梯度一直 ≈ 1.0(大且稳定)
Adam 记录的 v_t(梯度平方):
- w1 的
v_t ≈ 0.0001→√v_t ≈ 0.01→ 实际步长 ≈α / 0.01 = 100α(放大) - w2 的
v_t ≈ 1.0→√v_t ≈ 1.0→ 实际步长 ≈α / 1.0 = α(不变)
同一个 lr,对不同参数实际步长差 100 倍。这就是”自适应”的魔力——稀疏梯度的参数(如 embedding 表)自动得到更大的步长。
案例 3:偏差修正解决了什么
第 1 步时,m_1 = 0.1 · g_1(如果 β1=0.9),是真实梯度的 1/10。如果不修正,第一步走得太短,训练初期慢半拍。
修正:m̂_1 = m_1 / (1 - 0.9^1) = m_1 / 0.1 = g_1。回到了正常量级。Kingma & Ba 在论文 Figure 1 的对比图里展示了:去掉 bias correction,前 100 步明显发飘。
踩过的坑
-
Adam 在某些任务上比 SGD 泛化差:图像分类(CIFAR/ImageNet)用 Adam 训出来的模型在测试集上常常比 SGD+momentum 差 1-2%。Wilson et al. 2017 写过专门论文吐槽。原因仍有争议,工业界做法是:前期 Adam 快收敛,后期切回 SGD 提泛化。
-
Adam 的 weight decay 实现是错的:原版 Adam 把 weight decay 当 L2 正则混进梯度,这会被
√v̂_t缩放,等于”对大梯度参数惩罚弱、小梯度参数惩罚强”——和直觉相反。Loshchilov & Hutter 2019 提出 AdamW 修正:weight decay 直接从 θ 上扣,不进梯度。今天训 LLM 默认都是 AdamW,不是 Adam。 -
β2 在长训练里会出问题:β2=0.999 意味着
v_t大约平均最近 1000 步。训 10 万步、100 万步时,早期那个大梯度还在v_t里慢慢衰减,可能让某些参数永远走不动。Reddi et al. 2018 的 AMSGrad 提了个修复,但实践用得不多。 -
大 batch + Adam ≠ 自动收敛快:很多人以为 Adam 能解决一切。LLM 训练里,batch size 一大,Adam 也得调 warmup、调 lr schedule、调 β2。“无脑用默认参数”只在小规模时成立。
适用 vs 不适用场景
适用:
- 默认起点——任何深度学习任务,先用 Adam (或 AdamW) 跑通
- 稀疏梯度场景(NLP embedding、推荐系统)——自适应步长帮大忙
- 调参预算少、想快速试模型架构的研究/实习场景
- 大模型训练前期(再切 SGD 或继续 AdamW)
不适用:
- 计算机视觉的最终模型——SGD+momentum+cosine schedule 至今 SOTA
- 强化学习——梯度噪声特性不同,常用 RMSprop 或专门优化器
- 简单凸优化——SGD 收敛保证更好,Adam 反而绕路
- 内存极受限场景——Adam 要存 m 和 v 两个 state,是参数量的 2 倍。Adafactor / 8-bit Adam 是替代
历史小故事(可跳过)
- 2011 年:John Duchi 等人提出 AdaGrad——第一个自适应步长方法,但
v_t一直累加,越走越慢直至停下。 - 2012 年:Hinton 在 Coursera 课上口头讲 RMSprop——用滑动平均替代累加,解决 AdaGrad 慢死的问题。没发论文。
- 2014 年 12 月:Kingma(当时博士生)和 Ba 把 RMSprop + Momentum + 偏差修正拼起来,投稿 ICLR 2015。论文 9 页,算法只占半页伪代码。
- 2015-2017:从 Word2Vec 到 ResNet,从 GAN 到 Transformer,几乎所有里程碑模型的论文里都有”我们用 Adam 训练”。
- 2019 年:AdamW 被 PyTorch 默认收录,逐步取代 Adam 成为新基线。
Kingma 后来还共同发明了 VAE(变分自编码器),是 OpenAI 早期员工。
学到什么
- 简单的工程组合能产生巨大影响——Adam 没什么深奥数学,就是把已有想法(动量、自适应、修正)拼一起。但拼对了,影响整个领域 10 年。
- 默认值的力量——
lr=1e-3, β1=0.9, β2=0.999这组数字让 Adam 几乎免调参。一个算法被采用的速度,常常取决于它对小白多友好。 - “自适应”不是魔法——Adam 自适应的是”每个参数的步长尺”,不是”学习率本身”。lr 还是要调,schedule 还是要做,只是宽容度比 SGD 大。
- 早期顶会论文短而清晰——Adam 9 页里把动机、算法、收敛证明、实验全讲完了。今天动辄 30 页 supplementary 的论文应该反思。
延伸阅读
- 论文 9 页 PDF:Kingma & Ba 2014, arXiv:1412.6980(伪代码看这一页就够)
- 视频教程:Andrew Ng — Adam Optimization(10 分钟讲清楚动机)
- AdamW 改进:Loshchilov & Hutter 2019, arXiv:1711.05101
- 对比批评:Wilson et al. 2017, “The Marginal Value of Adaptive Gradient Methods”
- pytorch —— PyTorch 里 Adam 的源码
torch/optim/adam.py就 100 行,建议读 - jax —— JAX 生态里 optax 实现的 Adam 更接近论文原版
关联
- pytorch —— PyTorch 默认优化器之一
- jax —— JAX/optax 也内置 Adam
- keras —— Keras 默认
optimizer='adam' - tensorflow-osdi-2016 —— TF 早期就把 Adam 作为一等公民
反向链接
- adafactor-2018 —— Adafactor — 把 Adam 的优化器内存从 O(d) 压到 O(√d)
- adamw-2017 —— AdamW — 把 weight decay 从梯度里拆出来
- batchnorm-2015 —— Batch Normalization — 把每层激活值规整到 0 均值 1 方差,深网训练时间砍成 1/14
- double-descent-2019 —— Double Descent — 模型越大越准,过参数化时代的反常识曲线
- dropout-2014 —— Dropout — 训练时随机关掉一半神经元,反而学得更好
- gcn-2017 —— GCN 2017 — 把卷积搬到图结构上的最简版本
- goodfellow-fgsm-2014 —— FGSM — 用一行梯度让神经网络看错图片
- graphsage-2017 —— GraphSAGE 2017 — 给没见过的节点也能算嵌入
- grokking-2022 —— Grokking — 训练 loss 早归零,几千步后才突然学会
- jax —— JAX — Google 函数式数值计算
- label-smoothing-2016 —— Label Smoothing — 别让模型对正确答案过度自信
- layernorm-2016 —— Layer Normalization — 把归一化方向从 batch 转到 feature,让 RNN/Transformer 也能稳定训
- lion-2023 —— Lion — 让程序自己搜出来的优化器,比 AdamW 内存少一半
- lottery-ticket-2019 —— 彩票假设 — 大网里藏着一张能独立训出来的小网
- maml-2017 —— MAML — 学一个”好起点”,几步就能学会新任务
- mixup-2018 —— mixup — 把两张图按比例叠成一张,标签也一起叠
- mode-connectivity-2018 —— Mode Connectivity — 神经网络的两个最优解之间有低洼走廊
- ntk-2018 —— NTK — 把无限宽的神经网络变成一个可解的核方法
- pytorch —— PyTorch — 深度学习主流框架
- sophia-2023 —— Sophia — 让二阶优化器第一次在 LLM 预训练里跑得动
- szegedy-adversarial-2013 —— Szegedy 对抗样本 2013 — 一张图片骗过神经网络的开山之作
- td3-2018 —— TD3 — 给 DDPG 装两副刹车,连续控制终于稳了
- tensorflow-osdi-2016 —— TensorFlow — 把神经网络拆成数据流图再跑到任何机器上