新闻详情

新闻详情

首页 / 资讯中心 / 详情

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

发布时间:2026/10/1 19:29:39来源:尧图网络
深度学习优化器选型与实战调参指南:从SGD到AdamW
很多刚接触深度学习的同学看到Model-Optimizer这个名字时会有点困惑这到底是指一个具体工具还是某类算法的统称其实在训练管线里它指向的通常就是那个控制模型参数如何更新的优化器Optimizer。我见过太多项目数据清洗做得挺好、模型结构也照抄了知名开源仓库结果训练一开跑loss飙成NaN或者收敛极其缓慢最后排查半天问题全在优化器这一环。这篇文章想把优化器到底在做什么、主流方案怎么选、背后机制怎么理解以及我在实战里踩过的调参坑一次讲清楚。内容不追求华丽但保证都可以直接复现适合刚入门的小白也适合已经训练过一些模型但常常被loss曲线折磨的朋友。1. 先厘清概念Model-Optimizer在你的训练管线里到底管哪一段1.1 从数据到参数的完整链条先把训练过程拆成一条最简链路前向传播把batch数据喂给模型计算预测值和损失函数值。反向传播根据损失函数对每个权重求解梯度。优化器更新用梯度以及优化器自己维护的状态计算出权重的更新量。循环上面三步直到loss不再下降或达到预设epoch。这个链条里很多人会把损失函数和优化器混为一谈。损失函数负责回答当前模型有多差优化器负责回答下一步该怎么调整权重才能让模型变好。换句话说同样的模型和损失函数换一个优化器训练轨迹和最终效果可能天差地别。我在实际项目里经常做这样的验证同样的数据、同样的模型结构、同样的损失函数把优化器从SGD换成Adam前几十个step的loss下降速度会有肉眼可见的差异。这也是为什么Model-Optimizer值得单独花时间研究——它是整个训练动力学的主导者却往往是被新手忽略的一环。1.2 下山类比三个角色各管什么损失函数可以想象成一片起伏不平的山地模型权重就是你在山上的位置目标是走到最低的谷底。梯度告诉你当前位置哪个方向是下坡优化器则负责决定你每一步怎么迈。如果用这个类比拆解事情会非常清晰损失函数地形本身越高代表当前模型越差。梯度你脚下的坡度方向指向局部最陡的下坡方向。优化器你的走路策略。可以每步一样长SGD可以带着惯性往前冲Momentum也可以根据每个方向的地形自动调整步长Adam。学习率在这套类比里就是步长。步长太大你一步跨过山谷直接跳到对面山坡甚至滚下悬崖步长太小你走得慢吞吞训练几小时还在原地。而自适应优化器的本质是让每个方向的步长都不太一样陡峭的地方小步走平坦的地方大步走。这样一想就明白为什么优化器选型和调参这么重要了它对训练效率和安全性的影响甚至高过模型结构设计。1.3 容易被忽略的代价优化器要额外占显存聊优化器时很多人的第一反应是反正就是一行代码SGD换Adam而已但这里有一个非常现实的工程问题显存开销。假设模型参数量为N每个参数用4字节的FP32存储纯SGD除了模型本身的4N字节权重优化器不额外维护多少状态显存开销很低。SGDMomentum需要额外维护一份动量的buffer大约4N字节。Adam需要维护一阶矩m和二阶矩v两份buffer额外8N字节。换句话说在一个1B参数规模的模型上用FP32训练模型权重本身占4GB纯SGD基本就这个量级Adam则会额外吃掉8GB显存用来存优化器状态。这个数字在工程师眼里非常惊人很多大模型训练之所以用混合精度一部分原因就是优化器状态太吃显存全部用FP32根本放不进去。我后来在实际部署和训练大模型时专门留意过优化器显存优化方案。比较常见的做法是混合精度训练框架比如AMP梯度用FP16存储但优化器状态保留FP32精度。8-bit优化器把Adam的m和v用8位整数存储显存直接砍掉近四分之三。优化器状态CPU offload让GPU只在需要更新时同步状态。这里要提醒一句不要无脑上这些省显存的方案8-bit优化器在部分框架里和自定义算子之间存在兼容性问题建议先用小模型跑通再上大规模任务。2. 主流优化器选型从SGD到AdamW每一代解决什么问题2.1 为什么SGDMomentum至今没有被淘汰在深度学习早期SGD是最朴素的优化器公式极其简单权重 权重 - 学习率 × 梯度但这个方案有两个明显问题一是收敛速度慢二是在崎岖地形上震荡非常严重。为了让SGD走出更好的轨迹工程师们加入了动量Momentum机制每次都保留一部分历史更新方向相当于给参数更新加了惯性。这样在沟壑地形中垂直方向的震荡被相互抵消水平方向的运动被保持训练自然更稳定、更快。有趣的是即使到了今天SGDMomentum在一批任务里依然是首选很多图像分类网络的baseline还是用SGD因为它在够大的训练数据和较好的学习率调度下表现出很好的泛化性。一些强化学习算法和传统机器学习baseline也用SGD因为它状态少、行为可预测。SGD最大的问题在于学习率敏感。你调一个合适的学习率可能要跑很多次实验如果学习率设置不当它要么收敛慢要么直接发散。这也是为什么很多新手一上来就用Adam——Adam对学习率的适应能力强得多。2.2 Adam怎么火起来的又是怎么被AdamW纠正的Adam的全称是Adaptive Moment Estimation它同时维护一阶矩和二阶矩估计。简单理解一阶矩m对梯度的指数移动平均相当于动量。二阶矩v对梯度平方的指数移动平均大致反映了每个参数维度的梯度波动幅度。更新时Adam用m除以v的平方根相当于给每个参数生成了独立的自适应学习率。如果一个参数的历史梯度一直很大那么它的有效学习率会自动变小如果某个参数几乎没有梯度有效学习率就会变大从而不错过潜在的有价值方向。这个机制让Adam在大多数任务上开箱即用尤其是NLP模型和Transformer几乎成了标配。但Adam提出后不久研究者们发现了一个隐藏问题Adam里引入的权重衰减weight decay实现方式有问题。原始Adam把L2正则化和梯度耦合在一起而L2正则项的梯度也会被Adam的自适应学习率缩放导致正则化效果被扭曲。AdamW的解决方案是解耦把权重衰减从梯度计算中分离出来在每次参数更新时直接让权重乘以一个略小于1的系数。这个看似微小的改动在很多Transformer训练任务上显著提升了效果。这也能解释为什么现在我们看到的大模型训练代码几乎都在用AdamW而不是原始Adam。2.3 大模型与分布式训练时代的变种进入大模型时代后优化器出现了几个重要的工程变种需要开发者了解它们的存在优化器核心机制优点典型用途SGDMomentum动量累积泛化好、显存占用低图像分类、传统baselineAdam一阶矩二阶矩开箱即用、对lr不敏感通用任务、快速验证AdamWAdam 解耦衰减正则化更合理Transformer、大模型微调LAMB分层自适应学习率超大batch稳定训练大规模预训练Adafactor低秩近似二阶矩节省显存长序列Transformer8-bit Adam量化优化器状态显存大幅下降单卡/多卡大模型训练比如LAMB它可以在batch size达到几万甚至几十万时保持训练稳定原因是它把每一层的权重更新按层级归一化避免某一层的梯度scale过大影响全局训练。而Adafactor用低秩分解来近似Adam需要的二阶矩显存占用比Adam低不少适合在长序列上节俭地训练。但坦率说对于大多数个人项目、中小型公司业务模型来说这些变种不是必需品。最稳妥的组合仍然是AdamW warmup weight decay 余弦退火调度。变种优化器通常是大规模分布式训练里才需要考虑的事。3. 从零手写一个优化器真正看懂SGD、Momentum、Adam3.1 先写一个最朴素的SGD很多框架封装的优化器像黑盒但优化器本身并不神秘。我用PyTorch风格给你写一个最小实现全程只有一次参数更新。import torch class PlainSGD: def __init__(self, params, lr0.01): self.params list(params) self.lr lr def step(self): with torch.no_grad(): for p in self.params: if p.grad is None: continue p - self.lr * p.grad核心逻辑就是遍历所有参数。如果该参数没有梯度跳过。在torch.no_grad()环境中原地执行数值更新。这里的p - self.lr * p.grad意味着参数朝着负梯度方向移动这正是梯度下降名称的由来。这个实现没有任何状态刚跑起来速度最快但对学习率选择非常敏感。3.2 加上Momentum给更新方向增加惯性Momentum的核心是在每个参数上维护一个历史梯度的移动平均buffer每次更新时先用buffer做平滑再用平滑后的方向去更新权重。class SGDMomentum: def __init__(self, params, lr0.01, momentum0.9): self.params list(params) self.lr lr self.momentum momentum self.buffer [torch.zeros_like(p) for p in self.params] def step(self): with torch.no_grad(): for p, buf in zip(self.params, self.buffer): if p.grad is None: continue buf.mul_(self.momentum) buf.add_(p.grad) p - self.lr * bufbuf就是之前的动量方向。每次更新时新的动量等于旧动量乘以momentum_factor再加上当前梯度。momentum0.9表示保留90%的历史方向当前梯度只提供10%的修正。这个机制让权重更新在陡峭方向被削弱、在平坦方向能持续加速。我在图像分类任务上做过对比同样的学习率下加入momentum后收敛曲线明显平滑很多训练前期的震荡幅度大幅下降。3.3 Adam一阶矩、二阶矩和偏差修正Adam的实现比SGD复杂一个量级但核心仍然可以写在几十行代码内。class SimpleAdam: def __init__(self, params, lr1e-3, betas(0.9, 0.999), eps1e-8): self.params list(params) self.lr lr self.betas betas self.eps eps self.m [torch.zeros_like(p) for p in self.params] self.v [torch.zeros_like(p) for p in self.params] self.t 0 def step(self): self.t 1 b1, b2 self.betas with torch.no_grad(): for p, m, v in zip(self.params, self.m, self.v): if p.grad is None: continue g p.grad m.mul_(b1).add_(g, alpha1 - b1) v.mul_(b2).add_(g * g, alpha1 - b2) m_hat m / (1 - b1 ** self.t) v_hat v / (1 - b2 ** self.t) p - self.lr * m_hat / (v_hat.sqrt() self.eps)这段代码要把握四个关键点一阶矩m对梯度的指数移动平均相当于自适应动量。二阶矩v对梯度平方的指数移动平均刻画了梯度幅度的历史水平。偏差修正训练初期m和v从零开始数值会偏小直接使用会低估动量、高估自适应步长。所以用1 - b1 ** t和1 - b2 ** t做分母修正。eps避免除零也影响更新的数值稳定性。在FP16混合精度训练里eps往往需要调大到1e-7甚至1e-6。如果你跑一下这个实现会发现Adam的更新其实是一种标准化的下降方向与全局学习率的组合梯度大不一定更新多反而被自适应缩放抑制。这正是Adam对不同参数尺度都能有效训练的原因。3.4 用玩具任务实测三种优化器的差别为了让原理落地我一般建议用一个极小的回归模型来对比。比如拟合一条带噪声的直线或者最小化一个二次型函数。用同一份数据分别用PlainSGD、SGDMomentum、SimpleAdam训练观察loss下降速度。我实测下来大概是这样Adam在最初的几十个step内loss下降非常快因为它自适应调整了每个方向的步长几乎不需要人工选学习率SGDMomentum在合适的lr下也能快速收敛但要调参PlainSGD最容易出现一步跨过头的现象学习率稍大loss就反弹。这个实验虽然简单但对建立直觉非常有帮助。你自己跑一遍之后就会理解为什么说Adam适合快速验证但SGDMomentum在精心调参后往往能收敛到更优泛化解。4. 训练实战中最容易翻车的优化器参数4.1 学习率决定生死但很多人只顾抄默认值我见过太多训练失败案例最后定位到的问题就一条学习率不对。学习率过大loss曲线直接起飞甚至变成NaN学习率过小loss像蜗牛一样爬几个epoch过去几乎不动。在常见任务里经验性起始范围大概是SGDSGDMomentum0.1到0.01之间配合学习率调度器。Adam / AdamW1e-4到3e-4之间这个范围在NLP任务上尤其常用。大模型预训练通常从1e-4级别起步甚至更低。预训练模型微调1e-5到3e-5很常见因为预训练权重已经很接近局部最优步长过大容易破坏已有特征。除了起始值warmup也很关键。所谓warmup就是在最初几百或几千步内让学习率从一个很小的值逐渐升到目标值。这样做的原因在于训练刚开始时模型权重处于随机状态梯度方向可能非常不稳定直接上大学习率很容易让参数跳到危险区域。warmup给了模型一个预热过程先稳住再加速。我自己的一个典型案例是训练一个中型的Transformer文本分类模型直接把学习率设成3e-3结果loss在前几百步内直接变成NaN排查了很久才发现是学习率过高加缺少warmup。改成3e-4并加入500步线性warmup后训练稳定了最终效果也达到了预期。4.2 beta与epsAdam里容易被忽略的隐藏旋钮Adam的默认beta参数是(0.9, 0.999)绝大多数人一辈子不改它。但某些任务场景下调整beta带来的收益甚至大过换优化器。beta1控制一阶矩动量的衰减速度。beta10.9代表历史动量保留90%更激进beta10.5会让动量衰减更快更贴近当前梯度适用于某些需要快速响应梯度变化的场景。beta2控制二阶矩梯度平方的衰减速度。在长时间训练任务中如果beta2过大比如0.9999二阶矩的估计窗口太长自适应学习率衰减过慢导致后期更新幅度偏大、loss震荡如果beta2过小比如0.9二阶矩对梯度波动太敏感更新步长会被过度缩放。另外eps也很有讲究。在FP16混合精度训练中FP16能表示的最小正数远大于FP32默认的eps1e-8在转换成FP16后可能被归零导致除零或者数值异常。所以我在混合精度训练时会把eps设为1e-6甚至1e-7先在小规模实验上验证稳定后再上大任务。4.3 weight decayL2正则与解耦衰减不是一回事weight decay是一个非常容易混淆的点。传统SGD里L2正则等效于weight decay因为L2正则项引入的梯度等于2×lambda×weight和直接weight decay在数学上是等价的。但在Adam这类自适应优化器里L2正则梯度同样会被自适应机制缩放导致正则化强度被扭曲这时候L2正则和weight decay就不再等价了。AdamW的思路是让weight decay独立于梯度自适应过程每次更新完参数后直接让所有权重乘以一个(1 - lr * weight_decay)的系数。这样权重衰减的强度与梯度的scale无关行为更可控。这也是为什么现在几乎所有Transformer训练代码都用AdamW而不是Adam。实际操作里weight decay的默认值建议从0.01开始尝试。如果你发现模型过拟合严重可以适当调大比如0.05如果发现欠拟合、训练loss下不去可以调小甚至改成0。有一点要注意SGD和AdamW的weight decay设置不能互相照搬前者的0.0001可能合理后者的0.0001很可能几乎没有效果。4.4 梯度裁剪防止训练被一个异常batch击穿梯度裁剪是在优化器更新之前把梯度的范数限制在一定范围内。它对长序列Transformer、GAN、强化学习这类训练不稳定的任务尤其重要。使用顺序固定为前向传播计算loss。反向传播得到梯度。梯度裁剪限制梯度范数。优化器step。可能的调度器step。PyTorch里的典型写法是loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step()max_norm的选择要根据实际梯度分布来确定。我习惯在训练刚开始时打印一段时间的梯度范数取一个比较有代表性的中位数偏大的值作为阈值。太小的max_norm会压制正常更新太大的阈值形同虚设。有一个反直觉的点梯度裁剪并不是越频繁越好。有时梯度范数异常偏大反而是梯度本身有意义的更新强行裁剪会导致震荡。所以除非任务本身非常不稳定我一般只在NLP大模型或GAN训练里使用并且会先用小模型跑几十步观察。5. 不同场景的优化器选型别拿同一套参数跑遍所有任务5.1 CV任务从头训练和微调是两个世界图像分类、目标检测、分割这些CV任务里从头训练的情况下传统做法是用SGDMomentum搭配warmup和余弦退火。SGD在足够的训练时长下往往有更好的泛化性很多经典模型复现的官方代码仍然使用它。但如果你是在ImageNet预训练模型基础上做下游任务微调情况就完全不同了。这时模型权重已经包含很强的先验特征AdamW配合小学习率能更快适应下游数据而且不容易破坏原有特征。我在做迁移学习时常用AdamWlr从1e-5起步观察验证集loss变化再逐步调整。简言之CV任务别盲目迷信某一个优化器先问自己一句我是在从头训练还是在微调。5.2 NLP与大模型AdamW是事实标准NLP领域几乎全面被AdamW统治。从BERT、GPT到各类LLM整理训练日志你会发现几乎都是AdamW weight_decay0.01 warmup 余弦调度。原因在于Transformer对自适应优化器的依赖性很强SGD直接训练Transformer效果通常很差收敛慢且不稳定。大模型训练里还有一个突出问题就是优化器状态显存占用。我前面提过1B参数量模型用AdamW优化器状态就可能占掉8GB以上。在这个体量下很多人会结合混合精度、梯度累积甚至8-bit Adam等手段来压显存。如果batch size需要扩大到几千甚至上万LAMB可能比AdamW更稳。LAMB在每层计算归一化的更新量让不同层的尺度差异不影响整体训练。但LAMB对超参数更敏感复现成本更高个人项目一般没必要碰。5.3 GAN与强化学习不稳定任务的特殊策略GAN训练最大的特点是生成器和判别器的训练目标相互博弈所以优化器选择会影响整个平衡。很多经典GAN实现使用Adam而不是SGD因为Adam的自适应特性让双方在对抗中保持相对稳定的更新幅度。我做过一个图像生成项目生成器用lr2e-4、判别器用lr1e-4各自使用Adambeta1甚至被调到0.5来降低动量惯性避免对抗过程中的振荡。强化学习则是另一个极端。策略梯度方法里单次采样带来的梯度方差极大因此极少用纯SGDAdam或RMSProp更常见而且梯度裁剪几乎是必然选项。在这些不稳定任务里优化器的选择更多是为了稳住训练而不是追求极致的收敛速度。汇总一下我的选型经验任务类型推荐优化器常用学习率关键注意点图像分类从头SGDMomentum0.1 → 调度下降warmup cosine图像分类微调AdamW1e-5 ~ 3e-5尽早观察过拟合趋势目标检测SGD(Momentum) / AdamW0.01 / 1e-4训练时长较长用SGD更稳NLP预训练AdamW1e-4 ~ 3e-4weight_decay0.01, warmupNLP微调AdamW1e-5 ~ 3e-5小lr防止破坏预训练特征GANAdam1e-4 ~ 2e-4beta1常设0.5强化学习Adam / RMSProp3e-4 ~ 1e-3配合梯度裁剪大规模分布式预训练AdamW / LAMB1e-4级别优化器状态显存优化6. 一个真实案例复盘优化器如何影响整个训练节奏6.1 从现象到配置一次文本分类任务的完整调优过程去年我做了一个文本情感分类项目数据集不算大约有20万条样本模型用的是预训练BERT后接分类头。我当时最直观的冲动是直接拿官方AdamW默认配置开跑于是初始lr5e-5weight_decay0.01batch_size32。结果跑了5个epoch验证集准确率只有80%出头明显不如预期。排查时我做了个对照组实验组A固定初始lr5e-5不做warmupweight_decay0.01。组B初始lr2e-5加500步warmupweight_decay0.01。组C初始lr2e-5加500步warmupweight_decay0.1。组D初始lr2e-5加500步warmupweight_decay0.01改用SGDMomentum。结果很有意思组B比组A提升了1.2个百分点说明warmup和稍低的学习率在微调场景里很重要组C的weight_decay过大后出现了明显欠拟合组D用SGD在20个epoch内都没追上AdamW的收敛速度。最终我用组B的组合训练准确率达到85.8%。这个项目让我真正意识到优化器参数不是一个设置好就完事的东西它和warmup、weight_decay、batch size、训练epoch紧密咬合任何一个失衡都会反映在最终的指标上。6.2 复盘要点优化器永远不是孤立的一个旋钮复盘下来我总结了三条经验第一任何优化器配置都不要直接照搬论文或开源代码你需要克隆一个最小实验把学习率、weight_decay、warmup、训练轮数当成一组整体变量去调而不是单独拧某个旋钮。因为它们在训练过程中会互相影响单独调lr而不动warmup效果可能很差。第二训练日志里一定要记录优化器状态相关的指标尤其是梯度范数、学习率实际变化曲线。很多异常训练问题比如loss突然变高、梯度爆炸都可以从这些指标里提前发现端倪而不是等loss出问题后再去猜。第三快速验证阶段优先用AdamW跑通逻辑后再根据任务特点换优化器。我见过不少同学在项目早期就在SGD和Adam之间反复横跳结果每个优化器都只跑了几个epoch根本不足以形成有效结论白白浪费算力。这个项目之后我的Model-Optimizer认知有了明显变化它不是一个独立的调参步骤而是整个训练管线的控制器。你选择怎样的优化器、怎样的学习率节奏、怎样的衰减策略几乎决定了模型在数据资源下能否训练成功。与其追求某个神级优化器不如把SGD、Momentum、AdamW这些基础方案的原理吃透然后在一个具体任务上老老实实做几组对照实验。我自己在踩过那些NaN、震荡、收敛停滞的坑之后现在更愿意在优化器环节多花两天时间而不是等到模型训了一半再回头改配置。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

