训练一个模型loss卡在一个不上不下的位置试了加数据、调网络结构、改初始化都没什么起色。最后我只是把优化器从默认的Adam换成了AdamW把weight decay从0调到0.01然后加了线性warmup两轮之后loss肉眼可见地往下走。这种“改一行代码训练曲线大变样”的经历只要你做过模型训练大概率都碰到过。这就是我想认真聊一聊Model-Optimizer的原因。模型优化器通俗点说就是深度学习训练里那个决定“参数怎么更新”的组件。很多人把它当成一个带默认参数的API在用实际上优化器的选择、参数设置、与学习率策略的配合直接决定了你的模型能不能收敛、收敛多快、最终精度能到多少。这篇文章我会从优化器在训练闭环里的位置讲起逐一拆解主流优化器SGD、Momentum、RMSProp、Adam、AdamW、LAMB的核心原理和适用场景然后给出一套我实测下来比较稳的参数配置方案最后把loss不降、震荡、NaN这一类高频问题的排查思路整理成速查表。适合正在跑深度学习实验、被训练曲线折磨过、想系统理解优化器原理的工程师和研究者参考。1. Model-Optimizer是什么搞清楚优化器在训练闭环里的位置1.1 训练闭环里优化器到底在做什么很多人第一次接触深度学习框架时看到训练代码里有一行optimizer.step()下意识以为这是“把loss往回传”的动作。其实backward发生在它之前loss.backward()已经算好了梯度而optimizer.step()做的是另外一件事根据梯度去更新模型的权重参数。一个标准的训练闭环可以拆成四步。前向传播算loss反向传播算梯度优化器接收梯度和当前参数然后按某种规则算出参数更新量最后把更新量应用回参数上。整个过程循环往复直到loss收敛。所以严格来说优化器决定的是“拿到梯度之后下一步往哪走、走多大”它夹在梯度和参数之间是训练闭环里和梯度同等重要的一环。理解这一点很关键因为它解释了一个常见困惑为什么我梯度算对了网络结构也没问题训练还是不稳很可能是优化器这个“翻译官”没有把梯度正确地翻译成参数更新你在下游再怎么调上游都没用。1.2 用“下山”类比拆解优化器三大要素把训练过程想象成一个人蒙着眼睛下山目标是走到最低点。梯度告诉你当前位置哪个方向最陡相当于“脚底的感觉”而优化器就是你的“步幅策略和移动规则”。第一个要素是步长对应学习率learning rate简称lr决定你每一步走多远。步长太大容易一脚踩空冲过头步长太小则半天挪不动窝。第二个要素是方向修正对应动量momentum它让你在陡峭的山壁上不至于来回横跳而是平滑地沿着主方向前进相当于下山时会利用惯性。第三个要素是历史信息对应自适应类优化器维护的梯度统计量它让你在多方向尺度不一致的地形里自动调整每一步的有效幅度相当于对不同坡度的路段采用不同的步幅策略。理解这三个要素就够了。因为市面上的优化器本质上就是这三者的不同组合有的只做步长控制有的加动量有的叠加历史统计量做自适应。后面要讲的所有优化器都可以从“步长、方向修正、历史信息”这三个角度去理解这也方便你在实际问题里对症下药。2. 主流优化器的原理差异与任务适配2.1 从SGD到Momentum解决震荡问题随机梯度下降SGD是最基础的优化器更新公式就是参数 参数 - lr * 梯度。它的优点是简单、稳定、泛化性好尤其在CV领域很多人至今坚持用带Momentum的SGD训ResNet系列理由是小batch下SGD加动量得到的模型在验证集上往往比Adam更不容易过拟合。SGD的问题也很明显在损失面比较“狭长”的地形里梯度方向会在某个维度上反复横跳导致loss曲线震荡收敛慢。Momentum的解法是引入“速度”的概念每次更新不只是看当前梯度而是把历史梯度按指数加权累积起来再走。用一个容易理解的类比就是遛狗时松开手狗不会立刻停下而是会带着惯性往前冲一段。动量系数通常取0.9控制这个惯性保持多久。我在实际使用中的体会是SGDMomentum动量0.9是图像分类、目标检测这类任务的稳妥底牌。它前期收敛速度确实比Adam慢训练curve也没有Adam那么“漂亮”但最终测试指标经常更高。如果你的任务对精度要求高、对训练时间不是特别敏感优先尝试SGDMomentum。2.2 从Adagrad到RMSProp自适应学习率的由来SGD对每个参数用同一个学习率这在参数分布不均的场景里很吃亏。比如稀疏特征场景里有的参数经常被更新有的参数很少被更新使用固定学习率会导致频繁更新的参数在后期震荡而稀疏参数又更新不足。Adagrad的思路是按参数累计历史梯度的平方和让更新量除以这个累计值的平方根。被频繁更新的参数累计值大有效学习率自动变小稀疏参数累计值小有效学习率保持较大。这个方法在稀疏数据上有效但有一个致命问题分母的累计平方和单调增长训练后期学习率会被压得无限小直接“学不动”。RMSProp改进了这一点它用指数移动平均来计算梯度平方的均值而不是从头到尾累加。这样分母不会无限增大有效学习率能维持在一个相对稳定的水平。再加上它对每个参数单独缩放更新量这就是“自适应学习率”这一派系的开端。2.3 Adam系列与LAMB自适应加修正的集大成者AdamAdaptive Moment Estimation融合了动量和RMSProp两种思想。它维护两个状态一阶动量梯度的一阶指数移动平均和二阶动量梯度的平方的指数移动平均。一阶动量相当于带惯性的下坡方向二阶动量负责对每个维度缩放更新幅度。因为Adam同时做了方向修正和尺度自适应所以它在很多任务上“开箱即用”初始学习率可以设得比SGD小得多通常1e-3到3e-4就能跑起来。Adam有个重要的数学细节叫bias correction偏差修正。训练早期二阶动量是从零开始累计的估计值偏小会导致更新量被异常放大。Adam在更新公式里加入了一项对一阶、二阶动量的修正把早期估计偏差纠正过来。这就是为什么实现一个“简化版Adam”通常性能会比标准Adam差的原因少的那一步修正影响在训练初期尤其明显。AdamW是Adam的核心变体它解决的问题是weight decay权重衰减与L2正则化在Adam框架里的耦合问题。简单说Adam在计算二阶动量时会把L2正则的梯度也统计进去导致正则的效果被自适应缩放扭曲。AdamW把weight decay直接从梯度里拆出来在参数更新后单独执行一次衰减。这个改动看似微小但它在ViT、BERT、GPT等大批量Transformer类模型上表现更稳定现在几乎成了预训练和微调Transformer的事实标准。LAMB是针对大batch size训练设计的优化器。原理上它结合了Adam的自适应机制在层或全局级别上对更新的量级再做一次归一化保证batch size从几百扩大到几万时单步更新的尺度不会因为batch内样本增多而失衡。当年BERT在大规模预训练里能把batch size动辄设到几万LAMB功不可没。如果你需要在一个非常大的batch下训练可以考虑LAMB但要清楚它不是万能的大多数中小规模实验用AdamW就够了。2.4 优化器对比速查表优化器核心思想学习率经验值优势短板典型场景SGD原始梯度下降0.01~0.1稳定、泛化好收敛慢、震荡CV分类、检测SGDMomentum在SGD上引入惯性0.01~0.1动量0.9减少震荡、加速收敛超参数稍多ResNet系列、YOLOAdagrad按历史梯度平方缩放学习率0.01适合稀疏特征中期学习率趋零稀疏特征场景RMSProp用指数移动平均替代累加0.001自适应、不易衰减死对参数不如Adam鲁棒RNN、非稀疏深度结构Adam一阶动量二阶动量偏差修正1e-3~3e-4开箱即用、鲁棒泛化性有时不如SGD大多数任务首选AdamWAdam解耦weight decay1e-4~3e-4Transformer友好、稳定需要调weight decayLLM微调、ViT、NLPLAMBAdam 层级别更新幅度缩放1e-3~1e-2支持超大batch实现复杂、调参门槛高大规模预训练我个人的选型习惯是首次跑一个任务先用AdamW配一个中等学习率快速验证明网络是否能收敛、loss曲线是否健康确认没问题后再决定要不要换成SGDMomentum去冲最终指标。3. 优化器参数配置实操关键参数怎么调3.1 学习率最敏感的参数没有之一说到优化器参数第一个绕不开的就是学习率。它是最敏感、最影响训练结果的一个超参数也最能体现“优化器与训练策略”的联动关系。我的建议是先不要纠结具体数值而是先确立一个学习率策略框架。第一个框架是学习率预热warmup。训练刚开始时模型参数离最优解很远梯度方向也比较“野”如果直接用较大学习率很容易把参数一步推到一个离谱区域后面再难拉回来。warmup的思路是先让学习率从一个很小的值线性增长到目标学习率让优化器“热身”常见做法是前5%到10%的训练步数内完成升温。我自己做Transformer类模型时几乎必开warmup否则前几百步loss就可能飞起来。第二个框架是学习率衰减。训练后期需要更小的学习率来做精细收敛常见套路有Step Decay每N个epoch缩到0.1倍、Cosine Annealing按余弦曲线从目标学习率平滑降到接近0和ReduceLROnPlateauloss停滞后触发衰减。这些策略虽然不属于优化器本身但优化器的实际行为完全由“学习率×优化器更新公式”共同决定所以我建议把这俩绑定在一起调。具体数值上我的经验是Adam和AdamW的初始学习率在1e-4到3e-4之间通常比较稳SGD配合Momentum可以尝试0.01到0.1如果你用的是Cosine Annealing起始学习率可以比固定学习率高2到3倍。这些数值都会随batch size和模型规模变化后面第4章会再展开。3.2 weight decay、betas、epsilon这些容易被忽略的参数除了学习率AdamW里的weight decay是第二个值得认真调的参数。很多初学者把它当成单纯的L2正则化其实在AdamW里它是一个独立控制的“参数衰减项”直接影响模型的泛化能力。实测下来对于ViT类模型weight decay取0.01到0.05比较常见对于中小规模CV模型0.0001到0.001基本够用如果设置过大模型会出现欠拟合训练集loss都压不下去。再说betas也就是Adam里一阶、二阶动量的指数衰减系数默认是(0.9, 0.999)。这两项控制动量和历史梯度平方统计的“记忆长度”绝大多数场景不需要改。但如果你发现loss曲线震荡非常厉害可以试着把betas[0]从0.9降到0.8甚至0.5让模型更依赖当前梯度、减少历史惯性有时震荡就缓解了。代价是收敛速度变慢因此只在震荡明显时调整。epsilon是一个防止除零的小常数默认1e-8。它对结果的影响通常很小但在fp16、bf16混合精度训练中如果出现梯度更新过小导致的数值不稳定可以把epsilon改大到1e-6或1e-4。混合精度下Adam更新量过小且被截断时实际效果与纯FP32不同调高epsilon是我在不少大模型训练中实际用过的有效操作。3.3 PyTorch配置示例纸上谈兵这么久放一段可复现的配置示例。以PyTorch为例一个我实测下来在图像分类和NLP微调任务上都很稳的组合是AdamW配合线性warmup加余弦衰减。import torch from torch.optim import AdamW from torch.optim.lr_scheduler import LinearLR, CosineAnnealingLR from torch.optim.lr_scheduler import SequentialLR model build_model() total_steps len(train_loader) * epochs warmup_steps int(total_steps * 0.05) optimizer AdamW( model.parameters(), lr3e-4, # 目标学习率 betas(0.9, 0.999), eps1e-6, # 混合精度时可适当调高 weight_decay0.01 # 常用默认值 ) warmup LinearLR(optimizer, start_factor0.01, total_iterswarmup_steps) cosine CosineAnnealingLR(optimizer, T_maxtotal_steps - warmup_steps) scheduler SequentialLR( optimizer, schedulers[warmup, cosine], milestones[warmup_steps] ) for step, (inputs, targets) in enumerate(train_loader): optimizer.zero_grad() outputs model(inputs) loss criterion(outputs, targets) loss.backward() optimizer.step() scheduler.step()注意这里的SequentialLR是PyTorch 1.10之后才有的接口它会把两个调度器按milestone接起来前warmup_steps步走线性升温后面走余弦衰减。如果用旧版本可以直接手动写学习率更新逻辑或者用LambdaLR自定义。另一个容易被忽视的坑是scheduler.step()的位置PyTorch新版本的调度器默认按优化器实际更新步数来推进如果你用的是Lightning之类的框架调度器触发机制略有不同务必看框架文档确认是“每step触发”还是“每epoch触发”。我在早期踩过一次scheduler没生效的坑那轮训练全程都跑在固定学习率上等于白跑。4. 常见问题与排查技巧实录4.1 loss不下降换优化器不如查学习率训练了很多轮loss一直在一个较高位置震荡甚至纹丝不动很多人第一反应就是“换优化器”。我见过不少团队把Adam换到SGD再换回Adam问题依旧。实际上loss完全不下降最常见的原因有两个一个是学习率太小更新量微乎其微另一个是数据或网络本身有问题比如label错乱、数据预处理出错。排查顺序我很明确先用一个很小的过拟合实验几十个样本、训练几十步确认网络有没有基本的学习能力。如果小样本都拟合不了问题大概率不在优化器回头查数据、初始化、前向传播。如果小样本正常、全量数据不下降再调整学习率和warmup。我实测过多次把AdamW的学习率从1e-4调到3e-4甚至1e-3后loss下降速度立刻恢复正常。所以遇到loss不降先别急着换优化器先用小实验切分问题。4.2 loss震荡或发散动量和学习率是第一嫌疑人loss曲线前期降得不错中途突然发散或者剧烈震荡最大的嫌疑是学习率太大或者learning rate scheduler切换时机不对。比如warmup还没结束就直接进入高学习率段或者权重初始化不合适导致早期梯度异常。还有一种情况是优化器动量和batch size不匹配。当batch size变大时梯度估计更准确那么可以适当增大学习率但同时要重新审视动量。如果batch变大后loss震荡第一选择不是关动量而是先降低学习率或者把betas[0]调低到0.8。我记得有一回把batch size从32提到128后原来1e-3的学习率直接让loss在epoch 2爆炸降到3e-4之后训练才稳下来这充分说明优化器参数和batch size之间是一套联动关系。4.3 loss为NaN优化器不是凶手但需要配合排查NaN loss是让很多人头皮发麻的问题。排查时容易想到优化器但其实优化器常常是“背锅”的。几十次实测下来NaN的主要来源集中在梯度爆炸、模型输出Inf、数据中含有NaN值、学习率过大。优化器参数本身除了epsilon设置过小在混合精度下可能导致除零异常外它不是NaN的第一现场。我的排查顺序是先在第一个step后打印loss和部分参数的grad_norm。如果grad_norm已经是NaN问题在forward/backward或数据如果grad_norm正常但更新后参数变成NaN再去看优化器更新逻辑。对于梯度爆炸导致的NaN可以加梯度裁剪gradient clippingPyTorch里一行torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)就能搞定。这个手段和优化器无关但在Transformer训练里是标配。4.4 大batch size与优化器的关系训练数据没变把batch size从64提到1024同样代码结果变差了。这里其实有个很经典的“线性缩放规则”当batch size变为原来的N倍时步数会变为原来的1/N如果希望训练动力学大致不变学习率可以尝试乘以sqrt(N)或者N。这个规则不是万能的但作为初始参考非常有价值。使用大batch size时Adam类优化器的二阶动量统计会更稳定但SGD的收敛行为对batch size更敏感。如果发现大batch下SGD效果崩了可以往LAMB或者调整学习率的方向走。不过我还是建议大多数实验先把batch size控制在显卡能承受的合理范围内不要盲目追求大batch否则优化器跟着一起“加班”排查难度成倍增加。4.5 问题速查表现象第一嫌疑快速处置根治方向loss一直不降学习率太小或数据问题先跑几十个样本的小过拟合实验逐步提高学习率排查数据链路loss前稳后炸学习率过大立即降低学习率重启换warmup衰减策略loss高频震荡动量过大/batch变大调低betas[0]或动量重新校准学习率与batch关系出现NaN梯度爆炸/数值问题加梯度裁剪调初始化、降低lr、检查数据换优化器无效问题不在优化器检查数据与模型结构用控制变量法逐层排查5. 选型经验与踩坑实录5.1 不同任务的优化器选型参考CV分类/检测任务我优先推荐SGDMomentum动量0.9初始学习率0.01到0.1配合Cosine或Step Decay。原因是CV任务的损失面相对平滑SGD的钝感反而成了优点模型在验证集上的指标常比Adam高零点几到几个点。NLP任务尤其是Transformer结构AdamW是绝对主流。原因是Transformer的梯度方差大、尺度不一致很常见Adam的自适应机制能稳定训练同时AdamW的decoupled weight decay对Transformer的泛化直接有帮助。初始学习率建议从3e-4起步不要动辄上1e-3除非配合很好的warmup和衰减策略。我实测过很多NLP微调1e-3的AdamW在BERT类模型上经常会在开头几千步把loss推到NaN回来复盘时普遍发现是学习率过大。生成式模型、扩散模型这类任务训练过程对步长特别敏感AdamW配warmup衰减几乎是不二选择。如果你做的是分布式大batch预训练且batch size上万再考虑LAMB这一类专用优化器普通规模项目没必要引入额外复杂度。小样本或稀疏特征场景RMSProp、AdaFactor也有适用空间但它们在我近年看到的项目里出场率明显下降除非有明确理由我不建议新手直接用冷门优化器。5.2 我踩过的几个坑第一个坑是不备份初始权重就乱调优化器。很多实验结果是“换了优化器之后变好了”真实变量其实是跑了不同随机种子或者改了数据增强。我后来坚持每次只改一个变量这次专门动优化器其他设置冻结对照组记录得清清楚楚。这样踩过的坑才能变成可复用的经验。第二个坑是对learning rate scheduler的关注太少。优化器本身调好之后如果不配衰减后半段训练常常在无效震荡loss下不去。早期我有段时间只盯着AdamW的参数却忽视了scheduler结果跑了二十个epoch精度都上不去后来意识到是学习率从头到尾没衰减。现在我的每个实验几乎都默认带warmupdecay已经养成肌肉记忆了。第三个坑是混合精度下的优化器数值问题。开AMP后loss从正常变成NaN又变回去看起来偶发实际上可能出在优化器更新和梯度缩放GradScaler的配合上。梯度缩放会放大反向传播的loss如果optimizer.step()之前忘了scaler.unscale_()梯度会出现异常。这个和优化器原理无关但排查时要从优化器出发走一遍容易发现真正的问题在精度管理环节。最后再分享一个小技巧训练开始的前几百步是最容易暴露优化器问题的时间窗口。我习惯用一个小trick——把前几百个step的“更新量范数参数更新前后的差值范数”打出来和参数范数做个比值。如果这个比值在0.001到0.01之间说明更新步长健康远高于0.1说明学习率太大或梯度异常远低于0.0001说明学习率太小。这个方法比单纯盯loss更早发现异常也让我在训练新模型时对优化器的状态心里更有底。