机器学习数学基础专题【左扬精讲】—— 大模型的优化器详解:从 SGD 到 AdamW,从动量到自适应学习率

机器学习数学基础专题【左扬精讲】—— 大模型的优化器详解:从 SGD 到 AdamW,从动量到自适应学习率

优化器(Optimizer)是训练循环中唯一负责 "修改模型参数" 的组件。 前向传播产生预测,损失函数衡量错误,反向传播计算梯度——但真正把梯度转化为参数更新的,是优化器。

本文从最基础的 SGD 出发,逐步引入动量、自适应学习率和权重衰减,完整梳理从 SGD 到 AdamW 的设计演进,并给出大模型场景下的选择指南。

本文所有代码片段均为通用 PyTorch 2.x 写法,可直接在 PyTorch 2.0+ 环境中运行验证

本文涉及的核心 PyTorch 模块(PyTorch 2.x):
  torch.optim.SGD           ← 随机梯度下降 + 动量
  torch.optim.Adam          ← 自适应矩估计
  torch.optim.AdamW         ← 解耦权重衰减的 Adam
  torch.optim.Adagrad       ← 自适应梯度算法
  torch.optim.RMSprop       ← 均方根传播
  torch.optim.lr_scheduler  ← 学习率调度器(本文侧重优化器本身)
  torch.nn.utils.clip_grad_norm_  ← 梯度裁剪

优化器 SGD 动量 AdamW 自适应学习率 权重衰减 梯度裁剪 二阶矩 偏置修正 学习率

学习重点提示

★ 必须掌握

  • 必须掌握:SGD 的更新公式 — theta = theta - lr * grad
  • 必须掌握:动量(Momentum)解决的是 SGD 在峡谷地形中的震荡问题
  • 必须掌握:AdamW 的"W"代表 Weight Decay 解耦,这是它与 Adam 的核心区别
  • 必须掌握:大模型训练默认选 AdamW + 梯度裁剪

★ 建议掌握

  • 建议掌握:AdaGrad / RMSprop 的设计动机与局限性
  • 建议掌握:Adam 的偏置修正(bias correction)原理
  • 建议掌握:为什么 SGD + 动量在大数据集上依然有竞争力

本文目录

  1. 一、优化器在训练中的角色 — What & Why
  2. 二、SGD:最基础的梯度下降
  3. 三、Momentum:给 SGD 加上"惯性"
  4. 四、AdaGrad / RMSprop:自适应学习率的探索
  5. 五、Adam:动量 + 自适应学习率的结合
  6. 六、AdamW:解耦权重衰减的终极形态
  7. 七、优化器选择指南
  8. 八、常见问题 FAQ(20 组)
  9. 九、Roadmap 后续预告

一、优化器在训练中的角色 — What & Why

What — 优化器在训练循环中扮演什么角色?

优化器是训练循环中唯一的"决策者"。它接收反向传播计算出的梯度,决定每个参数往哪个方向走、走多远。没有优化器,梯度只是数字,永远不会变成参数更新。

Why — 为什么不能手动写 w = w - lr * grad

问题一:不同参数需要不同的步长

稀疏特征和稠密特征的梯度量级差异巨大。统一学习率会导致稀疏参数更新不足、稠密参数更新过度。

问题二:优化器维护了训练状态

Adam 等优化器维护了动量和二阶矩估计。这些状态需要在多步之间累积,手动管理极其复杂。

没有优化器会发生什么?

  • 梯度被计算但从未使用,模型参数永远不变。
  • 无法利用动量等加速收敛的机制。
  • 不同参数的步长无法自适应,调参变成纯经验艺术。
How — 优化器的统一接口

所有 PyTorch 优化器共享同一套接口契约:

# 1. 创建优化器,传入模型参数
  optimizer = torch.optim.AdamW(model.parameters(), lr=3e-5)
  
  # 2. 每个 iteration: 清零梯度
  optimizer.zero_grad()
  
  # 3. ... 前向传播 + 反向传播 ...
  
  # 4. 每个 iteration: 更新参数
  optimizer.step()
  
  # 5. 可选:调整学习率(由 scheduler 驱动)
  scheduler.step()
  

