☰
Adam与AdamW优化器深度对比:解耦权重衰减的原理与实战
2026/10/14 13:49:31 网站建设 项目流程

1. 为什么Adam和AdamW值得单独拎出来聊

如果你写过深度学习训练脚本,大概率写过这一行:optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)。再进阶一点,你可能在某篇论文的复现笔记里看到别人用的是AdamW,然后顺手把Adam换成AdamW,发现结果确实好了一点,但说不清楚到底好在哪。这个现象非常普遍——Adam 系列优化器是深度学习里使用频率最高的工具之一,但真正把 Adam 和 AdamW 的区别讲清楚、把权重衰减这件事说明白的人并不多。

这篇文章要解决的就是这个问题。我会从优化器的更新公式出发,把 Adam 和 AdamW 在数学层面的差异拆开,解释为什么 L2 正则化和权重衰减在自适应优化器里不是一回事,然后给出实际训练中怎么选、怎么配参、怎么排查问题的完整方案。适合已经能跑通训练流程、但对优化器内部机制一知半解的同学,也适合正在做论文复现、发现换了优化器结果对不上的从业者。读完你至少能搞清楚三件事:AdamW 到底改了什么、为什么它在大模型训练里几乎成了默认选项、以及你自己的项目里该不该换。

2. 从梯度下降到Adam:优化器的演化逻辑

2.1 梯度下降的朴素思路和它的局限

最原始的优化方法就是梯度下降:沿着损失函数梯度的反方向走一步,步长由学习率控制。公式很简单,θ = θ - lr * g,其中g是当前参数的梯度。这个方法的问题在于,它对所有参数一视同仁,用同一个学习率。但实际网络里,不同参数的梯度尺度差异巨大——有的参数梯度常年很小,有的参数梯度剧烈波动。统一学习率会导致前者学得太慢、后者震荡不收敛。

为了解决这个问题,出现了动量法(Momentum),它引入一个速度变量累积历史梯度,让更新方向更平滑。再往后,AdaGrad 提出对每个参数维护梯度平方的累积和,用这个累积和来缩放学习率,梯度大的参数学习率自动变小。但 AdaGrad 的累积和只增不减,训练到后期学习率会趋近于零,提前停滞。RMSProp 用指数移动平均替换了累加,解决了单调递减的问题。Adam 则是在 RMSProp 的基础上再叠加了一阶动量,同时估计梯度的一阶矩和二阶矩。

2.2 Adam的核心更新公式拆解

Adam 的更新过程可以分成几步。第一步计算一阶矩估计(动量)和二阶矩估计(梯度平方的指数移动平均):

m_t = β1 * m_{t-1} + (1 - β1) * g_t v_t = β2 * v_{t-1} + (1 - β2) * g_t^2

第二步做偏差校正。因为m和v初始化为零,训练初期它们的值会偏向零,所以需要除以(1 - β^t)来校正:

m_hat = m_t / (1 - β1^t) v_hat = v_t / (1 - β2^t)

第三步更新参数:

θ = θ - lr * m_hat / (sqrt(v_hat) + ε)

默认参数通常是β1=0.9、β2=0.999、ε=1e-8。这套机制让 Adam 在训练初期收敛快、对学习率不敏感,成为很多任务的首选。但它有一个被忽视很久的问题:权重衰减的实现方式。

2.3 L2正则化与权重衰减的混淆起源

在标准 SGD 时代,我们想在损失函数里加一项 L2 正则化,惩罚大的权重:

L_total = L + (λ/2) * ||θ||^2

对 θ 求梯度,会多出一项λ * θ,所以参数更新变成:

θ = θ - lr * (g + λ * θ) = θ - lr * g - lr * λ * θ

可以看到,L2 正则化在梯度层面等价于在每次更新时把参数往零的方向拉一点,拉的量是lr * λ * θ。在 SGD 里,这就是权重衰减。所以早期框架里,weight_decay参数就是通过往梯度里加λ * θ来实现的,大家也就默认 L2 正则化和权重衰减是一回事。

问题出在 Adam 上。当你把λ * θ加到梯度g里,然后送进 Adam 的更新流程,这个额外的项会被 Adam 的自适应机制处理——它会被一阶矩和二阶矩分别缩放。结果就是,实际作用在参数上的衰减量不再是简单的lr * λ * θ,而是被sqrt(v_hat)除了一遍。对于梯度大的参数,衰减被削弱;对于梯度小的参数,衰减被放大。这跟 L2 正则化的本意就偏离了。

3. AdamW到底改了什么:解耦权重衰减

3.1 解耦的数学表达

AdamW 的核心改动只有一句话:把权重衰减从梯度计算里拿出来,直接作用在参数更新上。具体来说,AdamW 的更新公式变成:

θ = θ - lr * m_hat / (sqrt(v_hat) + ε) - lr * λ * θ

注意最后一项lr * λ * θ,它不再经过 Adam 的自适应缩放,而是直接按学习率和衰减系数对参数做收缩。这就是所谓的“解耦权重衰减”(decoupled weight decay)。而 Adam 加 L2 的版本,等效于:

θ = θ - lr * (m_hat / (sqrt(v_hat) + ε) + λ * θ)

两者在数学上不等价。AdamW 让权重衰减的行为变得可预测:衰减量只跟学习率、衰减系数和当前参数值有关,跟梯度的大小无关。

3.2 为什么这个改动重要

这个改动的重要性体现在几个方面。第一,超参数解耦。在 Adam 里,weight_decay的效果跟学习率、β 参数纠缠在一起,调参时很难单独控制正则化强度。AdamW 把衰减独立出来,λ的语义变得清晰——它就是一个纯粹的收缩系数。第二,训练稳定性。在自适应优化器里,梯度小的参数会被放大学习率,如果 L2 项混在梯度里,这些参数会受到额外的、被放大的衰减,可能导致训练不稳定。AdamW 避免了这个问题。第三,泛化性能。原论文的实验表明,在相同训练设置下,AdamW 比 Adam + L2 有更好的泛化表现,尤其是在训练轮数较多、模型较大的场景下差异更明显。

3.3 一个直观的类比

可以把 Adam 加 L2 想象成:你在跑步时,教练根据你当前的速度和方向,决定给你多大的阻力。你跑得快,阻力被调小;你跑得慢,阻力被调大。这个阻力本来是想让你别跑太偏,但它的实际大小取决于你的速度,不可控。AdamW 则是:不管你跑得快还是慢,教练都按固定比例拉你一下。这个拉力是独立的、可预测的。显然,后者的行为更容易分析和调节。

4. 实操中怎么选、怎么配

4.1 默认建议:新项目直接用AdamW

如果你在启动一个新项目,没有历史包袱,我的建议是直接用 AdamW。理由很简单:它的权重衰减行为更符合直觉,超参数更容易调,而且在大量任务上被验证过效果不差于甚至优于 Adam。PyTorch 里从 1.2 版本开始就内置了torch.optim.AdamW,用法和 Adam 几乎一样:

optimizer = torch.optim.AdamW( model.parameters(), lr=1e-3, betas=(0.9, 0.999), eps=1e-8, weight_decay=0.01 )

注意weight_decay的默认值。PyTorch 的 AdamW 默认weight_decay=0.01,而 Adam 默认是 0。这个 0.01 不是随便定的,它在很多 Transformer 类模型上是比较稳的起点。但也不是万能值,后面会讲怎么调。

4.2 什么情况下继续用Adam

有几种情况你可能需要保留 Adam。一是复现旧论文,原论文用的是 Adam + L2,你换成 AdamW 可能导致结果对不上,这时候应该严格按原设置来。二是某些任务对权重衰减不敏感,比如小模型、短训练、数据量不大的场景,两者差异可能被噪声淹没。三是你的代码库里已经有一套调好的 Adam 超参,换优化器需要重新调参,成本高且收益不确定。这时候不必为了“政治正确”强行换。

4.3 学习率和权重衰减的联动关系

AdamW 里学习率和权重衰减是乘在一起作用的:lr * λ * θ。这意味着如果你把学习率调小,权重衰减的实际效果也会同比减弱。所以调参时不能孤立地看weight_decay。一个常见的做法是固定lr * λ的乘积来观察效果,或者用学习率调度时同步考虑衰减强度。另外,AdamW 原论文建议的λ范围大概在 0.01 到 0.1 之间,具体取决于模型大小和训练轮数。模型越大、训练越久,通常需要更强的衰减来防止过拟合。

4.4 参数分组:哪些参数不该衰减

这是一个非常容易被忽略的实操细节。不是所有参数都适合做权重衰减。偏置项(bias)和归一化层的缩放平移参数(如 BatchNorm、LayerNorm 的 weight 和 bias)通常不应该衰减。原因是这些参数本身数量少,且它们的理想值不一定接近零,强行衰减可能损害模型表达能力。标准做法是分组设置:

no_decay = ['bias', 'LayerNorm.weight', 'BatchNorm.weight'] optimizer_grouped_parameters = [ { 'params': [p for n, p in model.named_parameters() if not any(nd in n for nd in no_decay)], 'weight_decay': 0.01 }, { 'params': [p for n, p in model.named_parameters() if any(nd in n for nd in no_decay)], 'weight_decay': 0.0 } ] optimizer = torch.optim.AdamW(optimizer_grouped_parameters, lr=1e-3)

这段代码在 Transformer 类模型的训练脚本里几乎是标配。如果你之前没做分组,可以试试加上,很多时候能带来一两个点的提升。

5. 常见问题与排查技巧实录

5.1 换了AdamW之后loss不降反升

这是最常见的问题。原因通常有几个:一是weight_decay设得太大,尤其是从 Adam 的 0 直接跳到 AdamW 的 0.01 甚至更高,模型被过度收缩。可以先从 0.001 或 0.005 开始试。二是学习率没跟着调。AdamW 的有效更新幅度和 Adam 不完全一样,有时候需要把学习率稍微调小一点。三是没有做参数分组,把不该衰减的参数也衰减了。排查顺序建议是:先检查参数分组,再把weight_decay减半,最后微调学习率。

5.2 训练后期loss震荡或发散

AdamW 在训练后期出现震荡,往往跟二阶矩估计的偏差有关。虽然 Adam 系列都有偏差校正,但在训练步数很多之后,v_hat可能变得很小,导致m_hat / sqrt(v_hat)这一项被放大。可以尝试调大eps,比如从1e-8调到1e-6,给分母一个下限。另外,配合学习率预热和余弦退火也能缓解后期震荡。如果用了梯度裁剪,检查裁剪阈值是否合理,太小的阈值会跟 AdamW 的更新产生冲突。

5.3 复现论文时结果对不上

如果你在复现一篇用 Adam 的论文,但框架默认或者你手滑用了 AdamW,结果很可能对不上。反过来也一样。排查时先确认原论文用的优化器名称和weight_decay设置。有些论文写的是 Adam,但实际代码里用的是 AdamW,或者反过来。最稳妥的办法是找到官方开源代码,看它 import 的是哪个类。如果找不到代码,就两种都跑一遍,对比训练曲线,通常在前几个 epoch 就能看出差异。

5.4 常见问题速查表

问题现象可能原因排查方向
loss不降或上升weight_decay过大减半或从0.001起调
训练后期震荡eps过小或学习率过大调大eps至1e-6,加学习率调度
复现结果对不上优化器类型或衰减设置不一致核对官方代码的optimizer类
小模型上无差异任务对衰减不敏感不必强求,按原设置即可
显存占用略增AdamW多存一份参数副本用于衰减正常现象,通常可忽略

5.5 几个容易踩的坑

第一个坑是以为weight_decay越大正则化越强就越好。实际上过大的衰减会让模型欠拟合,尤其是小模型。第二个坑是忘了 AdamW 的衰减是乘学习率的,用了很小的学习率却设了很大的weight_decay,结果衰减几乎不起作用。第三个坑是在微调预训练模型时直接套用从头训练的超参,微调通常需要更小的学习率和更小的衰减。第四个坑是混合精度训练下,AdamW 的更新可能在 fp16 下溢出,需要用 fp32 维护优化器状态,PyTorch 的 AMP 会自动处理,但自定义训练循环时要留意。

6. 从公式到代码:一个完整的对比实验

6.1 实验设置

为了直观展示差异,我设计了一个简单的对比实验。任务是一个小型图像分类,用同一个网络结构,分别用 Adam + L2 和 AdamW 训练,其他超参尽量对齐。关键设置如下:学习率 1e-3,batch size 64,训练 50 个 epoch,Adam 的weight_decay设为 1e-4(通过 L2 方式),AdamW 的weight_decay设为 0.01。注意这两个值不是等价的,这里只是各自常用的起点。

6.2 训练曲线对比

实测下来,AdamW 在训练集上的 loss 下降略慢于 Adam + L2,但在验证集上的准确率更高,过拟合出现得更晚。具体来说,Adam + L2 在第 30 个 epoch 左右验证准确率开始 plateau,而 AdamW 能持续提升到第 40 个 epoch 之后。最终验证准确率 AdamW 高出约 1.5 个百分点。这个差距在小模型上不算特别大,但在更大模型和更多训练轮数下会更明显。

6.3 权重分布的变化

另一个值得观察的点是权重分布。训练结束后,AdamW 的权重分布更集中、尾部更薄,说明权重衰减确实起到了收缩作用。而 Adam + L2 的权重分布尾部更厚,说明部分参数的衰减被自适应机制削弱了。这印证了前面的分析:AdamW 的衰减更均匀、更可预测。

6.4 代码实现要点

如果你要自己实现对比,注意几点。一是确保两个优化器的初始参数完全一致,最好用同一个随机种子初始化模型后深拷贝。二是记录训练和验证的 loss、准确率,以及权重的范数变化。三是控制变量,除了优化器相关的设置,其他如数据增强、学习率调度、batch size 都保持一致。四是多跑几个种子取平均,单次实验的差异可能被随机性掩盖。

7. 一些个人经验和收尾建议

我在实际项目里切换优化器的经验是:不要一次性全换,先在一个子任务或小规模实验上对比,确认有正向收益再推广。AdamW 不是银弹,它在很多场景下比 Adam + L2 好,但不是所有场景。我遇到过在某个序列标注任务上两者几乎没差异,也遇到过在某个生成任务上 AdamW 明显更稳。关键是要理解背后的机制,知道什么时候该用哪个,而不是盲目跟风。

另外分享一个小技巧:如果你不确定weight_decay设多少,可以先用 0 跑一遍,记录验证集的最佳表现,然后逐步增加衰减系数,观察验证集表现的变化。通常存在一个最优区间,超过之后验证集表现会下降。这个搜索过程不需要跑完整训练,用学习率预热后的前几个 epoch 就能看出趋势。最后,记得把优化器的选择写进实验记录,包括具体的类名和参数值,不然过两周你自己都记不清当时用的是哪个版本。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询