news 2026/9/29 12:34:00

深度学习优化器全解析:从SGD到AdamW的训练调参实战

作者头像

张小明

前端开发工程师

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

模型优化器这个话题,我早就想好好写一篇了。Model-Optimizer,在深度学习圈子里被反复提起,却很少有人把它真正讲透。我个人的理解是:优化器是整个训练流程里最容易被低估、也最值得花时间研究的组件。你可以把模型结构设计得再精巧,数据预处理做得再干净,但如果优化器选错了、学习率没调对,前面所有努力都会葬送在一条不收敛的 loss 曲线上。这篇文章既讲优化器的算法原理,也把参数量、显存占用、分布式适配这些工程实际问题一并说清楚,还会延伸到我最近在做的模型压缩与推理加速。不管你是刚入坑的初学者,还是已经被 loss 震荡折磨到崩溃的调参老兵,都能在里面找到点能直接用的东西。

1. 训练卡壳时,问题大概率出在优化器选型

1.1 优化器在训练流程里到底扮演什么角色

一句话说清楚:模型是靠优化器“学”出来的。前向传播算出预测值,损失函数算出预测值和真实值的差距,反向传播算出每个参数的梯度,而优化器负责决定“接下来该怎么更新这些参数”。所以模型结构决定了模型的表达能力,数据决定了学习的上限,优化器则决定了这个模型能不能真正学到东西、学得快不快。

最朴素的更新方式是随机梯度下降(SGD):weight = weight - learning_rate * gradient。但实际训练中梯度本身有噪声、不同参数的量级差异大、损失曲面有崎岖和鞍点,单纯这样更新会导致收敛极慢甚至在鞍点附近原地打转。于是就有了动量法、自适应学习率、分层学习率等一系列改进。可以说,优化器的发展就是深度学习训练的“底盘进化史”。

1.2 选错优化器的典型症状

我在实际项目里见到的训练失败案例,很大一部分不是模型结构写错了,而是优化器配置不合理。典型症状有三种:

一种是 loss 完全不下降,曲线像条横线。这种情况多见于学习率设得太小,或者优化器与任务不匹配。比如你用纯 SGD 去训一个深层 Transformer,没有合适的学习率调度,收敛速度会慢到让你怀疑人生。

另一种是 loss 剧烈震荡、忽高忽低,像心脏起搏器记录图。这通常是学习率过大,参数在最优解附近反复弹跳。有时候震荡也发生在用 Adam 一类的自适应优化器时,因为二阶动量估计在训练初期不稳定,加上 warmup 没做好,前期 step 就崩了。

还有一种是“看起来收敛了,但验证集指标奇差”。这是过拟合吗?不一定。如果优化器过度拟合了训练集的噪声(比如 weight decay 设了 0,正则化完全缺失),或者早停策略没跟上,那么训练 loss 好看是假象。这类问题往往最难排查,因为 code 没错,但训练策略是错的。

2. 主流优化器算法拆解:SGD、Adam、AdamW、LAMB 各自擅长什么

2.1 SGD 与 Momentum:被低估的基线

很多人觉得 SGD 太古老、太慢,直接上 Adam。但在我做过的不少视觉模型(目标检测、分类、分割)和传统 NLP 任务里,SGD 配合 Momentum 和 cosine 学习率调度,最终的泛化精度经常能超过 Adam。原因在于 SGD 的更新轨迹更“稳”,它不做二阶矩归一化,所以对参数的更新幅度完全由学习率和梯度的真实大小决定,相当于一个不会过度放大历史梯度影响的简洁策略。在数据量充足、任务监督信号明确时,这种稳定性能带来更好的泛化。

动量(Momentum)的核心思想是把历史梯度的指数移动平均叠加到当前梯度上,相当于给参数更新加了“惯性”。数学形式是:

v = alpha * v + gradient weight = weight - learning_rate * v

这里 alpha 通常取 0.9 或 0.99。惯性带来两个收益:一是在梯度方向连续一致的平坦区域加速前进;二是在梯度方向频繁改变的沟壑区域抵消震荡。我调试时有个体会:如果 loss 曲线呈现高频小锯齿,先别急着降学习率,把 momentum 从 0.9 调到 0.99 往往比 learning rate 减半更有效。

