首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【Debug日志 | Adam“捣蛋鬼”】

【Debug日志 | Adam“捣蛋鬼”】

原创
作者头像
九年义务漏网鲨鱼
发布2025-09-15 14:34:49
发布2025-09-15 14:34:49
1000
代码可运行
举报
文章被收录于专栏:tencent cloudtencent cloud
运行总次数:0
代码可运行

准确率“卡着不动”:Adam(L2) 把 LayerNorm 与 bias 也权重衰减了——一次权重衰减配置错误的排障记录(含可复用分组脚本)

在我们在训练一个 Transformer 小模型(中文分类 + 预训练继续训练都试过)。loss 能降一点但很快平台化,验证集准确率一直在 70% 左右“挪不动”。尝试了调 LR/批量/warmup 都不灵。

❓ Bug 现象

  • 训练前 1–2 epoch loss 快速下降;验证集 acc 长期在一个低值附近抖动
  • 观察参数范数:很多 LayerNorm.gamma 与 bias 的权重范数逐 epoch 显著变小
  • weight_decay 暂时关掉,或改用 AdamW + 合理分组后,曲线立刻恢复“阶梯式上升”

📽️ 场景复现

代码语言:python
代码运行次数:0
运行
复制
import torch, torch.nn as nn

model = tiny_transformer()  # 含 Embedding + LayerNorm
opt = torch.optim.Adam(model.parameters(), lr=3e-4, weight_decay=0.1)

for step, batch in enumerate(loader):
    loss = model(**batch)
    opt.zero_grad(set_to_none=True)
    loss.backward()
    opt.step()

症状:很快平台,LayerNorm.weight 范数持续下降;embedding 也被明显压小。

Debug过程

1️⃣ 打印参数分组与范数/梯度范数

代码语言:python
代码运行次数:0
运行
复制
def stats(model):
    import math
    g = {}
    for n,p in model.named_parameters():
        if not p.requires_grad: continue
        g[n] = dict(
            shape=tuple(p.shape),
            norm=float(p.norm().item()),
            gnorm=float(p.grad.norm().item()) if p.grad is not None else float('nan')
        )
    print("LN.gamma example:", {k:v for k,v in g.items() if "LayerNorm.weight" in k and v['norm']})

观察几个 epoch 后 LN/bias 的 norm 是否持续走低、gnorm 是否异常大/小。

2️⃣ 快速 A/B

  • weight_decay=0 试 1–2k steps:若曲线明显回升,基本锁定 WD 相关。
  • AdamW(其他不变):若也回升,进一步确认“耦合 L2”的负面效应。

✅ 代码修改

1️⃣ AdamW + 参数分组

代码语言:python
代码运行次数:0
运行
复制
from torch.optim import AdamW

def build_param_groups(model, wd=0.01, layerwise_lr=None):
    no_decay = []
    decay = []
    for n, p in model.named_parameters():
        if not p.requires_grad: 
            continue
        # 规则:bias / 归一化层 / 嵌入层 -> no_decay
        if n.endswith(".bias") or "norm" in n.lower() or "layernorm" in n.lower() or "embedding" in n.lower():
            no_decay.append(p)
        else:
            decay.append(p)
    groups = [
        {"params": decay, "weight_decay": wd},
        {"params": no_decay, "weight_decay": 0.0},
    ]
    # 可选:层别学习率(layer-wise LR decay)
    if layerwise_lr:
        for g in groups:
            g["lr"] = layerwise_lr
    return groups

groups = build_param_groups(model, wd=0.01)
optimizer = AdamW(groups, lr=3e-4, betas=(0.9, 0.999))

经验值wd 0.01~0.1 需视任务;若用了强数据增广/Dropout,可适当调低。 兼容性:DDP/FSDP/AMP/torch.compile 正常;FSDP 下尽量在 wrap 之后构建参数分组。

2️⃣ 仍用 Adam,但采用解耦式衰减

如果必须用 Adam(比如与历史实验对齐),可以手动实现“解耦式”:

代码语言:python
代码运行次数:0
运行
复制
opt = torch.optim.Adam(build_param_groups(model, wd=0.0), lr=3e-4)

for step, batch in enumerate(loader):
    loss = model(**batch)
    opt.zero_grad(set_to_none=True)
    loss.backward()
    opt.step()
    # 额外做解耦衰减
    with torch.no_grad():
        for g in opt.param_groups:
            wd = g.get("weight_decay", 0.0)
            if wd > 0:
                for p in g["params"]:
                    p.mul_(1 - g["lr"] * wd)

3️⃣ 避免“双重衰减”

  • 不要同时 AdamW(weight_decay=...) + 在 loss 里手动加 L2(λ * ||w||²
  • 若用 EMA/WD schedule,注意与优化器的 WD 不要叠加出奇怪组合。

✔️ 验证

  • 换成 AdamW + 参数分组 后,同样超参下验证集有所提升
  • 训练更“阶梯式”上升,不再平台;
  • 记录的 LayerNorm.weight / bias 范数趋稳,不再被拉向 0。

🟢 常见易混点 Q&A

  • 为什么 LN/BN/Embedding 不做 WD?
    • LN/BN 权重控制尺度与分布对齐,被 L2 拉小会破坏表征稳定;
    • Bias 仅平移,做 WD 收益小且易妨碍收敛;
    • Embedding 维度大、稀疏更新、L2 容易让频繁词过度收缩。
  • HuggingFace 里怎么做?
    • get_parameter_names(model, [nn.LayerNorm]) 过滤;核心思路与本文一致。
  • 卷积/线性层的权重需要 WD 吗?
    • 一般需要(除非有强正则/Dropout);WD 能抑制过拟合、稳定训练。
  • FSDP/ZeRO 下怎么分组?
    • wrap/shard 完成后再构建 param groups,或用库提供的过滤器;确保名字/维度可访问。
  • 曲线仍然不升?
    • 再查:学习率调度是否按“优化步”步进、梯度裁剪是否过激、loss 是否正确(形状/掩码/shift)。参见我前几篇排障日志。

🔴 为什么这会致命?

  • Adam + L2Adam(..., weight_decay=λ))是耦合正则:在 梯度里加 λ·w做 Adam 的自适应缩放 → LN、bias 这类“尺度参数”被持续拉小,模型难以维持分布稳定。
  • AdamW解耦权重衰减更新时额外对 ww ← w - lr·λ·w,不进梯度统计,更稳定。
  • Transformer 类模型几乎都建议“不对以下参数做衰减”
    • 所有 bias
    • 归一化层权重LayerNorm.weight / BatchNorm.weight / RMSNorm.weight 等)
    • Embedding 权重(很多场景也不衰减,尤其词表大时)

总结

很多“怎么都学不动”的 Transformer 实际是权重衰减配置在作祟。最后定位是:把所有参数都做了 L2 正则(Adam(weight_decay=...)),导致 LayerNorm/Embedding/bias 也被衰减;再叠加“用 Adam + L2(耦合)而非 AdamW(解耦)”,等于双重惩罚关键参数,表现成“怎么调都上不去”。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 准确率“卡着不动”:Adam(L2) 把 LayerNorm 与 bias 也权重衰减了——一次权重衰减配置错误的排障记录(含可复用分组脚本)
    • ❓ Bug 现象
    • 📽️ 场景复现
    • Debug过程
    • ✅ 代码修改
    • ✔️ 验证
    • 🟢 常见易混点 Q&A
    • 🔴 为什么这会致命?
    • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档