news 2026/9/29 6:23:27

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深度学习优化器全解析:从SGD到AdamW的选型与调参实战指南

1. Model-Optimizer到底在优化什么:先聊聊背景和真实痛点

做深度学习训练的人应该都有过这种体验:模型结构照搬经典论文、数据也都干净,结果一跑起来loss死活不降,或者降一半突然NaN,再或者训练集都能满分、验证集却一路飘高。多数人第一反应是调学习率、换网络结构,但调来调去问题依旧。我最早遇到这类情况时也走了不少弯路,后来才慢慢理解:有时候问题压根不在网络,而在优化器——它就像车的方向盘和油门,动力总成再猛,转向手感不对,车照样开不稳。

这也是我整理"Model-Optimizer"这个项目的初衷。它不是某个公司的新框架,也不是什么论文里的新算法,而是一套我自己长期打磨、沉淀下来的优化器选型与调参方案集合。简单说,它回答三个问题:这个任务该用SGD还是Adam?学习率调度器该怎么配?训练不稳定时,到底是该裁剪梯度、换优化器,还是动损失函数?

目前这套方案统一封装了SGD、Momentum SGD、Adam、AdamW、RMSprop、Adagrad、Nadam以及Lion等常见优化器,同时支持warmup、cosine退火、step decay、OneCycle等调度策略,还内置了梯度裁剪、EMA、混合精度等配套模块。适合的人群也很明确:会跑模型但不想把时间耗在"反复试不同学习率"上的炼丹工程师、做毕设或科研复现的研究生、以及准备把模型训练流程规范化的算法团队。你要是已经踩过几次loss不收敛的坑,这篇内容能帮你省掉不少试错时间。

我把项目里的核心思考、配置细节、踩坑记录都整理在下面,尽量讲清楚每一步"为什么这么做",而不是给一张配置表让读者拿去瞎抄。

2. 核心设计思路:为什么优化器值得单独封装

2.1 优化器不是"换个算法"那么简单

很多人把优化器理解成一行代码的事:torch.optim.Adam(model.parameters(), lr=1e-3)。但实际工程里,优化器牵扯的东西非常多。同一个Adam,weight_decay放哪个位置、eps设多大、betas用默认值还是调整过、要不要做梯度裁剪、学习率调度器怎么衔接,这些细节组合起来的最终效果可能差好几个点。

举个例子,PyTorch早期版本的Adam里,weight_decay是直接加在梯度上的,也就是L2正则化;而AdamW把weight_decay从梯度里拆出来,在参数更新时单独衰减。这两种方式在数学上不等价,尤其在Transformer或大batch训练里,AdamW的泛化能力通常明显更好。这就是为什么HuggingFace的transformers库默认只用AdamW,而不是普通Adam。

所以封装优化器的第一个目的,是把这些"看似细节、实则致命"的差异固化成一套默认配置,避免每个新项目都从零踩一遍雷。Model-Optimizer里为每种优化器都配了合理的默认参数,同时保留显式覆盖的入口,而不是黑箱。

2.2 统一接口带来的"可复现性红利"

另一个让我坚持封装的原因是可复现性。训练深度学习模型,光记录一个优化器名字是不够的,betas、eps、weight_decay、是否AMSGrad、调度器名称和热启动步数,任何一项变了结果都可能不一样。用统一配置结构管理这些参数后,实验记录里只需要存一份配置文件,就能完整还原当时的优化器状态。

这个项目里每个实验的配置是一个YAML块,里面写清楚optimizer、scheduler、clip、ema等所有设置。训练脚本启动时读取配置,生成对应的优化器实例和调度器,并将完整配置一并写入日志。这个习惯陪我完成了上百组对比实验,回头复盘时再也不会出现"这组结果用的是哪个优化器"的混乱。

2.3 模块划分:不只是优化器,而是完整的训练动力学方案

真正开始整理后我发现,单独优化优化器远远不够。模型能不能稳定收敛,是优化器、学习率调度、梯度裁剪、梯度累积甚至混合精度一起作用的结果。比如你用Adam但没做warmup,前几个step的梯度方差很大,很容易把模型推到坏区域;再比如混合精度训练下,如果没有对梯度做裁剪,fp16的梯度溢出会直接导致loss变NaN。

所以Model-Optimizer被我拆成了五个模块:optimizer(基础优化器)、scheduler(调度器)、clip(梯度裁剪策略)、ema(指数滑动平均)、precision(混合精度与优化器状态优化)。每个模块都是可插拔的,用默认配置就能跑通全流程。

