做深度学习这几年,我见过太多人把精力全花在调loss函数、改网络结构上,结果模型训不收敛或者收敛得慢如蜗牛,转头怀疑数据有问题。其实有一个环节经常被低估——优化器。说白了,优化器就是决定模型参数往哪个方向走、每步走多远的那个“驾驶员”。
“Model-Optimizer”这个标题看起来简单,背后涉及的却是整个训练过程最核心的一环。从SGD到Adam,再到如今大模型时代绕不开的AdamW,不同优化器的选择直接决定你的模型是三天跑完还能涨点,还是三周跑完依然波动剧烈。这篇文章我会把优化器家族、参数直觉、场景选型和踩坑实录一次性讲透,适合刚入门的同学建立全局认知,也适合已经被loss曲线折磨到想转行的朋友拿来当排查手册。
1. 优化器家族全景:每个优化器什么时候该用
1.1 从SGD到Adam的演进逻辑
先梳理一下优化器的发展脉络。最朴素的SGD(随机梯度下降),就是沿着梯度反方向更新参数,每一步的步长由学习率决定。它的优点是泛化能力好,训练出来的模型在很多任务上表现扎实;缺点是收敛速度慢,容易陷在鞍点和局部极小值附近来回震荡。
为了解决震荡问题,研究者引入了动量(Momentum)概念,想象一个小球从山坡滚下,积累了历史速度,方向一致的梯度会加速,方向不一致的会被平滑掉。这就是带动量的SGD(SGD+Momentum)。后来Nesterov加速梯度在动量基础上“往前多看一步”,进一步减少了震荡。
另一条技术路线是自适应学习率。AdaGrad让稀疏特征对应的参数更新幅度更大,但累积平方梯度会让学习率过早衰减到零;RMSProp修正了这个缺陷,通过指数滑动平均来控制梯度累积量。2015年前后,Diederik Kingma和Jimmy Ba把动量和RMSProp的思路合在一起,提出了Adam,一出生就成为深度学习领域的事实标准之一。
Adam的两个核心机制在于:一阶矩估计(动量)提供前进方向,二阶矩估计(自适应缩放)让每个参数拥有独立的学习率。这让Adam在稀疏梯度和非平稳目标上表现极好,几乎不需要怎么调参就能收得动。
1.2 当前主流优化器横向对比
我把工程上真正值得记住的优化器整理成了下面的对照关系,别看它抽象,实际选型全靠这张表。
| 优化器 | 核心思想 | 优点 | 典型适用场景 | 需要重点调的参数 |
|---|---|---|---|---|
| SGD+Momentum | 历史梯度方向累积 | 泛化好、稳定 | 图像分类、检测、分割等CV任务 | momentum、learning rate |
| RMSProp | 梯度平方指数滑动平均 | 适合非平稳目标 | RNN、在线学习 | learning rate、alpha |
| Adam | 动量+自适应学习率 | 收敛快、鲁棒 | NLP、生成模型、快速迭代实验 | learning rate、beta1、beta2、epsilon |
| AdamW | Adam+解耦权重衰减 | 权重衰减更干净、泛化强 | Transformer、预训练、LLM微调 | learning rate、weight decay |
| Lion | 符号函数+动量 | 内存占用低、训练速度更快 | 大规模CV/NLP,Google提出后的各类新实验 | learning rate差别很大,通常小3-10倍 |
这套对比表是我在多个项目里反复确认过的,不是纸上谈兵。举个例子,图像分类任务里,用SGD+Momentum配合cosine学习率衰减,往往能拿到比Adam更平滑的loss下降曲线和更好的最终精度;但如果你今天只想快速验证一个新想法能不能跑通,Adam三分钟就给你一个“能看”的结果,根本没耐心等SGD热起来。这个“验证用Adam,刷点用SGD”的原则,在我自己的实验里几乎从未失手。
AdamW这个变体尤其值得单独说。原始Adam把权重衰减项直接加在梯度更新上,导致权重衰减与自适应学习率耦合,衰减效果被放大了。AdamW把权重衰减从梯度更新中解耦,单独对参数做衰减,理论和实验都证明这种做法在Transformer类模型上明显更稳。现在HuggingFace的Trainer里默认优化器就是AdamW,这不是偶然。
2. 优化器参数背后的数学直觉:不调参也要懂的5个核心概念
2.1 学习率不是越大越好,先理解loss曲面
很多人刚上手就喜欢把learning rate设成1e-2甚至更高,觉得步子迈大一点,loss掉得快一点。但loss曲面不是平的,参数空间里有沟壑、有陡坡、有平坦区。学习率过大,参数会在陡坡两侧来回弹跳,loss曲线表现为高频震荡甚至直接发散到NaN;学习率过小,更新幅度微乎其微,模型像是在锅底原地踏步,训练多少轮都像没跑。
你可以把优化器想象成一个人夜间下山,学习率是步幅。步幅太大,一脚踩空可能就滚下山崖;步幅太小,走到天亮还在半山腰。实际操作中,我习惯先用一个很小的学习率(比如1e-4)跑几十步,观察loss是否下降,再逐步放大找到“刚好能稳定收敛但又不震荡”的临界值。日志里出现loss突然飙升到几百上千,先检查的永远是学习率。
2.2 beta1、beta2、epsilon、weight decay各自管什么
Adam类优化器有四个超参数,大多数人只会改learning rate,剩下三个默认值一挂到底。理解它们各管什么,对你的训练稳定性会有质的提升:
- beta1控制一阶矩的衰减率,也就是动量衰减速度,默认0.9。它决定了模型对历史梯度方向“记多久”,越大,参数更新越平滑,但启动越慢。
- beta2控制二阶矩的衰减率,默认0.999。它负责估计梯度的大小范围。beta2太小会导致自适应学习率对近期梯度过于敏感,训练后期出现波动;太大则会让历史梯度范围的估计滞后,对突然出现的梯度尖峰反应迟钝。
- epsilon是为了防止除零加的一个极小值,默认1e-8。在混合精度训练时建议调大到1e-6或1e-7,因为fp16的最小正数比fp32小得多,epsilon太小会让除法结果溢出成NaN。
- weight decay是参数本身的L2惩罚系数。AdamW里的weight decay与学习率解耦,默认在1e-2到5e-2之间比较多。
这几个参数不是教条,它们和优化器内部的计算公式直接绑定。你不必背公式,但要知道:“当loss曲线在中后期出现反复横跳时,把beta2从0.999往0.99调,往往能压住这种波动;当模型训练后期精度上不去,检查weight decay是不是过大,限制了参数表达能力。”
2.3 学习率调度与优化器参数如何配合
优化器的步长并不只由learning rate一个数决定。训练过程中,学习率调度器(Learning Rate Scheduler)会在不同阶段改变学习率,这个配合关系比单独调任何一个参数都重要。
常见的调度策略有等间隔衰减(StepLR)、余弦退火(CosineAnnealingLR)和带热身的余弦退火(Warmup + Cosine)。预训练大模型和微调场景几乎必用Warmup:先用很小的学习率预热几百步,让优化器内部的动量估计从不准确的初始状态慢慢稳定,再升到目标学习率。如果一上来就给满学习率,前期梯度估计噪声很大,容易让模型在初始化附近就走偏,之后很难拉回来。
我在CV任务里最常用的是cosine退火,让学习率从初始值平滑地降到接近零,在中后期帮助模型在参数空间里做更精细的搜索,比StepLR那种阶梯式骤降更温和。在LLM微调里,warmup比例一般设为总步数的1%到6%,学习率通常不超过5e-5。这个参数搭配如果你能固定下来,很多实验的复现性都会大幅提升。
3. 实操场景选型指南:CV、NLP、LLM微调分别怎么选
3.1 图像分类、检测、分割:SGD还是Adam
在CV领域,卷积网络结构相对规整,梯度的分布比较稳定。大量经典论文(从AlexNet到ResNet再到各种检测框架)默认用的都是SGD+Momentum,动量设0.9,初始学习率0.1或0.01(配合batch size调整),配合weight decay设为1e-4到5e-4。实践下来,SGD在充分训练后泛化误差确实更低,因为它每一步更新都更“保守”,不会因为个别的梯度尖峰而大幅偏离主方向。
但注意,SGD对学习率和初始化相对敏感,收敛也慢。如果你是在做目标检测或语义分割的微调,尤其是从一个预训练权重开始继续跑,用AdamW往往更快达到可用效果。我自己的经验法则是:从头训练一个分类模型且你有充足的算力和时间,选SGD;在预训练模型基础上做迁移或微调,选AdamW;如果只是想快速验证数据管线或网络结构有没有bug,先丢给Adam。
3.2 NLP Transformer系列:AdamW成为标配的原因
Transformer结构里大量使用LayerNorm和残差连接,梯度分布跟卷积网络差异很大,尤其是在深层模型中,某些层或者某些参数的梯度幅度可能差好几个数量级。Adam的自适应学习率正好能缓解这个问题,让每个参数都能得到一个比较合理的归一化更新幅度,这也是为什么NLP一进入Transformer时代,Adam立刻成为主流。
但原始Adam的权重衰减是耦合在梯度更新里的,在Transformer这种“吃”正则项的模型上,耦合版本会让weight decay的实际效果被自适应缩放干扰。AdamW解耦之后,权重衰减对参数的影响变得更可预测,训练也更稳定。HuggingFace的transformers库默认训练配置就是AdamW,这个默认值是一线工程师们踩了无数坑之后沉淀下来的结果。
如果你在训练BERT或GPT类模型时发现训练不稳定,可以试试把AdamW的beta2从0.999调低到0.98,同时把epsilon设成1e-6。对于中等规模预训练任务(比如从零训练一个base-size模型),这个组合能让loss曲线明显平稳,我实测过很多次。
3.3 LLM微调场景:低学习率与适配器训练的实际经验
到了LLM微调,优化器的使用逻辑又变了。基座模型已经具备很强的通用能力,你希望它在保持原有能力的基础上适配特定任务,所以所有超参数都要偏保守。我自己做LLM微调的常用模板是这样的:
- 优化器:AdamW,beta1=0.9,beta2=0.999(或0.95),epsilon=1e-6
- learning rate:1e-5到5e-5,LoRA秩越高,学习率可以稍微大一点,但不要超过1e-4
- weight decay:0.01,对LoRA参数而言这个值比较安全
- warmup:总步数的3%左右
- 调度器:cosine,或线性衰减到峰值学习率的10%
LoRA这类参数高效微调方法,冻结了大部分原始参数,只有低秩矩阵参与更新,适配器参数量小,训练过程通常比全参数微调更稳定。但有几个细节容易踩坑:一是LoRA的alpha参数要与rank保持合适比例,alpha太大相当于隐式放大学习率,训练会震荡;二是优化器只作用于可训练参数,如果你忘了把冻结参数排除在optimizer构建之外,会白白浪费显存和算力。
我还遇到过一种情况,用低学习率微调时loss下降很慢,很多人急着调大学习率,结果模型能力迅速退化,生成内容开始胡言乱语。原因在于微调的本质是约束优化问题,而不是从零拟合。如果loss长时间不降,优先检查数据质量和任务构造,不要一上来就动优化器参数。
4. 训练不稳的排查手册:我踩过的10个优化器相关的坑
4.1 经典翻车现场与日志还原
先还原一个我印象特别深的翻车场景。那次是训练一个中小规模的GPT模型,loss前500步正常下降,到了840步左右突然从2.3爆成98.7,之后一直恢复不到正常水平。我一开始怀疑数据,清洗了一遍没发现问题;怀疑代码里梯度累积逻辑写错了,也没有;最后把优化器参数打印出来,才发现learning rate被误设置成1e-2,而warmup步数刚好在840步走完,学习率刚升到峰值就把训练炸掉了。
这种问题非常隐蔽,因为前几百步有warmup兜底,loss看起来非常正常,一旦warmup结束学习率暴露真实值就立刻出事。从那以后,我每次启动训练都会打印前100步的学习率变化曲线,确认峰值和计划一致,才放心去睡觉。
还有一个高频问题:梯度裁剪没设置或者clip值过大。Transformer类模型容易出现梯度爆炸,如果不做gradient clipping,优化器在个别极端batch上算出巨大的梯度,Adam的二阶矩估计又反应不过来,参数直接飞到异常区域,训练就再也回不来了。我习惯把max_grad_norm设为1.0,很多开源项目也是这么设置的,这个经验基本可以直接抄。
4.2 常见问题速查表
| 现象 | 最可能的原因 | 快速排查方法 | 解决方案 |
|---|---|---|---|
| loss一开始就NaN | 学习率过大、epsilon过小、数据里有异常值 | 打印前几步的参数梯度和更新量 | 降低学习率;混合精度下epsilon调大到1e-6;检查输入数据 |
| loss中期突然飙升 | warmup结束后学习率过高、梯度爆炸 | 对比学习率曲线和loss曲线的转折点 | 检查最大学习率设置,加入梯度裁剪 |
| loss震荡但整体下降 | beta2过小、batch size过小、学习率略高 | 观察震荡周期与batch更新频率的关系 | 调大beta2到0.999,或降低学习率10%-30% |
| 训练不收敛,loss纹丝不动 | 学习率过小、权重衰减过大、数据标签有问题 | 先跑过拟合小样本集验证模型容量 | 逐步把学习率从1e-2往下降,找到最早开始下降的点 |
| 精度涨不上去 | SGD梯度更新方向过度平滑、weight decay偏大 | 对比训练集和验证集loss差距 | 适当增大学习率、减少weight decay,或切到AdamW |
| 显存不足,训练中断 | 优化器状态占用过多 | 查看是否没冻结冻结层参数 | 使用LoRA/冻结部分层,或用Adafactor/Lion这类轻量优化器 |
这张表的价值在于,你不需要每次从零开始猜测问题出在哪。把日志、loss曲线、学习率曲线和显存占用放在一个面板里监控,任何异常都能快速对上号。
4.3 混精度训练下的优化器细节
现在很少有人用它声明式的fp32训练模型了,AMP(自动混合精度)几乎成了默认配置。但混精度对优化器的要求有变化,很多人没注意到。
fp16的梯度表示范围有限,特别是对于小梯度值,很容易在反向传播时下溢成0。优化器为了保证数值稳定,通常会在计算时把梯度升回fp32,这就是为什么PyTorch的AMP在更新参数前会调用optimizer的step之前做梯度缩放。具体到参数上,Adam的epsilon在fp16场景下建议从1e-8上调到1e-6或1e-7,否则二阶矩估计除以一个太小的epsilon值时容易溢出。还有一个实践经验:梯度裁剪要在梯度缩放恢复(unscale)之后再做,否则裁剪会被缩放系数干扰,等于没裁。
bf16则更省事,它的动态范围跟fp32相近,不太容易出现下溢问题,但精度位更少。在A100等支持bf16的硬件上训练大模型,我基本优先用bf16而不是fp16。优化器状态仍然建议用fp32保存,这样可以保证参数更新的精度不受训练精度的拖累。这些细节看似只是几行代码的差别,在长时间训练中积累出来的效果差距非常明显。
4.4 单一loss好看不等于模型好用,优化器也一样
排查了一圈技术问题之后,有一个容易被忽略的认知值得单独提出来。很多人把优化器的评判标准理解成“loss降得最快”,于是选择Adam;结果任务指标(比如BLEU、准确率、生成质量)并不见得好。因为loss低只代表模型在训练分布上拟合得好,不代表它能泛化。
你完全可以用SGD+Momentum训练出一个loss下降比Adam慢,但在测试集上表现更好的模型。选择优化器不只是看收敛速度,还要看最终结果和训练稳定性。这也是我在实际项目里坚持“用Adam做快速实验、用SGD或AdamW打磨最终指标”的原因。
养成了这个思维习惯后,你再去看那些开源项目的默认配置,就能理解作者为什么选某个优化器、为什么设置对应的学习率了。纸上得来终觉浅,如果你也在优化器上栽过跟头,欢迎在评论区聊聊你遇到的谜之loss曲线,说不定正好能帮别人避个坑。有好用的优化器组合,也别忘了分享出来,实践验证过的配置,才是最有说服力的配置。