无论 SGD、Adam 还是 AdamW,接口完全一致。替换优化器只需要改一行创建代码。

核心结论

优化器的核心任务是把梯度转化为参数更新。不同优化器的区别在于:① 是否对每个参数使用自适应步长,② 是否维护历史梯度状态,③ 如何处理权重衰减。

二、SGD:最基础的梯度下降

What — SGD 是什么?

随机梯度下降(Stochastic Gradient Descent)是最基础的优化算法。每次只用一个 batch 的梯度估计整体梯度,然后按固定学习率更新参数。

θ = θ - η · ∇θ L(θ)

其中 η 是学习率,∇θ L 是损失函数对参数的梯度。

Why — 为什么 SGD 虽然简单,却至今仍有价值?

问题一:全批量梯度下降不可行

对于百万级数据集,计算完整梯度需要遍历全部样本,每次更新的计算量是 batch 版的几千倍。

问题二:SGD 的噪声有正则效果

batch 梯度的随机噪声相当于给训练过程注入了隐式正则,有助于跳出 sharp minimum,提升泛化能力。

没有 SGD 会发生什么?

  • 大模型训练的时间成本将不可接受。
  • 无法享受 batch 噪声带来的正则效应。
How — SGD 在 PyTorch 中的两种写法

纯 SGD 不带动量,通常用于教学或简单任务:

# 纯 SGD(无动量)
  optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
  
  # SGD + 动量(工业界最常用)
  optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
  

注意:PyTorch 的 SGD 默认 momentum=0,即纯 SGD。实际项目中几乎不会用纯 SGD,都会配上动量

三、Momentum:给 SGD 加上"惯性"

What — 动量(Momentum)是什么?

动量是 SGD 的改进版本。它在每次更新时引入一个"速度"概念,当前步的更新方向是历史梯度的指数移动平均,而不是仅看当前梯度。

v = μ · v + (1 - μ) · g
θ = θ - η · v

PyTorch 中的实现略有不同:v = momentum * v + grad,然后 param -= lr * v

Why — 为什么动量能加速收敛?

问题一:SGD 在峡谷地形中震荡

损失曲面在某个方向上非常陡峭、另一个方向平缓时,SGD 会在陡峭方向上来回震荡,前进效率极低。

问题二:动量抑制了震荡方向

震荡方向的梯度方向交替变化,动量通过指数移动平均把它们抵消;而前进方向的梯度方向一致,被放大。

没有动量会发生什么?

  • SGD 在陡峭方向来回震荡,收敛速度极慢。
  • 容易卡在局部极小值或鞍点附近。
  • 大批量训练时噪声被平均掉,SGD 失去正则效应。
How — Nesterov 动量

PyTorch 的 SGD 还支持 Nesterov 动量,它在计算梯度前先"预览"动量方向的位置:

optimizer = torch.optim.SGD(
      model.parameters(),
      lr=0.01,
      momentum=0.9,
      nesterov=True,
  )
  

Nesterov 的核心思想是"先预测,再修正":先用动量方向更新一步,再在该位置计算梯度。这相当于给 SGD 装上了"导航系统",在凸优化中有理论保证的加速效果

四、AdaGrad / RMSprop:自适应学习率的探索

What — AdaGrad 和 RMSprop 分别是什么?

AdaGrad为每个参数维护一个历史梯度平方的累积和,频繁更新的参数步长自动变小,稀疏参数步长保持较大。RMSprop是 AdaGrad 的改进版,用指数移动平均替代累积和,避免步长过早归零。

AdaGrad: G = G + g ⊙ g, θ = θ - (η / (√G + ε)) ⊙ g
RMSprop: E[g²] = ρ · E[g²] + (1-ρ) · g⊙g, θ = θ - (η / (√E[g²] + ε)) ⊙ g

Why — 为什么需要自适应学习率?

问题一:统一学习率的困境

同一个模型里,词嵌入矩阵的梯度可能很大,而注意力 bias 的梯度可能很小。统一学习率无法同时满足两者。

问题二:AdaGrad 的步长单调递减

AdaGrad 的历史梯度累积会导致步长持续变小,在非凸优化中可能过早停止。RMSprop 通过指数移动平均解决了这个问题。