这套设计的核心逻辑是:把训练稳定性当作一个系统工程来管理,而不是孤立地调某一个参数。下面从优化器的底层原理开始,逐个展开。

3. 主流优化器逐个拆解:原理、差异、实际效果

3.1 SGD与Momentum:扎实的老派选手

SGD、Momentum SGD是优化器里的老前辈。现在很多新手一上来就用Adam,反而没接触过SGD。其实SGD的更新规则极其简单:参数减去学习率乘以梯度。它的问题是收敛速度相对慢,并且容易在病态曲面上震荡。Momentum就是针对这个问题加了"惯性",把历史梯度的指数加权平均当作更新方向,公式上可以理解成:velocity = momentum * velocity + gradient,参数更新时减去learning_rate * velocity。

启动Model-Optimizer里的SGD时,我习惯用momentum=0.9,nesterov视情况开启。Nesterov是在Momentum的基础上"往前多看一步",计算梯度时用了momentum展开后的位置,理论上更稳,实际在部分CV任务里确实有微弱提升,但不是所有任务都适合。

目前SGD在哪些场景还能打?我实际用下来的经验:图像分类大模型从头训练、风格迁移、超分辨率这类像素级回归任务,以及部分强化学习策略网络训练,SGD系列的表现依然不差,甚至比Adam更稳。原因也好理解——SGD的梯度是unbiased的,最终收敛点往往在更平坦的极小值区域,泛化更好;Adam的自适应步长在靠近最优解时容易被二阶动量拽住,不动了。

不过SGD最怕的就是参数尺度差异大、损失面特别陡峭的任务。这种场景下不调学习率调度几乎收不敛。所以如果你选SGD,调度器一定不能省。

3.2 Adam与AdamW:自适应学习率的标杆,但坑也最多

Adam是目前使用率最高的优化器,它的核心思想是为每个参数独立维护一阶动量(梯度均值)和二阶动量(梯度平方的均值),因此天然适应稀疏特征、非平稳目标,对学习率的敏感程度也比SGD低很多。PyTorch里一句Adam(model.parameters(), lr)就能跑,太省心了,反而让人忽略了细节。

关键的坑就在weight_decay。PyTorch的torch.optim.Adam里weight_decay是"L2正则化"的实现,相当于往梯度里加了一项权重本身;而AdamW的decoupled weight decay则是直接让权重乘以一个衰减系数。对于大模型预训练和微调,后者要稳定得多。我见过不止一个项目用普通Adam加weight_decay去微调BERT,训练曲线看着不错,但下游指标始终上不去,换成AdamW之后一切正常。

在Model-Optimizer的配置里,默认选择就是AdamW,除非你在做的任务有特别理由才切换。betas我一般保持(0.9, 0.999),eps设为1e-8,weight_decay根据任务设1e-2到5e-2之间。一个容易忽略的参数是eps:混合精度训练时,eps过小可能导致二阶动量更新时出现除零误差,进而让梯度变成Inf或NaN。我习惯在fp16环境下把eps调到1e-6甚至1e-4,牺牲少量精度换取稳定。

Adam的另一个问题是显存占用。因为它要保存两个动量变量,模型参数越大,显存开销越夸张。一个7B参数的模型,光优化器状态就能吃14B参数量的浮点内存。大模型时代这点尤其致命,后面会专门讲怎么压缩。

3.3 其他成员:RMSprop、Adagrad、Nadam、Lion

除了SGD和Adam两大阵营,还有一些优化器在特定场景很有价值。RMSprop在RNN和部分NLP任务里很常见,因为它对梯度的二阶动量做RMS归一化,能有效处理梯度尺度变化剧烈的情况;Adagrad适合特征极度稀疏的任务,比如广告点击率预估这种场景,每个参数独立学习率,缺点是学习率会随着训练单调递减,后期基本学不动;Nadam则是把Nesterov的思想套进Adam里,收敛速度有一定优势。

Lion是2023年谷歌提出来的优化器,思路非常另类,它用符号函数取代了一二阶动量,更新时只看梯度的正负方向。我实测在一些计算机视觉任务上Lion收敛速度确实比AdamW快,泛化也不错,但训练初期仍然需要小学习率和warmup,否则会非常不稳。现在大模型训练偶尔能看到它的身影,但总体应用还不算普及。