太阳能路灯升压恒流驱动:PWM转模拟调光无频闪方案详解 2026/10/1 20:16:19

太阳能路灯升压恒流驱动:PWM转模拟调光无频闪方案详解

去年我们做太阳能路灯项目时,遇到过最头疼的问题就是调光闪烁。默认用PWM直接斩波驱动LED,视觉上人眼看不出来,但一到晚间补光灯配摄像头监控的场景就露馅了——画面里LED灯珠区域全是横向条纹,红外模式下更是闪成一片。后来把方案…

阅读更多 →
Windows驱动救急:从正常电脑导出驱动并手动安装完整指南 2026/10/1 20:16:13

Windows驱动救急:从正常电脑导出驱动并手动安装完整指南

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

阅读更多 →
华为杯A题解析:神经网络处理器多核调度的软硬协同建模 2026/10/1 20:16:13

华为杯A题解析:神经网络处理器多核调度的软硬协同建模

1. 这不是一道“纯数学题”:先看清A题背后的硬件战场“【2026年华为杯A题】通用神经网络处理器下的多核调度问题”——光看标题,很多人第一反应是:又一道带“调度”二字的运筹学老面孔,无非是把任务分给几个核,目标函数…

阅读更多 →
claudes-c-compiler前端完全拆解:C语言编译器四阶段解析深度指南 2026/10/1 20:16:12