没有自适应学习率会发生什么?

  • 稀疏特征(如 rare words)更新不足,模型学不到低频模式。
  • 稠密特征步长过大,参数在最优解附近震荡。
How — RMSprop 的 PyTorch 实现

RMSprop 在 PyTorch 中的默认参数 ρ=0.99,ε=1e-8:

optimizer = torch.optim.RMSprop(
      model.parameters(),
      lr=1e-3,
      alpha=0.99,      # rho: 移动平均衰减率
      eps=1e-8,        # epsilon: 数值稳定项
      weight_decay=0.0,
  )
  

RMSprop 在 RNN 训练中曾有良好表现,但在 Transformer 大模型场景中已被 AdamW 取代。了解它的设计有助于理解 Adam 的演进脉络

五、Adam:动量 + 自适应学习率的结合

What — Adam 是什么?

Adam(Adaptive Moment Estimation) combines 一阶动量(梯度均值)和二阶动量(梯度平方均值),对每个参数自适应地调整学习率。它是当前最流行的优化器之一。

m = β1 · m + (1-β1) · g
v = β2 · v + (1-β2) · g⊙g
&hat;m = m / (1 - β1t)
&hat;v = v / (1 - β2t)
θ = θ - η · &hat;m / (√&hat;v + ε)

Why — 为什么 Adam 如此受欢迎?

问题一:动量 + 自适应是互补的

一阶动量加速收敛(沿一致方向加快),二阶动量自适应步长(稀疏参数大步、稠密参数小步)。两者结合效果叠加。

问题二:偏置修正保证早期稳定性

初始时 m=0, v=0,前几步的估计会有偏。偏置修正 m_hat = m / (1 - beta1^t) 在 t 较小时放大估计值,保证早期更新方向正确。

没有 Adam 会发生什么?

  • 需要手动在 SGD 和 RMSprop 之间做选择,且两者各有短板。
  • 非凸优化中自适应方法通常收敛更快、超参更鲁棒。
How — Adam 在 PyTorch 中的默认参数

PyTorch 的 torch.optim.Adam 默认参数:lr=1e-3betas=(0.9, 0.999)eps=1e-8

optimizer = torch.optim.Adam(
      model.parameters(),
      lr=1e-3,        # 默认学习率比 SGD 大
      betas=(0.9, 0.999),  # 一阶/二阶动量衰减率
      eps=1e-8,       # 数值稳定项
      weight_decay=0,  # 注意:Adam 默认 weight_decay=0
  )
  

关键注意点:PyTorch 的 Adam 默认 weight_decay=0,L2 正则实际上是加到梯度上的(而非直接对参数做衰减),这在实践中会导致权重衰减与自适应学习率耦合

六、AdamW:解耦权重衰减的终极形态

What — AdamW 是什么?

AdamW(Adam with decoupled Weight Decay)是 Adam 的改进版本。它将权重衰减与梯度更新解耦:权重衰减直接作用于参数,L2 正则不再混入梯度中。

AdamW 两步更新:
1. 梯度更新:θ = θ - η · &hat;m / (√&hat;v + ε)
2. 权重衰减:θ = θ - η · λ · θ

Why — 为什么解耦如此重要?

问题一:原始 Adam 的 L2 正则实现有缺陷

原始 Adam 将 L2 正则直接加到梯度中:grad = grad + lambda * param。这意味着权重衰减的步长也被自适应学习率缩放,当自适应学习率对某个参数很大时,权重衰减也被过度放大。

问题二:AdamW 的权重衰减是固定比例的

AdamW 先做自适应梯度更新,再独立做权重衰减 param *= (1 - lr * weight_decay)。衰减步长与学习率自适应无关,行为更可预测。

没有解耦会发生什么?

  • 大学习率时权重衰减被过度放大,参数被压缩过度。
  • 小学习率时权重衰减几乎不起作用,正则效果丢失。
  • 不同论文报告的"weight_decay=0.01"效果不一致,因为实现不同。
How — 大模型训练的 AdamW 标准配置

大模型训练几乎都使用 AdamW,配合梯度裁剪和 warmup:

optimizer = torch.optim.AdamW(
      model.parameters(),
      lr=3e-5,             # 大模型典型学习率
      weight_decay=0.01,   # 标准权重衰减
      betas=(0.9, 0.95),   # 二阶动量常用 0.95(vs Adam 默认 0.999)
      eps=1e-8,
  )
  