把这些优化器放进同一个配置中心的好处是切换成本极低。我之前做过一组对比实验:同样一个语义分割模型,SGD要练到120个epoch才能收敛,AdamW大概80个epoch就能到同等精度,而Lion只用了约60个epoch,但验证集上的最终精度略低于AdamW。没有统一封装之前,光是切换优化器就得改一堆训练代码,现在改配置就行。

3.4 选型地图:到底该用哪个优化器

很多读者问有没有一张表可以直接告诉我"什么任务该选什么"。我根据自己的实验和参考经验,大致列了一张判断表,但它只能是起点,不代表绝对真理。

使用场景 | 推荐优化器 | 理由 图像分类从头训练 | SGD(Momentum) | 收敛稳定、泛化好,配合cosine退火效果佳 目标检测/语义分割 | SGD或AdamW | SGD更稳但需细调调度器;AdamW上手快,精度差距可接受 NLP模型微调 | AdamW | 默认配置,对Transformer架构稳定,配合线性warmup 大规模预训练 | AdamW/LAMB | AdamW稳定;LAMB适合超大batch,收敛快 GAN训练 | Adam | 生成器与判别器都常用,betas建议(0.5, 0.999) 强化学习策略梯度 | Adam | 自适应利于非平稳回报分布,学习率不宜太大 稀疏特征任务 | Adagrad/Adam | 特征维度高且稀疏时效果好

这张表帮我解决过大量模型"起步跑不起来"的问题。比如GAN训练,如果把betas按默认(0.9, 0.999)来,生成器很容易模式崩塌,换成(0.5, 0.999)通常更稳定。这类经验在文档里是查不到的,只有自己复现对比才能体会到差距。

4. 完整实操:Model-Optimizer的配置、训练与调度策略

4.1 配置示例:一个可复现的优化器配置模块

下面给出一份Model-Optimizer的典型配置,对应一个图像分类任务的训练流程。它不是一个玩具示例,而是我实际从多个项目中抽取出的通用模板。

optimizer: name: AdamW lr: 0.001 betas: [0.9, 0.999] eps: 1.0e-8 weight_decay: 0.02 scheduler: name: cosine_with_warmup warmup_steps: 500 total_steps: 10000 min_lr_ratio: 0.01 clip: max_norm: 1.0 clip_type: norm ema: enabled: true decay: 0.999

这份配置的含义是:用AdamW优化模型参数,初始学习率0.001,权重衰减0.02;学习率在前500步线性warmup,再按cosine退火降到初始学习率的1%;同时对梯度做全局范数裁剪,阈值为1.0;另外维护一份EMA参数副本用于最终推理。

配置中最容易被低估的是cosine_with_warmup和min_lr_ratio。很多人只设置了初始lr,训练全程不变,模型往往在最后阶段精度还差一口气。cosine退火的好处是让学习率在中后期逐渐减小,帮助模型收敛到更稳定区域,这个操作不需要任何额外计算成本,几乎稳赚不赔。

4.2 学习率到底怎么定:从一个案例看LR Range Test

关于初始学习率,大家最常问的就是"lr设多少合适"。SetFit、transformers这些库都会根据任务自动给默认值,但依然逃不脱手动尝试。我的做法是先做一个LR Range Test:用很小的lr启动训练,每个batch逐步调大lr,同时记录loss变化,最后画一条loss-lr曲线。曲线中下降段最陡的位置附近,往往就是合适的初始lr。

这个测试大概只需要几百步训练,跑不了太久。我自己跑分类任务时,常见结果是AdamW在1e-4到3e-3之间表现比较好,SGD在0.01到0.1区间。合成的曲线非常直观,比拍脑袋猜lr靠谱得多。Model-Optimizer里把LR Range Test也封装成了一个小工具,输入模型、数据loader、优化器类型、lr上下界,自动输出推荐范围。

这个工具的底层逻辑并不复杂,就是逐步提高学习率并记录loss,然后把loss达到最小值或开始发散前对应的lr作为参考。很多人图省事直接用默认lr,结果模型不是发散就是收敛太慢,最后浪费的时间远比跑一次LR Range Test多得多。

4.3 梯度裁剪:稳定训练最简单有效的保险丝

梯度裁剪可能是整个优化器配置里性价比最高的模块。它对梯度的全局范数设定一个上限,训练时如果梯度范数超过阈值,就按比例缩放到这个上限。这个操作不会改变梯度的方向,只限制步长大小,很像是给训练过程加了一道保险丝。

