标签:pytorch、autograd、自动求导、反向传播
前言
神经网络训练本质就是:前向计算损失 → 反向求梯度 → 更新参数。
PyTorch的Autograd自动求导引擎帮我们自动完成链式求导,不用手写导数。
核心概念一句话:
只要张量设置requires_grad=True,PyTorch会自动构建计算图,调用backward()就能自动算出梯度。
一、基础入门:requires_grad
python
import torch
# requires_grad=False 默认,不记录梯度
x = torch.tensor([1.0, 2.0], requires_grad=False)
# requires_grad=True,开启梯度追踪
w = torch.tensor([3.0, 4.0], requires_grad=True)
y = w * x
print(y)
print(y.requires_grad)
requires_grad=True:这个张量参与计算,会记录计算图,后续可以求导- 叶子张量:自己创建、不是运算生成的张量,比如上面的w
- 非叶子张量:由运算得到的y,只保存计算关系,默认不保留梯度
注意:整数张量默认不能求导,必须是浮点型(float32/float64)
二、backward() 反向求导
举一个简单函数:out=w0∗x0+w1∗x1out = w_0*x_0 + w_1*x_1out=w0∗x0+w1∗x1
我们对w求导:∂out∂w=x\frac{\partial out}{\partial w}=x∂w∂out=x
python
import torch
x = torch.tensor([1.0, 2.0])
w = torch.tensor([3.0, 4.0], requires_grad=True)
out = torch.sum(w * x)
# 反向传播,计算梯度
out.backward()
# 查看w的梯度
print(w.grad)
运行输出:tensor([1., 2.]),和我们手动求导结果一致。
⚠️ 重要规则:
- 标量(单个数值)可以直接调用
backward() - 如果out是向量,backward需要传入权重向量
backward(torch.ones_like(out))
三、计算图理解
上面例子的计算图:
w(叶子节点) → 乘法 → sum → out(标量)
Autograd沿着图反向走,链式法则求导,梯度存在.grad属性里。
四、梯度累加问题(高频踩坑点)
PyTorch梯度不会自动清零,会累加!
python
w = torch.tensor([3.0, 4.0], requires_grad=True)
x = torch.tensor([1.0, 2.0])
out = torch.sum(w * x)
out.backward()
print(w.grad) # 第一次梯度
out = torch.sum(w * x)
out.backward()
print(w.grad) # 梯度叠加,变成 [2,4]
训练循环里必须手动清零梯度:
python
w.grad.zero_()
带下划线
_代表原地in-place操作
五、with torch.no_grad() 禁用梯度
预测、验证阶段不需要计算梯度,关闭autograd可以节省显存、提速:
python
with torch.no_grad():
res = w * x
print(res.requires_grad) # False
✅ 场景:模型推理、验证集评估,不要占用显存存计算图。
六、detach() 截断计算图
如果想要一个张量,数值和原张量一样,但是脱离计算图、不追踪梯度:
python
y = w * x
y_detach = y.detach()
print(y_detach.requires_grad) # False
区别:
torch.no_grad():上下文管理器,里面所有运算都不建图.detach():单独把某个张量剥离出计算图
七、叶子张量 & retain_graph
正常情况,backward跑完之后,计算图会被释放,不能二次backward。
如果需要多次反向传播,设置 retain_graph=True
python
out.backward(retain_graph=True)
慎用,会占用更多显存,一般调试才用。
八、完整小demo(模拟一轮训练)
python
import torch
# 参数
w = torch.tensor([1.0], requires_grad=True)
# 输入
x = torch.tensor([2.0])
# 标签
y_true = torch.tensor([5.0])
# 前向
y_pred = w * x
loss = (y_pred - y_true) ** 2
# 反向求导
loss.backward()
print("w的梯度:", w.grad)
# 参数更新(注意:更新参数要关掉梯度!)
with torch.no_grad():
w -= 0.01 * w.grad
# 梯度清零,准备下一轮
w.grad.zero_()
print("更新后的w:", w)
这就是SGD梯度下降最简单的原生实现,后面optimizer底层就是这套逻辑。
九、常见坑汇总
| 问题 | 原因 | 解决 |
|---|---|---|
| 报错:Can only create a graph from a scalar | backward的输出是向量,不是标量 | 传入backward(torch.ones_like(out))或者sum |
| 梯度越来越大,loss爆炸 | 梯度没有清零,不断累加 | 每次循环末尾 xxx.grad.zero_() |
| 推理时显存占用很高 | 还在构建计算图 | 用with torch.no_grad()包裹推理代码 |
| 报叶子节点错误 | 对非叶子张量尝试修改grad | 只对自己定义的参数张量做梯度更新 |
十、小结
requires_grad=True:开启梯度追踪,构建计算图backward():反向传播,自动求导,梯度存入.grad- 梯度默认累加,训练循环必须清零
torch.no_grad()推理关闭梯度,省显存- 自动求导是后面神经网络、loss、优化器的底层基础