claudes-c-compiler前端完全拆解:C语言编译器四阶段解析深度指南

claudes-c-compiler前端完全拆解:C语言编译器四阶段解析深度指南 【免费下载链接】claudes-c-compiler Claude Opus 4.6 wrote a dependency-free C compiler in Rust, with backends targeting x86 (64- and 32-bit), ARM, and RISC-V, capable of compiling a boo…

阅读更多 →
NVMe驱动开发速通:从PCIe枚举到QEMU实战 2026/10/1 20:16:06

NVMe驱动开发速通:从PCIe枚举到QEMU实战

刚接触NVMe的时候,我干过一件很蠢的事:插上新固态,系统识别了,就以为“驱动这东西不用管”。直到有一次在服务器上看到dmesg里一堆nvme相关日志,又在一个嵌入式项目里被要求把一块NVMe盘从底层跑起来,我才意…

阅读更多 →
艾思控电机驱动器深度拆解:双路/4路工业级选型与调试指南 2026/10/1 20:16:06

艾思控电机驱动器深度拆解:双路/4路工业级选型与调试指南

1. 项目概述:为什么“艾思控双路/4路电机驱动器”值得花时间拆解 “艾思控双路/4路电机驱动器”这个标题看起来平平无奇,就是个产品型号加功能描述,但如果你真在自动化设备、智能小车、3D打印机、CNC雕刻机或者教育机器人项目里摸爬滚打过&am…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