2.2 Adam 与 AdamW:自适应学习率的代表和它的大模型微调版

Adam 在 2015 年提出来之后,迅速成为 NLP 和生成模型的默认选择。它维护两个状态:一阶动量(梯度的指数移动平均)和二阶动量(梯度平方的指数移动平均)。实际更新时,用一阶动量除以二阶动量的平方根,再乘上学习率。这样每个参数都有了自己的有效学习率:梯度大的方向更新幅度被压缩,梯度小的方向更新幅度被放大,对稀疏特征和不同尺度的参数都比较友好。

但 Adam 有个知名问题:权重衰减(weight decay)的处理不干净。标准 Adam 里的 L2 正则化直接把衰减项加进梯度里,再参与一阶动量和二阶动量的计算,会在 Adam 的归一化作用下产生不期望的影响,大学习率场景下尤其明显。AdamW 的改进是把 weight decay 从梯度里拆出来,直接在参数更新时做:

# AdamW 风格:weight decay 独立于梯度 param = param - lr * decay * param - lr * (m / (sqrt(v) + eps))

这个解耦在 Transformer 微调、大语言模型预训练和微调中被证明效果更好。所以我现在的原则是:新项目默认上 AdamW,特别是在做任何带 Attention 结构的模型时。Adam 那种“同时做归一化和正则化”的旧风格,在大模型时代基本退居二线了。

2.3 LAMB 与 LARS:大 batch 训练下的分布式救星

当训练 batch size 从几百涨到几万甚至几十万,普通 SGD 和 Adam 都会遇到一个麻烦:大 batch 下每个 step 的梯度估计方差变小,但更新步长不变,等于学习率相对变小了,收敛速度急剧下降。线性缩放学习率是一种办法,但缩放太大会让训练不稳定。LARS 和 LAMB 的做法是对“每层”或“每个参数”单独计算更新幅度,再用一个 trust ratio 去控制参数范数和更新范数的比例,使得大 batch 下仍然保持合理的相对步长。

LAMB 是 LARS 的 Adam 化变体,在 BERT 预训练、图像分类大 batch 训练里经常能看到它的身影。我在多机多卡训练实验里有这样的直观感受:同样的 ResNet-50 分类任务,batch size 从 256 涨到 8192,如果继续用普通 SGD,收敛 epoch 数几乎翻倍,换 LARS 后和 256 batch 的效果接近。如果你的实验环境允许大 batch,LARS/LAMB 值得认真考虑。

2.4 主流优化器属性速查

优化器核心机制显存额外占用适用场景常见问题
SGD原始梯度 + 学习率极低(仅动量)视觉模型、充分调参的基线收敛慢、对学习率敏感
Momentum梯度惯性叠加低(一个动量状态)大多数有监督任务方向噪声大时仍会震荡
Adam一阶+二阶矩归一化高(两个状态)Transformer、稀疏特征泛化略差、权重衰减耦合
AdamWAdam + 解耦权重衰减高(两个状态)大模型微调、预训练超参多、需要 warmup
LAMB/LARS逐层/逐参数信任比高大 batch 分布式训练对小 batch 收益不明显

3. 优化器超参调优实践:学习率、weight decay、warmup 和梯度裁剪

3.1 学习率:唯一的“一键决定成败”旋钮

学习率是优化器所有超参里最致命的一个。设置太低,训练需要上百甚至上千 epoch 才能收敛,而且容易陷入局部极值;设置太高,loss 直接爆炸成 NaN 或者永远不收敛。

我常用的策略是 cosine 退火 + 线性 warmup。线性 warmup 解决的是训练初期统计量还没稳定(尤其是 Adam 类优化器的二阶动量估计方差很大)时,防止大更新把参数推到糟糕区域。cosine 退火解决的是训练后期需要更小步长去精细逼近最优解的问题。这两者配合是当前最稳妥的通用策略。

