聊到 Model-Optimizer,很多人第一反应是“选哪个优化器接口”,但真正跑过模型训练的人都知道,优化器不是训练脚本里顺手填的一个参数,而是整个训练过程能否收敛、收敛多快、最终精度高低的控制中枢。我见过太多项目,模型结构没问题、数据也没问题,就因为优化器选错或者参数配错,loss 曲线要么原地打转,要么直接炸成一片噪声。这篇文章不打算写教科书式的公式堆砌,而是从我实际的调参经验出发,把优化器的原理、选型、调参和排障讲透,让正准备训练模型的朋友能少走弯路。
这篇文章适合两类人:一类是刚入门深度学习、想搞清楚“SGD、Adam、AdamW 到底该用谁”的新手;另一类是已经跑过不少实验,但经常被 loss 震荡、不收敛、精度上不去等问题折磨的工程师。我会尽量用生活化的类比解释底层原理,再给出可以直接抄作业的配置思路,最后分享我踩过的坑和排查经验。
1. 优化器为什么是训练成败的分水岭
1.1 先搞懂优化器在整个训练流程里的位置
训练一个模型,本质上是在做一件事:找到一组参数,让损失函数的值尽可能小。这个“找”的过程,就是优化。而优化器,就是负责根据损失函数的梯度信息,决定参数往哪个方向走、走多远的那个执行者。
你可以把训练过程想象成在一个漆黑的山谷里下山。模型参数是你在山谷里的坐标,损失函数的值是当前海拔,梯度告诉你哪个方向是下坡。优化器的作用就是根据这个下坡方向,迈出下一步。如果你每一步都走得很稳,很快就能到谷底;如果步子太大,可能直接跳过谷底冲到对面山坡上;如果方向判断失误,那就可能在山谷里来回震荡,永远下不去。
这个类比很粗糙,但能帮你建立第一层直觉:优化器的核心问题,是如何根据历史梯度信息和当前梯度信息,稳健地逼近损失曲面的最低点。这里的关键词是“稳健”。因为实际训练中,损失曲面远比“山谷”复杂得多,它可能是崎岖的、有大量局部坑洼的高维地形,所以优化器还需要解决“别被困在局部低点”“别在鞍点附近停滞”“别因噪声太大而震荡”等一系列问题。
1.2 损失曲面比想象中麻烦得多
很多人第一次训练模型时都会有一个朴素想法:只要梯度下降,loss 就应该一直下降。但实际跑起来往往会发现,loss 曲线会呈现各种奇怪的形态——先降后升、平台期不动、剧烈震荡、甚至直接变成 NaN。
这些现象背后的原因,都指向损失曲面的几何性质。高维参数空间里,损失曲面最常见的“陷阱”不是局部极小值,而是鞍点。鞍点附近,梯度在某些方向上接近零,但并不是真正的谷底。普通 SGD 在这种地方步伐会变得极慢,看起来就像训练卡住了。另外,损失曲面在不同方向上的曲率差异可能非常大,有的方向坡很陡,有的方向坡很缓。如果所有方向都用同一个学习率,就会出现“陡坡方向震荡、缓坡方向龟速”的局面。
优化器的发展史,本质上就是一部对抗这些损失曲面困境的历史。从 SGD 到动量优化,从自适应学习率到当前的 AdamW,每一代优化器都在解决某一类特定的训练难题。理解了这些困境,你才能理解为什么需要不同的优化器,也才能在面对具体问题时做出正确的选择。
2. 主流优化器横向对比与选型思路
2.1 从 SGD 到 Momentum:动量的价值
先说最基础的 SGD(随机梯度下降)。它的更新规则极其简单:参数朝着当前梯度方向前进,前进的距离等于学习率乘以梯度。这个方案在凸优化理论上很漂亮,但在深度学习实践中问题很大。
最大的问题是它对梯度噪声非常敏感。实际训练中,我们计算的是一个小批量样本的梯度,而不是全量数据的真实梯度,所以梯度本身带有随机噪声。如果直接用带噪声的梯度去更新参数,路径就会非常曲折。你可以想象在滑雪时,每一秒脚下都有人从侧面推你一把,你很难走出直线。
Momentum(动量)的出现解决了这个问题。它的思路特别接地气:维护一个速度变量,每次更新时,把当前梯度和历史速度做一个加权融合。如果历史速度方向与当前梯度方向一致,速度就会越来越大,相当于加速通过平坦区域;如果方向不一致,速度就会被抑制,相当于缓冲掉噪声带来的反复。
我个人的经验是,在 CV 任务中,SGD + Momentum 的组合至今仍是很多优秀模型的标配。它不会像自适应学习率优化器那样引入过多的隐式调节,行为更可预测,配合精心调好的学习率 schedule,往往能得到更好的泛化性能。很多人以为“新人就该用 Adam”,但我反而建议新人在简单的分类任务上先用 SGD + Momentum 把训练流程跑通,因为它的行为更直接,更容易帮助你建立调参直觉。
2.2 RMSProp 和 Adam:自适应学习率的崛起
SGD 系优化器有一个共同痛点:所有参数共享同一个学习率。但正如前面提到的,损失曲面在不同方向上的曲率差异很大,这会导致某些维度更新太慢、某些维度更新太快。
RMSProp 的想法是:为每个参数单独维护一个梯度平方的滑动平均,然后用它来归一化当前梯度的量级。直观解释是,如果某个参数的历史梯度一直很大,说明这个方向很陡峭,那就把有效学习率调小一点;反之,如果某个参数的历史梯度很小,说明这个方向很平缓,那就把步子迈大一点。这相当于给每个参数装了一个自动的“地形感知器”。
Adam 则在 RMSProp 的基础上又加上了动量项,同时维护一阶动量(梯度的均值)和二阶动量(梯度平方的均值),因此它既对每个参数自适应调节学习率,又能在方向上有一定的平滑能力。理论上说,Adam 是一种非常强大的组合,加上它对初始学习率的敏感度比 SGD 低得多,所以它成了很多深度学习项目的默认选择。
但 Adam 并不总是最好的答案。它的二阶动量归一化,会把梯度尺度归一化到接近单位级别,这在训练早期可能造成参数更新过大,影响泛化;而它对学习率的自适应,也使得它的最终收敛行为在接近最优解时不够精细。这就是为什么很多从论文里复现的项目,最终会在训练后期把优化器切回 SGD 做微调,以获得更低的最终损失。
2.3 AdamW 与解耦权重衰减:一个容易被忽略的修正
说到 Adam 的改进,就不得不提 AdamW。它最初是苏黎世联邦理工的研究者提出的一个小改动,但影响非常深远。传统做法是用 L2 正则化来惩罚过大的权重,而在 Adam 里,如果直接把权重衰减项加进梯度,再参与二阶动量归一化,那么权重衰减的实际效果会被归一化操作削弱,变成一个随梯度尺度变化的不稳定量。
AdamW 的做法很直接:把权重衰减从梯度计算中拿出来,在参数更新时直接对权重做一个衰减缩放。这样权重衰减就不再受梯度归一化的干扰,表现得更像一个独立的“参数缩水”操作。实践中的差异是切实存在的:用 Adam 配 L2 权重衰减,和用 AdamW 配解耦权重衰减,训练出来的模型权重分布、泛化能力都有可感知的区别。
现在很多主流框架已经默认把 AdamW 作为推荐优化器。比如 Hugging Face 的 Transformers 库,默认配置就是 AdamW。如果你正在训练预训练语言模型、视觉 Transformer 或者多模态模型,直接选 AdamW 基本不会出错。但如果你是在训练一个小型的 CNN 分类模型,数据集规模也不大,那 SGD + Momentum 反而可能更稳,要针对具体任务去权衡。
2.4 一张表看清主流优化器的适用场景
为了让大家在选型时不纠结,我把常见优化器的特点和适用场景整理成一个对照表:
| 优化器 | 核心机制 | 优点 | 典型适用场景 | 我的建议 |
|---|---|---|---|---|
| SGD | 直接沿梯度方向更新 | 简单、行为可预测、泛化性好 | CNN 图像分类、目标检测 | 有调参时间时优先考虑,后期收敛更干净 |
| SGD + Momentum | 引入历史速度平滑梯度噪声 | 加速训练、抑制震荡 | 大规模 ImageNet 训练、超参数已对齐的经典任务 | CV 复现论文时的默认选择之一 |
| RMSProp | 按梯度平方滑动平均归一化学习率 | 适应不同方向的曲率差异 | RNN、强化学习、非平稳任务 | 对序列模型比 Adam 有时更稳 |
| Adam | 一阶动量 + 二阶动量自适应 | 参数自调节、初始学习率敏感度低 | Transformer、生成模型、多模态 | 跑通流程的首选,注意后期收敛问题 |
| AdamW | Adam + 解耦权重衰减 | 权重衰减更可控、泛化更稳 | BERT/GPT 等预训练模型 | 预训练与微调场景下的长期首选 |
这个表不是绝对的,但它能帮你建立一个初步的选型框架。核心逻辑就一句话:自适应优化器帮你省心省力快速出结果,SGD 系优化器帮你精雕细琢逼近最优。
3. 优化器关键参数与实操调参指南
3.1 学习率:最容易被低估的超参数
现在很多框架都有自动学习率调节工具,比如 PyTorch 里的 CosineAnnealingLR、OneCycleLR,让我感觉“手动选学习率”这项基本功正被慢慢淡化。但说到底,学习率才是所有超参数里和优化器交互最密切的一个,必须理解它。
学习率决定的是参数沿着梯度方向前进的步长。步长太小,训练速度慢得像蜗牛爬,还容易陷入局部坑洼;步长太大,loss 会在最小值附近反复横跳,甚至发散。给一个参考:对于 Adam/AdamW,常用初始学习率在 1e-4 到 3e-4 之间;对于 SGD + Momentum,常用初始学习率在 0.01 到 0.1 之间,搭配 step decay 或 cosine schedule 使用。
我踩过的一个真实教训是:有一次训练一个视觉 Transformer 模型,参考别人配置直接用了 3e-4 的初始学习率,结果训练前 2000 步 loss 完全没有下降。后来我把学习率调到 1e-3,loss 立刻开始稳步下降。所以很多“不收敛”问题,根源不是优化器选错了,而是学习率和优化器、模型结构不匹配。我的建议是,遇到不收敛时,先用对数刻度去扫学习率,比如从 1e-5 到 1e-2,每隔 10 倍试一组。这个成本不高,但能帮你快速定位问题。
3.2 batch size 与优化器行为的联动
batch size 直接影响梯度噪声的大小,进而影响优化器的行为。小 batch 的梯度噪声大,模型在优化过程中会不自觉地“探索”更多区域,这在某些场景下反而能帮助逃离局部极小值;大 batch 的梯度更接近真实梯度,训练更平滑,但需要更高的学习率才能充分利用大步长优势。
实际工程中,batch size 往往受到显存限制,能选的空间不大。但你需要知道的是:如果你把 batch size 从 128 提高到 1024,那么学习率也应该相应调大。一种常用的经验规则是线性缩放法则:学习率按 batch size 的倍数同比放大,比如 batch size 扩大 8 倍,学习率也乘 8。但注意,这只是起点,实际训练过程中还要观察 loss 曲线再做微调。
另外,大 batch 配合 AdamW 时,权重衰减的强度可能需要重新调整。因为 batch size 变大后,梯度噪声减小,模型的“隐式正则化”效果变弱,这时适当增强权重衰减、或者加入一些数据增强,才能保持相同的泛化水平。这个细节很容易被忽略,等你发现大 batch 训练出来的模型精度明显低于小 batch 时,往往就是这个原因。
3.3 warmup、梯度裁剪与 schedule 的组合
很多优化器在训练初期是不稳定的。尤其是 AdamW 这类自适应优化器,它的一阶动量和二阶动量在初始阶段都是零,如果一开始就用较大学习率,就会导致参数更新幅度异常大。所以现在主流做法是加一个 warm-up 阶段,让学习率从很小的值线性增长到目标学习率。这个做法在 Transformer 类模型的训练中是标配,它能有效避免初期训练崩溃。
梯度裁剪则是另一个常和优化器搭配使用的策略。当你发现 loss 曲线偶尔会出现尖峰、但随后又能恢复时,多半是梯度在某些 batch 上异常偏大,这时用梯度裁剪会让训练稳定不少。PyTorch 里直接clip_grad_norm_(model.parameters(), max_norm=1.0)就能实现。对于语言模型、多模态模型这类容易积攒超大梯度的任务,我建议默认开启梯度裁剪。
把这三个东西组合起来,一个典型的高质量训练配置大概是这样的:AdamW + CosineAnnealingLR + 前 5% 步数做线性 warmup + 梯度裁剪阈值 1.0。这套逻辑我用了很多年,在多个项目上都非常稳定。当然,具体任务不同,这些数字都需要根据实际曲线微调,但比“什么都默认”要可靠得多。
4. 常见问题与排查技巧实录
4.1 训练震荡不收敛怎么排查
训练中最常见的问题就是 loss 曲线震荡幅度很大,或者一直在高位徘徊不下来。面对这个问题,不要急着换优化器,先按顺序排查。
第一步看数据。我遇到过好几次,loss 一直降不下来,最后发现是数据标注有错、或者数据加载顺序存在批次不均衡。先跑一个小的基准实验,在少量数据上看看模型能不能过拟合,如果连小数据都学不进去,那跟优化器关系不大,要回到数据和模型结构上找问题。
第二步看学习率。把学习率降低几个数量级试一试。如果降低后 loss 变得平缓但能稳定下降,那就说明之前学习率太大。注意观察 loss 曲线的形态:如果 loss 在某个值附近反复震荡但不发散,通常是学习率偏大;如果 loss 快速反弹到 NaN,那就不仅是学习率问题,还要看数值稳定性。
第三步检查输入的数据尺度。有些场景下输入特征没有做归一化,导致梯度在不同维度上量级差异巨大,Adam 的归一化机制在极端情况下也无法处理。把输入标准化到均值为 0、方差为 1 附近,问题往往迎刃而解。
4.2 梯度消失与梯度爆炸的解决经验
梯度爆炸的典型表现是 loss 突然变成 NaN 或者 inf。这时除了检查学习率,还要检查网络结构里有没有不稳定的操作,比如深层网络的连乘、注意力机制里的数值过大等。梯度裁剪可以兜底,但根本解决方案是在模型设计上避免不稳定的数值流动,比如使用残差连接、LayerNorm、合适的激活函数。
梯度消失则表现得更加隐蔽:损失曲线下降得极其缓慢,网络似乎在学但效率低下。碰到这种问题,我通常先查看网络各层的梯度范数。PyTorch 里可以通过注册钩子记录每层梯度的 L2 范数,看看从输出层到输入层,梯度是不是指数级地衰减。如果是,说明网络深度过深或者激活函数选择不当,比如在不合适的场景用了 Sigmoid。另外,初始化方法也很重要。以前我习惯用 PyTorch 的默认初始化,但后来发现在深层网络上,Kaiming 初始化配合合理的残差结构,对缓解梯度消失有明显的帮助。
这里必须提醒一句:优化器解决不了所有优化问题。有些模型结构本身就已经让损失曲面变得极度恶劣,再好的优化器也只能勉强维持训练不崩溃。当你在优化器层面做了各种尝试仍然无解时,回头审查网络结构,往往才是真正的出路。
4.3 从 Adam 切到 SGD 微调:稳定精调的小技巧
在实际项目中,我们经常遇到这样的情况:用 Adam 训练出来的模型,验证集精度到了一个瓶颈,怎么调学习率都上不去。这个时候,可以试试“学习率衰减后切换优化器”的技巧。
具体做法是:先用 AdamW 以正常的 schedule 训练大部分步数,让模型接近收敛;然后在最后几百步,把 optimizer 换成 SGD + Momentum,并把学习率调低一个数量级,比如从 1e-4 降到 1e-5,再做一段短训练。这个方法在一些论文里被称为 “Adam-to-SGD 切换”。背后的原理是,Adam 在接近最优解时,由于自适应的归一化机制,参数的更新实际上不够“干净”,而 SGD 在低学习率下能把参数精确地推向更优的位置。
我用这个方法在几个视觉模型上做过实验,验证集精度通常能提升 0.3% 到 0.5%,虽然不多,但在打比赛或者刷 benchmark 时可能就是决定性的差距。需要注意的坑是:切换优化器后,动量 buffer 会被清空,SGD 初期会有一段自适应过程,所以切换时的学习率一定不能太高,否则会出现 loss 先反弹再下降甚至扩散的现象。
4.4 Model-Optimizer 常见问题速查表
| 现象 | 首要怀疑因素 | 快速尝试方案 |
|---|---|---|
| loss 完全不下 | 学习率过小或数据/模型错误 | 对数区间扫学习率;试跑小数据过拟合实验 |
| loss 震荡剧烈 | 学习率过大、batch size 过小 | 降低学习率;增大 batch size、调大权重衰减 |
| loss 突然变 NaN | 梯度爆炸、数值不稳定 | 开启梯度裁剪;降低学习率;检查输入数据是否有 inf/nan |
| loss 降到一半停住 | 陷于鞍点或局部极小值 | 调大初始学习率、增加 warmup;换 AdamW 试一下 |
| 训练正常但加速瓶颈 | 学习率 schedule 不合理 | 尝试 cosine schedule 代替 step decay |
| 之后精度上不去 | 泛化欠佳 | 切 SGD 微调、增强数据增强、调整权重衰减 |
这张表不算全面,但基本能覆盖我这两年遇到过的大部分训练异常。遇到问题时别慌,按表里的逻辑一步步排查,多数情况下 30 分钟内能定位到根因。
5. 实战案例:CLIP 风格多模态模型的优化器配置
5.1 案例背景与显存约束
说一个我自己完整跑过的实战案例,帮助你把前面讲的原理串起来。去年我在有限显存条件下训练一个类 CLIP 的图文对比模型,约束很明显:batch size 不可能开大,单卡只有 24G 显存,但同时又要保证模型能用。
在这种约束下,我直接排除了 SGD + Momentum,因为大 batch 才有足够稳定的梯度支撑 SGD。最终选定 AdamW,并把 batch size 定在 128 左右,配合梯度累积实现等效 batch size 512。学习率则按照线性缩放法则,先以 batch size 128 为基准定一个 2e-4 的初始值,再随梯度累积步数适当调整到 4e-4 附近。
5.2 实际配置与训练曲线分析
最终采用的训练配置如下:
- 优化器:AdamW,初始学习率 4e-4,betas=(0.9, 0.98),eps=1e-6
- 学习率 schedule:CosineAnnealingLR,最小学习率设为 1e-5
- Warmup:前 500 步线性从 0 增加到 4e-4
- 梯度裁剪:max_norm=1.0
- 权重衰减:0.2(这个值偏大,是为了在大 batch 隐式正则化变弱时弥补泛化损失)
- 等效 batch size:512(物理 batch 128 + 梯度累积 4 步)
这个配置跑下来,loss 曲线非常健康:前期 warmup 阶段缓慢下降,中期快速收敛,后期余弦衰减阶段平稳逼近最低点。在验证集上的 zero-shot 指标,比同参数规模的 baseline 高出了一截。事后复盘,几个关键选择起了决定性作用:AdamW 保证了训练稳定性,解耦权重衰减让模型的泛化表现更可预期,梯度裁剪则兜底保护了训练过程,让我可以放心地看着曲线而不必时刻提心吊胆。
这次训练也让我意识到,优化器从来不是孤立存在的。它和学习率 schedule、batch size、权重衰减、梯度裁剪是一整套协同系统。只看单点、盲目照抄别人的配置,是最容易踩坑的路径。
写在最后的个人体会
在我做过的大大小小几十个模型训练项目里,真正让我意识到优化器重要性的,不是某一次理论推演,而是一次次亲眼看到“换一个优化器、调一组参数之后,同样的模型和数据,最终结果天差地别”。SGD、Adam、AdamW 没有绝对的优劣,它们是在不同约束条件下找到的平衡点。
如果你只记得住一条经验,我会说:先花时间理解你的数据和任务,再用系统性方式去扫超参数,而不是盲目迷信某一个优化器。模型能不能训练起来、能训练到多好,优化器是那个关键开关,但操控开关的那个人,才是决定最终结果的核心。
最后再分享一个小技巧:每一个新项目里,我都会把优化器参数单独抽成一个配置类,记录下每次实验的优化器类型、学习率、schedule、batch size 和最终指标。当你积累了十几条实验记录以后,再看这些记录,你对自己的任务域会有一个远超常人的判断力。这比网上任何“推荐配置”都更靠谱。