注意:大模型通常把 betas[1] 从 0.999 降到 0.95,因为大 batch 下二阶矩估计更稳定,不需要那么长的窗口

优化器演进速记

  • SGD:基础,固定步长,噪声有正则效应
  • SGD + Momentum:加惯性,抑制震荡
  • AdaGrad:自适应步长,但步长单调递减
  • RMSprop:指数平均替代累积,解决 AdaGrad 过早衰减
  • Adam:动量 + 自适应 + 偏置修正
  • AdamW:解耦权重衰减,大模型事实标准

七、优化器选择指南

What — 如何为不同场景选择优化器?

没有万能优化器。选择取决于任务类型、数据规模、模型架构和训练资源。

Why — 为什么同样的模型换优化器效果差异巨大?

问题一:不同优化器的隐式正则强度不同

SGD + 动量 + 大 batch 的隐式正则很强,可能在小数据集上就欠拟合;AdamW 的隐式正则较弱,需要显式 weight decay 配合。

问题二:学习率的最优范围因优化器而异

SGD 的推荐学习率通常为 0.1~1.0,Adam 为 1e-3~1e-4,AdamW 为 1e-5~3e-5。直接套用会导致训练失败。

How — 场景化选择速查表
# 场景 1: CNN 图像分类(ResNet 等)
  optimizer = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9, weight_decay=1e-4)
  
  # 场景 2: Transformer 大模型预训练(BERT/GPT 类)
  optimizer = torch.optim.AdamW(model.parameters(), lr=3e-5, weight_decay=0.01, betas=(0.9, 0.95))
  
  # 场景 3: 小数据集 fine-tuning
  optimizer = torch.optim.AdamW(model.parameters(), lr=1e-5, weight_decay=0.01)
  
  # 场景 4: RNN / 序列模型
  optimizer = torch.optim.RMSprop(model.parameters(), lr=1e-3, alpha=0.99)
  

核心原则:大模型用 AdamW,CNN 用 SGD+Momentum,小模型 fine-tuning 用更低学习率的 AdamW。

八、常见问题 FAQ(20 组)

FAQ 常见问题解答

Q1. SGD 和 Adam 的根本区别是什么?

SGD 对所有参数使用相同的学习率,Adam 为每个参数自适应调整学习率。SGD 的更新只依赖当前梯度,Adam 还维护了一阶和二阶动量的历史估计。

Q2. 为什么大模型不用纯 SGD?

纯 SGD 在大模型上收敛极慢且不稳定。大模型参数量大、条件数差,需要自适应学习率来平衡不同参数组的更新幅度。SGD 需要极精细的超参 tuning 才能工作

Q3. 动量的物理意义是什么?

动量模拟了物理中的惯性。在损失曲面的峡谷中,梯度方向交替变化;动量通过对历史梯度做指数移动平均,抑制震荡方向、加速一致方向。

Q4. Adam 和 RMSprop 的区别是什么?

Adam 在 RMSprop 的基础上增加了一阶动量。RMSprop 只有二阶自适应学习率,Adam 同时使用一阶动量和二阶动量,配合偏置修正。

Q5. 为什么 AdamW 比 Adam 更适合大模型?

AdamW 将权重衰减与梯度更新解耦。原始 Adam 的 weight decay 实现有缺陷,会与自适应学习率耦合;AdamW 的权重衰减是独立操作,行为更可预测

Q6. AdamW 的 weight_decay 和 L2 正则一样吗?

数学形式相似但实现不同。L2 正则加到损失函数中,通过反向传播影响梯度;AdamW 的 weight decay 直接作用于参数,不影响梯度计算。这就是"解耦"的含义。

Q7. Adam 的 betas 参数怎么理解?

beta1 控制一阶动量的平滑度,beta2 控制二阶动量的平滑度。beta1=0.9 表示当前梯度占 10%,历史占 90%;beta2=0.999 表示二阶矩的窗口极长,适合稀疏梯度场景。

Q8. 什么是偏置修正(bias correction)?