一个可复用的配置示例(PyTorch 风格):

import torch.optim as optim from torch.optim.lr_scheduler import LinearLR, CosineAnnealingLR optimizer = optim.AdamW(model.parameters(), lr=1e-3, betas=(0.9, 0.999), eps=1e-8, weight_decay=0.01) # 线性 warmup + cosine 退火 total_steps = epochs * steps_per_epoch warmup_steps = int(0.1 * total_steps) scheduler1 = LinearLR(optimizer, start_factor=0.1, end_factor=1.0, total_iters=warmup_steps) scheduler2 = CosineAnnealingLR(optimizer, T_max=total_steps - warmup_steps) # 训练循环中前 warmup_steps 用 scheduler1,之后切换用 scheduler2

实际调参中的第一原则:先用一个小数据集(比如 1000 张图 / 几千条文本)跑 20 步,观察 loss 是否下降。如果下降缓慢,把学习率乘以 3 再试;如果 loss 升高但下降过头,learning rate 太大。这一步能快速锁定数量级。我对学习率的经验值范围是:

  • 纯 SGD + Momentum:0.01 到 0.1(视觉任务常见 0.01 起步)
  • Adam/AdamW:1e-4 到 1e-3(Transformer 微调常见 1e-5 到 5e-5)
  • 大模型(十亿参数以上)微调:1e-6 到 1e-5

3.2 weight decay 到底该设多少

weight decay 的作用是让模型的权重往零方向收缩,等效于 L2 正则化。在 SGD 下,weight decay 融合进梯度,相当于在每次更新时额外乘以(1 - lr * decay / N),会让大权重更快被压缩,控制模型复杂度,降低过拟合风险。

但 AdamW 里 weight decay 是解耦的,它不参与梯度计算,直接作用在参数更新上。这意味着即使梯度方向是让参数变大的,decay 项也会温和地拉回参数。这样表达力更强的同时,也要求你更谨慎地设置 decay 系数。

我的经验:视觉任务 weight_decay 从 1e-4 到 5e-4 起步;Transformer 微调通常用 0.01 到 0.1;超大模型(几十亿参数)反而经常把 weight decay 设成 0 或极小的 1e-3,因为参数已经足够多,正则化主要靠数据和随机性(dropout、数据增强)。如果你用的是旧版 PyTorch 的 Adam,建议不要设置非常大的 weight_decay,因为耦合进梯度的衰减会被二阶矩归一化放大,导致训练后期出现位姿漂移。

3.3 梯度裁剪:忘记它会让你半夜接到报警

梯度裁剪(Gradient Clipping)是在反向传播之后、参数更新之前,对梯度的范数做限制。它防止的是梯度爆炸,在带有 RNN、Transformer 深层结构、或者训练时间序列模型时尤其重要。不裁剪梯度的话,偶尔几条极端样本就能让参数瞬间跳到一个不可恢复的位置,loss 直接 NaN。

裁剪有两种常见方式:按值的裁剪(clip_value)和按范数的裁剪(clip_norm)。按值裁剪是逐元素把梯度限制到[-max_norm, max_norm],操作简单但会破坏梯度方向和不同参数之间的比例关系。按范数裁剪是把全局梯度向量缩放到最大范数以内,保留方向信息,实践中更推荐。我的默认配置是把 max_norm 设为 1.0,如果训练不稳定再降到 0.5。

# 训练步骤中,优化器.step() 之前 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

补充一句:裁剪不是越激进越好。max_norm 设太小(比如 0.01)会严重干扰真正的梯度信号,让训练变成“原地蠕动”。先设置为 1.0,再观察 loss 的波动幅度和梯度统计,按需调整。

3.4 一套我常用的调参路径

很多朋友问过我:“优化器参数到底从哪入手调?”我给的路径一般是这样:

