
1. 为什么我劝你先别急着换模型而是先搞懂优化器干深度学习这些年我见过太多同学一遇到模型效果不好第一反应就是换网络结构、堆数据、加 trick折腾一圈下来loss 曲线还是跟心电图一样乱跳。说实话很多时候问题压根不在模型本身而在那个天天在跑、却又最容易被忽略的组件——优化器。Model-Optimizer 这个名字听起来像某个具体项目其实它指向的是深度学习训练中最核心的一道工序怎么让模型的参数朝着正确的方向、以合适的速度、稳定地收敛到理想的点。说得更直白一点优化器就是那个决定“模型参数每次该往哪走、走多大步”的引擎。没有它反向传播算出来的梯度再准也只是摆设。这篇文章不是给你堆理论公文的而是想把我自己在实际项目中反复调试优化器的经验、踩过的坑、以及最后沉淀下来的一套可直接上手的优化器实战方案完整地整理出来。不管你是刚入门的初学者还是已经训过几个模型但总觉得效果不稳定的进阶玩家这篇内容应该都能让你少走不少弯路。你会搞清楚Adam、SGD、AdamW 到底该怎么选学习率设多少才算合理warmup 到底起什么作用梯度爆炸、loss 震荡、收敛过慢这些老大难问题又该怎么通过优化器层面逐一击破。先说结论优化器选型和超参配置对你的模型最终效果影响之大往往超出你的想象。下面所有的内容都是我在实际训练中真金白银砸出来的经验希望能帮你省下几个通宵调参的夜晚。2. 优化器选型别只会 Adam选错可能直接毁掉你的模型2.1 从 SGD 到 AdamW四款主流优化器的性格差异每次有人问我“优化器应该用哪个”我第一反应不是直接给答案而是反问他你的任务是什么类型你的数据量大概多少你的模型有多深因为不同优化器的“性格”差别太大了选错代价真的很高。先说说最老牌、但依然能打的 SGD带动量。它就像一位稳健的传统派老师傅每一步都基于当前梯度方向加上历史动量的惯性走得很稳。实践中最稳妥的用法是把 momentum 设成 0.9 左右。用 SGD 的好处是最终收敛点通常更“平缓”泛化能力普遍比自适应类优化器好一些尤其在 CV 分类、检测这类任务上表现很稳。但它的致命缺点也随之而来收敛太慢动不动就要训几百上千个 epoch而且学习率特别敏感设大了震荡设小了半天不动对新手极不友好。Adam 的出现解决了大部分人的痛点。它同时利用了一阶动量梯度的均值和二阶动量梯度的平方均值让每个参数都拥有独立的自适应学习率。这意味着你不用花太多心思在人工调学习率上默认的 0.001即 1e-3在不少任务上都能直接跑起来。但 Adam 也有它自己的“性格缺陷”训练后期学习率被历史梯度累积压得太低容易收敛不到极优点而且它对学习率虽然比 SGD 包容却远比很多人想象中敏感。RMSProp 在 RNN、时间序列这类任务上是老牌强手它通过滑动平均来调节每个参数的步长很擅长处理梯度尺度差异大的场景。但它在 CV 大模型上的泛化表现不如 SGD 稳定用的场景反而越来越窄了。AdamW 是我目前的主力推荐。它的核心改动是在权重衰减weight decay和梯度更新之间做了“解耦”让每一轮都会对权重做一个独立的、均匀的衰减而不是像原版 Adam 那样把 L2 正则混进梯度里。别小看这个改动实践表明 AdamW 能让模型收敛到更平的极小值泛化能力明显优于原版 Adam而且对大 batch 训练、Transformer 类模型格外友好。2.2 我是怎么根据任务类型选择优化器的根据我的实际经验你可以直接照着下面这张“选型表”来做初筛基本不会出大错任务类型首选优化器备选方案适用场景说明图像分类、目标检测CNNSGD MomentumAdamW数据量充足、追求极致精度时SGD 收敛点更稳Transformer、BERT、GPT 系列AdamWAdam必须配合学习率 warmup权重的解耦衰减很关键图像生成GAN、DiffusionAdamWAdam生成任务对稳定性要求高AdamW 更可控强化学习、RNN 时序任务Adam / RMSPropAdamW非平稳目标自适应学习率能救你的命小数据快速实验跑通AdamAdamW先用 Adam 搭基线稳定后再换最优算法大模型预训练 / 大规模并行训练LAMBAdamW超大 batch 场景下 LAMB 更好地平衡了精度和速度这套选型逻辑背后不复杂如果你的训练数据足够多、任务足够“传统”SGD 这种简单优化器反而容易带来更好的泛化如果你的模型又大又深、梯度分布极其不均衡那 Adam 家族的自适应特性才能把训练稳住。我做项目时一般会先用两个优化器各跑几个 epoch 对比一下 loss 下降趋势用数据说话而不是凭感觉赌。2.3 一个真实的对比实验同样模型优化器不同结果天差地别去年我在一个医疗影像分类项目上模型结构用的是 ResNet50数据量大约八万张。一开始我图省事直接上了 Adam学习率设 0.001结果训练集 loss 蹭蹭往下掉看起来一切顺利。结果一到验证集准确率卡在 88% 左右再也上不去换了好几个数据增强方案都没用。后来我冷静下来把优化器换成了 SGD Momentum0.9初始学习率设为 0.01并加了 Cosine 学习率衰减。跑完同样的 epoch 之后验证集准确率直接干到了 92.5%。这个提升在比赛或者发论文里可能就是决定性的。那是不是说 Adam 不好呢也不是。同一个项目我用 AdamW 重新测了一轮把 weight decay 调到 0.05、学习率调成 0.0001最终准确率也能接近 92%。这个对比给我的教训很深优化器不是一个可以随手选完就忘的配置项它需要你像做实验一样去对待去和你的任务本身匹配起来。3. 学习率与超参数真正拉开效果差距的关键角力场3.1 学习率策略从固定值到 Cosine 衰减的进阶之路学习率是优化器里最核心、也最容易翻车的超参数。很多学员最开始习惯从训练到结束都用同一个学习率这在复杂任务上几乎注定失败。原因不难理解训练初期需要大步快跑快速跨过崎岖区域找到大致方向训练后期需要在最优解附近小步慢走精细地收敛到低点。一个僵硬的固定学习率不可能同时满足这两种需求。我目前最推荐的方案是 warmup Cosine Annealing 的阶梯式策略。原理上在训练刚开始时模型参数还是随机的梯度方向往往不准确。如果这时候直接用大学习率很容易让模型在早期就跑偏甚至炸掉。warmup 的作用是让学习率从 0 或一个极小的值线性爬升到预设峰值给模型一个“热身期”让梯度统计量稳定下来然后再进入正常训练节奏。至于 Cosine Annealing则是让学习率在每一个周期内按照余弦函数从峰值平滑地降到接近 0。相比常用的 Step 衰减比如每隔 30 个 epoch 除以 10Cosine 曲线下降得更加平滑、没有突变能够更从容地帮助模型探索和收敛到尖锐的局部极小值之外的平缓区域。这个策略在 ViT、BERT 和很多 Kaggle 比赛方案里都是默认配置确实是简单高效的良心 trick。3.2 关键超参定位learning rate、weight decay、momentum、batch size学习率怎么定初始值这里分享一个我长期使用的经验法则把 batch size 翻倍的同时学习率也可以近似翻倍线性缩放法则。比如你的 baseline 是 batch size 64、学习率 1e-3那么扩到 128 时学习率可以试 2e-3。不过要注意这个法则只在合理范围内成立学习率过大依然会导致收敛不稳定。再来说 weight decay。很多人忽略了它但它对最终泛化性能影响巨大。在 AdamW 中weight decay 是直接施加在权重上的替代了传统 L2 正则。实践中我常用的默认值是 0.01 到 0.05 之间。注意Transformer 类模型通常要比 CNN 模型更喜欢稍大的 weight decay而过大真的会让模型“欠拟合”过小则会让训练后期权重震荡。你可以这样想weight decay 就是给权重加的“阻尼”防止参数为了拟合个别样本而丧失泛化能力。momentum 在 SGD 里的默认值我几乎固定用 0.9它对加快收敛、平滑梯度方向至关重要。而在 Adam 家族里一阶动量系数 beta1 一般保持默认 0.9二阶动量 beta2 在 NLP 任务中从 0.999 调到 0.98 甚至 0.95反而会让训练更稳定因为 beta2 太接近 1 时历史梯度平方累积过重后期更新步长会过小。3.3 参数计算实例从一个 Kaggle 项目看完整配置我拿一个真实的人物属性识别项目做完整演示。模型结构是 EfficientNet-B3图片输入尺寸 256x256batch size 我设定为 64。整体配置我做了以下几步计算和选择第一步根据默认经验把初始学习率设为 0.001。第二步我把总训练 epoch 设为 30并决定前 3 个 epoch 做线性 warmup从 0.0001 线性升到 0.001。第三步剩余 27 个 epoch 采用 Cosine 衰减最终学习率降到 0.00001 以下。第四步weight decay 设为 0.02SGD 的 momentum 设为 0.9。关于 warmup 时长的具体计算我一般是按“总迭代步数的 5% 到 10%”来确定。假设训练集 5 万张图batch size 64一个 epoch 就是约 781 步30 个 epoch 就是 23430 步。那么 warmup 选择 3 个 epoch 约 2343 步正好略少于 10%完全符合经验区间。这套配置最终线上测试 AUC 达到了 0.978相比我最初用默认 Adam 固定学习率的一版提升了整整 2 个百分点左右验证集的表现也更加平滑。4. 高阶优化技巧从“能用”到“高效”的临时和长期方案4.1 混合精度训练与优化器的隐藏联动如果你还在用 FP32 训练大模型那我建议你尽快试试 AMP自动混合精度。它把一部分运算用 FP16 执行从而显存占用直接减半训练速度也能提升一倍以上。但这里有个极其隐蔽的坑如果你不了解优化器和 AMP 的关系很容易把模型训崩。AMP 在反向传播时梯度值是以 FP16 精度参与计算的。如果某个参数的梯度值特别小低于 FP16 能表示的下限那么梯度会被直接“刷新”成 0。优化器一看梯度是 0就不更新这个参数了模型自然怎么训都训不动。所以实际框架里都内置了一个动态损失缩放Dynamic Loss Scaling机制先把 loss 放大一定倍数再反向传播等优化器更新前再缩小回来。这样小梯度就能被保留在 FP16 可表示范围内了。说到优化器和 AMP 的联动我踩过一次印象特别深的坑。有一次我开了 AMP 之后发现训练开始阶段 loss 在正常下降但几十步之后突然变成 NaN一度怀疑是模型出了问题。后来才排查到是优化器状态参数比如 Adam 里的 moment 和 variance没有同步做缩放处理。解决办法也很直接确保优化器参数初始化精度和模型参数一致并且在保存和加载检查点时用 FP32 保存 optimizer 的 state_dict。如果你用的是 PyTorchAMP 要配合 GradScaler 使用并且 step 操作要写在 scaler.scale(loss).backward() 和 scaler.step(optimizer) 这个固定流程里顺序错了必炸。4.2 梯度累积与梯度裁剪在有限显存下实现稳定大 batch经常有同学问我“我显存不够batch size 设小一点不就行了为什么效果就是变差了”这里涉及到一个关键知识点小 batch size 带来的梯度噪声大更新方向不稳定模型的 BN 统计量也很难估计准确。这时候梯度累积就是显存瓶颈下的最佳替代方案。梯度累积的玩法很简单假设你目标 batch size 是 128但显存只支持 32那就连续以 32 的 batch size 跑 4 个 forward/backward期间不让优化器 step只是把梯度累加起来。攒够了 4 个 batch 的梯度再用累加后的梯度去更新一次参数。但我必须提醒你两个关键细节第一loss 要记得除以累积步数例如 4 个 batch 的 loss 相加后除以 4否则梯度的量级会放得过大直接毁掉你的训练曲线第二如果模型里有 BatchNorm 层梯度累积不能完全等价于真正的加大 batch因为 BN 的均值/方差估计仍来自每个小 batch可能引起统计量振荡。还有一个几乎是大模型标配的防爆武器——梯度裁剪Gradient Clipping。我用 max_norm1.0 作为默认值它做的事情是计算整个参数梯度的 L2 范数如果超过阈值就对所有梯度等比例缩放。这样做能防止某个异常 batch 把参数推出安全的参数空间。对于 Transformer 这类模型梯度裁剪基本是必备操作否则训到一半 loss 突然跳个 NaN 是常有的事。4.3 EMA一个让最终模型表现再上一个台阶的免费午餐EMA指数滑动平均是我在做任何“吃着碗里看锅里”的竞赛时必上的一招。它的思路非常优雅训练时我们不只用当前权重去评估而是维护一个额外的“影子权重”它是历史所有权重的指数平均。因为训练后期模型权重比较剧烈地振荡而影子权重相当于给这些振荡做了一次低通滤波往往能得到一个更稳定、更平滑的模型。买一送一的是EMA 写起来极其简单。比如一个 PyTorch 里实现 EMA 的常见流程大致如下ema_weights {} for name, param in model.named_parameters(): if param.requires_grad: ema_weights[name] param.detach().clone() # 每个 optimizer.step() 之后调用 decay 0.999 for name, param in model.named_parameters(): if param.requires_grad: ema_weights[name].mul_(decay).add_(param.detach(), alpha1 - decay) # 评估时把影子权重替换到模型上评估完再换回来这里 decay 值一般设 0.99 到 0.9999 之间。意思是影子权重对近期更新的依赖权重约 0.001 到 0.01既能跟上训练节奏又能过滤掉大部分高频抖动。我在表格识别和图像分割任务上分别加了 EMA成绩普遍能提升零点几个点到一两个点几乎没有成本。唯一的注意点是EMA 只作用于可训练参数BN 的 running_mean/running_var 不参与 EMA而且保存模型时最好把影子权重一并保存方便后续想换回来做实验。4.4 优化器状态压缩大模型训练时的救急手段训练超过一亿参数模型的时候你可能沮丧地发现光是优化器的状态占用的显存就比模型本身还要多。Adam 家族的标准状态由 fp32 的一阶动量momentum和二阶动量variance组成加上模型参数和梯度本身显存压力非常大。我的一台 24GB 显存的卡在训练近亿参数的模型时差点把显存拉爆。好在业界已经沉淀了一套成熟方案优化器状态分片如 ZeRO、混合精度存储和 8-bit 优化器。如果用的是 HuggingFace 生态直接开启 zero_optimization stage 1 就能把优化器状态分片到多卡进一步降低单卡显存单卡场景则可以用 bitsandbytes 加载 8-bit Adam 优化器——它把一阶和二阶动量压缩成 8-bit 整数存储极大地削减显存开销。实测下来这种做法几乎不伤害最终精度而显存节省非常可观。如果你正在被显存不足折磨这是非常值得优先尝试的路径。5. 训练过程的常见问题与排查技巧从现象到根因5.1 训练 Loss 不下降先别怀疑模型八成是优化器在作怪Loss 纹丝不动是训练中最绝望的场景之一。我见过的绝大多数情况都不是模型结构问题而是学习率设置和优化器配置出了问题。排查我建议按下面的顺序来第一条学习率过小可以尝试把学习率放大 10 倍来粗测一下 loss 是否开始下降如果动了再逐步细调到合适值。第二条优化器选型和任务不匹配比如序列任务用了轻率的 SGD梯度方向不稳定自然难收敛。第三条数据预处理有问题特征没有归一化会导致某些维度的梯度幅度远大于其他维度自适应优化器会努力去适配但普通 SGD 就很容易卡住。第四条模型长期没有收敛也可能诊断出初始化和激活函数饱和的问题这时候优化器能做的帮助有限但依旧值得先用精细的学习率扫描一遍。我自己的习惯是遇到 Loss 不下降先第一时间把学习率范围扫描一遍画出不同学习率下的 loss 变化曲线找一个最陡峭、最稳定的区间再回来调整优化器参数。这个过程耗时半小时省下的可能是后面几天的无效训练。5.2 训练早期 Loss 剧烈震荡或飙升如果你刚训了几个 steploss 不是直线下降而是在高位剧烈震荡甚至直接涨上天恭喜你遇到了典型的优化器超参数激进问题。最常见的原因是学习率太大。这就像开车起步就踩死了油门车身猛地窜出去直接冲出赛道。解决办法是先将学习率降一个数量级比如从 1e-3 降到 1e-4观察曲线是否恢复平稳。另一个被我反复强调的原因是 warmup 没设置或太短。我在训练一个多模态模型时最初没有做 warmup前 100 步 loss 直接爆表之后量级也恢复不到正常水平。但加上 2000 步的 warmup 后一切回归正轨。尤其对 Adam 这类有累积二阶动量的优化器冷启动初期梯度方差极高你不给一点缓冲时间它就会把参数推到奇怪的区域去。5.3 训练后期 Loss 下降很慢怎么办训练进行到后半段loss 逐渐趋于平缓这是正常的。但如果平缓得像条水平线而且验证指标一直上不去那就要考虑是不是训练“撞墙”了。这时我常用的手段是降低学习率或者开启余弦退火重启。简单来说就是给训练过程一个“第二次呼吸”通过调低学习率让模型在更小的范围内精细搜索低点。还有一种情况是优化器的 momentum 或 beta2 让更新变“懒”了。这时可以适当调低 beta2从 0.999 降到 0.98 左右这样能更及时地响应新的梯度方向不至于让历史梯度把当前更新的步长压得寸步难行。如果再配合 EMA 做评估往往最后几个百分点就是这样抠出来的。5.4 一个屡试不爽的调优路径先复现基线再单变量迭代最后一个我想认真分享的经验是想让优化器发挥出最佳状态最重要的不是某一个出厂默认值而是你围绕默认值展开的系统化实验循环。我自己的流程已经非常固定第一步在默认配置下把项目跑通一遍确保数据、模型、优化器三者的链路没有问题把这条基线认真记录下来。第二步从学习率开始动刀单独调大或调小数次画出学习率与最终验证指标的关系曲线。第三步在最优学习率附近固定下来再单独调 weight decay 或 beta2一次只动一个变量记录对比表。这个方法论看起来简单却极其有效。很多人失败是因为同时改了学习率、优化器、batch size、数据增强最后出了问题都找不到是哪一步引起的。单变量迭代确实慢一些但在优化器的世界里“快”往往是幻觉“稳”才是真正的快。6. 从 Model-Optimizer 延伸开来训练提速和部署的最后一公里6.1 训练加速从优化器层面也能挤出水来很多人以为训练速度只跟 GPU 算力有关其实优化器的选择和使用方法也能显著影响训练速度。一个明显的地方是SGD 类优化器状态占用极少训练时显存占用更小因此 Batch Normalization 的统计量估计更稳定小批量下的泛化更好训练速度也更快。而 Adam 家族因额外的二阶动量计算单步耗时相对更高所以如果你的任务用 SGD 就能跑出好效果用 SGD 反而是性价比更高的选择。另外打开 cudnn.benchmark 并把输入数据固定为同类尺寸都会带来一定的速度提升这个属于基建层面但配合优化器的“减少 state 访问”逻辑可以从系统底层节省访问显存的时间。遇到大模型可以直接使用 fused optimizer比如 Apex 提供的 FusedAdam这类算子把优化器的多步更新融合进一个 kernel能明显减少 kernel launch 的开销。单步更新虽然只省几毫秒积少成多就是按小时计算的时间节省。6.2 算子融合与 gradient checkpoint 的搭配战术说起显存优化gradient checkpointing 是更彻底的手段。它用额外的重计算来换取显存下降原理是通过在 forward 过程中不保存中间激活反向传播需要时重新算一遍。使用之后显存占用可以降低 50% 以上代价是训练时间增加约 30%。这是一笔典型的“空间换时间”交易和优化器本身没有直接关系但配合起来相当重要。我曾在一个 6GB 显存的显卡上训练 ViT-Base如果不做任何优化batch size 只能设为 1优化器状态占了大量空间。开了 gradient checkpointing 并把 batch size 提到 8同时搭配 AdamW 后模型效果才逐渐显现出来。所以如果你想用 save memory 的方式训练大模型这三件事建议一起做gradient checkpointing 半精度训练 优化器状态压缩。6.3 如何把优化器最终效果延续到推理和部署阶段训练完模型只是第一阶段很多人在部署时发现训练集和验证集指标不错但真实环境下却大打折扣。这正是因为训练时模型只学会了“训练时的数据分布”到了真实世界便不太适应。这个时候除了在训练时依赖优化器寻找更平缓的极小值例如用 AdamW 替代 Adam在推理阶段也要加入一些投入小收益大的操作。最典型的是把 BN 层的统计量重新校准一次俗称“fold BN”。具体做法是使用训练集的一小部分数据重新跑一遍前向统计 BN 层的 running_mean 和 running_var然后把它们固化进模型。这一步能有效消除训练和推理时数据分布不一致带来的偏差。再配合上训练阶段已经通过 EMA 学到的平滑权重部署后的模型会更稳定也能更好地应对实际数据的波动。7. 实操后的总结与建议把优化器变成你自己的工具箱这篇文章从优化器的选型讲到超参设置再从 AMP、梯度累积讲到 EMA 和问题排查信息量确实不小。但这些内容底层贯穿的其实是同一个理念优化器从来不是训练流程里一个once-and-for-all的默认开关而是你可以反复打磨和调优的重要武器。我自己的项目经验是当你把优化器从随手用默认 Adam 升级成有策略、有依据、有实验支撑的配置之后模型的收敛稳定性、最终精度和训练效率都会有肉眼可见的提升。在这里我也根据自己的实践经验再给几点很实际的小建议一是写实验日志给每次训练记录下优化器类型、学习率、batch size、warmup 和 weight decay这样在任何时候都能回溯当初的设定二是勇于尝试替代方案AdamW 未必在每个任务上都比 Adam 强SGD 也未必永远是最佳选择一切用验证集指标说话三是关注显存和速度的平衡优化器的选择不只是精度问题更是工程资源的全局优化问题。我在实际训练中发现很多看起来高深莫测的操作拆解开无非是一步步实验、对比、分析之后沉淀下来的习惯。调参的速度会随着实验数量逐渐上升你对优化器“手感”也会越来越准。希望这篇内容能帮你把 Model-Optimizer 这块拼图真正拼进你自己的训练流程里也希望你下次再打开训练脚本不是随手敲一行 optimizer Adam而是能真正想清楚这个优化器到底为什么值得出现在这里。