尧图精选

深度学习优化器选型与调参实战:从SGD到AdamW的避坑指南

🕒 发布时间:2026/10/1 6:17:44 📁 来源:尧图网络
从入行到现在我数不清调过多少次“Model-Optimizer”这个词。刚接触深度学习那会儿我以为它就是个“选优化器的按钮”后来踩了无数坑才意识到真正吃透优化器决定了一个模型能不能收敛、收敛得快不快、最终泛化好不好。很多朋友训练模型loss一乱跳第一反应是改网络结构或者调数据其实八成问题出在你用的优化器配置上。这篇内容我会彻底拆解模型训练中的优化器选型与调参思路从底层原理讲到可以直接上手的参数模板重点覆盖SGD、Adam、AdamW这些主流方案以及我在实际项目中反复验证过的避坑经验。适合正在训练CV、NLP或推荐模型的工程师也适合那些被默认参数坑过、想搞清楚背后逻辑的新手。简单说看完你可以少走我走过的那些弯路。1. 先搞懂优化器在干嘛再谈选型优化器这件事听起来只是一个深度学习框架里的一个参数选项但它直接影响模型能不能学到东西。我遇到过不止一个同事模型精度上不去先怀疑数据增强不够、网络不够深折腾半天最后发现只是因为在微调阶段用了全量从头训练的默认学习率。这种排查思路的问题在于大家普遍把优化器当成“别人已经调好的黑盒”忘记了它其实是在手动控制模型权重更新的每一步。用一个类比来说训练模型就像你站在山顶要下山网络结构的loss曲面是这座山优化器就是你的步伐策略。每一步往哪个方向走、走多远、如何根据坡度调整步幅这些都由优化器决定。如果步幅太大你会跳过山谷直接跑到另一座山顶如果步幅太小下山速度慢到让人怀疑人生。这里的“方向”对应梯度而“步幅”就是学习率优化器负责把这两者结合起来。再往深处说整个优化器家族的发展史本质上是“如何在不过度依赖人工调参的前提下让模型快速且稳定地走到好的极小值”。早期大家用SGD一杆子走到底训练速度慢但结果扎实后来有人发现加上动量能加速越过平坦区域减少波动再后来自适应方法火了因为它们能对每个参数单独调节学习步长解决了稀疏数据下某些参数更新缓慢的问题。理解这条演进线你就不会被五花八门的优化器名字绕晕。那选型到底有没有统一标准我的结论是没有万能药但有清晰的判断框架。如果你训练的是卷积网络做图像分类SGD配动量、加好学习率调度常常能在泛化上胜过Adam如果你处理的是大规模稀疏特征比如搜索、推荐场景Adam或者AdamW基本是标配因为它们对不常出现的特征更友好如果是Transformer结构微调那AdamW几乎成了默认答案。这个初步判断会在后面给出更具体的说明。2. 主流优化器逐一拆解原理、对比与适用场景2.1 SGD与Momentum最朴素但依然能打的方案SGD随机梯度下降的更新规则很简单权重朝着当前梯度的反方向移动移动距离等于学习率乘以梯度大小。听起来很基础但恰恰是这种“没什么花样”的更新方式让它在不少任务上泛化表现特别好。因为其更新过程没有累积过多的历史状态权重寻优更“老实”不容易陷入那种看似训练指标很漂亮、测试却一塌糊涂的困境。不过纯SGD有个明显的毛病就是梯度方向噪声大、收敛慢尤其当loss曲面有狭长山谷时SGD会来回震荡导致训练迟迟不收敛。这时候引入Momentum也就是动量就像给下山的人加了一个惯性如果前几步方向一致步幅会越来越大如果前几步方向老变则会被平均掉一些抖动。Momentum的典型系数是0.9这也是PyTorch和TensorFlow里最常用的默认值。在实际项目中我会在两类场景优先考虑SGDMomentum一是数据量不大、任务比较标准比如在ImageNet这种主流数据集上训练ResNetMomentumSGD配合余弦退火精度经常能压过Adam二是模型微调阶段比如用预训练权重做风格迁移、目标检测等项目这时候如果直接上Adam很容易破坏预训练学到的特征分布SGD的“温和”反而更能保留原有能力。要注意的是用了SGD就得比Adam更重视学习率调度否则收敛速度和稳定性都会很难看。2.2 Adam为什么收敛快又有什么代价Adam在2015年提出后迅速成为默认选择因为它把两个思想结合了起来一个是对梯度做平滑处理类似动量另一个是根据每个参数的历史梯度大小自适应调整学习率。翻译成人话就是频繁更新的参数步子迈小点罕更新甚的参数步子迈大点。这样你就不用费心去为每个特征单独设计学习率尤其适合特征分布很不均匀的稀疏场景。有一类情况我强烈推荐直接用Adam推荐系统、点击率预估或者NLP里词表巨大但每个词出现频次差异极大的任务。在这种任务里如果只用SGD那些低频但重要的特征几乎得不到有效更新而Adam的自适应机制天然解决了这个问题这也是为什么早期的深度学习广告模型大多跑在Adam上。但Adam的代价也让人头疼最典型的问题就是泛化性通常弱于SGD。在一些论文里研究者发现Adam收敛到的最优点往往处于更“尖锐”的极小值区域测试误差比SGD找到的宽平坦极小值更高。还有一个技术细节很多人不知道Adam在训练初期存在严重的偏差因为一阶矩和二阶矩估计是从零开始的虽然算法里加了偏差修正但如果数据量特别少、训练步数非常短偏差修正的效果可能依然不够导致前期loss曲线出现波浪状。2.3 AdamW把权重衰减从“坑”里捞出来说到AdamW这是我在实际工作中使用频率最高的优化器。它和Adam的区别看起来只有一个小改动就是把权重衰减从损失函数的“L2正则项”改为直接作用在权重更新公式中。L2正则和权重衰减在SGD场景下几乎等价但在Adam场景下二者却差异巨大Adam因为自适应学习率会把L2正则产生的梯度“归一化”掉导致正则效果被稀释这也就是为什么用Adam配L2正则总觉得过拟合压不住。AdamW直接绕开这个问题在更新权重时独立减去一个“权重衰减项”不受梯度缩放影响。Transformer、BERT这类预训练模型微调时默认配置几乎都是AdamW配合线性学习率调度和warm-up阶段。我在公司里做文本分类、语义匹配模型时固定用AdamW加权重衰减0.01稳定性和最终效果都比单纯调AdamL2要可靠。2.4 主流优化器对比速览优化器核心思想优点缺点典型场景SGDMomentum梯度方向加惯性泛化好、收敛稳定、内存占用小对学习率敏感、收敛偏慢图像分类、目标检测微调Adam自适应学习率动量平滑收敛快、稀疏特征友好泛化偏弱、显存占用高推荐系统、NLP大词表AdamW解耦权重衰减正则有效、适合大规模预训练需要多调一个权衰参数Transformer、BERT微调LAMB逐层自适应学习率适合超大batch实现复杂、不易捉摸超大规模预训练用这个表作为选型参考就够了。SGD适合你想细细打磨精度、资源有限的项目Adam适合快速验证想法、特征稀疏的数据集AdamW适合一切预训练模型微调和主流业务模型LAMB这种偏小众的工具则更多用于大厂做千亿参数模型训练的场景。实际中你只需要在这几个之间切换基本能覆盖90%以上的训练任务。3. 实操配置学习率、权重衰减和一版能直接用的参数3.1 学习率到底怎么定学习率是整个训练配置里最敏感的一个旋钮。我从一次实际项目中总结出的规律是初始学习率过高时loss第一轮可能就会冲到NaN学习率过低时loss下降得像是蜗牛爬你甚至误以为程序卡住了。常规做法是先从一个大范围里粗粗搜索比如在1e-4到1e-2之间以10倍为步长试几轮观察loss的下降速度再逐步缩小范围。对这个过程很多框架提供了学习率查找器工具原理是让模型从一个很小的学习率开始每个迭代逐步增大学习率同时记录loss曲线。曲线呈现“下降后反弹”的那段通常就是合适学习率所在的区间。我一般会在正式训练前花十分钟跑一遍LR Finder找到loss下降最陡峭且没有震荡的区间选中间偏保守的值。不同模型结构的学习率经验值差别很大。图像分类的ResNet系列我从SGD 0.1起步配合batch size缩放Adam则常见1e-3。Transformer类的模型初始学习率建议在1e-4附近太大很容易发散太小则训练效率太低。做迁移微调时会把预训练网络主干的学习率设为基础值的0.1倍新加的分类头则用完整学习率这样既保留预训练知识又让新增部分快速适应。3.2 权重衰减和梯度裁剪到底该不该开权重衰减这个参数很多人直接忽略。我早期也这么干后来发现过拟合会悄悄发生。现在我的习惯是用AdamW就设置weight_decay为0.01到0.1之间做NLP微调喜欢用0.01做视觉任务喜欢用0.05这个区间覆盖了大部分项目。如果模型本身就是小规模、数据量也没有很紧张权重衰减设太大反而会压制模型表达能力导致训练不足。梯度裁剪是另一个容易被忽略却经常救命的开关。当loss突然变为NaN或者训练曲线出现剧烈波动多半是梯度爆炸了。解决方式很简单在优化器更新前对梯度做截断通常设置max_norm在1.0到5.0之间。Transformer训练里我把max_norm设置为1.0几乎能完全避免梯度爆炸而某些CNN任务里设置5.0比较合适裁剪太狠会拖慢收敛。3.3 一份可以直接套用的训练配置思路以我用PyTorch训练一个文本分类模型为例优化器相关代码如下这个配置基本可以直接搬到类似任务上from torch.optim import AdamW from torch.optim.lr_scheduler import LinearLR, SequentialLR optimizer AdamW(model.parameters(), lr2e-5, weight_decay0.01) scheduler SequentialLR( optimizer, schedulers[ LinearLR(optimizer, start_factor0.1, total_iters500), LinearLR(optimizer, start_factor1.0, total_iters3000) ], milestones[500] )这里的思路是这样的先通过500步线性warmup把learning rate从2e-6缓慢拉升到2e-5这个阶段给模型一个“热启动”避免一上来就用大学习率冲乱预训练权重之后保持2e-5训练总训练步数控制在一两万步内。经验是预训练模型微调的常规学习率区间就在1e-5到5e-5之间超过这个区间你会看到训练集指标涨得飞快但验证集从第一个epoch开始就停滞不动那基本就是学习率太大破坏特征了。如果做图像分类我常用的替代方案是MomentumSGD加CosineAnnealingLR初始学习率0.01或0.05batch size调整时按比例同步调整。比如batch size从64翻到128学习率也翻倍这是线性缩放法则的基本应用。你不必机械照搬但要理解这个缩放背后的逻辑是保证每次更新梯度的分布不会因为batch size变化而产生巨大偏移。3.4 训练曲线怎么看配置写好后训练过程中的监控同样重要。我最关注三个信号训练loss曲线、验证集指标、以及梯度统计量。训练loss如果一路平滑下降中途没有反弹说明学习和学习率调度基本正常如果loss早就降下去了但验证集指标不涨那要小心过拟合这是权重衰减或数据增强没跟上的信号梯度统计量则可以通过日志记录一下全局梯度范数如果这个数值突然比前几个step大一个数量级那说明模型进入了一个非常陡峭的区域最好把学习率减半。还有一个容易被忽略的点优化器状态也会占用显存。Adam需要额外保存一阶矩和二阶矩这两项加起来的显存开销比SGD大不少。如果你在有限的显卡上训练大模型显存紧张时可以考虑切换到SGDMomentum虽然训练时间变长但吞吐量和显存占用都有改善。混合精度训练则可以把优化器状态显存进一步压缩是我在大模型实验中的默认选择。4. 常见问题排查loss不降、震荡、显存爆掉4.1 训练loss不下降时先查这些地方loss不来下去“这件事我排第一的反应永远是先看学习率。有一次训练一个语义分割模型连续三个epoch loss纹丝不动我反复查数据处理最后发现学习率被设成了1e-7等于模型每一轮都在“原地踏步”。把学习率提到1e-4之后loss立刻活动起来。所以遇到loss不降第一步就是用LR Finder验证当前的学习率是否处在合理区间。第二步看优化器选型。如果用SGD配默认初始学习率0.001训练BERT几乎肯定不收敛或收敛极慢因为结构差异太大。第三步检查权重衰减过大的weight_decay会让模型参数被压缩得太厉害连学习的空间都变得很小。第四步才去看网络结构、数据标签这些外围因素。这个排查顺序帮我节省了大量时间。4.2 loss震荡剧烈、指标忽高忽低训练曲线像心电图一样上下乱跳常见原因有好几个。最直接的是学习率太大。你可以降低学习率到原来的一半或者四分之一观察曲线是否变得平滑。其次batch size太小也会导致梯度噪声大让loss曲线起伏剧烈。我一般调整batch size时保持一个原则扩大batch size后适当提高学习率缩小batch size时必须同步降低学习率否则两者之间的不平衡就会表现为震荡。warmup缺失也是NLP任务里常见的震荡原因。尤其是大规模预训练模型加载权重后第一波更新就顶着大学习率对模型扰动很大。我的惯例是加入总训练步数5%到10%的warmup阶段曲线会平稳很多。如果做完这些优化器层面的调整后曲线还是像疯马一样再去检查数据预处理和label噪声也不迟。4.3 优化器选型对显存和训练速度的影响显存优化这件事优化器的影响经常被低估。以BERT-base为例参数量约1.1亿如果用fp32训练光参数就是440MB左右Adam的二阶矩和动量各占一份同尺寸的状态合计可能吃掉超过1GB显存。对比之下SGDMomentum只需要保存一阶动量显存占用接近Adam的一半。这也是我有时候在4GB老显卡上调模型会特意从AdamW退回到SGD的原因。混合精度AMP也是省显存利器它把某些张量压到fp16计算同时保留一份fp32的权重副本。这只为优化器状态增加了少量开销换来的却是显存占用成倍下降训练速度也可能翻倍。我在训练BERT类模型时默认开启AMP并把loss scaling交给框架自动处理几乎不会遇到精度问题。4.4 常见问题速查表现象可能原因优先排查方向loss完全不动学习率过小、梯度计算有误用LR Finder测学习率区间loss前期降很快、验证集不涨学习率过大、正则不够降低学习率并增加权重衰减loss曲线剧烈震荡学习率大、batch小、缺warmup学习率减半并加大batch训练到中途出现NaN梯度爆炸、学习率过高开启梯度裁剪并降低学习率显存溢出模型优化器状态超限切换SGD或启用混合精度相同代码多次训练结果差异大随机种子未固定锁定全局随机种子并保持数据加载可复现这张表是我排查问题的第一份参考。多数情况下顺着“学习率—batch size—warmup—权重衰减”这条路检查问题通常都能被定位。5. 跳出训练视角Model-Optimizer也可以是推理优化说完了训练阶段我想再聊一个关于这个标题的延伸。Model-Optimizer这个词可以有另一层含义就是模型部署阶段的压缩与推理加速。训练时我们称优化器为optimizer部署时我们同样做“优化”模型的量化、剪枝、蒸馏其实都是某种意义上的model optimizer只不过优化的对象从loss变成了模型体积和延迟。在工业界一个模型从训练到上线通常会走两遍优化第一遍训练阶段用AdamW等优化器把模型精度练出来第二遍部署阶段通过INT8量化把权重从fp32压缩到两个或四个字节再把权重剪掉一部分、把计算图融合一层让模型在CPU或边缘设备上跑得更快。这两个阶段的目的截然不同但都叫“优化”也都需要你理解底层原理。这也是我为什么建议不要把优化器理解成“一个调参按钮”。训练时的优化器控制的是权重的寻优过程而部署优化控制的是计算效率。两者一个管效果、一个管性能组合起来才是完整的Model-Optimizer概念。有些团队对牌照很追求训练集精度却忽略部署时的压缩损失结果模型上线后延迟不达标或者精度回退很明显原因就是没有把两个优化阶段同时设计。6. 几个我用过觉得很实的小经验最后分享一些未必写在文档里、但真实影响过我工作效率的习惯。第一固定随机种子。很多模型训练不稳定的问题其实不是优化器造成的而是随机种子没有固定导致每次实验结果都不一样。在你判断优化器好坏之前先把所有随机源锁住包括Python、NumPy、PyTorch还有数据加载器的shuffle这样才能确定收敛曲线差异是优化器带来的。第二先在小数据集上做优化器对比。换优化器这种决策不需要一开始就跑完整数据集。我会从训练集里随机取5%的数据用相同的网络结构和同样的训练步数跑几个候选优化器看谁在同样轮数内能把loss降得更低、更平稳。这个实验几分钟就能出结果比起直接赌某个优化器要稳妥得多。第三理解“默认参数”不等于“最适参数”。框架提供的默认学习率或权重衰减大多来自论文在特定数据集上的经验值你可以用它跑通流程但不该指望它一定适合你自己的数据。我习惯在正式训练前把优化器相关参数当成超参写一个简单的调参实验哪怕只搜索10个组合也比死磕默认值强。第四训练日志里加梯度范数。我在日志中加入global grad norm以及每个参数组的平均梯度范数几行代码而已但能快速定位网络哪一层的梯度异常。比如发现最后一层梯度普遍比其他层大几十倍说明分类头的学习率设置不合适把它单独降下来问题就能解决。Model-Optimizer这个名字看着简单背后其实覆盖了从训练原理到工程部署的一整条链路。我的个人体会是绝大多数训练不顺的模型问题都出在“优化器与学习率组合跟任务不匹配”上而不是模型结构本身有多差。只要你愿意花一两个小时做几组对比实验认真读一读loss曲线和梯度曲线很多看起来诡异的现象都会有答案。希望这篇内容能帮你少踩几次我当年踩过的坑。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →