PyTorch 深度学习专题【左扬精讲】—— AdamW 优化器详解:解耦权重衰减的正确实现
PyTorch 深度学习专题【左扬精讲】—— AdamW 优化器详解:解耦权重衰减的正确实现
AdamW 是深度学习中广泛使用的优化器,全称是 "Adam with Decoupled Weight Decay",即带解耦权重衰减的 Adam。
2019 年由加州大学伯克利分校的研究者提出,是对标准 Adam 优化器的重要改进。
本文将详细讲解 AdamW 的核心改进、与 Adam 的区别、PyTorch 中的使用方法,以及在实际训练中的最佳实践。
torch.optim.AdamW ← AdamW 优化器(解耦权重衰减)
torch.optim.Adam ← Adam 优化器(耦合权重衰减)
torch.optim.lr_scheduler ← 学习率调度器
PyTorchAdamW优化器正则化权重衰减
学习重点
- 必须掌握
- AdamW 与 Adam 的核心区别:权重衰减的解耦方式
- PyTorch 中 AdamW 的参数含义和默认值
- AdamW 的更新公式和代码实现
- 理解即可
- AdamW 的收敛性质和收敛速度分析
- AdamW 与其他优化器的对比
目录
一、Adam 优化器回顾
What — Adam 优化器是什么?
Adam(Adaptive Moment Estimation)是由 Diederik P. Kingma 和 Jimmy Ba 于 2014 年提出的自适应学习率优化器,结合了 RMSProp 和动量法的优点。它通过估计梯度的一阶矩(动量)和二阶矩(自适应学习率)来自适应地调整每个参数的学习率。
Adam 的核心特点:
- 自适应学习率:每个参数有自己的学习率,由二阶矩估计控制
- 动量加速:使用指数移动平均的梯度一阶矩来加速收敛
- 偏差校正:对初始阶段的一阶和二阶矩估计进行校正
Why — 为什么需要 Adam?
问题一:传统 SGD 的问题是什么? 传统 SGD 对所有参数使用固定学习率,导致收敛慢且不稳定。对于稀疏特征,学习率过大会导致频繁更新的参数发散,学习率过小又导致稀疏特征收敛慢。
问题二:Adam 如何解决这些问题? Adam 为每个参数维护自适应的学习率。对于梯度较大的参数,学习率自动变小;对于梯度较小的参数,学习率保持较大。这使得 Adam 特别适合处理稀疏梯度的场景。
问题三:Adam 的参数有哪些?
- lr:学习率,默认 0.001
- betas:一阶和二阶矩估计的指数衰减率,默认 (0.9, 0.999)
- eps:数值稳定性常数,默认 1e-8
没有 Adam 会发生什么?
- 稀疏特征训练困难,需要手动调整不同参数的学习率
- 收敛速度慢,容易陷入局部最优
- 对学习率选择敏感,需要精细调参
Adam 的核心更新公式:
# 梯度计算
g_t = gradient # 当前梯度
# 一阶矩估计(动量)
m_t = beta1 * m_{t-1} + (1 - beta1) * g_t
# 二阶矩估计(自适应学习率)
v_t = beta2 * v_{t-1} + (1 - beta2) * (g_t ** 2)
# 偏差校正
m_hat = m_t / (1 - beta1 ** t)
v_hat = v_t / (1 - beta2 ** t)
# 参数更新
theta_t = theta_{t-1} - lr * m_hat / (sqrt(v_hat) + eps)
本节小结
- Adam:自适应矩估计,结合动量和 RMSProp
- 自适应学习率:每个参数有独立的学习率
- 核心参数:lr、betas、eps
二、AdamW 的核心改进
What — AdamW 是什么?
AdamW 是 Adam with Decoupled Weight Decay 的缩写,由 Loshchilov 和 Hutter 于 2019 年在论文《Decoupled Weight Decay Regularization》中提出。AdamW 的核心改进是将权重衰减从梯度更新中解耦出来,单独应用于参数更新。
AdamW 与 Adam 的唯一区别在于权重衰减的处理方式:
- Adam:权重衰减与梯度更新耦合(在梯度中混入权重衰减)
- AdamW:权重衰减与梯度更新解耦(单独从参数中减去权重衰减)
Why — 为什么需要 AdamW?
问题一:Adam 中的权重衰减有什么问题? 在标准 Adam 中,权重衰减项会被二阶矩估计缩放,导致不同参数的权重衰减强度不同。这使得权重衰减的实际效果难以预测,超参数选择变得困难。
问题二:AdamW 如何解决这个问题? AdamW 将权重衰减独立出来,直接从参数值中减去衰减量,与梯度更新分开计算。这样每个参数的权重衰减强度是均等的。
问题三:AdamW 的收敛性更好吗? 论文证明,解耦权重衰减使得 AdamW 在理论上更接近 SGD 的收敛性质,同时保留了 Adam 的自适应学习率优点。这使得 AdamW 在训练 Transformer 类模型时表现尤为出色。
没有 AdamW 会发生什么?
- 权重衰减效果不可预测,超参数难以调优
- 大模型训练中可能无法有效控制权重增长
- 收敛性质偏离理论预期
AdamW 的更新公式与 Adam 的区别在于权重衰减的应用方式:
# AdamW 的更新步骤
# 步骤1:计算一阶和二阶矩估计(与 Adam 相同)
m_t = beta1 * m_{t-1} + (1 - beta1) * g_t
v_t = beta2 * v_{t-1} + (1 - beta2) * (g_t ** 2)
# 偏差校正
m_hat = m_t / (1 - beta1 ** t)
v_hat = v_t / (1 - beta2 ** t)
# 步骤2:梯度更新(不含权重衰减)
theta_temp = theta_{t-1} - lr * m_hat / (sqrt(v_hat) + eps)
# 步骤3:应用权重衰减(解耦,独立于梯度)
theta_t = theta_temp - lr * weight_decay * theta_temp
# 等价于 L2 正则化项:lambda * ||theta||^2 / 2
本节小结
- AdamW:解耦权重衰减的 Adam
- 核心改进:权重衰减独立于梯度更新
- 优势:权重衰减效果可预测,收敛性更好
三、权重衰减:耦合 vs 解耦
What — 权重衰减是什么?
权重衰减(Weight Decay)是一种正则化技术,通过在参数更新时额外减去一部分参数值来防止权重过大。在数学上,权重衰减等价于 L2 正则化,但实现方式不同。
权重衰减的作用:
- 防止权重过大,提高模型泛化能力
- 约束模型复杂度,减少过拟合
- 改善数值稳定性
Why — 耦合与解耦的区别是什么?
问题一:Adam 中权重衰减是如何耦合的?
在标准 Adam 中,如果我们想加入权重衰减,实际上是通过将 weight_decay * parameters 加到梯度中来实现的。这意味着:
- 权重衰减的效果会被二阶矩估计 v_t 缩放
- 不同参数的衰减强度不同(取决于其二阶矩)
- 超参数 weight_decay 的含义变得模糊
问题二:AdamW 如何解耦权重衰减?
AdamW 直接将 lr * weight_decay * theta 从参数中减去,独立于梯度更新。这意味着:
- 每个参数的权重衰减强度相等
- weight_decay 参数的含义清晰明确
- 与 SGD + L2 正则化的行为更接近
问题三:为什么解耦后效果更好?
解耦后,权重衰减的作用不受梯度历史影响。在 Adam 中,大的梯度会导致小的二阶矩,进而导致大的衰减;而小的梯度会导致大的二阶矩,进而导致小的衰减。这种依赖关系使得权重衰减的效果不可预测。
用代码对比 Adam 和 AdamW 在权重衰减处理上的差异:
# Adam 的权重衰减(耦合)
# 衰减项被 v_hat 缩放,强度不一致
def adam_update_coupled(params, grads, m, v, lr, weight_decay, eps, v_hat):
# 梯度中混入权重衰减
grad_with_decay = grads + weight_decay * params
# 更新
params = params - lr * m / (sqrt(v_hat) + eps)
# 效果:衰减强度受 v_hat 影响
# AdamW 的权重衰减(解耦)
def adamw_update_decoupled(params, grads, m, v, lr, weight_decay, eps, v_hat):
# 步骤1:先做梯度更新(不含衰减)
params_after_grad = params - lr * m / (sqrt(v_hat) + eps)
# 步骤2:再应用权重衰减(独立于梯度)
params = params_after_grad - lr * weight_decay * params_after_grad
# 效果:每个参数的衰减强度相等
重要区别
- Adam 的 weight_decay 效果被二阶矩缩放,不等价于真正的 L2 正则化
- AdamW 的 weight_decay 等价于 SGD + L2 正则化
- 当 weight_decay 较大时,两者的差异更明显
本节小结
- 耦合权重衰减:衰减效果被梯度缩放,不同参数强度不同
- 解耦权重衰减:衰减效果独立,每个参数均等
- AdamW:解耦方式使得权重衰减可预测
四、PyTorch 中的 AdamW
What — torch.optim.AdamW 是什么?
torch.optim.AdamW 是 PyTorch 官方实现的 AdamW 优化器。它继承自 torch.optim.Optimizer 基类,提供了与标准 Adam 完全一致的接口,只是更新逻辑使用了解耦的权重衰减。
Why — 如何正确使用 AdamW?
问题一:AdamW 的主要参数有哪些?
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| params | iterable | — | 待优化参数 |
| lr | float | 0.001 | 学习率 |
| betas | tuple | (0.9, 0.999) | 一阶和二阶矩估计的衰减率 |
| eps | float | 1e-8 | 数值稳定性常数 |
| weight_decay | float | 0.0 | 权重衰减系数 |
| amsgrad | bool | False | 是否使用 AMSGrad 变体 |
问题二:weight_decay 的推荐值是多少?
常用的 weight_decay 值在 0.01 到 0.1 之间。对于 Transformer 类模型(如 BERT、GPT),常用的值是 0.01 或 0.05。实际值需要根据具体任务调整。
问题三:amsgrad 参数是什么?
AMSGrad 是 Adam 的一个变体,使用全局最大二阶矩而不是指数移动平均。这确保了学习率单调递减,有助于收敛到更稳定的解。对于某些任务,AMSGrad 可能表现更好。
import torch
import torch.nn as nn
import torch.optim as optim
# 创建模型
model = nn.Linear(10, 5)
# 创建 AdamW 优化器
optimizer = optim.AdamW(
model.parameters(), # 待优化参数
lr=1e-3, # 学习率,默认 0.001
betas=(0.9, 0.999), # 动量参数,默认 (0.9, 0.999)
eps=1e-8, # 数值稳定性,默认 1e-8
weight_decay=0.01, # 权重衰减,常用值 0.01~0.1
amsgrad=False # 是否使用 AMSGrad
)
# 训练循环
for data, target in dataloader:
optimizer.zero_grad() # 清零梯度
output = model(data) # 前向传播
loss = nn.MSELoss()(output, target) # 计算损失
loss.backward() # 反向传播
optimizer.step() # 更新参数
from torch.optim.lr_scheduler import CosineAnnealingLR, OneCycleLR
# 方法1:配合余弦退火学习率
optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.01)
scheduler = CosineAnnealingLR(optimizer, T_max=10) # 10 个 epoch 周期
for epoch in range(10):
train(model, dataloader)
scheduler.step() # 更新学习率
# 方法2:使用 OneCycleLR(推荐用于 Transformer 训练)
optimizer = optim.AdamW(model.parameters(), lr=1e-4, weight_decay=0.01)
scheduler = OneCycleLR(
optimizer,
max_lr=1e-3, # 最大学习率
epochs=10, # 总 epoch 数
steps_per_epoch=len(dataloader)
)
for epoch in range(10):
for batch in dataloader:
optimizer.zero_grad()
loss = model(batch)
loss.backward()
scheduler.step() # 每个 step 更新学习率
AdamW vs Adam 的选择建议
- 对于 Transformer 类模型(BERT、GPT、VIT 等),优先使用 AdamW
- 对于其他任务,可以尝试 AdamW,通常效果不会差
- 当需要精确控制权重衰减效果时,使用 AdamW
本节小结
- AdamW API:与 Adam 接口一致,只需替换优化器类型
- weight_decay:常用 0.01~0.1
- amsgrad:可选的收敛稳定化选项
- 配合调度器:CosineAnnealingLR、OneCycleLR 等
五、FAQ(20 组)
FAQ — 精选 20 问,深入理解 AdamW
Q1. AdamW 和 Adam 的核心区别是什么?
权重衰减的应用方式不同:Adam 将权重衰减耦合到梯度中,AdamW 将权重衰减解耦出来单独应用。 在 Adam 中,权重衰减的效果被二阶矩估计缩放,导致不同参数的衰减强度不同。在 AdamW 中,权重衰减独立于梯度更新,每个参数的衰减强度相等。
Q2. 为什么 AdamW 在 Transformer 训练中表现更好?
Transformer 模型参数量大、训练轮次多,权重增长问题更明显。 AdamW 的解耦权重衰减可以更有效地控制权重增长,同时保持 Adam 的自适应学习率优势。这使得训练更加稳定,收敛到更好的解。
Q3. AdamW 的 weight_decay 和 L2 正则化有什么区别?
在 SGD 中,weight_decay 等价于 L2 正则化。但在 Adam 中,两者不等价。 标准 Adam 的 weight_decay 会被二阶矩缩放,实际效果偏离 L2 正则化。AdamW 的 weight_decay 等价于真正的 L2 正则化。
Q4. weight_decay 的值如何选择?
常用的 weight_decay 值在 0.01 到 0.1 之间。 对于小型数据集或容易过拟合的任务,可以使用较大的值(如 0.1)。对于大型预训练模型微调,通常使用较小的值(如 0.01 或 0.05)。
Q5. amsgrad 参数的作用是什么?
AMSGrad 使用全局最大二阶矩而不是指数移动平均,确保学习率单调递减。 这有助于收敛到更稳定的解。对于某些任务,AMSGrad 可能帮助改善泛化性能,但并非所有任务都需要它。
Q6. AdamW 可以和梯度裁剪一起使用吗?
可以,且经常一起使用。 梯度裁剪(通常设置为 1.0)可以防止梯度爆炸,AdamW 可以控制权重增长。两者配合使用是大模型训练的标准做法。
for batch in dataloader:
optimizer.zero_grad()
loss = model(batch)
loss.backward()
# 梯度裁剪
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
Q7. AdamW 和 SGD 哪个更好?
取决于任务和模型规模。 对于小型模型和简单任务,SGD + 动量可能足够好。对于大型模型(如 Transformer)和复杂任务,Adam/AdamW 通常更容易调优且收敛更快。
Q8. 为什么 AdamW 的学习率通常比 SGD 低?
Adam 使用自适应学习率,已经对梯度进行了缩放。 如果使用 SGD 的学习率乘以相同的缩放因子,会导致更新过大。因此 Adam 的学习率通常比 SGD 低 10-100 倍。
Q9. AdamW 中的 eps 参数有什么用?
eps 用于防止除零错误,提高数值稳定性。 当二阶矩估计 v 很小时,除以 sqrt(v) + eps 可以避免数值溢出。默认值为 1e-8,对于某些高精度要求的场景可以适当增大。
Q10. 如何判断 AdamW 是否收敛正常?
观察 loss 曲线和梯度范数。 正常的 loss 应该稳定下降。如果 loss 震荡或发散,可能是学习率过大或权重衰减过大。可以观察参数范数,正常情况下应该保持稳定。
Q11. AdamW 可以用于强化学习吗?
可以,但需要谨慎。 强化学习中梯度通常噪声很大,Adam 的自适应学习率可以帮助处理。但强化学习的优化器选择通常依赖于具体算法,某些算法可能有特定要求。
Q12. weight_decay 对偏置项有影响吗?
通常不对偏置项应用权重衰减。 在 PyTorch 中,bias 通常和权重分开处理。如果对 bias 也应用权重衰减,可能会影响模型的表达能力。实践中通常只对权重(weight)应用权重衰减。
# 只对权重应用权重衰减
optimizer = optim.AdamW([
{'params': model.weight, 'weight_decay': 0.01},
{'params': model.bias, 'weight_decay': 0.0} # 偏置不衰减
], lr=1e-3)
Q13. AdamW 和 Adam 的收敛速度有区别吗?
两者收敛速度相近,但 AdamW 的收敛轨迹更接近 SGD。 由于权重衰减的解耦,AdamW 的收敛性质更稳定,最终解的质量通常更好。
Q14. 为什么预训练模型微调常用 AdamW?
预训练模型已经学习了良好的特征表示,微调时需要小心不要破坏这些表示。 AdamW 的可控权重衰减可以防止模型在微调过程中权重变化过大,从而保留预训练学到的知识。
Q15. AdamW 的 betas 参数如何调整?
betas 的默认值 (0.9, 0.999) 在大多数情况下效果良好。 如果数据噪声较大,可以增大 beta1(如 0.95)来获得更平滑的动量。如果需要更快的响应,可以减小 beta1。
Q16. AdamW 可以和其他正则化技术一起使用吗?
可以,AdamW 通常与其他正则化技术配合使用。 常见的组合包括:梯度裁剪、Dropout、标签平滑、数据增强等。
Q17. 为什么有时候 AdamW 训练出来的模型泛化性能不如 SGD?
Adam 的自适应学习率可能导致模型停留在较宽的极小值点。 SGD 的固定学习率天然倾向于收敛到更窄的极小值点,后者泛化性能更好。通过适当的 weight_decay 和学习率调度,可以改善 AdamW 的泛化性能。
Q18. AdamW 的 weight_decay 和 learning rate 有什么关系?
两者需要配合调整。 较大的学习率需要配合较大的权重衰减来平衡。如果发现 loss 震荡,可以同时减小学习率和权重衰减,或者只减小其中一个。
Q19. 如何从 Adam 迁移到 AdamW?
只需将优化器类型从 Adam 改为 AdamW,参数保持不变。 由于 AdamW 只是更新方式不同,weight_decay 的效果会更接近 SGD + L2 正则化。迁移后可能需要微调 weight_decay 参数。
# 从 Adam 迁移到 AdamW
# 之前
optimizer = optim.Adam(model.parameters(), lr=1e-3, weight_decay=0.01)
# 之后
optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.01)
Q20. AdamW 在分布式训练中如何使用?
PyTorch 的 DistributedDataParallel 与 AdamW 兼容。 只需在每个进程中创建独立的 AdamW 优化器实例,同步由 DDP 框架处理。
import torch.distributed as dist
# 每个进程独立创建优化器
model = DistributedDataParallel(model)
optimizer = optim.AdamW(model.parameters(), lr=1e-3)
for batch in dataloader:
optimizer.zero_grad()
loss = model(batch)
loss.backward()
optimizer.step()
FAQ 总结
- AdamW vs Adam:权重衰减的解耦方式不同
- Transformer 训练:推荐使用 AdamW
- weight_decay:常用 0.01~0.1
- AMSGrad:可选的收敛稳定化
- 配合使用:梯度裁剪、学习率调度器
六、Roadmap 预告
下篇预告:《学习率调度器详解:从 StepLR 到 CosineAnnealing》
优化器决定了如何更新参数,而学习率调度器决定了学习率如何随时间变化。在下一篇中,我们将深入学习:
- 学习率调度的重要性
- StepLR、MultiStepLR、ExponentialLR 的原理
- CosineAnnealing 和 Warmup 的使用
- OneCycleLR 和 ReduceLROnPlateau
敬请期待!

浙公网安备 33010602011771号