1. 项目概述
训练过几轮模型的朋友应该都有体会:同样的网络结构、同样的数据,换一个优化器,收敛速度可能差好几倍,最终精度也往往有明显差距。Model-Optimizer所关注的,正是深度学习训练流程里这个容易被低估、却直接影响模型上限的关键组件。我接到这个项目时,第一时间就想把它拆成一条完整的技术主线来写——不光是罗列SGD、Adam这些名字,而是把“优化器到底在做什么”“不同优化器分别适合什么场景”“实际训练中怎么调参、怎么排坑”串起来,形成一套可以直接抄作业的经验集。
这篇文章适合三类读者:刚入坑深度学习、还在无脑用Adam的初学者;已经跑通训练流程、但经常被loss震荡和收敛停滞搞到头疼的调参选手;以及准备在团队内部做训练框架选型、需要给不同业务配置优化器方案的技术负责人。我会先把优化器的数学直觉讲清楚,再逐个拆解主流的优化器家族,最后给出完整的实操流程和踩坑记录,确保读完能直接上手。
提示:本文所有实验结论均基于常规视觉任务和Transformer类模型,NLP大模型预训练的极端场景会单独说明,避免误导。
2. 核心机制拆解:优化器究竟在优化什么
2.1 梯度下降的本质与基本变体
优化器的核心任务听起来很简单:沿着loss下降的方向更新参数。但真正实现“快速、稳定、不震荡”地走到最优点,需要解决一系列问题。先看最初级的批量梯度下降,它的参数更新公式是:
theta_{t+1} = theta_t - lr * grad_t这行公式里隐藏着三个可以直接调优的突破口:梯度本身可能带噪声、不同参数对方向变化的敏感度不同、学习率固定时无法适应不同训练阶段。针对这些痛点,后续的优化器基本都在回答同一个问题——如何利用梯度的历史信息和一阶、二阶统计量,动态地决定每一步迈多大、往哪迈。
从直觉上理解,可以把训练过程想成下山。固定学习率的SGD是一个只知道当前坡度、每步都迈固定脚程的登山者,遇到陡峭地段容易一步踩空,碰到平缓地形又半天挪不动。而带Momentum的SGD相当于给这位登山者加了一个惯性记忆,下坡方向一致的步子会越走越快,方向突然反转时惯性也能拉他一把,减少来回震荡。实际使用时,Momentum系数默认取0.9,意味着当前更新方向里,有90%来自历史累积方向,只有10%来自当前梯度的修正。
2.2 自适应学习率的核心思想
自适应学习率优化器解决的则是另一个问题:不同参数的最优步长往往相差悬殊。以图像分类任务为例,靠近输入的卷积层和靠近输出的分类层,其梯度量级可能差好几个数量级。如果在所有维度上使用同一个学习率,梯度小的参数更新太慢、梯度大的参数又容易发散。
AdaGrad是最早引入“按参数维度调整学习率”思路的工作,它会累积历史梯度的平方和,让更新步长除以这个累积值的平方根。直观效果是:更新量大的维度后续步长自动收缩,更新量小的维度步长相对变大。但这个策略有个致命缺陷——历史梯度平方累积量单调递增,到训练后期分母大得离谱,学习率几乎被压成零,训练直接停滞。RMSProp做了关键改进,改用滑动平均来估计梯度平方的均值,让分母只反映最近一段时间的梯度规模,而不是从训练开始到现在的总和。加上这个修正之后,自适应学习率方法才真正可以支撑大规模训练的全程走完。
2.3 Adam如何融合两大思路
Adam把Momentum的一阶动量思想与RMSProp的二阶动量思想合并到一起,同时维护两个滑动平均变量:
m_t = beta1 * m_{t-1} + (1 - beta1) * grad_t v_t = beta2 * v_{t-1} + (1 - beta2) * grad_t^2问题在于训练初期m_t和v_t都被初始化为零,估计值会严重偏向于小值,在原始公式里相当于学习率被非线性地放大了,所以Adam专门设计了偏差修正项。如果只看原始公式而不注意这个修正,初期迭代几乎必然出现loss异常跳变。
我倾向于用这样一个类比向团队新人解释:SGD是个只看脚下路况的稳健派,Adam像一个对每个路段都有实时更新的老司机——刹车灵敏度会看当前路段的平均坡度(二阶矩),方向盘转向幅度参考历史路况惯性(一阶矩)。所以Adam在绝大多数场景下开箱即用的体验远优于SGD,但“好用”不等于“最优”,尤其在需要精细控制泛化性时,它反而可能因为自适应性太强而掉进尖锐极小值,最终精度反而不如调好的SGD。这个点直接引出了我们第3节要讲的优化器选型问题。
3. 主流优化器全家族对比与选型逻辑
3.1 六大优化器的核心参数与适用场景
先把主流优化器的特性和适用场景整理成一张对照表,方便快速查阅:
| 优化器 | 核心机制 | 需要调的核心参数 | 典型适用场景 | 常见风险 |
|---|---|---|---|---|
| SGD | 原始梯度下降 | lr, momentum | CV任务、MLP、已有成熟调参方案 | 收敛慢、对lr敏感 |
| SGD+Momentum | 累积惯性方向 | lr, momentum | 分类/检测/分割大部分视觉模型 | 需要配合warmup和lr调度 |
| AdaGrad | 累计梯度平方 | lr | 稀疏特征场景(如推荐系统部分模块) | 学习率衰减过快 |
| RMSProp | 滑动平均梯度平方 | lr, alpha | 循环网络、强化学习部分场景 | 对beta值敏感 |
| Adam | 一阶+二阶动量 | lr, beta1, beta2 | Transformer、生成模型、多模态 | 泛化性弱、可能收敛到尖锐极值 |
| AdamW | Adam+解耦权重衰减 | lr, weight_decay | BERT/GPT等预训练、微调 | 需要重设weight_decay,不等于沿用Adam的L2 |
3.2 为什么CV任务里SGD依然占据半壁江山
很多初学者不理解,既然Adam开箱效果这么好,为什么主流目标检测和图像分类库的默认配置依然是SGD。这里面的关键在“泛化性”三个字。
前面提到,Adam自适应地将每个参数的更新幅度归一化到相近的量级,这让它在训练集上能快速下降甚至过拟合,但带来的问题是不容易走到平坦的极小值区域。平坦极值通常意味着参数微小扰动不会对输出造成剧烈变化,对应到测试集上就是更稳的泛化表现。大量实验观察到,通过SGD+动量训练得到的模型权重,对输入扰动和权重噪音的鲁棒性往往优于Adam训练出来的模型。这也是很多CV任务论文的标配依然是SGD+momentum,而不是Adam的直接原因。
实际工程里,我在检测类项目上复现过一篇论文,作者用的是Adam,训练指标全部对齐,但到验证集上精度就是差0.5%-1%。后来把优化器切回SGD+momentum,按原论文的lr schedule设置,验证精度就完全对上了。这说明至少在视觉任务上,SGD依然是一个非常稳健的选择。反过来说,Transformer类模型里,Adam/AdamW的地位又几乎不可撼动,因为自注意力机制带来的梯度分布更加不均匀,SGD那种固定学习率的更新方式在深层次Transformer训练里往往慢到让人绝望。
3.3 L2正则与权重衰减的隐蔽陷阱
L2正则和权重衰减看似等价,实际在Adam这类自适应优化器上会表现出完全不同的行为,这是选型中最容易被忽略的细节。
朴素L2正则是在loss里加入w^2项,梯度更新时相当于在原始梯度上额外加上一个和权重成正比的惩罚项。问题在于Adam会把这个惩罚项的梯度也放进二阶动量中去归一化,效果是:权重比较大的参数,其惩罚对应的有效学习率被缩小了;而权重小的参数反而惩罚更新更激进。这不是我们期望的“等比例压缩权重”的行为。
解耦权重衰减的做法则绕开了二阶动量的干扰,直接在每个step的参数更新末尾,额外执行一次等比例的权重缩放:
theta_{t+1} = theta_t - lr * m_t_hat / sqrt(v_t_hat + eps) - lr * weight_decay * theta_t这一项不受自适应学习率影响,行为才是真正意义上的权重衰减。AdamW之所以在预训练和微调场景里成为默认底座,除了权重衰减行为更正确之外,另一个可观测的优势是训练更稳定、极端权重值和loss尖峰出现的频率更低。我在跑GPT类小规模预训练时对比过:Adam加L2在2K步左右偶发一次5倍以上的loss尖峰,切到AdamW后这个现象直接消失。
4. 模型训练中优化器的选型实操
4.1 快速选定优化器的决策路径
每次接到新的训练任务,我会先回答三个问题,基本能锁定优化器范围:
- 模型结构是CNN为主还是Transformer为主?CNN走SGD/momentum路线,Transformer走AdamW路线。
- task对最终精度的要求有多高,还是只要baseline效果够快?竞争性benchmark用SGD精细调,探索性实验和diffusion类模型直接Adam。
- 训练规模是百级step还是几十万step?小规模快速验证阶段用Adam,正式大规模训练换SGD可能更稳妥。
举个例子,我在一个小型图像分类任务上先用了Adam快速验证数据管线、loss设计和模型结构是否合理,整个验证阶段只花半天时间。确认方案可行后,再切换到SGD+momentum重新训练正式模型——因为那个任务对最终精度要求高,SGD在充分训练后的泛化性比Adam好。这个“先用Adam探路、再用SGD精调”的组合拳,在预算有限的项目里非常实用。
4.2 学习率与优化器参数的联动配置
学习率不能脱离优化器单独讨论。不同优化器的最优学习率区间可以差出10倍以上,直接沿用默认学习率大概率出问题。
我在实际工程中的经验值如下:
- SGD+momentum:初始学习率常用0.1(配合batch size 256),小数据集或微调场景降到0.01,配合cosine退火或step decay使用。
- Adam:初始学习率在3e-4到1e-3之间,Transformer类模型从3e-4起步最稳。1e-3以上需要谨慎观察是否出现loss尖峰。
- AdamW:预训练场景常用1e-4到3e-4,微调场景常用2e-5到5e-5。
学习率调度也很少有人会用固定常量,最常用的是warmup + 线性衰减或cosine退火。warmup阶段学习率从极小值线性爬到预设峰值,目的是避免训练初期大梯度震荡打乱预训练好的特征分布。Transformer训练尤其依赖warmup这一环,我见过直接跳到峰值学习率训练BERT类模型的项目,前几百步loss直接冲到NaN。
4.3 超参数调优的高效策略
优化器的超参数空间并不是越高维越好的,常用的高效策略是“分阶段粗细结合”。粗调阶段用对数均匀分布采样学习率,比如在1e-4到1e-2之间随机采样10组,每组只跑几百个step,用loss下降趋势和稳定程度筛出2-3组有潜力的候选。细调阶段针对候选组在小验证集上做更细的网格搜索,结合每100step记录一次的loss曲线,观察是否存在剧烈震荡或者长时间平台期。
除学习率外,Adam类的beta2参数在部分场景下值得单独调。默认的beta2=0.999意味着二阶动量考虑最近1000步左右的梯度信息,但对某些非平稳loss形态的任务,把beta2调到0.98甚至0.95会明显改善稳定性。我自己的一个经验教训是,在视频预测类任务上,把beta2从0.999降到0.98,直接让收敛步数缩短了约30%,原因就是这类任务中梯度统计量变化较快,滑动窗口太长会导致更新步长和当前梯度状态严重脱节。
注意:对权重衰减的调整要独立于学习率来看。AdamW里weight_decay的常规区间是0.01到0.1,但实际还要结合模型大小和数据集规模。数据量越少,weight_decay通常要越大来压制过拟合。
5. 复现训练过程中的问题排查与心得
5.1 loss为NaN的根因定位
优化器相关的NaN问题,我按发生频率从高到低排了一个排查清单:
- 学习率过高。尤其是Adam类优化器初始学习率超过1e-3时很容易炸掉loss。先降到1e-4验证一下,如果不再出现NaN,基本就锁定是学习率问题。
- 梯度爆炸。检查梯度范数,超过一定阈值后对梯度进行裁剪,或者调整参数初始化方式。
- 二阶动量分母小于epsilon导致除零异常。Adam公式里epsilon默认1e-8,但用混合精度训练时,因为数值范围被压缩,适当调大到1e-6往往更安全。
- 数据和标签问题,比如出现Inf特征值,优化器只是背锅。
5.2 收敛停滞与平台期的判断
学习率降到很低时loss曲线进入平台期是正常现象,但需要区分“正常平台期”和“真的收敛不动了”。正常平台期一般出现在训练末期,loss曲线在长时间段趋于平缓后仍有极缓慢下降。虚假平台期则往往发生在训练中期,可能持续几千步没有变化。这时候第一反应不应是换优化器,而是检查学习率是否设置了底限、batch size是否太大导致梯度更新方向彼此抵消。
有一个我常用的判断方法:取最近500个step的loss值,拟合一条线性趋势线。如果斜率绝对值远小于学习率本身的量级,说明可能进入虚假平台期,可以尝试把学习率临时提高5倍跑几百步观察loss能否突破平台,能突破继续训练,不能则考虑调整模型结构。这个方法操作成本极低,缓解了我曾经多次白白等待几千步平台的焦虑。
5.3 batch size、数据规模与优化器的联动变化
优化器选择不能脱离batch size和数据规模单独做决策。大batch size意味着单步梯度估计更稳定、方差更小,这会让自适应学习率的优势被稀释,而大batch本身对学习率的敏感性会增强。这时候通常要做两个联动调整:线性缩放学习率规则,batch size从256涨到1024时,学习率也要相应上调约4倍;同时适当增加warmup步数,避免大学习率在初期破坏特征分布。
在小数据集和低资源场景下,我的体感是Adam类优化器对初学者的容错率明显高——初始化略微不理想、学习率稍微偏高,Adam都能靠自适应机制拉回来,不会立刻飞掉。而SGD对这两个因素极其敏感,没调好就是在loss曲线上看到完全无法下降的直线。所以给新手的建议是,第一版模型直接上Adam,牺牲一点可能的最终精度换快速迭代,等整个实验链路稳定了,再回头用SGD做精度冲刺。
5.4 混合精度训练下的优化器参数微调
混合精度训练已经成了大模型训练的标配,但很多人不知道FP16下优化器的状态参数很容易溢出。Adam的一阶动量和二阶动量如果保持FP32存储,显存占用会额外增加1倍以上,所以主流框架一般用FP32的master weight和优化器状态。实际操作中我习惯把epsilon从默认1e-8调大到1e-6甚至1e-4,因为FP16的动态范围比FP32窄很多,过小的epsilon在数值上更容易触发尖峰。
使用AMP(自动混合精度)时,配合Dynamic Loss Scaling策略,优化器本身不需要做太多改动,但要注意grad clip的阈值通常建议设在1.0附近,因为FP16下的梯度分布在缩放后更容易出现极端值。我曾经在某个生成模型项目里,因为没改epsilon导致训练每跑几百步就出现一次loss尖峰,把epsilon调到1e-4后问题彻底消失——这属于优化器在混合精度场景下一个非常重要的细节。
5.5 优化器状态检查与训练回滚机制
做长时间训练任务时,优化器参数和模型参数一样需要被监控和保存。很多训练中断后loss异常跳变的案例,根因都是只保存了模型权重、没有保存优化器状态,恢复训练时默认初始化了Adam动量,整个轨迹被打断,相当于模型带着新记忆重新起步。
我的建议是,每隔固定步数将优化器的state_dict一并写入checkpoint,且至少保留最近两个版本的checkpoint。回滚时可以直接恢复到上一个稳定版本,而不是从头再来。有一个附加经验:恢复训练时如果发现loss短暂升高,不要急着调优化器参数,先观察200-300步,大多数情况下是warmup状态丢失导致的,可以把warmup步骤重新跑一遍再进入正式训练。
6. 优化器模型的扩展思路与个人实践总结
6.1 从优化器精度到显存优化的进阶路径
优化器的选型和调参已经能覆盖大部分工程场景,但大规模模型训练中还要考虑显存优化。Adam类优化器需要为每个参数额外维护一阶动量和二阶动量两个浮点张量,对百亿级模型来说这就是数百GB的显存开销。业界方案有两条路线:一是将优化器状态下沉到CPU,GPU只保留参数和梯度,每步通过通信把更新算完再传回GPU,这会增加通信开销;二是采用Adafactor这类简化版优化器,用低秩分解近似二阶动量,降低显存占用。对个人实验来说,如果只是训练亿级参数以内的模型,AdamW配合标准AMP已经足够,不必过早引入复杂度。
6.2 优化器之间的切换策略
不是说选了一个优化器就得从一而终。我实际做过几次把训练中期的SGD切换成Adam的尝试,方向和时机对了,确实能救活一些训练停滞的模型。关键原则是:切换后一定要大幅降低学习率,大概是原来的一半甚至三分之一,同时重置优化器的动量状态,不要留恋旧状态。切换后还要给模型一个热身期,不要立刻按目标学习率硬跑。
反向操作——从Adam切换到SGD——我踩过的坑更多一些。因为Adam本身已经归一化了不同参数的更新幅度,切到SGD后局部大梯度参数会突然失控,表现为loss瞬间拉升。如果项目底层是视觉模型的微调任务,我依然优先推荐直接沿用SGD+momentum从零开始训练,而不是中途切换。
6.3 瞬时的个人工程体感
做了这么多年模型训练,优化器其实没有绝对的银弹。每个项目开始前,我都会先明确一个事:这个模型到底是“快速出结果更重要”还是“最终精度更值钱”。前者我会直接上Adam,把时间花在数据和模型结构上;后者我宁愿多花半天调SGD的学习率调度和momentum参数,也不愿意在训练后期对着一个精度上不去的Adam模型发呆。这套决策逻辑帮我省下了大量无效调参时间——希望这篇Model-Optimizer拆解文章也能帮你在真实项目中少走弯路。
如果下次再遇到模型“死活不收敛”的窘境,别急着怀疑数据和模型结构,先从优化器和学习率这个最简单的切入口入手,往往就有立竿见影的改善。