先在 1/10 数据量上做短训练(比如 5~10 个 epoch),固定 batch size(常见 32 或 64),用 AdamW 起始配置,lr 设为 1e-3。看 loss 趋势:

  • loss 下降但很慢:先调大 lr 到 3e-3 或 5e-3。
  • loss 剧烈上升或 NaN:把 lr 降到 3e-4,并检查梯度是否爆炸。
  • loss 降到平台期后不再动:加入 cosine 退火和 warmup,观察最后期是否进一步下降。
  • 验证集精度与训练集差距过大(过拟合):增大 weight decay,或掉头换 SGD + momentum 并配合更强的数据增强。

这中间有个我踩过的坑:早期项目里我直接用别人代码里的 lr=1e-3 跑一个非常深的网络,连续两天 loss 不降,后来发现那个任务的 batch 只有 8,梯度噪声极大,1e-3 对这个小 batch 来说太大,每步都跑飞。所以 lr 不是绝对数,它和 batch size、任务难度、模型深度强相关。每次换 batch 大小,学习率都要重新审视。

4. 工程化落地:混合精度、分布式训练与优化器状态的显存优化

4.1 混合精度训练时优化器怎么“分身”

混合精度训练(AMP)在近年已经成为标配,尤其是大模型和视觉大 Batch 训练。核心思想是:前向和反向传播用 FP16 计算,用 FP32 保存一份“主权重”副本,更新参数时在 FP32 上做优化器更新,再把更新后的权重转回 FP16。为此,优化器需要维护两份状态:一份是 FP32 主权重,一份是历史梯度统计量。这意味着显存开销相比纯 FP32 并没有按 50% 减半,反而优化器状态占比更高。

计算显存的一个实用方法:对于 AdamW,每个参数需要保存 4 个 FP32 值:主权重、一阶动量、二阶动量、梯度本身,如果优化器再维护一个 FP32 的“参数主权重副本”,实际是 8 字节(两个 FP32)的基线开销。相比之下,纯 SGD + momentum 只需要 4 字节。也就是说,用 AdamW 时每 10 亿参数的显存开销至少是 8~12 GB,全加在优化器状态上。这就是为什么大模型训练倾向于用 SGD/LAMB 或引入优化器状态分区。

4.2 分布式训练中的优化器状态分区(ZeRO / DeepSpeed)

聊到分布式训练,就绕不开 ZeRO 策略。它的核心洞察是:数据并行训练时,每张卡都保存了一份完整的模型参数和优化器状态,但更新参数时各自只用了自己 batch 的那一小部分梯度,大部分显存其实在重复存储。ZeRO 把优化器状态(Adam 的一阶、二阶动量)、梯度、参数按层切分到不同 GPU 上,各卡只保存自己负责的那部分,需要时通过通信协议取回。现在 DeepSpeed 和 PyTorch FSDP 都已经把这些内置化了。

实际操作中,我在 8 卡 A100 上训练千亿参数模型时,第一步就是用 FSDP 或 DeepSpeed ZeRO 阶段 2/3。阶段 1 只切分优化器状态,显存节省大约 4 倍(相对 AdamW 而言);阶段 2 再切分梯度;阶段 3 连参数也切分。如果不用这些策略,模型直接 1 张 A100 都装不下,更别提训练了。所以“优化器状态怎么切”已经是工程层面比“优化器选哪种”更值得优先考虑的问题。

4.3 一个大模型场景下的优化器选型全景

到了十亿到百亿参数规模,纯 AdamW 的训练显存压力已经非常大。这时的主流方案有几个方向:

  • 继续用 AdamW,但开启 ZeRO 阶段 2/3 或 FSDP,把优化器状态切分出去。
  • 换用 LAMB,在大 batch 下保持稳定收敛,同时配合梯度累积降低通信频率。
  • 用 Adafactor(因子分解的二阶矩状态),把 Adam 的动量状态从每参数两个向量降低到行列低秩分解,推理时还能进一步节省内存。
  • 对超大规模稀疏模型(推荐系统、图模型),直接用 Adagrad 或类似稀疏友好优化器,因为特征维度极高、大量参数长期不更新,维护完整二阶动量是个浪费。

我自己在跨模态模型上试过 Adafactor 和 AdamW 的对比,效果差距在 1~2 个点内,但显存下降明显。如果显存吃紧,Adafactor 是个不该被忽视的选择。