max_norm设多少合适呢?我的经验是1.0到5.0之间。设小了会拖慢收敛,设大了起不到保护作用。Transformer类模型训练我普遍用1.0;CNN训练我有时放到5.0。混合精度训练尤其建议开启裁剪,我见过不少fp16发散案例,开裁剪后立刻稳定。

裁剪方式上,我推荐全局范数裁剪,而不是逐参数裁剪。clip_grad_norm_对应的就是全局范数,PyTorch里一行代码就能用。如果裁剪后发现梯度方向被频繁影响,说明模型本身就存在问题,这时候要去看是不是数据里有异常值、loss里有没有数值不稳定的项,而不是一味加大max_norm。

4.4 EMA滑动平均:免费提升模型精度的小技巧

EMA(指数滑动平均)维护一组参数副本,每次更新时按衰减率朝当前权重方向移动。公式大致是:ema_weights = decay * ema_weights + (1 - decay) * current_weights。训练过程中我们实际上在优化当前权重,但评估和推理时可以使用EMA权重,因为EMA权重相当于多个历史权重快照的平均,更平滑且更不容易陷入尖锐的极小值。

我自己的实际经验是:EMA decay设0.999在长时间训练中效果不错,但训练初期EMA会滞后很多,所以在warmup阶段我把EMA的decay临时调低(比如0.99),训练进入中后期再调回0.999。这个细节能避免前期EMA和当前权重偏差过大。Model-Optimizer里加入了"EMA warmup"配置,目的就是解决这个问题。

EMA对最终精度的提升通常是零点几个点到两三个点之间,尤其在目标检测、分割这类任务里收益比较明显。成本几乎没有,就是多存一份参数副本的显存,训练时每个step多一次参数拷贝操作。如果你想在验证集上不大动干戈就提几点,EMA是最省事的选择之一。

5. 我踩过的坑:三个典型训练失败案例的系统排查

这一节全部来自我的真实复盘,不少配置和操作可能和你当初遇到的情况高度相似。

5.1 案例一:loss突然NaN,到底先怪谁

现象:用AdamW训练一个文本分类模型,前几百步正常,到第470步时loss直接变NaN,而且接下来一路都是NaN。我去看过数据,没有明显的脏数据,于是开始逐项排查。

排查思路是先看学习率:如果lr过大,loss一般会先剧烈震荡再发散,而不是突然跳到NaN,所以锁定问题不是lr。再打印梯度统计,发现某层的梯度范数在NaN出现前就已经冲到1e8级别,所以问题的核心在于梯度爆炸。虽然AdamW做了自适应缩放,但对极端大的梯度仍然无能为力,尤其混合精度下梯度除以一个极小分母时会溢出成Inf,再一运算就变成NaN。

最后我的解法是:先给所有Transformer层做梯度裁剪,max_norm=1.0;再把优化器eps从1e-8升到1e-6,防止二阶动量分母过小导致数值溢出;最后将损失函数里的log操作加上一个极小常数保护。三步叠加之后问题完全消失。

这里有两条硬经验:一个是有任何数值不稳定的迹象,梯度裁剪总是第一个该上的保险;另一个是不要把NaN问题轻易归咎于数据和代码,先打印梯度和优化器状态,很多情况下问题就出在数值动态上。

5.2 案例二:loss曲线震荡,训练半天没有起色

一个图像分割模型使用SGD训练,发现loss在30个epoch后一直处于上下震荡状态,怎么看都不像要收敛。我首先确认了LR Range Test给出的推荐范围,发现我用的0.01已经偏向上限,于是将lr降到0.003。降完后震荡幅度小了一些,但收敛速度还是不如预期。

接着把batch size的因素加了进来:batch size原来设32,总loss和梯度估计震荡明显。我尝试把batch size翻倍到64,同时把学习率按线性缩放法则简单调整到0.006,这一步之后曲线明显平滑了许多。后来我还检查了数据加载顺序,确认shuffle是打开的,因为有时候shuffle关闭也会导致每个epoch内loss呈现周期性规律,让人误以为模型出了问题。

这个案例的教训是:loss震荡首先看学习率和batch size的匹配关系,其次确认shuffle和数据增强是否规范。优化器的选择有时候反而是最后才考虑的。

5.3 案例三:大模型微调,显存爆掉之后优化器怎么取舍

