
优化器这个东西说大不大说小不小但你在AI项目里只要稍微认真调过模型就会知道它绝不是“换个名字那么简单”。我早期做视觉模型的时候习惯性地把优化器当成固定参数模型训不出来就怪网络结构、怪数据增强折腾半天才发现问题有时候就出在这个最容易被忽略的组件上。后来我专门花了很长时间研究优化器的选型与调参才意识到一个事实优化器本质上是训练系统的“底层操作系统”它决定了模型能在多大程度上利用好你的数据和算力。这篇就围绕Model-Optimizer这个话题把我踩过的坑、验证过的配置、以及背后的原理逻辑都整理出来希望能帮你少走几条弯路。1. 为什么Model-Optimizer值得单独聊1.1 优化器不是超参数是训练系统的“底层操作系统”很多人把优化器当作一个可有可无的超参数落地的时候默认用Adam学习率给个1e-3然后就去调别的了。但实际训练中优化器选择的差异可能比模型结构带来的差异还明显。打个比方网络结构决定了模型的理论容量上限数据决定了信息的上限而优化器决定了你实际能逼近这个上限多少。一个不好的优化器会让你在同样的算力下多花3到5倍的训练时间甚至直接不收敛。我最早意识到这一点是在一个文本分类任务上。当时同一个BERT变体我换了两套优化器设置一套是AdamW加线性warmup另一套是普通Adam配固定学习率结果前者的验证集准确率比后者高出将近2个百分点。注意模型结构、数据、种子完全一样差别全在优化器及其配套策略上。这还不算完后来在YOLO类的检测任务上我又发现SGD配上合适的momentum和weight decay竟然比Adam跑出更高的mAP。从那时起我就养成一个习惯每到一个新任务先花半天把优化器和学习率策略挨个试一遍而不是直接沿用上次的配置。所以与其说“优化器”是一个超参数不如说它是一个训练系统。它包含了更新规则、学习率调度、权重衰减策略、梯度裁剪等多个层次。你在项目里需要把它当成一个整体来设计而不是单独调一个learning rate。1.2 一个案例对比同样的模型不同的命运这里分享一个我自己做过的小实验。同样是ResNet-50在ImageNet子集上的训练我固定batch size为256训练90个epoch只改优化器其他一切不变。结果如下表所示优化器学习率最终Top-1准确率达到70%所需epochSGD (momentum0.9)0.1 (cosine decay)76.4%38Adam1e-3 (cosine decay)74.8%45AdamW1e-3 (cosine decay)75.6%41SGD (momentum0.9)0.01 (固定)72.1%52可以看到SGD配大学习率加余弦退火在这个任务上赢了Adam反而没那么理想。但这里有个关键前提SGD需要一套精心设计的学习率策略才能发挥威力而且它对初始学习率极其敏感Adam的优势在于省心默认参数在很多任务上都能跑出差不多的结果但想吃满模型性能就需要更多的调参配合。这个实验也引出一个重要观点选优化器不是选“最好的”而是选“最适合当前任务和资源约束的”。如果你有足够的调参时间SGDmomentum在多数视觉任务上仍然有一战之力如果你的项目强调快速迭代Adam/AdamW更合适如果你在训Transformer大模型基本就锁死AdamW了。2. 主流优化器的核心原理与选型逻辑2.1 SGDMomentum经典但不好伺候随机梯度下降SGD是所有优化器的基石它做的事情很简单沿着当前batch梯度的反方向更新参数。但纯粹SGD有两个让人头疼的问题一是收敛慢尤其在损失面比较“平缓”的区域步子太小容易卡死二是容易震荡在梯度方向剧烈变化的区域参数会来回摆动训练不稳定。Momentum的引入就是为了解决这两个问题。它的核心思想是让参数更新不再只看当前梯度而是累积历史梯度的“动量”就像推一辆沉重的购物车你推一下它会借着惯性继续往前滑一段。对于方向一致的梯度动量会不断加速对于方向频繁变化的梯度动量会中和掉震荡。动量系数通常取0.9表示保留90%的上一步动量加上10%的当前梯度。这个0.9的取值经验性很强实际调参中我也试过0.95和0.8效果差异不算大但0.9在不同任务上表现最稳定。SGDmomentum的脾气是“上限高但门槛也高”。它的收敛效果非常依赖学习率策略通常需要一个较大的初始学习率比如0.1配合warmup和cosine decay才能在视觉任务上发挥威力。如果你直接用默认学习率0.001跑SGD大概率会在loss还很差的时候就陷入瓶颈。我早期就吃过这个亏总以为模型不行实际上是我没给SGD配一个像样的学习率“剧本”。2.2 Adam自适应学习率的代表作与脾性Adam的全称是Adaptive Moment Estimation它融合了两个核心机制一是像momentum一样维护一阶动量梯度的指数滑动平均用于平滑更新方向二是维护二阶动量梯度平方的指数滑动平均用于感知每个参数的历史梯度幅度进而对每个参数自动调整学习率。这里的直觉是对于梯度幅度大的参数说明这个方向可能比较“陡峭”步长应该缩小对于梯度幅度小的参数说明方向比较“平缓”步长应该放大。Adam就是这么通过二阶动量实现了“自适应”学习率所以它对初始学习率的敏感度远低于SGD。但Adam也有一个深层的毛病它会挤出模型的泛化能力。原因比较复杂一个直观的解释是Adam的自适应机制本质上是对梯度的缩放这种缩放会改变参数更新路径让模型到达的极值点“尖锐度”偏高泛化性相对SGD略逊。另一个常见问题是Adam在训练后期二阶动量累积偏大导致有效步长越变越小不利于收敛到更优的极值区域。我自己用下来的感觉是Adam非常适合NLP、多模态这类模型结构和数据分布都比较复杂的任务因为它能让训练快速稳定下来不需要花太多时间在初始学习率上纠结。但如果要在CV任务上追求极致精度Adam往往需要配合大幅度的学习率衰减才能稍微弥补它泛化性偏弱的问题。2.3 AdamW解耦权重衰减才是正解AdamW这个W指的是它的创建者发现Adam默认实现里做L2正则化的方式有问题进而提出的改进版本。L2正则化的标准做法是把权重衰减项加到损失函数里这样计算梯度时正则项的梯度会混入其中。但Adam在自适应更新规则下会把正则项的梯度也做“自适应缩放”导致不同参数的权重衰减强度变得不一致正则效果被扭曲。AdamW的解耦思路是把权重衰减从损失函数中拆出来不参与梯度的自适应计算而是直接在参数更新的时候减去一个固定比例的衰减值。这样做的好处是权重衰减不会再被Adam的缩放机制影响正则强度更可控而且不需要在损失函数里加额外项写代码也更干净。我在实际项目中几乎都用AdamW替代掉Adam尤其是在Transformer类模型上二者的差异非常明显。同样的任务AdamW能够让训练更稳定而且在小数据集上不容易过拟合。搭配上一些新出现的优化器或调度策略AdamW已经成了我训练大模型时的默认选择。3. 学习率策略优化器真正的胜负手3.1 学习率基线不同优化器的不同脾气很多人问“学习率该设多少”这个问题没有标准答案但有默认基线。SGDmomentum的常用基线是0.01到0.1需要注意配合batch size调整Adam一般取1e-3batch size偏大时则降到1e-4量级AdamW在大模型场景下通常直接给1e-4或更低配合warmup慢慢爬升。这里面有一个容易被忽视的“batch size和学习率联动”原则batch size翻倍时梯度估计更稳定可以适当调大学习率。最简单的缩放规则是线性缩放batch size从64涨到128学习率相应乘以2。但这种规则在超大batch size下会失效所以在8卡、16卡并行训练时我更习惯用平方根缩放也就是batch size翻倍时学习率乘以根号2这样会更稳一些。上次我做一个多卡训练任务时直接把单卡学习率用在多卡上导致训练损失上升得厉害。排查了半天才发现是等价batch size变了学习率却没有对应调整。这就是优化器“周边”细节的典型坑。3.2 Warmup的必要性Warmup指的是训练早期让学习率先从一个很小的值开始逐步升温到目标学习率。它的必要性在于模型初始化时参数是随机的早期的梯度信号往往比较“杂乱”如果一开始就用大学习率容易把参数推到不理想的区域而且这种早期破坏往往是不可逆的。尤其是Transformer这类模型没有预训练时候的稳定特征基底对warmup非常依赖。我训过一个小型GPT模型没有warmup时loss在最初几百步直接冲到9以上训练两三个小时后都无法回到正常水平加上warmup之后模型很快就进入稳定下降状态。实现上warmup一般有两种线性warmup从0逐步线性增加到目标学习率和指数warmup从很小值逐步指数增长到目标学习率。前一种简单直接后一种更平滑。对于短训练任务我建议min(warmup_steps, 500)个step内完成升温对于长训练任务比如上万step可以按总步数的5%到10%来设定warmup时长。3.3 余弦退火与重启余弦退火Cosine Annealing是我个人最喜欢的学习率调度方式思路很简单让学习率按余弦曲线的形状逐步衰减到接近0。它的好处是前半程降得比较慢给模型充足时间来探索较好的区域后半程降得快一点帮助模型收敛到更精细的极值点。这里的“前半程慢、后半程快”并不是玄学。训练的目标是先找到大方向的“盆地”再在盆地底部找到“坑底”。如果一开始就降低学习率模型就没有足够的探索能力离开较差的局部区域如果到最后才降模型又可能在坑底附近来回震荡。余弦退火正好提供了一个自然的尺度。还有一种进阶玩法是带热重启的余弦退火Cosine Annealing with Restarts也就是学习率衰减到一定程度后突然跳回高位让模型跳出当前盆地。这种策略在部分任务上能提升最终精度但它对训练时间的要求较高而且重启时机不容易掌握。我在图像分类和OCR识别任务上试过有时候有效有时候只是白白浪费训练时间所以除非你有充裕的算力去试否则谨慎使用。4. 实战调参一份可抄作业的配置清单4.1 视觉模型怎么配以我最近做的一个图像分割项目为例这个任务用的骨干是类ResNet结构我在调参时发现视觉领域确实存在一套比较通用的默认配方。这里整理成表格方便直接参考训练场景优化器学习率权重衰减学习率策略小型CNN几十万参数AdamW1e-30.01~0.05Step DecayResNet级CV模型SGDMomentum0.05~0.11e-4~5e-4Cosine Decay检测/分割类复杂任务SGDMomentum0.01~0.025e-4Cosine Decay数据量极小1万AdamW1e-40.1固定早停需要注意小数据量场景下过拟合风险比对优化器的追求更重要所以权重衰减会设置得更大一些。另外当数据集比较小时我直接用固定学习率加早停效果往往比复杂调度更好因为模型没有足够的梯度信号来支持长时间的学习率变化。4.2 Transformer类模型怎么配Transformer类模型BERT、GPT、ViT等几乎成了深度学习的“通用积木”它们的优化器配置也基本统一。我的默认配置是AdamW、学习率1e-4到5e-5、权重衰减0.01到0.1、线性warmup加线性衰减或余弦衰减。这里有一个重要细节Transformer的权重衰减对象需要区分为“所有参数”和“部分参数”。实际应用中像LayerNorm的bias、归一化层的gamma/beta这类参数不应该施加权重衰减因为它们本身是数值稳定的关键正则化反而会干扰它们的自适应调整。一个常见做法是只有矩阵类权重如Linear层、Embedding矩阵施加weight decaybias和LayerNorm参数不施加。很多成熟的代码库中会用optimizer_grouped_parameters来分组设置。我早期做BERT微调的时候不懂这个细节给所有参数统一加weight decay 0.01结果收敛速度明显变慢。后来我把bias和LayerNorm参数单独拎出来设置weight decay为0效果才恢复正常。所以如果你是手动写训练循环这个点值得特别留意。4.3 强化学习里的特殊处理强化学习项目里用的优化器逻辑和监督学习有区别核心在于样本分布的变化。强化学习的数据不是静态的模型每个epoch的行为会改变下一个epoch的数据分布这对优化器提出了更高要求。在实际的RL训练中我会用AdamW但学习率要比监督学习更低一些一般取3e-4到1e-4同时要配合梯度裁剪。这里的逻辑是强化学习的优势函数估计存在较大方差如果学习率太大一个batch的偶然高估就会让策略参数剧变导致后续回合整体崩溃。梯度裁剪的作用类似安全带它能保证单次更新的幅度不会过大给训练一个相对平稳的步调。还有一个提升实践是引入KL penalty来约束策略更新的跨度这个属于策略层面的措施但从优化器角度看它实际上也间接限制了有效梯度的大小和梯度裁剪一起共同维持RL训练的稳定性。5. 优化器周边那些容易翻车的配套细节5.1 混合精度训练下的scaler与optimizer混合精度训练AMP现在几乎成了大模型训练的标配因为它能显著加速训练并降低显存占用。但在AMP模式下优化器相关的一个细节很容易翻车梯度在FP16格式下幅度太小直接做梯度更新会出现精度不足的问题。这时就需要GradScaler参与它先把loss乘以一个缩放因子放大梯度到FP16的表示范围再在更新前把梯度缩放回去。这里的坑是如果忘了调用scaler.scale(loss)和scaler.step(optimizer)模型可能看起来在训练但loss降不下去因为你更新用的梯度全是接近0的噪声。还有一个小细节是scaler.update()的调用位置它必须在每次迭代最后执行用来动态调整缩放因子的数值否则训练到后期可能出现溢出问题而不自知。我一个朋友曾经在AMP训练中反复遇到“loss突然变NaN”的问题我帮他排查时才发现他在每次迭代结束后忘了调用scaler.update()导致缩放因子永远停留在初始值无法感知梯度溢出。修完之后训练一下子稳定下来。这类问题一般不会在模型结构上体现但训练日志会非常诚实地告诉你“不对劲”。5.2 多卡DDP下优化器状态如何保存与恢复多卡训练DDP时的优化器状态管理是一个看起来不起眼但实际很容易出错的环节。很多人在每张卡上都保存一份模型checkpoint然后加载时随机拿一张卡的结果来用这会导致优化器状态不一致进而让“断点续训”变成“断点重训”。正确的做法是只在rank 0进程上保存模型和优化器状态其他进程只保留模型结构。加载checkpoint时所有进程要加载同一份优化器状态确保每个进程的优化器内部统计值完全一致。这里特别要注意的是AMP模式下还涉及scaler的状态它也需要一并保存和恢复完整的checkpoint应该包含model.state_dict()、optimizer.state_dict()、scaler.state_dict()以及epoch和global_step。我之前就有过一次断点续训时acc突然下降的经历查了半天最后发现是scheduler的step计数没有保存恢复时学习率跳回了初始值直接导致模型在一轮大学习率更新后偏离了原来的优化轨迹。所以如果要用断点续训优化器、学习率调度器和梯度缩放器的状态必须作为整体保存。5.3 梯度裁剪与EMA的平衡取舍梯度裁剪是防止梯度爆炸的手段常见做法是设置一个max_grad_norm比如1.0然后将整个梯度的范数截断到这个值以内。这个操作在RCNN、Transformer和RNN训练里属于标配。需要注意的是如果你用Adam类优化器梯度裁剪的作用会被部分“吸收”因为Adam对梯度本身有缩放效果但对SGD来说梯度裁剪的意义非常大它直接决定了更新步长是否越界。EMA指数移动平均是我在几乎所有视觉任务里的固定搭配思路是维护一份参数的历史滑动平均值用于最后的模型推断。这份平均后的模型往往比训练末尾的即时参数表现更稳定尤其在目标检测和语义分割任务上能带来0.2到0.5个百分点的提升。它能与任意优化器搭配但要注意它的滑动系数比如0.999需要配合总训练步数来设定步数太短会让EMA模型偏离真实参数太远反而劣化。5.4 权重衰减到底该加在哪权重衰减的目的是让模型参数不要太大从而抑制过拟合。但具体实现里并不是所有参数都需要被“衰减”。我现在的习惯是除了bias和归一化层参数之外其他权重都施加weight decay。很多主流框架的默认做法也类似比如Hugging Face的Trainer工具就是按照这个规则自动分组的。如果你用PyTorch写手动训练循环可以这样构造optimizer的参数组optimizer_grouped_parameters [ {params: [p for n, p in model.named_parameters() if bias not in n and LayerNorm not in n], weight_decay: 0.01}, {params: [p for n, p in model.named_parameters() if bias in n or LayerNorm in n], weight_decay: 0.0} ] optimizer AdamW(optimizer_grouped_parameters, lr1e-4)这样明确分组能让正则化作用到真正需要约束的权重上又不干扰归一化的自适应调整。这个细节在论文里通常只是脚注但实际训练中却能真实影响收敛速度。6. 一个真实的调优复盘把F1从0.87抬到0.916.1 基线情况最后分享一个近期做过的完整案例。当时是在一个多标签文本分类任务上模型用的是bert-base版本数据量大概3万条标签有26类初始F1是0.87。最初的配置是常规操作AdamW学习率2e-5线性warmup加线性衰减weight decay默认0.01batch size 16训练3个epoch。这个效果能接受但离上线要求差了一点。我决定从优化器这个维度做一轮系统调优。说实话这类任务在绝大多数项目里就是“默认配置直接训练”很多人不会在优化器上深挖但我觉得只有把优化器这条线彻底捋清楚才能真正把模型潜力榨出来。6.2 调整过程第一轮我把学习率从2e-5提到3e-5同时把weight decay提高到0.05。结果验证集F1涨到了0.882提升幅度不大但说明方向是对的。这轮调整的核心逻辑是数据量不算小让模型多“跑”一点并加强正则化避免过拟合。第二轮我把warmup从10%降到6%并把衰减策略从线性衰减切到余弦衰减。这里的原因是在长序列任务上后期过快降低学习率容易让模型固化到不够好的局部极值余弦衰减的后半段更平滑能给模型更多时间在精细区域搜索。这一轮F1到了0.895。第三轮我在优化器层面做了一次更激进的改变把一阶动量beta1从0.9调整到0.95。这个调整的动机是让优化器对近期梯度的平滑更强减少梯度噪声对更新的影响。出人意料的是F1突破了0.9达到了0.904。第四轮我又把梯度裁剪从无到有max_grad_norm设为1.0稳定了训练后期的震荡最终F1停在0.91。6.3 最终结果分析整个调优过程没有动模型结构没有换模型体量数据增强也没有变化纯粹靠优化器及配套策略的组合拳F1从0.87提到了0.91。这个绝对值看起来不算大但在多标签分类任务里68个样本的正确与否就对应了约0.1的F1变化所以提升非常可观。更重要的是这些调整不增加任何训练时间实际只是换几个配置然后跑实验的成本。从我的实践来看优化器调优不是“碰运气”它背后有几个明确杠杆学习率的调度方式是第一杠杆权重衰减的位置和量级是第二杠杆优化器自身的超参数momentum、beta值是第三杠杆梯度裁剪和EMA则是压轴的第四杠杆。一开始从这些方向入手基本都能拿到稳定的提升。7. 我的一些保留心得优化器这个组件很多人以为用默认参数就够了但实际上它是你与模型性能之间最后一道可调门槛。我自己踩过不少坑也有一些怎么用都不太对的地方这里一并分享出来。首先优化器参数一定要配合你的数据和任务量级来调整不能照搬论文或别人的配置。论文里给出的学习率和weight decay往往是在他们特定的任务和数据规模下调的直接拿来用效果可能完全相反。我之前用某篇论文的优化器配置去复现一个图像分类模型结果收敛极慢后来才发现是因为我batch size比论文小了一半学习率却没有减。其次如果你要做一个需要长期训练的大规模模型我强烈建议你预留一个“优化器对照实验”的预算至少跑三组SGDmomentum、AdamW、以及你当前任务最推荐的优化器每个配置都配上合理的学习率调度方式。这个对照看起来费时间其实能帮你建立对这个任务的深度理解后续所有模型迭代都会受益。最后分享一个小技巧在每一次训练结束后记录下优化器相关的全部配置包括初始学习率、warmup步数、衰减方式、weight decay、梯度裁剪阈值以及当时的验证集指标。这些看起来琐碎的记录实际上是你后续调优最宝贵的资产。我自己的项目笔记里优化器配置永远排在模型结构之前因为在我踩过这么多坑之后发现模型结构决定了能力的上限而优化器决定了你能实际到达的高度。