首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【Debug日志 | 模型loss不降调试】

【Debug日志 | 模型loss不降调试】

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

模型“就是不学”:loss 不降、梯度全是 None

在我们进行深度学习网络训练的过程中,经常会遇到损失不降、训练完全不收敛的情况,并且在训练期间, acc 接近随机、学习率/优化器怎么调都无效。为了更系统的剖析其中的原因,本章节将从实际例子出发,记录debug的过程以及最终的可能问题定位。

❓ Bug 现象

  • 训练 3–5 个 epoch:loss ≈ 0.693±0.001(二分类随机水平),acc ≈ 50%。
  • 调大学习率、换 Adam/SGD、关 AMP、换 batch size,均无改善
  • 打印 grad_norm 发现经常是 0非常小;很多参数 p.grad is None

📽️ 场景复现

为了“便于日志和可视化”,我在前向里对特征做了 .detach(),同时在正则里用了 .data 原地裁剪权重,顺手还做了个原地归一化。

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

class Net(nn.Module):
    def __init__(self): 
        super().__init__()
        self.feat = nn.Sequential(
            nn.Conv2d(3, 16, 3, padding=1), nn.ReLU(inplace=True),
            nn.AdaptiveAvgPool2d(1), nn.Flatten(),
        )
        self.fc = nn.Linear(16, 1)

    def forward(self, x):
        f = self.feat(x)              # [B, 16]
        f_log = f.detach()            # ❌ 为了可视化,提前 detach
        # …日志里用到了 f_log …

        # 原地归一化(in-place)
        f /= (f.norm(dim=1, keepdim=True) + 1e-6)   # ❌ in-place 可能破坏版本计数
        return self.fc(f).squeeze(-1)

net = Net().cuda()
opt = torch.optim.AdamW(net.parameters(), lr=1e-3)
scaler = torch.cuda.amp.GradScaler()

def l2_sp_regularizer(m: nn.Linear, tau=1e-4):
    # 为了稀疏化,做了个软阈值
    with torch.no_grad():
        m.weight.data = torch.clamp(m.weight.data, -1.0, 1.0)  # ❌ .data + 原地
    return tau * (m.weight.abs().mean())  # 看似没问题

for step in range(200):
    x = torch.randn(64, 3, 224, 224, device="cuda")
    y = (torch.rand(64, device="cuda") > 0.5).float()

    opt.zero_grad(set_to_none=True)
    with torch.cuda.amp.autocast(True):
        logits = net(x)
        base = F.binary_cross_entropy_with_logits(logits, y)
        reg  = l2_sp_regularizer(net.fc, 1e-4)
        loss = base + reg

    scaler.scale(loss).backward()   # ⬅️ 梯度经常是 None 或极小
    scaler.step(opt); scaler.update()
可能存在的问题
  • 为了记录前向传播的过程,同时保证模型训练不变影响,我们常常通过detech来使特征脱离梯度计算图的计算,但f.detach() 之后对 f 的原地写入(f /= …)可能触发 version counter 冲突或让 Autograd 选择不追踪某些路径;
  • 正则里对 weight.data 的原地操作绕过 autograd,破坏优化器状态(如 Adam 的动量/二阶矩),出现“学一下又被硬改回去”的震荡;

Debug过程

1️⃣ Step 1:确认梯度有没有走到最后
  • 在关键层注册backward hook或对非叶子张量调用 retain_grad(),观察梯度。
代码语言:python
代码运行次数:0
运行
复制
def tap_grad(t, name):
    t.retain_grad()
    def _hook(grad): print(f"[{name}] grad_norm={grad.norm().item():.4e}")
    t.register_hook(_hook)
    return t

with torch.cuda.amp.autocast(True):
    f = net.feat(x)
    tap_grad(f, "feat")     # ✅ 非叶子张量需要 retain_grad 才能看到 .grad
    logits = net.fc(f)
    loss = F.binary_cross_entropy_with_logits(logits, y)

loss.backward()
# 观察是否有打印;若无,则在更前面打点,直到发现哪一段“消失”

现象:feat 的梯度没有打印,说明链路到这里已断。

2️⃣ Step 2:搜索 .detach() / .data / 原地操作
  • 全局搜关键字:.detach(.datainplace=True+=/-=/*=//=
  • 暂时改掉所有原地写法(使用 out = f / norm 之类非原地的重写),看看是否恢复。
  • 把正则里对权重的 .data 改成正常的损失或优化器钩子。
3️⃣ Step 3:开启异常检测确认链路
代码语言:python
代码运行次数:0
运行
复制
torch.autograd.set_detect_anomaly(True)
  • 在有些 in-place 修改场景下,能报出 one of the variables needed for gradient computation has been modified by an inplace operation,快速定位。

解决方案

1️⃣ 移除错误的 .detach(),日志/可视化用副本
代码语言:python
代码运行次数:0
运行
复制
# ✅ 用 clone().detach() 生成只用于日志的副本,不参与计算
f = self.feat(x)                  # 参与反传
f_for_log = f.detach().clone()    # 仅用于可视化,别再写回去
# …用 f_for_log 画图/记录…
2️⃣ 避免原地归一化
代码语言:python
代码运行次数:0
运行
复制
# ❌ f /= norm
# ✅
norm = (f.norm(dim=1, keepdim=True) + 1e-6)
f = f / norm
3️⃣ 正则/约束不要用 .data,改为显式 loss 或 optimizer hook
代码语言:python
代码运行次数:0
运行
复制
# ✅ 显式正则,进入计算图,由优化器“看得见”
def l2_sp_regularizer(m, tau=1e-4):
    return tau * m.weight.abs().mean()

# ✅ 如果要“硬裁剪”,用 optimizer hook 或 step 之后统一 clamp
@torch.no_grad()
def clamp_weights_(m: nn.Module, lo=-1.0, hi=1.0):
    for p in m.parameters():
        p.clamp_(lo, hi)

# 训练循环中:
scaler.scale(loss).backward()
scaler.step(opt); scaler.update()
clamp_weights_(net.fc)     # ✅ 在优化器更新后、no_grad 下原地裁剪

总结

“模型不学”的绝大多数原因,不在“学习率宇宙之谜”,而在计算图被不经意地剪断了。把 .detach() / .data / 原地操作这三件事盯住,防止计算图被切断。

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 模型“就是不学”:loss 不降、梯度全是 None
    • ❓ Bug 现象
    • 📽️ 场景复现
    • Debug过程
    • 解决方案
    • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档