跑一个约3B参数的模型做指令微调,单卡A100 40GB,batch size最多只能塞1。训练到中途就OOM了。这时第一反应是降低batch size,加上gradient accumulation,把有效batch size维持在32。这一步解决了显存不足问题,但训练速度下来了,因为同样的step数对应更少的参数更新次数。

接下来我把目光转向优化器状态。完整AdamW在3B模型上会额外占用约24GB的显存(两个动量变量乘以模型参数量再乘以4字节),这占了整个显存很大一块。我把配置切换为8-bit AdamW,这是通过量化优化器状态节省显存的方式,具体来自bitsandbytes库,优化器状态从32位浮点压缩到8位整数,显存占用降为原来的四分之一左右,精度损失在实际微调中基本感觉不到。

如果连8-bit的条件都不具备,另一个方案是换用Adafactor或Lion,这两者的优化器显存开销比AdamW小不少,但收敛行为需要重新调。Adafactor在transformers里用得较多,Lion胜在简化和状态小。大模型时代,"选优化器"已经不仅仅是收敛精度的选择,更是显存预算下的综合权衡。

5.4 问题排查速查表

我把常见的训练不稳定现象和对应的排查顺序整理成一张速查表,适合贴在工位边。

现象 | 优先排查 | 次级排查 | 常见有效手段 loss突变为NaN | 梯度范数是否爆炸 | eps是否过小、混合精度是否溢出 | 开启梯度裁剪、调大eps、换bf16 loss震荡不收敛 | 学习率是否过大 | batch size是否过小、shuffle是否开启 | 做LR Range Test、调低lr、增大batch 收敛速度过慢 | 学习率是否过小 | 调度器是否生效、优化器是否匹配任务 | 开启warmup+cosine、考虑换优化器 验证集精度瓶颈 | 是否只用当前权重 | 权重衰减是否过大/过小 | 开启EMA、调整weight_decay 显存不足导致跑不动 | 优化器状态占用 | batch size是否太大 | 用8-bit优化器、gradient accumulation、Adafactor

这张表不能直接给出万能解答,但大概率能帮你把排查范围缩小到两三个变量以内,剩下就要靠单变量实验来定位了。

6. 最后说几句:我把这套方案沉淀成Model-Optimizer后的体会

整理这套项目最大的收益,并不是某个优化器比另一个好这种结论,而是让我养成了把训练稳定性当作一个整体来审视的习惯。现在每次启动新训练,我都会按固定顺序检查:数据格式是否正确、优化器配置是否匹配模型类型、学习率范围有没有测过、要不要开梯度裁剪和EMA、混合精度会不会引入数值问题。这个清单看起来琐碎,但它帮我避开了大量"训练了好几天最后发现白跑"的悲剧。

关于优化器的选择,我自己也经历了从迷信Adam到重新审视SGD,再到理解AdamW细节的转变。现在我不会随便说哪个优化器最好,因为脱离任务谈优化器没有意义。同一个模型,换一个优化器、换一组调度策略,训练曲线和最终精度都可能截然不同。

如果这篇内容对你有一点帮助,那我的建议是:不要照抄任何一份配置,先跑一次LR Range Test,再做一组小规模单变量对比实验,然后用统一配置中心把这组实验固定下来。Model-Optimizer本身只是工具,真正有价值的,是你对模型训练过程的理解和尊重。下次训练不稳定时,至少不再只是盯着loss曲线发呆,而是能按逻辑一步步找到问题所在。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 6:22:23

Agentic AI能跑Demo,为什么一上项目就崩?先把这三笔账算清楚

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 6:21:24

Hatch 构建配置完全指南:从文件选择到可复现构建

开发工具构建工具 【免费下载链接】hatch Modern, extensible Python project management 项目地址: https://gitcode.com/gh_mirrors/ha/hatch 点击查看 免费下载 本篇技术指南以 Hatch 项目的 docs/config/build.md 为骨架,系统讲解构建配置的核心主题…

作者头像 李华
网站建设 2026/9/29 6:19:22

TensorFlow工业落地实战:从环境配置到边缘部署全链路避坑指南

1. 这不是“又一个深度学习框架”——TensorFlow 是怎么从实验室走向产线的你搜“tensorflow”,页面上跳出来的全是安装报错、版本冲突、CUDA不匹配、GPU识别失败……但真正用过三年以上 TensorFlow 的人,第一反应不是“怎么装”,而是“这个模…

作者头像 李华