1. 项目概述:Model-Optimizer 到底是什么
1.1 核心需求解析
先别被 Model-Optimizer 这个名词唬住。在深度学习训练流程里,它指的是那一层负责“更新模型权重”的优化器组件——从最早的 SGD,到后面烂大街的 Adam、AdamW,再到最近几年重新被重视的 LAMB、Lion 这些新面孔,都属于这个话题的范畴。
为什么单独拿“优化器”出来说?因为模型能不能收敛、收敛得快不快、最终精度高不高,很多时候不是网络结构决定的,而是优化器及其配套参数决定的。网络结构决定了模型的上限,优化器决定了你实际能摸到这个上限的几分之几。我见过太多项目,模型结构照着论文抄得一模一样,结果训练出来的效果差一大截,最后排查了半天,问题出在优化器选型和学习率设置上。
这篇内容适合谁看?两类人。一类是刚刚入门深度学习、正在跑自己的第一个训练脚本的初学者,你需要搞清楚 loss 曲线为什么抖、为什么不降、换 Adam 和换 SGD 到底改了什么东西;另一类是已经在调模型、但总觉得训练过程“差点意思”的从业者,你可以把这篇当成一份优化器层面的排查手册和调参心得索引。
另外多说一句:Model-Optimizer 这个名字有时候也会被用在模型量化、剪枝、蒸馏这些“优化模型本身”的场景里,比如 TensorRT、ONNX Runtime 的图优化工具。但绝大多数时候,圈里人一说“优化器”,默认就是指训练阶段的权重更新算法。我下面所有内容都围绕后者展开,这也是实际项目里出现频率最高的场景。
1.2 优化器在整个训练流程中扮演的角色
为了把这件事讲清楚,我们先看一个最简单的训练循环。每个 step 里发生的事情其实只有四步:前向传播算 loss、反向传播算梯度、优化器根据梯度更新权重、清零梯度进入下一步。模型权重能不能被高效更新,完全取决于第三步里的优化器怎么做决策。
打个比方:训练模型就像下山。网络结构决定了这座山长什么样,损失函数决定了哪个方向是往下,而优化器决定你每一步迈多大、怎么迈、要不要看看历史脚印再决定方向。如果只看当前坡度就闷头冲,容易在沟壑里来回震荡;如果一步迈得太大,可能直接从半山腰飞出去;如果每个方向都用同一种步子,那遇上峡谷地形就会进退两难。市面上所有优化器,本质上都是在解决“怎么迈步”这件事。
理解了这一层,后面所有算法细节都不难懂。SGD 是只看当前坡度迈步,Momentum 是带着惯性迈步,AdaGrad 和 RMSProp 是给不同方向配不同步幅,Adam 是既带惯性又配步幅,AdamW 则额外把权重衰减从梯度里拆出来单独处理。
2. 从梯度下降到自适应学习率:优化器的进化脉络
2.1 最朴素的 SGD:把所有参数一视同仁
先聊最基础的随机梯度下降。SGD 的更新公式非常简单:theta = theta - lr * grad。每个参数减去学习率乘以它对应的梯度,完事。它好在哪里?一是实现极其简单,几乎没有额外的内存开销;二是在足够调参的情况下,它的泛化能力在不少任务上确实优于自适应方法——这一点后面细说。
但 SGD 的痛点也很明显。它对学习率非常敏感,lr 稍微大一点就直接发散,小一点就龟速收敛。而且因为所有参数共享同一个学习率,遇到稀疏特征时表现并不好:频繁出现的特征权重更新幅度大,稀疏但重要的特征权重更新幅度小,模型很难在两者之间找到平衡。
我把这个阶段称为“一视同仁”的时期。它把所有维度都当成同一个问题来处理,好处是没有偏见过拟合,坏处是效率低、对地形适应性差。早期深度学习因为网络浅、任务简单,SGD 勉强够用;等网络深了、任务复杂了,SGD 就撑不住了,所以才有了后面的各种改良。
2.2 动量:给梯度下降装上惯性
Momentum 是对 SGD 最直观的改良。它的想法是:不要只看当前这一步的梯度,把前面积累的梯度方向也考虑进来。更新公式从 SGD 的一步换成了两步——先更新动量缓冲,再更新权重。相当于下坡的时候给你加了一个惯性,方向如果连续一致,步子会越迈越大、越走越快;方向如果来回变,惯性会帮你抵消掉一部分震荡,走得更稳。
还有 Nesterov 变体,也就是所谓的 NAG,它在计算梯度之前先“看一眼”下一步的位置,用那个位置的梯度来更新动量。效果上比普通 Momentum 更稳,收敛方向的预判性更好。我的经验是:如果项目里用 SGD + Momentum,优先考虑 Nesterov 版本,它几乎不会有什么坏处,但收敛曲线通常更平滑。
动量在这里解决的本质问题是梯度的“震荡”和“锯齿形前进”。尤其在高维空间里,梯度方向经常在局部最优附近来回摆,动量就是一个低通滤波器,把这个高频抖动滤掉,留下更一致的前进方向。
2.3 自适应方法:每个参数都有自己的学习率
自适应学习率方法的里程碑是 AdaGrad。它按参数维度累计梯度平方和,然后用这个累计值去缩放学习率。逻辑上很直接:更新多的方向,学习率自动变小;更新少的方向,学习率保持较大。这在稀疏数据场景下很有价值。
但 AdaGrad 有个要命的毛病:梯度平方和只增不减,训练到后期累计值奇大无比,学习率被压到接近零,模型基本停止更新。所以后来出现了 RMSProp,把“累计全部历史”改成“累计指数滑动平均”,让过去的梯度贡献随时间衰减,解决了 AdaGrad 学习率单调衰减到零的问题。
到了这个地方,两条技术线已经汇合了:动量线负责加速和平滑,自适应线负责按维度调整步幅。Adam 就是这两条线的合体——它同时维护一阶动量(梯度的滑动平均)和二阶动量(梯度平方的滑动平均),再分别做偏差校正,让训练前期的估计更准确。
3. 主流优化器横向对比:Adam 不是万金油
3.1 SGD + Momentum:老派但可靠的默认选择
先亮一个我个人的观点:如果你的任务和 CNN 图像分类沾边,SGD + Momentum 依然是一个值得优先尝试的配置。很多人觉得它老、它慢,但它在 ImageNet 级别的任务上,配合合适的训练策略,几乎总是能拿到比 Adam 更高的精度。
SGD + Momentum 这种组合的优势在于它的“钝感”。它不会像 Adam 那样在训练后期还在做大幅度的自适应更新,反而会老老实实地收敛到更平坦的极小值。学界一直有研究说,自适应优化器倾向于收敛到尖锐的极小值区域,泛化性能偏弱;SGD 因为更新相对保守,有更强的“平谷”倾向,测试集表现更好。
不过要付出代价:它对超参数更挑剔。学习率要么设成线性缩放(batch size 翻倍,学习率也翻倍),要么用 cosine 衰减配合 warmup,总之不能一个固定值跑到底。动量系数一般取 0.9,权重衰减取 5e-4 到 1e-4 之间,这套配置在 ResNet 系列上我跑了很多次,表现很稳定。
3.2 RMSProp 与 AdaGrad:各有地盘
RMSProp 在它出现的年代是很好的方案,尤其是处理非平稳目标函数时,比 AdaGrad 稳健得多。但现在其实处于一个比较尴尬的位置——它在 RNN/语音任务里还有记载,但绝大多数新项目已经默认上 Adam 了。我的建议是,除非你明确知道区域里非平稳性极强,否则 RMSProp 没有特别的必要去选。
AdaGrad 则在稀疏场景里还留有一席之地。比如推荐系统的 embedding 参数特别稀疏,你用 SGD 会发现大量 embedding 参数的梯度要么为 0 要么为 1,很难通过一个全局学习率去适配。AdaGrad 这类按参数频率自适应调整的方案,天然适配这个场景。当然,实际工程里大家也会用 Adam 配合 embedding 的 L2 约束来替代,但了解 AdaGrad 的原理能帮你理解为什么自适应方法适合处理稀疏参数。
3.3 Adam 与 AdamW:称霸,但别盲目
Adam 是过去十年里用得最多的优化器,尤其在 NLP、多模态、生成模型这些领域。它的收敛速度快,对初始学习率不敏感,几乎不用怎么调就能跑出不错的效果。2014 年提出到现在,长时间内都是新任务的默认选择。
但这里有两个点需要知道。第一个是 Adam 过拟合风险。它更新幅度大、自适应强,所以在部分任务上验证集表现不如 SGD 调好之后的水平。第二个更关键:Adam 原论文里把权重衰减等同于 L2 正则,这个等号在自适应方法里根本不成立。AdamW 把它俩拆开后,训练稳定性明显改善。
我现在做新项目,除非是纯视觉 CNN 分类这种明确该用 SGD 的场景,默认优化器基本都选 AdamW。原因很简单:它既保留了 Adam 的便捷性,又解决了权重衰减耦合问题,还能方便地用 decoupled weight decay 来配合 Transformer 类结构。这已经是当前论文里出现率最高的出现了。
3.4 一张表送走选择困难症
| 优化器 | 推荐场景 | 学习率经验值 | 主要风险 |
|---|---|---|---|
| SGD + Momentum | CNN 图像分类、需要高泛化精度的任务 | 0.1 起步,配合 warmup 和 cosine | 对 lr 和 warmup 太敏感 |
| RMSProp | RNN 序列建模、语音类任务 | 0.001 附近 | 已经不如 Adam 主流 |
| AdaGrad | 稀疏 embedding 更新 | 0.01 起步 | 后期学习率过小 |
| Adam | NLP 基础任务、生成模型、快速出效果 | 1e-4 到 3e-4 | 可能收敛到尖锐极小值 |
| AdamW | Transformer、扩散模型、大模型微调 | 1e-4 到 3e-4 | 需要正确设置 weight decay |
| LAMB | 大批量分布式训练 | 按 batch size 线性缩放 | 实现复杂,单卡没必要 |
| Lion | 部分大模型训练、内存受限场景 | 3e-4 到 1e-3 | 较新,经验积累少 |
这张表不是绝对的,但可以作为第一次选型的起点。选错了也别慌,优化器换来换去成本并不高——只要你理解了每种优化器的“脾气”,换过去最多跑几个 epoch 就能看出趋势。
4. 实操调参经验:比选哪个优化器更重要的几件事
4.1 学习率:最不应该偷懒的超参数
很多同学一上来就问“哪个优化器最好”,实际上对训练影响最大的往往是学习率。同一套模型、同一个优化器,学习率从 3e-4 改成 3e-3,可能直接决定模型是收敛到 70% 还是当场崩成 random guess。我强烈建议,每次启动一个训练任务时,先花小半天做一次学习率扫描实验。
具体做法不复杂:从 1e-4 到 1e-2 取五到六个值,每个值用小 batch 跑几十上百个 step,画 loss 曲线。观察曲线形态——如果 loss 持续下降没有平台期,说明学习率偏小;如果 loss 直接冲上天或者剧烈震荡,说明偏大;最理想的是 loss 快速下降后逐渐进入平台期。这种方式比盲调节省时间得多。
另外要养成一个习惯:当模型结构或数据分布改了之后,之前调好的学习率很可能失效。很多项目中途换主干网络、换了数据增强策略,结果 loss 曲线变得不正常,第一反应不应该是改模型,而是重新扫一遍学习率。我踩过这坑的次数一只手掌数不过来。
4.2 权重衰减与正则化的配合
权重衰减(weight decay)是另一个容易忽略、但作用巨大的参数。它的本意是让模型权重不要太大,抑制过拟合。在 SGD 时代,weight decay 和 L2 正则基本等价;但到了 Adam/AdamW 时代,这个“等价”被打破了,所以必须按 AdamW 的方式显式设置 weight decay,而不是在优化器里加一个 L2 惩罚项。
不同任务的 weight decay 经验值差异很大。小模型小数据量,weight decay 设 1e-4 到 5e-4 比较稳;大规模预训练,比如 GPT 类模型,经常看到 0.1 甚至更大的 weight decay 设置。这个数值看着吓人,但配合 AdamW 的解耦机制,它实际作用范围只在权重本身,不会干扰自适应统计量,所以大一点反而更稳。
调 weight decay 的时候我习惯和 dropout、数据增强一起看。它们都在约束模型的“复杂度”,但机制不同。weight decay 是直接压权重幅值,dropout 是破坏共适应性,增强是扩大数据分布覆盖。三者配套使用,而不是单独死磕一个参数,效果通常更好。
4.3 配合学习率调度器
优化器本身再强,没有一个好的学习率调度策略,效果都要打折扣。训练初期用 warmup 防止早期梯度过大导致模型不稳定,训练中后期用 cosine 衰减让模型慢慢落到最小值附近,这是目前最主流的方案。
Warmup 步数的设置有个简单经验:小任务几百步就够,大任务几千步。步数设置太长,浪费训练时间;太短,起不到稳定作用。Transformer 类模型对 warmup 尤其敏感,不少人直接沿用论文里的设置,比如前 10% 的训练步数做 warmup,实测大体可用。
Cosine 衰减的好处是可以给 final lr 设一个很小的值,比如最大学习率的百分之一甚至更低。这会让 loss 在后期出现明显下降,很多模型在最后几个 epoch 提升的精度就是这么来的。我建议不要用固定学习率跑完全程,哪怕你用 SGD,最后阶段也加一层衰减,效果差异肉眼可见。
5. 常见问题与排查技巧实录
5.1 loss 不降反升:先分清楚是发散还是震荡
模型训练早期 loss 一路走高,常见原因有三类:学习率过大、数据/标签处理出错、模型结构不稳定。要快速定位,我的习惯是把第一步的学习率压小十倍,如果 loss 立刻稳定下来,那基本就是学习率问题。
如果换了小学习率依然不降,就要检查输入数据了。归一化是否统一、标签是否对齐、数据增强是否引入了异常样本。这些业务层问题在优化器这层是看不出来的,但会直接表现为 loss 异常。
还有一类是 nan loss,通常是梯度爆炸引起的。此时除了降学习率,还可以检查是否有 NaN 的梯度出现。结合梯度裁剪,能够很好地防止单步更新幅度过大。这个手段在 NLP 里几乎是标配,计算机视觉任务反而用得少——后者数据更平稳,但也不能排除。
5.2 训练震荡:要么学习率太大,要么 batch 太小
震荡的表现是 loss 曲线像锯齿一样上下跳动,偶尔还能看出下降趋势。如果训练前期震荡剧烈,先怀疑学习率设置过大。如果学习率已经很小还是震荡,就要看 batch size 是不是太小了。batch 小,梯度噪声大,每一个 step 的更新方向都偏,自然容易抖动。
解决震荡的另一个技巧是适当增大动量,或者干脆换成 Adam 这类自适应方法。自适应方法天然对梯度噪声不敏感,因为它用滑动平均平滑了梯度估计。
我建议把震荡分两个阶段判断:前几十步震荡是正常的,因为参数从随机初始化开始动,梯度方向比较乱;训练中期出现持续震荡,就算有下降趋势也得关注了,说明优化过程不够稳定。这种情况下可以尝试先加大 batch size 或放缓学习率,不要直接换优化器。
5.3 收敛慢但准确率上不去
你的 loss 在降,但降得像蜗牛爬,这种“假性健康”反而最难排查。首先要确认是不是优化器选型过于保守:SGD 收敛慢在预期内,Adam 应该明显快于 SGD。如果你的任务里两者差异很小,那说明主导因素可能是数据/模型容量问题,而非权重更新算法。
还有一个常见原因是没有给模型一个合适的“起点”。预训练模型微调,收敛慢往往是学习率设太小,微调阶段常见用 2e-5 到 5e-5;从头训练,收敛慢往往是学习率设太大导致模型始终在震荡,未能稳定进入下降通道。这个对比测试很容易做:把初始权重换成熟练的预训练权重,如果收敛速度立刻上来,那就是训练起点的问题。
另外,梯度裁剪阈值、gradient accumulation 这些小配置也会间接影响收敛速度。比如梯度累积步数增加,等效 batch size 变大,学习率也要相应放大,否则更新幅度偏小、收敛变慢。
5.4 不同任务的优化器默认选择
最后直接给一份我自己的默认配置,仅供参考:
- 图像分类(CNN):SGD + Momentum(0.9)+ warmup + cosine,weight decay 5e-4。
- 目标检测:与图像分类类似,但很多检测框架默认用 SGD,效果稳定。
- Transformer 类:AdamW + warmup + cosine,weight decay 0.01 到 0.1。
- 生成模型(扩散模型):AdamW,learning rate 1e-4 左右,权重衰减往往不太需要。
- 稀疏推荐场景:Adam 或 AdaGrad,注意 embedding 维度的学习率是否适配。
这套配置不是真理,但它能保证你在新任务上有一个相对稳的起点。优化器选型这件事,本质上是一种经验驱动的权衡,不用抱着某个优化器不放,适合任务的就是好的。
我自己实际做项目时,习惯在项目启动时把优化器配置单独写成一个配置文件,和模型结构解耦。这样想换优化器做对比实验,只需要改配置而不动代码,批量实验的效率会高很多。还有一个心得,很多开源复现项目里,解决方案的差异不是来自网络结构,而是来自优化器和调度器的配套设置。读论文时多看 appendtext 里的训练细节,调参水平就是这么一点一点攒出来的。