pytorch中loss不下降的首要原因是未在loss.backward()前调用optimizer.zero_grad(),导致梯度累加或清零错位,使参数更新失效;需确保每个batch训练前独立、正确执行zero_grad()。

梯度无限累加,loss彻底卡死或爆炸,模型根本训不起来。
zero_grad() 放在 loss.backward() 之后就完蛋了
这是最典型的错位:先 loss.backward(),再 optimizer.zero_grad(),最后 optimizer.step()。此时梯度已经算完并存进 p.grad,但清零动作发生在更新前——等于把刚算出来的梯度直接抹掉,optimizer.step() 实际用的是全零梯度,参数完全不动。
- 现象:loss 曲线水平拉直,一动不动,哪怕训练几十个 epoch 也毫无变化
- 本质:不是“没更新”,而是“用零梯度更新”,等效于学习率为 0
- 注意:这种写法不会报错,极易被忽略,尤其当代码块较长、缩进混乱时
zero_grad() 只在 epoch 开头调一次也不行
有人图省事,在 for epoch in range(...) 循环开头调一次 optimizer.zero_grad(),以为“清过一次就够了”。但 PyTorch 的梯度是按 batch 累加的,每个 batch 的 loss.backward() 都会往已有 .grad 上叠加。
- 后果:第 2 个 batch 的梯度 + 第 1 个 batch 的梯度 → 第 3 个 batch 再叠加 → 梯度范数指数增长
- 典型表现:loss 先降后突升、出现
nan、权重爆炸、GPU 显存缓慢上涨 - 验证方法:在循环里加
print(torch.norm(torch.cat([p.grad.view(-1) for p in model.parameters() if p.grad is not None])),数值持续飙升就是它
多个优化器共存时,只清一个的坑
GAN、VAE 或 encoder-decoder 架构常含多个子网络和对应优化器(如 opt_g 和 opt_d)。若只对其中一个调 zero_grad(),另一个的梯度就会跨 batch 累积。
- 常见错误:训练判别器时清了
opt_d.zero_grad(),却忘了opt_g.zero_grad(),导致生成器梯度越积越多 - 正确做法:每个 optimizer 的
zero_grad()必须紧邻其对应的loss.backward()前,且各自独立执行 - 示例顺序:
opt_d.zero_grad()→loss_d.backward()→opt_d.step();紧接着opt_g.zero_grad()→loss_g.backward()→opt_g.step()
真正容易被忽略的点是:梯度清零不是“仪式性操作”,而是每次反向传播前的强制前置条件——它不看上下文,不讲逻辑,只认位置:必须在 loss.backward() 之前、且每个 batch 都要执行一次。漏一次,整个训练链就偏航。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