5. 训练好了,优化器下岗了?模型推理优化同样不能缺位

5.1 推理阶段的模型压缩:剪枝、量化和蒸馏

必须澄清一个点:优化器只在训练阶段存在。模型训练完成部署时,我们面对的是另一类“模型优化”问题:如何在尽量不损失精度的前提下,把推理速度和显存占用降到可接受水平。我在实际项目里常用的三件套是结构化剪枝、量化(INT8/INT16)和知识蒸馏。

结构化剪枝的核心是去掉网络中对最终输出贡献最小的通道或层。做法是先训练一个完整模型,然后评估各通道的重要性(常见指标是权重幅度或梯度敏感度),把不重要的通道直接删除,再微调恢复精度。这里一个关键细节是:剪枝后必须做“重训练”或至少短周期微调,否则精度会掉得厉害。我遇到过一个 MobileNet 分类项目,直接剪掉 30% 通道再评估,精度掉了 4 个百分点,但加上微调 20 个 epoch 后,不仅精度回到原水平,速度还提升了近 40%。

量化做得更细:把 FP32 权重映射到 INT8,计算也改为整数运算。量化过程中的一个坑是“校准集”选择。我用过 1000 张训练图做校准,效果不错;但换到不同 domain 的图片做校准,激活值分布变化明显,精度直接崩。所以量化校准数据集必须贴近真实推理时的数据分布。

5.2 什么时候该做知识蒸馏

知识蒸馏的思路是训练一个小的学生模型,去模仿大教师模型的输出分布。很多人的误区是只要老师够大,学生就一定能学得好。实操中我摸出来的规律是:学生模型的容量不能太小,否则学不到教师模型的“暗知识”;教师模型的输出温度要调好(通常 3~5 之间),太低会只学到硬标签,太高会把信息完全抹平。我自己做语义分割蒸馏时发现,让学生同时学习教师模型的中间层特征会比只学输出 logits 稳定得多,这是受 1D/注意力蒸馏等工作的启发。

这三件套不是互斥的,实际部署时可以组合:先蒸馏一个轻量模型,再做通道剪枝,最后 INT8 量化。每一步都要验证精度回退,如果掉点超过可容忍范围,就回溯到上一步减少压缩力度。

6. 训练不收敛、指标上不去?这份排查清单我用了三年

6.1 loss 变成 NaN 的排查顺序

NaN 是训练里最让人头疼的问题。我排查它的顺序是:

  1. 检查学习率是否过大。这是最简单也最常见的因素,直接把 lr 乘 0.1 看 loss 是否恢复。
  2. 检查数据是否有 NaN/Inf 值。某些文本序列 padding 出错、图片含损坏像素,都能让反向传播算出 NaN。
  3. 检查损失函数计算过程:是否除以 0、是否取了负数的 log、是否在 softmax 前出现极端数值。
  4. 检查梯度是否爆炸:开启梯度裁剪再看是否稳定。
  5. 检查混合精度的 FP16 下溢/溢出:AMP 更新 master weight 前 main scale 是否有保护。

如果以上都查过还 NaN,就要看优化器状态里是不是混入 NaN 了。这时可以 dump 一下梯度和一阶/二阶动量,找找看是哪个 step 引入的异常。我印象里最好笑的一回是:权重初始化里有一个 std=1 的线性层,在深层网络里经过几十层累积后梯度彻底爆炸,换初始化方式比改优化器还见效。

6.2 验证集精度上不去,不是优化器问题怎么办

如果 loss 在验证集上没有持续下降,先别急着继续调 lr 和 weight decay。这时候往往是这些地方出了问题:数据分布和任务不匹配、标签噪声过高、模型容量不足、正则化太重。

我有一个判断习惯:看训练集的 loss 是不是降到非常低。如果训练集 loss 已经很低但这个模型在验证集上表现仍然差,那大概率是过拟合或者数据代表性不足,和优化器的关系不大。这时候先把优化器固定为当前查到的合理配置,转而去清理验证集、增加数据增强、做交叉验证,往往比反复调优化器参数有效。

