<p>AdamW的weight_decay是解耦式权重衰减,非L2正则化:它在梯度清零后、参数更新前单独执行p.data -= lr wd p.data,避免与自适应学习率冲突,比Adam中耦合L2更稳定。</p>

AdamW的weight_decay参数不是L2正则化
很多人以为 weight_decay 就是直接往 loss 上加 0.5 * wd * sum(p**2),其实不是。AdamW 把权重衰减从优化器更新逻辑里单独剥离出来,在梯度清零后、参数更新前,对每个可训练参数 p 执行 p.data -= lr * wd * p.data。这避免了 Adam 原始实现(在 loss 中加 L2)和自适应学习率的冲突。
所以:如果你用 torch.optim.Adam 并设 weight_decay=1e-2,那它做的是带 L2 的 Adam;而用 torch.optim.AdamW 设同样值,才是标准的解耦式权重衰减——效果更稳定,尤其在大 batch 或预训练微调时。
正确传入weight_decay的方式
必须显式传给 AdamW 构造函数,不能靠 param_groups 里的默认值覆盖全局行为。常见错误是只在某组参数里写 weight_decay=0,却忘了其他组仍继承初始化时的全局 weight_decay。
- 全局统一衰减:
AdamW(model.parameters(), lr=3e-4, weight_decay=0.01) - 分层衰减(如 bias 不衰减、LayerNorm 不衰减):
no_decay = ["bias", "LayerNorm.weight"] param_groups = [ {"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 = AdamW(param_groups, lr=3e-4) - 注意:即使某组
"weight_decay": 0.0,也必须显式写出,否则该组会沿用构造函数的默认值
weight_decay设为0后仍有数值下降?
这是正常现象。权重衰减只作用于 requires_grad=True 的参数,且仅在 optimizer.step() 时生效。但以下情况会导致“看似有衰减”:
立即学习“Python免费学习笔记(深入)”;
- 模型中有未冻结的 BatchNorm 参数(
running_mean/running_var),它们不参与梯度更新,但weight_decay不影响它们 - 你用了
model.train()但没关 dropout,参数值波动是随机失活导致的,不是衰减 - 检查是否误把
lr_scheduler的 warmup 或 decay 当成了 weight_decay 效果(比如get_linear_schedule_with_warmup只调lr,不动wd) - 验证方法:打印某参数
p.data.norm().item()和p.grad是否为None,再确认该参数是否在当前param_groups中被正确归类
CPU/GPU下weight_decay行为一致吗?
一致。PyTorch 的 AdamW 在 CPU 和 CUDA 后端都实现了相同的解耦更新逻辑,包括权重衰减的时机和公式。但要注意:
- 混合精度训练(
torch.cuda.amp)下,weight_decay仍作用于 FP32 主参数(master weights),不受autocast影响 - 如果用了
torch.compile,目前(2.3+)已支持AdamW的完整语义,包括weight_decay,无需额外处理 - 极少数旧版本(
真正容易被忽略的是:当你用 torch.nn.utils.clip_grad_norm_ 之后再 step(),权重衰减仍会执行——它不看梯度是否被裁剪,只认参数本身。这点和学习率缩放不同,得心里有数。


















