HelloAI
L2 第 9 篇 🐣 难度 🕒 16 分钟

优化器深度解析:SGD / Momentum / Adam 为什么是这样

你以为梯度下降只是"沿梯度反方向走"——其实有 10 多种花式走法。Adam 凭什么成为深度学习默认款?

阿莱
2026/6/14

L1-03 我们讲了梯度下降的基本公式:

θnew=θoldαL\theta_{new} = \theta_{old} - \alpha \cdot \nabla L

但这只是最朴素的版本——叫 vanilla SGD。

实际训练 ChatGPT 这样的模型,从来没人用纯 SGD——大家都在用 Adam。 为什么?这一篇我们彻底搞懂”优化器家族”。

🎮 强烈建议:先去 梯度下降登山者可视化 玩 5 分钟,你会有直观的感受。本文是它的”伴读”。

第一站:vanilla SGD

最朴素的梯度下降:

for batch in data:
    grad = compute_gradient(batch)
    theta -= lr * grad

它的问题:

问题 1:在”狭长山谷”里疯狂震荡

想象一个椭圆形损失曲面:x 方向缓、y 方向陡。

SGD 沿梯度最陡方向走——它会在窄方向反复横跳,慢慢往最优点挪。走的”路径”特别浪费

问题 2:被”鞍点”卡住

鞍点(saddle point):梯度 = 0 但不是最优。在高维空间里到处都是鞍点——研究表明,几十亿参数的网络损失曲面上,鞍点比局部最优多得多。

SGD 一到鞍点附近,梯度 ≈ 0,就基本不动了

问题 3:所有参数共享同一个学习率

但不同参数其实需要不同步长——有的参数应该走大步,有的应该小心翼翼。

第二站:Momentum(动量)

灵感:滚雪球

如果一个球已经在朝某个方向滚——它继续滚比临时改方向更省力。

v = 0  # 速度
for batch in data:
    grad = compute_gradient(batch)
    v = beta * v - lr * grad      # 累积速度
    theta = theta + v             # 沿速度方向走

其中 β=0.9\beta = 0.9 是经验值——意思是”90% 保留旧速度 + 10% 加入新梯度”。

Momentum 解决了什么

  • 狭长山谷震荡:窄方向上的震荡互相抵消,宽方向上的”前进”累加——速度越来越快
  • 鞍点:靠累积的”惯性”冲过梯度为 0 的区域
  • 训练加速:通常比 SGD 快 2-3 倍收敛

但它仍有问题

  • 仍然全参数一个学习率
  • 在最优点附近会”过冲”——靠惯性冲过去再回来
  • β\beta 这个超参数需要手动调

第三站:AdaGrad

灵感:让经常更新的参数学小步,让很少更新的参数学大步

G = 0  # 历史梯度平方累积
for batch in data:
    grad = compute_gradient(batch)
    G = G + grad ** 2
    theta = theta - lr * grad / (sqrt(G) + eps)

注意:GG 是按参数维度独立累积的。梯度大的参数,分母大,实际步长小;梯度小的参数,分母小,实际步长大

AdaGrad 解决了什么

  • 稀疏数据:自然语言处理中,词频分布极不均匀——常见词高频更新、罕见词低频。AdaGrad 给罕见词更大步长,让它们也能被学好。

但它有致命问题

  • GG单调累积的——它只增不减
  • 训练越久,分母越大——学习率最终趋于 0
  • 训练后期,模型几乎”停学”

第四站:RMSProp

Hinton 在 Coursera 课程上提出的”修复”。

把”累积所有历史梯度”改成”指数加权平均”:

v = 0
for batch in data:
    grad = compute_gradient(batch)
    v = beta * v + (1 - beta) * grad ** 2    # 只看最近
    theta = theta - lr * grad / (sqrt(v) + eps)

β=0.99\beta = 0.99 是常见值——意思是”99% 旧梯度方差 + 1% 新的”。只看最近的梯度,老旧的会被遗忘

RMSProp 解决了什么

  • 修复了 AdaGrad “学习率趋零”的问题
  • 仍然保留了”自适应学习率”的好处

但它没融入 Momentum

我们已经知道 Momentum 有用——RMSProp 没用它。

第五站:Adam(Momentum + RMSProp 的合体)

Adam 全称 Adaptive Moment Estimation,2014 年提出。它把上面所有想法揉在一起

m = 0  # 一阶矩(动量)
v = 0  # 二阶矩(梯度平方)

for batch in data:
    t = t + 1
    grad = compute_gradient(batch)

    # 1. 累积动量(像 Momentum)
    m = beta1 * m + (1 - beta1) * grad

    # 2. 累积梯度平方(像 RMSProp)
    v = beta2 * v + (1 - beta2) * grad ** 2

    # 3. 偏置修正(小技巧,让早期估计更准)
    m_hat = m / (1 - beta1 ** t)
    v_hat = v / (1 - beta2 ** t)

    # 4. 更新参数
    theta = theta - lr * m_hat / (sqrt(v_hat) + eps)