6.3 加速训练时显存爆了怎么办

大模型训练最容易遇到显存爆掉(OOM)。这不一定是模型太大,也可能是优化器状态占用的。推荐的排查顺序:

  • 如果用的是 AdamW,先减少 batch size(比如从 32 减到 16),同时按比例降低 lr,观察显存变化。
  • 开启梯度累积,等效增加 batch 但每步只用一小批数据更新,显存立刻降下来。
  • 开启混合精度 AMP,很多情况下能节省近一半显存。
  • 如果还不行,用 checkpointing(重计算)技术在反向传播时重新算前向结果,以时间换空间。
  • 最后才考虑模型架构精简,比如减少层数/隐藏维度。

这些手段优先级我基本没变过:AMP 永远先开,梯度累积次之,checkpointing 作为兜底。模型架构精简是最后手段,因为它会改变模型容量,影响精度和迭代节奏。

6.4 训练问题排查速查表

症状最可能原因优先处理动作次要手段
loss 完全不降学习率过小 / 优化器与任务不匹配lr 乘 10 试跑 20 步换 AdamW 或加 warmup
loss 爆炸或 NaN学习率过大 / 梯度爆炸 / 数据脏lr 乘 0.1,检查数据开梯度裁剪
loss 高频震荡学习率偏大 / 动量偏小lr 减半 / 调大 momentum换精确梯度裁剪
训练集 loss 低但验证集差过拟合 / 数据不均衡增大 weight decay换 SGD 或加数据增强
显存 OOM优化器状态占用大开 AMP / 梯度累积checkpointing、ZeRO
大 batch 收敛慢学习率缩放不足LAMB / LARSAdamW + 更大 warmup

最后再分享一个我个人的实际体会:优化器不是什么神秘黑魔法,它就是一把“训练方向盘”,转向灵敏度和阻尼都可调。选型和调参的底层逻辑永远是匹配你的数据规模、模型结构、显存约束和收敛目标。每次换模型或换数据集,我都会先回到这篇文章里的检查清单过一遍,基本没有踩过无法挽回的坑。把优化器理解透,训练事故至少能少一半。

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

你管这破玩意叫 MCP?用 TaoToken 统一 Key 打通 Cline 配置

/* 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 12:29:24

Jupyter Notebook 7.0 汉化与默认路径修改完整指南

/* 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 12:28:42

基于pygame的节奏游戏敲击判定与双语歌词同步实现

做节奏类互动项目时,最容易让人头疼的往往不是界面怎么画,而是音频时间轴、画面渲染和玩家敲击这三者怎么对齐。网上资料要么只讲音游策划概念,要么只给零散的代码片段,真正能照着跑通一个完整示例的很少。标题里那句“【双语Full…

作者头像 李华
网站建设 2026/9/29 12:26:42

小型企业局域网搭建实战:从拓扑规划到VLAN、路由与ACL配置

简介:这份文档资料是一份完整的课程设计报告,主题为组建小型企业局域网,面向计算机网络相关专业的学生及需要完成组网实训的初学者。报告以50台计算机规模的小型企业网络为背景,系统梳理了从需求分析、设计原则到设备选型、综合布…

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

看懂 Git 团队协作全流程:分支、提交、PR、rebase 到底在干嘛

文章目录看懂 Git 团队协作全流程:分支、提交、PR、rebase 到底在干嘛一、一句话总览二、用"改合同"理解每个核心概念三、完整流程图(8 步)四、术语速查表(对照流程位置)五、最常问的几个问题1. 为什么不能直…

作者头像 李华
网站建设 2026/9/29 12:18:02

技嘉主板BIOS黑苹果关键设置全解析:CFG Lock、DVMT与Above 4G

1. 项目概述:为什么技嘉主板的BIOS设置是黑苹果落地的第一道生死关黑苹果玩家圈里有句老话:“装系统靠D大,进系统靠BIOS,稳运行靠驱动。”这话听着糙,但把技嘉(GIGABYTE)主板推上风口浪尖——不…

作者头像 李华