weight_decay是优化器参数而非模型参数,仅在优化器初始化时生效,对param_groups中所有参数统一施加L2正则;需手动分组才能跳过偏置,且不可与手动添加L2 loss等价。

weight_decay 是优化器参数,不是模型参数
很多人以为 weight_decay 是加在模型层上的(比如误在 nn.Linear 里设),其实它只在优化器初始化时生效,是优化器对所有可训练参数统一施加的 L2 正则项。PyTorch 不会自动区分“权重”和“偏置”,只要参数在 optimizer.param_groups 里,就全被正则——除非你手动分组。
- 正确写法:
torch.optim.SGD(model.parameters(), lr=0.01, weight_decay=1e-4) - 错误写法:
nn.Linear(10, 5, weight_decay=1e-4)(weight_decay不是nn.Linear的合法参数) - 如果你只想对权重正则、跳过偏置,得手动拆分参数组:
optimizer = torch.optim.Adam([ {'params': [p for name, p in model.named_parameters() if 'bias' not in name]}, {'params': [p for name, p in model.named_parameters() if 'bias' in name], 'weight_decay': 0} ])
weight_decay 和手动加 L2 loss 效果不等价
直接在 loss 里写 loss + 1e-4 * sum(p.pow(2).sum() for p in model.parameters()) 看似一样,但实际梯度更新逻辑不同:前者在 optimizer.step() 内部做梯度缩放(grad += weight_decay * param),后者是 loss 前向传播中叠加,涉及二阶导或混合精度训练时行为可能偏差。尤其用 torch.cuda.amp 时,手动加 loss 容易因 scaler 缩放导致正则强度失真。
- PyTorch 官方推荐始终用优化器的
weight_decay,而非手写 L2 项 - AdamW 就是为修正 Adam 在
weight_decay上的实现缺陷而生——老版 Adam 的weight_decay实际是 L2 loss 模拟,AdamW 才是真·权重衰减 - 验证是否生效?打印
optimizer.param_groups[0]['weight_decay'],别只看初始化有没有写
不同优化器对 weight_decay 的处理差异很大
weight_decay 在 SGD 和 Adam 中默认都支持,但 Adam 的原始实现(PyTorch torch.optim.Adam 默认修复了该问题(通过 decoupled_weight_decay=True),但旧代码或自定义优化器仍可能沿用旧逻辑。
- 稳妥起见,显式用
torch.optim.AdamW替代Adam,尤其做对比实验时 - SGD 的
weight_decay行为稳定,就是标准 L2 梯度项,无歧义 - AdamW 的
weight_decay参数意义和 SGD 一致,但实现路径不同,不能简单认为“数值相同效果就相同”
weight_decay 大小需要配合学习率一起调
decay 强度不是绝对值,而是相对于学习率的相对强度。例如 lr=0.1, weight_decay=1e-4 和 lr=1e-3, weight_decay=1e-4 的实际正则力度差 100 倍。常见做法是把 weight_decay 设为 lr 的 1%~10%,比如 lr=3e-4 时用 1e-5~3e-5。
- 过大(如
weight_decay=1e-2配lr=1e-3)会导致参数快速趋零,训练停滞 - 过小(如
weight_decay=1e-8)基本不起作用,尤其 batch size 大时梯度噪声本身就有隐式正则效果 - ResNet 类结构常用
weight_decay=1e-4,ViT 类常用weight_decay=0.05(因 LayerNorm 和 embedding 不适合强 L2)
实际项目里最常漏掉的是:没检查 param_group 是否真的包含了全部待优化参数,或者用了冻结参数(requires_grad=False)后还往里面塞——这些参数会被 weight_decay 忽略,但你自己可能以为“全模型都在正则”。