训练初期 m 和 v 初始化为 0,前几步的估计会偏小。偏置修正用 m/(1-beta1^t) 和 v/(1-beta2^t) 放大早期估计,t 越大修正越弱。

Q9. 为什么 Adam 的学习率通常比 SGD 小很多?

因为 Adam 的更新幅度受动量累积和自适应缩放影响。相同学习率下,Adam 的实际步长通常比 SGD 大得多。使用相同学习率会导致 Adam 训练不稳定。

Q10. 梯度裁剪和优化器是什么关系?

梯度裁剪发生在反向传播之后、optimizer.step() 之前。它是一个独立的操作,不依赖特定优化器。所有优化器都可以配合梯度裁剪使用。

Q11. 为什么 SGD 配合动量在 CNN 上表现很好?

CNN 的损失曲面相对平滑,SGD 的噪声有助于泛化。ImageNet 竞赛中,SGD + Momentum + 大 batch + 学习率调度长期占据主导地位

Q12. AdaGrad 为什么不再流行?

因为它的步长单调递减。历史梯度平方的累积使得步长持续变小,在非凸优化的后期几乎停止更新。RMSprop 和 Adam 都通过指数移动平均解决了这个问题。

Q13. 优化器的 weight_decay 应该多大?

视模型和数据集而定。大模型常用 0.01,CNN 常用 1e-4,小模型可能用 0.1。weight_decay 过大导致欠拟合,过小导致过拟合

Q14. 学习率和 weight_decay 有交互吗?

有。在 SGD 中,weight decay 加到梯度上,受学习率缩放。在 AdamW 中,weight decay 是独立步长,但仍受学习率影响。两者需要协同 tuning。

Q15. 多组参数能否用不同优化器?

可以,使用 param_groups。PyTorch 优化器支持传入多个 param_group,每组可以有不同的 lr、weight_decay 等。常见做法是对 bias 和 LayerNorm 参数禁用 weight decay。

Q16. 为什么 BERT 训练用 Adam 而不是 SGD?

因为 BERT 需要快速收敛且对超参鲁棒。Adam 的自适应学习率减少了手动 tuning 的工作量,配合 warmup 可以在约 1M 步内完成预训练

Q17. 优化器状态占用多少显存?

Adam/AdamW 需要存储两倍参数量的动量状态。对于 7B 参数模型,AdamW 的优化器状态约占用 14B 浮点数的显存(fp32 约 56GB)。这就是 FSDP 等参数分片方案要解决的问题。

Q18. 训练到一半可以换优化器吗?

技术上可以,但不推荐。因为新优化器没有历史动量状态,相当于从头开始积累。如果必须换,建议保存 checkpoint 后重新初始化优化器。

Q19. 什么是 Lookahead 优化器?

Lookahead 是一种优化器包装器,在基础优化器外额外维护"慢权重"。它让基础优化器(如 Adam)快速探索,同时用慢权重跟踪更稳定的方向,提升泛化能力

Q20. 优化器中的 amsgrad 是什么?

AMSGrad 是 Adam 的变体,使用 v 的累积最大值而非当前值。论文声称它解决了 Adam 在某些问题上泛化差的问题,但后续研究表明差异不大。PyTorch 支持 amsgrad=True 参数。

全篇总纲

  • 1 个核心接口:optimizer.zero_grad() / step() — 所有优化器一致
  • 1 条演进主线:SGD → SGD+Momentum → AdaGrad → RMSprop → Adam → AdamW
  • 2 个关键设计:动量(一阶自适应)+ 二阶自适应学习率
  • 1 个终极选择:大模型默认 AdamW + 梯度裁剪 + warmup
  • 3 个避坑:不用纯 SGD、不用原始 Adam(用 AdamW)、不用统一学习率

九、Roadmap 后续预告

后续预告

  • 第 8 篇:学习率调度器全解 — Warmup、Cosine、Linear、Polynomial、ReduceLROnPlateau
  • 第 9 篇:混合精度与分布式训练 — 从单机单卡到 DDP、FSDP、DeepSpeed
  • 第 10 篇:大模型推理与部署 — 从 PyTorch 到 ONNX / TensorRT / vLLM
posted @ 2026-07-13 19:19  左扬  阅读(99)  评论(0)    收藏  举报