Appearance
自动求导 Autograd:PyTorch 的魔法所在
深度学习的"学习",本质是:算出损失对每个参数的梯度,然后沿梯度反方向微调参数。手推梯度公式是折磨人的体力活,PyTorch 的自动求导引擎(Autograd)把它变成了一行
.backward()。本篇讲清楚它是怎么做到的。
从"要求梯度"开始:requires_grad
不是所有张量都需要求梯度。只有显式声明 requires_grad=True 的张量,Autograd 才会记录它参与的运算:
python
import torch
x = torch.ones(2, 2, requires_grad=True)
print(x.requires_grad) # True
print(x.grad) # None —— 还没反向传播过,没有梯度模型的可学习参数(后面 nn.Module 里的 nn.Parameter)默认就是 requires_grad=True,普通输入数据则不需要。
计算图:边算边记"小抄"
当你做运算时,Autograd 在后台默默构建一张有向无环图(DAG)——每个运算节点都记得"我是由谁、经过什么运算得来的":
python
a = torch.tensor([2.0], requires_grad=True)
b = torch.tensor([3.0], requires_grad=True)
c = a * b # 节点记录了:c = mul(a, b)
L = c ** 2 # 节点记录了:L = pow(c, 2)对应的计算图(正向):
a ──┐
├──(×)──> c ──(²)──> L
b ──┘对 L.backward() 时,PyTorch 从 L 出发逆着图往回走,用链式法则把梯度一层层传回去:
python
L.backward()
print(a.grad) # tensor([36.])手算验证:c = 2×3 = 6,L = c² = 36;∂L/∂c = 2c = 12;∂c/∂a = b = 3;链式法则 ∂L/∂a = 12 × 3 = 36。Autograd 的结果和手算完全一致——这就是它可信的原因:它不是近似,是精确的链式求值。
自己动手改几个数字再跑一遍,是理解链式法则最快的方式。
三个必须记住的行为
1. backward() 的调用者必须是标量
python
L.backward() # ✅ L 是 0 维标量
v = torch.tensor([1.0, 2.0], requires_grad=True)
(v * 2).backward() # ❌ RuntimeError: grad can be implicitly created
# only for scalar outputs(输出是向量,梯度形状不明确)损失函数最终输出标量(一个数),正是为了满足这个约定。如果确实对非标量求导,需传 gradient= 参数(权重),新手阶段基本用不到。
2. 梯度默认累加,不自动清零
连续两次 backward(),.grad 会相加而不是覆盖:
python
a.grad = None # 传统清零方式
# 或
a.grad.zero_() # 原地清零在训练循环里,这个工作由 optimizer.zero_grad() 统一完成。忘记清零是新手训练不收敛的头号原因,实战章节会故意踩一次这个坑。
3. 中间张量的图会被释放
backward() 后计算图即被销毁(省内存),所以对同一张图二次 backward 会报错,除非 L.backward(retain_graph=True)。正常情况下每轮前向都会建新图,不需要你操心。
关掉梯度:no_grad 与 detach
推理/评估阶段不需要求梯度,关掉它能省内存、提速:
python
with torch.no_grad():
pred = model(x_test) # 这段内的运算不建计算图另外两种常见写法:
y = x.detach():从 x 上"剪断"梯度,得到不带图的新张量。常用于记录指标、保存可视化数据。param.requires_grad_(False):冻结参数。微调预训练模型时冻结底层就是这个API。
坑预警: 想把张量转 NumPy 时直接
x.numpy()会报Can't call numpy() on Tensor that requires grad(若张量在 GPU 上还会多报一条 device 错误)。正确姿势:x.detach().cpu().numpy()—— 先剪断梯度,再搬回 CPU,最后转数组。三个动作各对应一条常见报错。
torch.no_grad vs eval()
两者职责完全不同,推理时通常要同时用:
| 调用 | 作用 |
|---|---|
model.eval() | 切换层的模式:关闭 Dropout、BatchNorm 改用全局统计量 |
torch.no_grad() | 关闭计算图构建:省内存提速,不影响数值结果 |
小结
- Autograd = 前向建图 + 反向链式求导,
requires_grad=True是入图许可证。 backward()只填.grad,不清零、不更新参数。- 推理时记住口诀:先 eval,再 no_grad。
下一篇:用 nn.Module 搭模型——把参数、层、前向逻辑包装成模型。