深度学习优化器实战指南:从SGD、Adam到AdamW与调参策略 这篇博文不预设读者有深厚的数学背景但我假设你已经跑通过一个基础的图像分类或文本模型——如果连反向传播都没跑过建议先找个框架跑通一个Demo再回来看效果会好很多。1. 优化器选型为什么Adam不是万能药1.1 优化器的本质梯度下降的三个流派先说一个我自己的体会模型训练效果上不去十次里有七次不是网络结构的问题而是优化器选错了或者参数压根没调。优化器干的事说白了就一件——根据反向传播算出来的梯度决定把参数往哪个方向挪、挪多大。但这个“挪”的策略不同流派之间差别大了去了。第一类是纯SGD就是最朴素的“顺着梯度反方向走一步”。它的问题在于步伐太死板碰到峡谷地形某个方向梯度大、另一个方向梯度小会来回震荡收敛又慢又不稳。第二类是带动量的SGDMomentum它把物理世界的惯性引入进来上一次的更新方向会对下一次产生影响。这招在损失面比较崎岖的时候特别好使相当于一个球从山坡上滚下来遇到小坑能顺势冲过去不容易卡在局部极小点。第三类是自适应学习率流派以Adam为代表。它的核心思路是给每个参数单独配学习率梯度大的方向步子迈小一点梯度小的方向步子迈大一点再加上一阶动量类似Momentum和二阶动量梯度平方的滑动平均来做修正。好处是基本不用怎么调参默认学习率1e-3就能在很多任务上跑出不错的结果。这也是Adam成为事实标准的原因——省心。但问题恰恰出在“省心”上。默认参数在CV分类任务上很稳到了NLP生成任务、推荐系统CTR预估、强化学习这类场景Adam的表现经常不如调好的SGDMomentum。我见过不止一次有人用Adam训GANLoss曲线像心电图一样上下乱跳换回SGD加个动量反而稳稳往下走。这就是选型时第一个要建立的意识没有最好的优化器只有最适合当前任务的优化器。1.2 在NLP任务上Adam为何会“翻车”用Adam训BERT这类Transformer模型刚开始几千步效果确实好Loss降得飞快。但训练后期会出现一种奇怪现象验证集指标震荡剧烈甚至出现Loss还在降、指标不涨反跌的情况。这里面的原因主要有两个。第一个原因是Adam的自适应机制在稀疏梯度场景下会放大噪声。Transformer里Embedding层的梯度天然稀疏很多token出现次数少对应梯度为零Adam会给出现次数多的token学出一个很小的学习率给出现次数少的token一个很大的学习率这本身是优点。但到了后期模型接近收敛梯度本身已经很小Adam会把某些历史梯度累计很小的参数突然放大更新量导致Loss出现尖峰。第二个原因更隐蔽Adam的权重衰减实现有问题。原始Adam论文里没有规范的权重衰减实现大多数框架采用的方式是把L2正则的梯度加到主梯度上再整体做Adam更新。这会导致一个结果——头几个迭代里那些本身梯度很小的参数其权重衰减项会被二阶动量归一化后放大使得正则力度很不均匀。说白了就是正则没起到该有的正则作用反而干扰了参数更新。这个问题被Loshchilov等人发现后催生了AdamW这个变体后面我专门讲它。所以在NLP任务上我个人的经验是如果模型不大、训练步数不多Adam可以凑合用一旦训练步数超过几万步或者模型规模到了亿级参数强烈建议换成AdamW并且把权重衰减单独拎出来调。另外训练后期如果发现验证集Loss抖动厉害可以着手做两件事一是把学习率按计划降到初始的十分之一甚至百分之一二是给梯度加一个全局裁剪clip把更新量限制在一定范围内这两个办法能解决绝大多数“后期震荡”问题。1.3 我踩过的选型坑一个图像分类案例分享一个真实的案例。去年我在做一个小型图像分类项目网络用的ResNet18数据集是自采的工业缺陷样本大概一万多张图类别不平衡挺严重。最初我图省事直接用Adam默认参数跑了五十个epoch验证集准确率死活卡在87%。后来我换了SGDMomentummomentum设0.9初始学习率0.1加了余弦退火同样五十个epoch准确率直接冲到93.5%。那一次把我给震住了。后来我仔细查了相关研究发现CV任务里SGD的泛化能力普遍优于Adam一个主流解释是Adam的自适应学习率会让模型收敛到损失面比较“尖锐”的极小点而SGD更容易找到“平坦”的极小点平坦极小点对验证集的泛化性更好。虽然不是所有任务都这样但确实是一个值得记住的规律。当然这个结论不能一概而论。如果你做的是大规模预训练比如训练一个BERT或者GPT类模型AdamW或者LAMB几乎是必选因为这类任务需要处理海量稀疏参数纯SGD根本跑不动训练效率完全不在一个量级。选型这件事归根到底是个权衡小数据、小模型试试SGDMomentum大数据、大模型直奔AdamW或LAMB不确定时先在公开benchmark上用小规模子集跑一遍对比再决定主线方案。2. AdamW、LAMB与LARS那些被低估的改进版本2.1 AdamW把权重衰减这件事彻底改对了接着上面说的Adam权重衰减问题往下聊。AdamW的“W”就是Weight Decay它跟Adam最大的区别就是权重衰减不再混在梯度里一起被二阶动量归一化而是直接在参数更新完成后对参数乘上一个衰减系数。代码层面上看AdamW的更新公式是这样的# AdamW核心更新逻辑伪代码 param param - lr * (momentum_term / (sqrt(second_moment) eps) weight_decay * param)看到没有weight_decay * param这一项是在梯度归一化之后直接加上去的不再经过二阶动量的缩放。这样正则力度就变得跟梯度尺度无关了不管某个参数的历史梯度是大是小它被衰减的程度是一样的这才是“权重衰减”该有的样子。从实用角度看AdamW带来的直接好处就是训练BERT、GPT这类大模型时超参数敏感性降低了微调阶段更容易奏效。HuggingFace的Trainer里默认优化器就是AdamW这不是随便选的是踩了无数坑之后沉淀下来的默认值。我自己做了个对比实验同样一个序列标注任务BERT微调跑二十个epochAdam的验证F1在90.2上下波动AdamW能稳定在91.0左右而且训练后期不会出现指标突然跳水的情况。2.2 LAMB大Batch训练时的救星量化投资里常说“不要把鸡蛋放在一个篮子里”大模型训练里反过来——恨不得把几万个样本塞进一个Batch里因为Batch越大一次迭代的算力利用率越高。但Batch太大会带来一个新问题Batch Size从256增加到8192时普通AdamW的训练效果会明显变差收敛变慢甚至发散。这就是LAMBLayer-wise Adaptive Moments for Batch training出现的背景。它的思路是给每一层单独算一个学习率缩放系数。具体来说先用类似Adam的方式算出一个更新量然后用“更新量范数对比参数范数”的比值对每一层做一个归一化调整。这么做的效果是不同层的更新尺度能保持在一个比较合理的相对范围内避免某些层步长太大而另一些层步长太小。我实际用LAMB跑过一次数据并行的BERT预训练四卡机器Batch Size从256拉到2048同样两千步训练LAMB的Loss下降曲线跟小Batch的AdamW几乎重合而直接用AdamW开2048的Batch大概两百步就炸了。需要提醒的是LAMB对学习率比较敏感通常要配一个线性warmup初期学习率不能太高。参考设置Batch Size 2048时峰值学习率0.002到0.004之间warmup步数占总步数的5%到10%这个范围基本够用。2.3 LARS和SAM什么时候该搬出这两尊大神LARSLayer-wise Adaptive Rate Scaling跟LAMB类似也是按层调整学习率但它是基于SGD设计的不是基于Adam。它的典型应用场景是超大Batch的ResNet训练比如ImageNet上把Batch Size开到上万LARS能保持住精度这一点在自监督对比学习比如SimCLR里尤其常见。如果你做的是视觉模型大Batch训练LARS比LAMB更对路。SAMSharpness-Aware Minimization则是另一条路。它不追求训练Loss降得快而是专门寻找“平坦极小点”。做法是在原始梯度方向上先做一次小步扰动计算扰动后的梯度然后用这个扰动后梯度做实际更新。你可以理解成“往四周探探路再决定往哪走”。这套机制在不少任务上能显著提升泛化性尤其是模型微调和医疗影像这类小样本场景。我自己在肺部CT分类任务上做过测试ResNet50 SAM比ResNet50 SGD准确率高出两个百分点左右代价是训练时间多了约30%因为每次迭代要多算一次Forward和Backward。总结一下这几个改进版优化器的使用场景我整理成了一张表优化器核心改进适用场景关键参数AdamW修正权重衰减实现主流Transformer、大模型预训练与微调weight_decay建议0.01~0.1LAMB按层缩放自适应学习率超大Batch训练NLP为主需配合warmup峰值lr适度调低LARS按层缩放学习率超大Batch视觉训练配合SGD动量lr可偏高SAM寻找平坦极小点小样本、分布外泛化要求高的场景rho通常取0.05~0.23. 学习率、warmup与梯度裁剪实操参数是怎么调出来的3.1 学习率策略不是从1e-3开始就万事大吉进入实战环节先聊聊学习率。很多框架默认给1e-2或1e-3但这是给常规量级模型用的不代表你的任务就该用这个值。我调学习率的方式很简单先找一个基准值然后做一次“学习率扫描”。具体操作是让模型在很小的数据集比如十分之一的训练数据上跑几十步每N步把学习率乘以一个固定系数比如1.5观察Loss变化。Loss开始下降时的学习率可以作为底Loss出现震荡或者上升时对应的学习率可以当顶实际训练时在这个区间里取中间值偏小一点。举个例子扫描中发现学习率在1e-4左右Loss开始有效下降到1e-2附近Loss震荡那我就会把初始学习率定在5e-4到1e-3这个区间再结合warmup和decay进一步微调。这个办法虽然原始但比盲目相信默认值靠谱得多。3.2 warmup为什么能保住训练初期的稳定性Warmup是另一个容易被忽略的细节。它指的是训练最开始的一小段步数内学习率从0或者一个很小的值线性爬升到设定的峰值。为什么要这么做因为模型参数刚初始化时梯度方向噪声极大如果一上来就用大学习率猛冲很容易一头扎进糟糕的损失区域后面再想跳出来就难了。尤其对于Transformer这类深网络warmup几乎是必需的。常见的warmup方式有两种线性warmup和余弦warmup。线性就是学习率随时间等差上升余弦warmup不仅包含上升阶段整个训练周期的学习率按照余弦曲线从峰值降到接近0两者连在一起用非常普遍。现在很多框架都有现成实现比如PyTorch里的get_cosine_schedule_with_warmup。from transformers import get_cosine_schedule_with_warmup optimizer AdamW(model.parameters(), lr5e-5, weight_decay0.01) scheduler get_cosine_schedule_with_warmup( optimizer, num_warmup_steps2000, # 通常占总步数5%~10% num_training_steps40000 )3.3 梯度裁剪防止Loss突然爆掉的最廉价保险丝梯度裁剪Gradient Clipping这招我从一开始调模型就在用因为它成本极低收益却很大。原理一句话如果梯度的范数超过了阈值就把梯度按比例缩小到阈值范围内。它防止的是“梯度爆炸”导致模型参数瞬间被冲坏尤其适合RNN、Transformer和GAN这类结构。实操上PyTorch里最简单可以直接用clip_grad_norm_clip_grad_norm_(model.parameters(), max_norm1.0)这个max_norm取多少经验值在0.5到5之间具体看任务。我在文本生成任务上通常取1.0图像分类取5.0也没太大问题。一个判断标准如果训练时Log里经常出现Loss为NaN或者超大值先不要怀疑代码写错先把梯度裁剪加上看看。很多时候模型“炸掉”的根源就在这里。3.4 一套我常用的基础配置模板直接给出一套经过验证的基础配置模板。假设做的是一次文本分类微调模型是BERT-base训练集两万条参数项推荐值备注优化器AdamW优先于Adam初始学习率3e-5微调场景不宜过高权重衰减0.01与学习率同量级Warmup步数总步数的6%约200步学习率调度余弦退火稳定后期收敛梯度裁剪阈值1.0防止异常梯度Batch Size32或64根据显存调整这套模板我复用在很多任务上不敢说最优但至少能保证基本盘。如果你不想从零开始调先照这份跑再针对自己任务的特点做增量实验会比瞎试高效很多。4. 训练完成之后量化、剪枝与蒸馏另一个“优化器”战场4.1 模型部署时的“优化器”不是训练优化器很多人提到Model-Optimizer只想到训练阶段的优化算法但实战里还有一个更痛的优化环节——模型推理优化。训练好的模型可能几百兆单次推理几十毫秒这在离线实验里无关痛痒部署上线后就是真金白银的服务器成本和用户体验问题。这个方向的优化工具五花八门但核心就三件事量化、剪枝、蒸馏。我自己的排序是先做蒸馏再做剪枝最后根据硬件做量化。原因是蒸馏直接降低模型容量剪枝进一步去掉冗余结构量化则在最后压缩数值精度每一步都对精度的影响可控。4.2 知识蒸馏让小模型继承大模型的能力知识蒸馏的原理不复杂先训练一个性能很好的大模型Teacher然后用它指导一个小模型Student训练。关键点在于Student不仅学大模型的硬标签类别结果还学它的软标签类别概率分布。软标签里包含了类别间的相似度信息比如一张图既像猫又像狐狸概率分布里这种“模糊性”恰恰是知识蒸馏的精华。举例来说图像分类任务里大模型对一张图片输出概率猫0.7、狐狸0.2、狗0.1。如果只看硬标签Student只能学到“这是猫”但通过软标签Student还能学到“猫和狐狸在特征上有相似之处”。这样学出来的Student模型往往比直接从硬标签训练出来的同构小模型效果高出好几个点。温度系数T是这里的关键超参数T越大软标签分布越平滑信息越丰富T太大又会让类别完全平均化、失去区分度。一般T取2到4之间我用得最多的是3。4.3 剪枝与量化实操笔记剪枝分为结构化剪枝和非结构化剪枝。非结构化剪枝把权重矩阵中接近零的元素直接置零稀疏度高但硬件不友好结构化剪枝直接去掉整个通道或滤波器可以配合推理框架做实际加速。做剪枝时我建议渐进式做训练一段、剪掉10%到20%、再微调一段重复这个过程。一次剪太多再集中微调模型的损失面会变得过于剧烈很难恢复。量化方面最常用的是INT8量化。原来32位浮点数变成8位整数模型体积直接缩到四分之一推理速度在支持INT8指令集的硬件上能提升2到4倍。但量化会带来精度损失解决的办法是量化感知训练QAT也就是在训练过程中模拟量化误差让模型自己去适应低精度表示。有一件事我必须提醒量化、剪枝、蒸馏每一步做完都要在验证集上重新测一遍。评估双人影像分割任务时发现剪枝20%后Dice系数掉了3%但经过一轮知识蒸馏补偿后反而涨回原有水平。所以优化的正确姿势是“组合拳”单一手段用到底往往达不到最好的权衡。5. 实测调参笔记与踩坑清单5.1 监控是一切的起点TensorBoard不再是可选调优化器参数最怕的就是两眼一抹黑。我见过太多人训练时不记录任何指标跑完一看结果不行然后开始瞎试参数。正确的做法是从跑第一步开始就把训练Loss、验证Loss、梯度范数、学习率、参数分布全部记录下来。TensorBoard是基础但我现在更喜欢用WandBWeights Biases因为它可以轻松对比多组实验。重点监控四个量训练Loss曲线是否平滑下降、验证Loss是否与训练Loss同步变化、梯度范数是否出现突然尖峰、学习率变化是否符合预期调度曲线。这四个量能解释绝大多数训练问题。梯度范数特别值得关注如果训练过程中梯度范数突然飙升说明参数可能掉进了坏区域需要调低学习率或者增强梯度裁剪如果梯度范数一直很小说明学习率过低模型可能在原地打转。5.2 一套高效的调试策略单参数变量与网格搜索调试优化器参数有一个“大忌”——同时改动两个以上参数。比如把学习率从1e-3改成1e-4又把优化器从Adam换成SGD结果效果变好了你根本无法判断是哪个操作起的作用。所以我的原则是一次只改一个变量要么只换优化器要么只动学习率调度。大规模探索参数时网格搜索虽然老土但有效。我用得比较多的是探索维度常用离散取值优化器类别SGD, AdamW, LAMB, SAM初始学习率[1e-5, 3e-5, 1e-4, 3e-4, 1e-3]权重衰减[0.001, 0.01, 0.1]Warmup比例[0.03, 0.06, 0.1]每组合跑一个较短周期比如总步数的20%用验证集指标筛选出Top 3组合再把这几个组合跑完整周期。这样可以保证在有限算力下得到相对最优的参数组合而不是凭感觉猜。5.3 容易忽视但影响巨大的几个细节最后记录几个我踩过的“非典型”坑每一个都曾让我浪费过一整天时间。第一个是随机种子。对比实验如果不统一随机种子模型初始化不同差异可能直接盖过优化器参数的影响导致结论完全失真。做任何优化器对比实验前务必固定好全局随机种子。第二个是数据顺序的影响。如果每次实验的数据读取顺序不同模型看到的样本顺序就不同尤其对于自适应学习率的优化器梯度历史累积方式会因此改变最终结果也会受到影响。建议对数据集设置固定的Shuffle种子。第三个是框架默认值差异。同样是AdamWPyTorch和TensorFlow对epsilon、权重衰减位置的实现可能不完全一致同样的超参数在不同框架下复现的结果会有细微差别。跨框架复现结果时一定要对照源码确认实现细节不能只看文档描述。第四个是混合精度训练的影响。用混合精度训练时某些优化器在低精度下的数值稳定性会变差需要同步调整epsilon或者梯度裁剪阈值。我在半精度训练Bert时发现默认的Adam epsilon1e-8在半精度下会导致Loss尖峰把这个值调到1e-6后问题消失。这几个细节都不起眼单拎出来任何一个都可能让实验结果大相径庭。我的习惯是把所有环境配置、随机种子、框架版本、数据顺序全部固化到一个配置文件中每次实验从头到尾可复现这样调试优化器参数才有意义不然就是在迷雾里打转。优化器这件事说到底没有银弹。我见过用默认Adam一把好牌打得稀烂的也见过精调SGD把模型训到超越benchmark的。我自己的体会是先把基本功吃透——理解SGD家族和Adam家族各自的脾气再学会如何系统化地调参、记录实验、排除干扰变量最终形成自己对优化策略的直觉。纸上得来终觉浅自己动手跑几轮对比实验比看一百篇文章都有用。如果你正在为模型收敛问题头疼别急着改网络结构先去把你优化器的参数一个个过一遍大概率能找到症结所在。