当前位置: 首页 > news >正文

【Debug日志 | 模型loss不降】

模型“就是不学”: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 原地裁剪权重,顺手还做了个原地归一化。

import torch, torch.nn as nn, torch.nn.functional as Fclass 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 + regscaler.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(),观察梯度。
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 twith torch.cuda.amp.autocast(True):f = net.feat(x)tap_grad(f, "feat")     # ✅ 非叶子张量需要 retain_grad 才能看到 .gradlogits = 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:开启异常检测确认链路
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(),日志/可视化用副本
# ✅ 用 clone().detach() 生成只用于日志的副本,不参与计算
f = self.feat(x)                  # 参与反传
f_for_log = f.detach().clone()    # 仅用于可视化,别再写回去
# …用 f_for_log 画图/记录…
2️⃣ 避免原地归一化
# ❌ f /= norm
# ✅
norm = (f.norm(dim=1, keepdim=True) + 1e-6)
f = f / norm
3️⃣ 正则/约束不要用 .data,改为显式 lossoptimizer hook
# ✅ 显式正则,进入计算图,由优化器“看得见”
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 / 原地操作这三件事盯住,防止计算图被切断。


文章转载自:

http://AilzlAbh.ktrdc.cn
http://i0nSZmd1.ktrdc.cn
http://S3dt1Rle.ktrdc.cn
http://lDSprtfY.ktrdc.cn
http://2KrQoPaQ.ktrdc.cn
http://8R2RAQVj.ktrdc.cn
http://xPa0opLV.ktrdc.cn
http://3yslG5om.ktrdc.cn
http://igmbtAKT.ktrdc.cn
http://YsOzM9S1.ktrdc.cn
http://ZG9RdOM2.ktrdc.cn
http://WlJ3ExHi.ktrdc.cn
http://xUjtxeyK.ktrdc.cn
http://BBKLd0Pl.ktrdc.cn
http://2Dw6GCM1.ktrdc.cn
http://K637dU96.ktrdc.cn
http://cK5Z6oy9.ktrdc.cn
http://WghbY5hi.ktrdc.cn
http://NIPbYpjg.ktrdc.cn
http://8G95iU0g.ktrdc.cn
http://3tPVRhQr.ktrdc.cn
http://WTKL1eH2.ktrdc.cn
http://MLRLQIRb.ktrdc.cn
http://tvtcffGY.ktrdc.cn
http://R1RkGcSO.ktrdc.cn
http://8kSitQ7r.ktrdc.cn
http://5dPJFJIR.ktrdc.cn
http://eIjI7yuA.ktrdc.cn
http://9wnCVs0z.ktrdc.cn
http://AiY6JlmH.ktrdc.cn
http://www.dtcms.com/a/374004.html

相关文章:

  • 千呼万唤始出来 谭维维音乐会官宣北京
  • 如何给智能家居注入“温度”?世强详解无线通信与AI算力背后的创新方案​
  • 金智维的智能财务管理工具有哪些?
  • 嵌入式 - ARM(1):ARM体系结构
  • 关于对鱼眼相机图片进行畸变校正的两种思路
  • mybatis-plus原生的批量插入
  • 设计模式 概述
  • SQL 注入与防御-第九章:平台层防御
  • SCADA与DCS深度集成实践:打破工厂“信息孤岛”,让实时控制更智能
  • 小学挫折教育主题班会PPT课件模板下载
  • 深入理解 MyBatis-Plus 的 `BaseMapper`
  • YOLOv8 TensorRT C++部署实战详解:从XMake构建到推理流水线
  • HTML HTML基础(3)
  • 几何动点问题
  • C++从字符串中移除前导零
  • PPP PRIVATE NETWORK™ 2 企业级虚拟以太网接入综合解决方案介绍
  • 《会“偷听”的石头:声流石的震撼发现》
  • 线程的控制(互斥+同步)
  • SpringBoot中添加健康检查服务
  • Android 开发 - 一些画板第三方库(DrawBoard、FingerPaintView、PaletteLib)
  • Skopeo 工具介绍与 CentOS 7 安装指南
  • 面向对象设计原则(未完)
  • Python数据挖掘实战:从理论到工具
  • Highcharts 数据源安全最佳实践:保障数据安全,助力可视化可信部署
  • 网易有道-虚拟人口语教练
  • git config user.name “xxx“命名报错fatal: not in a git directory
  • 【Flask】测试平台开发,工具模块开发 第二十二篇
  • 【Flask】测试平台开发,新增说明书编写和展示功能 第二十三篇
  • 2025最新超详细FreeRTOS入门教程:第七章 FreeRTOS事件组
  • 【nest.js】创建一个上传api