默认超参数:

  • β1=0.9\beta_1 = 0.9(动量衰减)
  • β2=0.999\beta_2 = 0.999(方差衰减)
  • ϵ=108\epsilon = 10^{-8}(数值稳定)
  • α=0.001\alpha = 0.001(学习率)

Adam 解决了所有上面提到的问题

问题SGDMomentumRMSPropAdam
山谷震荡
鞍点卡住部分
自适应步长
训练后期不停学

这就是为什么 Adam 是 PyTorch 默认优化器

💡 一个细节

偏置修正(bias correction)是 Adam 的精髓之一。早期 mmvv 都从 0 开始累积,会”偏小”——除以 (1βt)(1-\beta^t) 修正这个偏差。等 tt 大了,修正系数趋近 1,影响消失。

AdamW:解决 Adam 的一个隐藏 bug

后来研究者发现 Adam 跟 weight decay(权重衰减,一种正则化)配合不好。

传统做法:在损失里加 λθ2\lambda \|\theta\|^2

AdamW:直接在更新公式里减一项 θ=θαmhat/vhatαλθ\theta = \theta - \alpha \cdot m_{hat} / \sqrt{v_{hat}} - \alpha \lambda \theta

这个小修改让 Adam 在 Transformer 上的表现显著提升——所以今天训 LLM 几乎都用 AdamW,不是裸 Adam。

一些”花哨”的变种

优化器特点
NAdamAdam + Nesterov momentum(“先看一眼再决定怎么走”)
AdaBelief优化 Adam 的方差估计
Lion2023 年 Google 提出,只用符号信息,省内存
Shampoo用全矩阵预条件,效果好但贵
AdamW + Schedule-free2024 年提出,不用 LR schedule

真实情况:Adam / AdamW 覆盖了 95% 的深度学习场景。其它优化器都在跟它比性能或省成本。

学习率调度

光选 Adam 不够,学习率怎么变也很关键

常见策略:

1. Constant(不变)

最简单但通常次优。

2. Step Decay

每 N 个 epoch 把 lr 减半。简单粗暴。

3. Cosine Annealing

lr 按余弦曲线降到 0,效果稳定。LLM 训练常用

4. Warmup + Cosine

前 N 步 lr 从 0 升到目标值(warmup),然后余弦下降。几乎所有 Transformer 都这么做

import torch.optim as optim
from torch.optim.lr_scheduler import CosineAnnealingLR

optimizer = optim.AdamW(model.parameters(), lr=1e-3)
scheduler = CosineAnnealingLR(optimizer, T_max=100)

for epoch in range(100):
    train_one_epoch()
    scheduler.step()

怎么挑

场景推荐
标准深度学习AdamW + Cosine schedule
训练 LLMAdamW + Warmup + Cosine
训练 CNN(视觉)SGD with Momentum(出乎意料!)
内存极度紧张Lion 或 8-bit AdamW
研究新场景先试 AdamW,再考虑别的

一个反常识:在视觉任务(CNN)上,SGD + Momentum 经常打败 Adam。原因不明,可能与损失曲面性质有关。所以 CV 论文里 SGD 仍然常见。

用 PyTorch 切换很简单

# 切换优化器只是一行
optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.01)
# 或
optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9)

# 训练循环不变
for batch in dataloader:
    optimizer.zero_grad()
    loss = compute_loss(batch)
    loss.backward()
    optimizer.step()

一句话总结

SGD 是步行,Momentum 是骑滑板,AdaGrad 是助力车,Adam 是混动车。

你训 ChatGPT 不会用步行——所以默认用 AdamW。

但每种工具都有它的场景,别迷信任何一个。

想”看见”它

👀 梯度下降登山者可视化 —— 在四种地形上比较 SGD / Momentum / Adam 的轨迹差异。这次读完文章再玩一遍,你会理解为什么它们走得不同。

🔬 L2 路径下一站

你已经懂了:损失函数(L1-04)+ 梯度下降(L1-03)+ 优化器(本篇)。 下一步推荐到 L3 路径——把这些数学武器组合起来,理解 Transformer 等真实神经网络。

📬

读到这里说明你认真在学 🎯

订阅每周精选 —— 下一篇新文章 / 新可视化第一时间送到邮箱。

💬

讨论区

· 用 GitHub 账号登录评论
⚠️ Giscus 评论未配置 —— 在 src/components/Comments.astro 顶部填入 仓库 ID 和分类 ID(见组件注释里的配置步骤)。