核心一句话
y.backward() 是触发自动微分反向传播的开关 ,不调用它,PyTorch 只会记录计算链路,但不会执行求导、不会计算、不会填充 x.grad。
1. 分步拆解执行流程(对应你代码)
第1步:前向传播 y = x ** 2
python
x=torch.randn(1,requires_grad=True)
y=x**2
requires_grad=True:PyTorch 记录运算历史,给y绑定梯度函数PowBackwardU(y.grad_fn);- 此时只存"怎么求导"的公式,还没真正计算梯度数值;
x.grad = None,梯度是空的。
第2步:执行 y.backward()
这一行才开始反向链式求导:
- 从输出
y出发,初始化上游梯度 dydy=1\frac{dy}{dy}=1dydy=1; - 进入
PowBackwardU算子,套用导数公式 dydx=2x\frac{dy}{dx}=2xdxdy=2x; - 梯度流入
AccumulateGrad节点,把算出的 2x2x2x 存入x.grad; - 执行完毕后,
x.grad才有具体数值。
第3步:out=y.sum() 对你这段代码无影响
你画图传的是 y,不是 out,求和操作不会出现在计算图;且 y 只有1个元素,sum 不改变梯度结果。
2. 关键原理:PyTorch 是惰性求导
PyTorch 遵循「先记录,后计算」:
- 前向运算:只记录计算图、保存反向求导函数(
grad_fn),不做求导计算,节省算力; .backward():主动触发反向遍历整张计算图,按链式法则算出所有叶子参数的梯度,写入.grad属性。
对比直观理解
- 前向传播:只是画好求导公式草稿,不算结果;
.backward():拿起草稿代入数值算出导数,存到参数梯度里。
3. 补充2个高频疑问
(1)为什么 y 没有 .grad?
python
print(y.grad) # 输出 None
y 是非叶子张量(中间计算结果),PyTorch 默认只保留叶子参数(x) 的梯度,中间张量梯度计算完直接释放,节约内存。
想保存中间梯度需要手动:y.retain_grad()。
(2)能不能不写 .backward() 直接有梯度?
不能。没有任何自动执行反向的机制,训练网络时每一轮都必须调用一次 loss.backward(),否则参数永远无梯度、无法更新。
4. 简化代码演示前后对比
python
import torch
x = torch.tensor(2., requires_grad=True)
y = x ** 2
print("调用backward前 x.grad:", x.grad) # None,无梯度
y.backward()
print("调用backward后 x.grad:", x.grad) # tensor(4.),算出梯度2*x=4