模型优化器选型与调参实战:从SGD到AdamW的稳定训练指南

发布时间:2026/9/30 8:16:40
模型优化器选型与调参实战:从SGD到AdamW的稳定训练指南 模型训练跑得不好十次里有八次问题出在优化器上。损失不降、梯度爆炸、收敛到奇怪局部最优换数据集就失效——这些现象背后往往就是优化器选型或者超参数配置出了问题。我这些年做过图像分类、目标检测、语义分割也调过几版Transformer和生成模型每次把“Model-Optimizer”这一层参数理顺之后训练过程才算是真正稳下来。这篇东西不聊大道理纯粹是我实际调模型过程中的经验记录优化器该怎么理解、怎么选、怎么配参数以及踩过的那些坑。这篇文章适合正在训练深度学习模型、但经常被损失曲线折腾得头疼的人看。无论你是刚入门的新手还是已经在用PyTorch/TensorFlow做实验的工程师只要想把“训练过程变稳、收敛速度变快”这件事搞清楚下面这些内容都是可以直接套用的。我会把优化器背后的原理用大白话讲明白也会给出可直接抄走的参数配置方案。1. 先把模型优化这件事想清楚1.1 “Model-Optimizer”到底是什么Model-Optimizer字面意思是“模型优化器”。在深度学习训练流程里它负责回答一个核心问题梯度算出来了接下来参数往哪个方向更新、每一步走多远。举个生活化的例子你站在山顶要摸黑下山手里只有一个手电筒能照见脚下一小块地方。优化器就是你的下山策略每一步该朝哪个方向迈、步子迈多大、中途踩滑了怎么调整、连续一段路都平缓的时候是不是该大胆点。方向感太差你会绕圈步子太大你会滚下去步子太小你走到天亮也到不了山脚。Model-Optimizer干的就是这件事。放到具体训练流程里它的位置在反向传播之后、参数更新之前。网络前向算损失反向算梯度然后优化器拿出当前梯度、历史梯度、学习率这些信息算出一组更新量加到模型参数上。一轮迭代就完成了。整个过程简单说就几行代码但这一小步的算法设计直接决定了你的模型能不能收敛、收敛多快、收敛到什么质量。1.2 为什么选对优化器比调网络结构还重要我对这个问题的体会特别深。有一段时间我复现一篇论文里的图像分类模型网络结构、数据增强、学习率schedule都对得上但训练结果始终比论文低两个点。后来仔细对比才发现论文用的优化器是AdamW我用的还是最朴素的SGD加momentum。就这一个差异最后准确率差了将近两个点。原因在于优化器不仅仅影响收敛速度还影响你最终落在损失曲面上的位置。不同优化器对梯度噪声的处理方式不同有的偏向找到平坦极小值flat minima有的容易钻进尖锐极小值sharp minima。平坦极小值的泛化能力通常更好测试集表现也更稳。这是很多人在调模型时容易忽略的一点——你以为自己在调参实际上是在选一条完全不同的下山路径。还有一个现实问题计算资源有限训练时间有限。SGD收敛可能要跑一百多个epochAdam可能四十个epoch就到位了。在这个维度上优化器的选择直接决定了你一天能迭代多少个实验版本。所以我说想清楚Model-Optimizer这层比在网上找一个“神奇网络结构”要实在得多。1.3 三种主优化器派系SGD系、自适应系、解耦系业界用得最多的优化器归纳下来主要是三个派系。第一个是SGD系包括最基础的SGD、带momentum的SGD、带Nesterov动量的SGD。特点是简单、可靠、可解释在CV领域尤其是图像分类、检测、分割任务中表现一直很稳。缺点是超参数敏感学习率要调得准收敛速度相对慢。第二个是自适应系代表是Adam、RMSprop、AdaGrad。核心思想是给每个参数分配不同的学习率哪个参数梯度大就少更新点梯度小就多更新点。这个特性让Adam在NLP、Transformer、扩散模型这些任务里非常好用训练初期几乎不需要怎么调学习率就能稳定跑起来。缺点是泛化性有时候不如SGD而且Adam的权重衰减实现方式在早期版本里有问题这直接催生了第三个派系。第三个是解耦系最典型的代表是AdamW。它把权重衰减从梯度更新中解耦出来单独处理解决了Adam里L2正则和自适应学习率相互作用导致的泛化问题。我在实际项目中尤其是Transformer架构和预训练模型微调场景基本上无脑选AdamW。它既保留了Adam的稳定性和速度又改善了最终的泛化效果。这三派并不是谁完全替代谁更多是“看场景下菜”。下面我会展开讲每个派系的参数细节和适用场景。2. 优化器核心细节拆解参数背后是什么逻辑2.1 学习率所有优化器都绕不开的“步子”学习率是优化器最重要的超参数没有之一。它决定每一步参数更新的幅度。学习率设得太大损失函数可能在最小值附近来回震荡甚至直接发散设得太小训练过程慢得让人怀疑人生。深度学习领域有一个经验法则学习率通常从0.1、0.01或0.001这个量级开始试。SGD在CIFAR和ImageNet这类数据集上常用0.1Adam系常用0.001。但这不是绝对的需要结合batch size一起看。我常用的经验是batch size翻倍学习率也可以大致翻倍因为梯度噪声变小了每一步可以更自信一点。实际操作中我很少只设一个固定学习率。更常见的做法是配合学习率调度器scheduler比如余弦退火CosineAnnealing、StepLR、ReduceLROnPlateau。余弦退火是我个人最常用的因为它不需要太多人工干预训练后期自动把学习率降下来帮助模型在极小值附近精细搜索。2.2 动量让下降过程有“惯性”动量的提出很有意思它解决的是优化过程中的振荡问题。想象一下你下山的时候如果每一步都完全根据当前坡度决定方向遇到一个坑就容易左右横跳。动量相当于给这个过程加了惯性你不仅看当前这一步的坡度还要考虑之前几步的运动方向。带momentum的SGD更新公式里一般有个系数 \( \beta \)常见取值0.9。这个0.9的含义是当前更新方向有90%来自历史累积方向只有10%来自当前梯度。历史方向包含了过去的运动趋势所以在山谷状的地形中动量能帮你沿着谷底快速前进而不是在两侧来回震荡。实测下来动量对SGD的提升非常大。很多人在CV任务里说“SGD好用”实际上用了momentum的SGD。Nesterov动量则是动量的改进版它先按照历史方向“预跳一步”再计算那个位置的梯度。听起来有点绕但实战中Nesterov在一些任务上收敛更稳尤其在批量大小较大时。2.3 自适应学习率Adam为什么效果好Adam的全称是Adaptive Moment Estimation自适应矩估计。它的核心思想是维护两个状态梯度的指数移动平均一阶矩以及梯度平方的指数移动平均二阶矩。一阶矩相当于动量二阶矩相当于给每个参数动态调整学习率的依据。通俗解释就是如果一个参数的梯度长期很小那Adam会自动给它放大更新步长如果某个参数梯度忽大忽小Adam就给它缩小更新步长。这让Adam在稀疏梯度场景下表现特别好比如NLP任务里词嵌入矩阵的梯度分布通常很不均匀Adam能照顾到那些低频更新的参数。Adam还有两个重要的超参数\( \beta_1 \)和\( \beta_2 \)默认值分别是0.9和0.999。\( \beta_1 \)控制一阶矩的衰减速度\( \beta_2 \)控制二阶矩的衰减速度。默认值在绝大多数任务上表现都很好我一般很少动它们除非遇到训练不稳定的情况才考虑把\( \beta_2 \)从0.999调大到0.9999让学习率变化更平缓。还有一个很重要的细节是bias correction偏置校正。Adam在训练初期的移动平均估计偏差较大因为一开始累积量太少所以算法会做一个修正。PyTorch和TensorFlow的实现里都默认做了这步但如果你自己在TensorFlow的早期版本自定义优化器这块就很容易漏掉漏掉的话前期训练会明显不稳。2.4 权重衰减与AdamW一个容易踩坑的细节权重衰减weight decay本质上就是给大权重施加惩罚防止过拟合。传统SGD里权重衰减和L2正则化是等价的都是把参数往零的方向拉一点点。但Adam出现后事情变得复杂了。Adam的更新方式是每个参数除以它自己的梯度累积量这导致L2正则项的梯度也被“自适应”调整了实际效果和预想的不一致。简单说在Adam里加L2正则并不等于做权重衰减。AdamW的解决办法是把权重衰减从梯度更新过程中分离出来直接在参数更新时按固定比例衰减权重不参与自适应学习率计算。这个看似微小的改动对Transformer和预训练模型的泛化效果影响很大。我在HuggingFace的transformers库源码里看到几乎所有预训练模型的训练脚本默认优化器都是AdamW这就是原因所在。实操里AdamW的权重衰减系数一般取0.01到0.1之间。我惯用的值是0.05配合余弦退火学习率在多个数据集上表现都不错。如果你要在PyTorch里用AdamW直接torch.optim.AdamW即可不要用手动实现因为一些细节处理很微妙手动写容易出错。3. 实操过程从零配置一套可复用的优化方案3.1 工具选型我为什么首选PyTorch在做模型训练这件事上我目前的主力框架是PyTorch。它提供的torch.optim模块基本覆盖了所有主流优化器调用方式统一便于快速切换。TensorFlow也有Keras的optimizers模块但我觉得PyTorch在自定义优化器、控制参数分组、钩子函数方面更灵活一些。举个例子不同层用不同学习率是一种常见操作预训练骨干网络用小学习率新加的预测头用大学习率。PyTorch的param_groups机制可以很方便地实现这种分层配置这是我经常用的功能。TensorFlow也能做但代码写起来没那么直接。另外PyTorch 2.0之后引入了torch.compile在训练时可以进一步加速。不过要注意一点如果有自定义优化器需要确保优化器和编译后的模型兼容。我的经验是先把优化器调整稳妥了再考虑编译加速否则排错会很痛苦。3.2 一套标准配置CV图像分类任务的优化器方案我在这里给出一套我经常用的、可复现的CV分类任务优化方案。这个配置在CIFAR-10、CIFAR-100以及小型自定义数据集上都验证过效果稳定。优化器选SGDmomentum0.9weight_decay5e-4。初始学习率0.1配合余弦退火调度器训练epoch数设为200。batch size选128。如果显存紧张降到64也可以但学习率也要对应调整比如从0.1降到0.05左右。有人会问为什么这里不用Adam而是用SGD因为CV分类任务里的输入分布相对稳定SGD配合momentum在这些任务上收敛出来的模型泛化性往往更好尤其在测试集上的表现更稳。Adam的优势在NLP和生成式任务里更明显那里的梯度分布更不均匀。代码层面大概是这样的import torch import torchvision model torchvision.models.resnet18(num_classes10) optimizer torch.optim.SGD( model.parameters(), lr0.1, momentum0.9, weight_decay5e-4, nesterovTrue ) scheduler torch.optim.lr_scheduler.CosineAnnealingLR( optimizer, T_max200, eta_min1e-5 )nesterovTrue是我个人习惯加上的它通常不会带来明显坏处有时还能让收敛更稳。不过也有实验表明Nesterov和标准momentum差别不大具体用哪个可以试了再定。3.3 另一套标准配置NLP预训练模型微调方案如果做BERT、GPT这类Transformer模型的微调我的套路完全不一样。优化器直接选AdamW学习率从2e-5到5e-5之间起步。权重衰减设0.01配合线性预热warmup加线性衰减的调度器。预热这个操作很重要。Transformer模型在前几个step时参数还没稳定梯度方向噪声很大。如果一开始就用较大的学习率很容易让模型跳到不理想的区域甚至发散。所以前几百步先用很小或者线性增长的学习率“热热身”等训练稳定了再加到设定值。HuggingFace的Trainer里内置了这个功能warmup_ratio一般设0.05到0.1。我自己在写原生PyTorch训练循环时会手动实现这个调度逻辑下面给个示例from torch.optim import AdamW from torch.optim.lr_scheduler import LambdaLR def lr_lambda(current_step): num_warmup_steps 500 num_total_steps 10000 if current_step num_warmup_steps: return float(current_step) / max(1.0, num_warmup_steps) progress float(current_step - num_warmup_steps) / max(1.0, num_total_steps - num_warmup_steps) return max(0.0, 1.0 - progress) optimizer AdamW(model.parameters(), lr5e-5, weight_decay0.01) scheduler LambdaLR(optimizer, lr_lambda)这个配置我在很多NLU任务里试过效果都很稳。如果你做生成任务、对话模型也可以在此基础上把梯度裁剪值设成1.0防止长序列训练时梯度爆炸。3.4 混合精度训练时优化器的特殊处理现在训练大模型基本离不开混合精度AMP。PyTorch的torch.cuda.amp提供自动混合精度功能优化器处理上有一个重点梯度缩放Gradient Scaling。AMP在反向传播时会把损失放大防止梯度过小被精度截断然后在优化器更新前把梯度缩回到正常范围。如果你自己写训练循环一定要记得在optimizer.step()之前调用scaler.scale(loss)然后用scaler.step(optimizer)最后scaler.update()。我自己曾吃过这个亏最初写AMP训练循环时漏掉了梯度缩放结果训练损失一直在一个高位震荡降不下去。后来对比官方示例才找到问题。这个坑在网上也非常常见所以特别提醒一句。scaler torch.cuda.amp.GradScaler() with torch.cuda.amp.autocast(): output model(input) loss criterion(output, target) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() optimizer.zero_grad()这个模式已经很成熟建议直接用不要自己去改顺序。4. 常见问题与排查技巧实录4.1 损失不下降多半是学习率和初始化的问题模型训练跑起来loss完全不降这时先别急着怀疑优化器。首先检查输入数据是否归一化、标签是否正确、网络是否进入训练模式。这些都排除了再回头看优化器配置。学习率太小是最常见的原因loss会下降得极其缓慢你看着像没降实际上在水平移动。解决办法是先用一个小实验把学习率临时调大一两个数量级比如从0.001调到0.01或0.1看看loss是否有明显下降趋势。有下降趋势再调回合适范围。还有一种情况网络输出的数值范围不对比如超过合理区间。排查方法是看第一轮训练的loss值如果非常大比如比随机猜测的loss还高好几个数量级那可能是权重初始化有问题或者模型结构写错了。这时候要先解决网络本身的问题再谈优化器。4.2 损失震荡剧烈学习率大了或者batch size小了loss波动厉害但整体有下降趋势这种情况常见于batch size偏小、学习率偏大。梯度噪声大每一步的方向都不太稳定loss就会出现震荡。我的排查思路是先把学习率降一个数量级观察波动是否减小。如果波动明显减小说明学习率确实偏大。再把batch size适当增大降低梯度方差训练会更平滑。如果震荡始终无法消除考虑提高Adam的\( \beta_2 \)让二阶矩估计更平滑学习率变化不那么剧烈。我曾在某个Transformer训练任务中把\( \beta_2 \)从0.999改成0.9999震荡问题就缓解了很多代价是收敛变慢了一点但最终效果更稳。4.3 梯度爆炸裁剪和权重衰减不是一回事梯度爆炸是训练RNN、Transformer时常见的问题表现为loss突然飙到特别大甚至出现NaN。原因通常是梯度值累积过大参数一步更新太猛直接把权重推到了数值溢出区。最直接的解决手段是梯度裁剪gradient clipping。PyTorch里一行代码torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)。我一般把这个放在optimizer.step()之前。max_norm取值常见是1.0或0.5具体可以通过实验观察梯度范数分布来决定。需要注意权重衰减是为了抑制过拟合它的力度很小无法防止梯度爆炸。一些人误以为加大了weight_decay就能解决爆炸问题实际上效果很有限。梯度爆炸要靠裁剪、降低学习率、合理初始化来应对。4.4 收敛结果差试试换一个优化器派系还有一种情况loss降得很快但验证集指标就是上不去。这有点诡异。loss低但表现差通常说明模型过拟合或者收敛到了不那么好的局部极小值。如果已经在做数据增强、加了正则指标还是不行那可以考虑更换优化器派系。我之前遇到过实例分割任务里AdamW收敛快但最终mAP比SGDmomentum低了0.5个点左右。后来查了很多讨论结论是SGD类优化器更容易走向平坦极小值泛化性更好。于是我把优化器换回SGD调好学习率和momentummAP确实上来了。对于这种情况最实用的做法是一个项目里同时跑两组对比实验一组用AdamW一组用SGDmomentum最后比较验证集表现。两个优化器的学习率量级不一样起步可以分别从1e-4和0.01这两个值开始试。5. 优化器调试的独家经验5.1 可视化梯度范数比盯着loss更早发现问题我调试优化器时习惯在每次迭代末尾打印梯度范数grad norm和参数更新范数update norm。梯度范数能直观反映训练是否稳定如果梯度范数突然放大几个数量级基本可以判断要爆炸了如果梯度范数长期趋近于0说明梯度消失网络可能学不到东西。代码很简单total_norm 0.0 for p in model.parameters(): if p.grad is not None: param_norm p.grad.detach().data.norm(2) total_norm param_norm.item() ** 2 total_norm total_norm ** 0.5 print(fgrad norm: {total_norm:.4f})我一般每隔固定步数打印一次观察梯度范数的分布。正常训练中梯度范数会随着训练推进逐渐下降偶尔有小的波动。如果出现尖峰式暴涨就要提前干预而不是等loss发散了才去查原因。5.2 学习率热身和退火是训练稳定的两大法宝学习率热身不只是Transformer的专利。我在CV任务中也发现前几个epoch用较小的学习率让模型先“站稳”之后再把学习率提到目标值训练会更稳定尤其是数据集比较小或者batch size比较大的时候。热身阶段通常占整个训练step数的2%到5%不用太长。热身后学习率回到设定值再配合余弦退火或线性退火逐步降下去。退火阶段对最终收敛质量的影响很大后期学习率如果太高模型会在极小值附近来回震荡无法精确定位。所以我的习惯是不管用什么优化器都配上热身加退火的调度器。这一步对最终效果的提升往往比换一个更复杂的优化器还要明显。5.3 不要迷信任何“万能优化器”网上经常能看到“换了XXX优化器训练速度翻倍”之类的帖子。我的态度是可以测试但不要无脑换。优化器的选择本质上是经验性工程决策同一个优化器在不同任务上表现差异很大不存在绝对的万能方案。我自己的做法是固定两到三个常用配置AdamW用于Transformer和生成式任务SGDmomentum用于CV分类检测分割必要时再加上LAMB或Lion针对超大batch做尝试。新项目上来先用这套熟悉的配置打个底跑通流程、确认没有bug之后再考虑针对性地换优化器调到更好的结果。6. 工具箱式总结Model-Optimizer速查表6.1 不同任务场景的优化器推荐我根据这些年的项目经验整理了一个选型速查表方便大家直接对号入座。任务场景推荐优化器常用学习率关键配置备注CV图像分类SGD momentum0.1momentum0.9nesterovTrue测试集泛化通常最好CV检测/分割SGD momentum 或 AdamW0.01 / 1e-4视backbone和head分层设置检测任务两个都可以跑NLP分类/匹配AdamW2e-5 到 5e-5warmup_ratio0.05~0.1Transformer标配生成式模型/扩散模型AdamW1e-4 到 3e-4有时需要提高beta2训练稳定性优先大规模预训练LAMB / LARS1e-3 到 5e-3配合大batch一般只有做预训练才需要考虑强化学习Adam3e-4配合grad clipRL训练极不稳定clip很重要这张表不是铁律但作为起步点很靠谱。每个项目具体数值还是要靠实验确认。6.2 超参数调节顺序调优化器相关超参数时我推荐按照下面的顺序来这样可以少走很多弯路先确定优化器类型是SGD系还是Adam系设置一个中规中矩的学习率SGD用0.1Adam用1e-3或1e-4跑少量迭代确认loss在下降训练流程没有bug开始调学习率用几个量级做快速对比固定学习率后再调动量或者weight decay最后加上学习率调度器和热身继续跑完整训练。这个顺序的核心思想是先用最简单配置跑通再逐步增加复杂度。很多人调参一上来就同时改五六个参数出了问题根本不知道是哪个改动导致的。6.3 推荐的工具和框架除了PyTorch自带的torch.optim我还常用几个周边工具。Weights Biases做训练曲线记录和超参数对比非常好用可以直接把不同优化器的loss曲线画在一起。tensorboard也可以内置在PyTorch里不用额外部署。如果涉及超参数搜索可以试试Optuna。它支持并行搜索我一般用它来找学习率和weight_decay的最佳范围。虽然大规模深度学习训练里每搜一次都挺贵但用小数据集跑几个epoch的快速实验性价比很高。还有一点如果是分布式训练优化器的状态同步机制要特别注意。比如使用PyTorch DDP时优化器状态是每个进程独立维护的这时候最好在model.parameters()之外再单独保存optimizer的state_dict。我曾因为忽略了这一步在分布式恢复训练时优化器状态丢失导致后面训练效果异常。7. 从踩坑中积累的实操原则刚开始做深度学习那会儿我也特别喜欢追求新优化器、新论文里的“SOTA配置”。后来发现训练效果好的项目往往不是用了多高级的优化器而是把基础配置调得太稳了数据没问题学习率合适调度器合理优化器状态管理正确。一个印象很深的经历是某个医疗影像分割项目模型结构比较深数据量不大。起初我一直用AdamW结果损失降是降但分割的边界总是不太干净。后来换回SGDmomentum配好学习率和weight_decay分割效果肉眼可见地变好了。这个项目之后我养成了一个习惯每次换优化器之前先想清楚自己的数据特点、任务类型、训练规模而不是盲目追新。另外优化器调试过程中记录实验日志非常重要。我的做法是每一次修改配置都生成独立的实验标签记录下优化器类型、学习率、动量、weight_decay、调度器参数、batch size、最终指标。积累到一定程度你就能形成一套属于自己的“任务-配置”映射表新项目上来直接查表起步效率会高很多。把Model-Optimizer这一层搞明白训练模型这件事就成功了一半。它不是最出风头的模块但却是所有训练实验的共同底座。我建议你手里至少备好SGDmomentum和AdamW两套方案理解它们各自擅长什么场景然后用实验日志去验证自己的判断。这套方法论远比我上面给的任何一组具体参数都更有价值。最后再分享一个小技巧如果你不确定自己该用哪个优化器先用AdamW快速验证模型的训练流程和数据pipeline有没有问题一旦确认无误再切到SGD做正式的实验。这两个阶段用不同的优化器效果会好很多。Model-Optimizer说到底不是选“最好”而是选“最合适当前阶段和当前任务”的那一个。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询