自动求导与计算图 — 框架到底替你做了什么
这一章讲三件事: 深度学习为什么离不开自动求导;计算图是什么、反向传播怎么沿它走; 以及同一个回归任务用三种方式(NumPy 手工、autograd、TensorFlow 静态图)实现后的对照—— 看完你就知道「框架」这个词背后,真正被包起来的是哪几件事。
1. 顶层全景:正向记路,反向照路走
前向传播(算出结果,顺便记下每步) 反向传播(照记录往回推)
x ──┬─ mul → y ─┬─ add → z ── loss loss
w ──┘ │ ↑ 链式法则逐层回代
b ──────────────┘ y.grad ← z.grad ← ...
↓
w.grad、b.grad 落进各自的 .grad
图说:图上的圆是变量(数据),方是算子(运算)。正向只算一遍数值;
反向不重算数值,只把「损失对每个变量的导数」沿刚才的记录传回来。
反向传播(backpropagation)本身不是深度学习发明的——它就是微积分的链式法则(复合函数的导数等于各层导数相乘)套在图上逐层执行。框架的贡献是:你只写正向,反向它照着正向的记录自动生成。这套机制在 PyTorch 里叫 autograd1。
2. 计算图:z = wx + b 长什么样
先看最小的例子。表达式 z = wx + b 在框架眼里不是一行代码,而是三个节点、两个算子的图:
x(叶子) ──┐
mul → y(中间)
w(叶子) ──┘ │
add → z(根)
b(叶子) ────────────┘
三类节点各有名字,也各有固定行为:叶子节点是用户直接创建的变量(x、w、b);中间节点是算出来的(y、z);最末端要优化的叫根。谁需要求导,创建时标 requires_grad=True——默认 False,凡依赖它的后续节点自动变 True2。每个算出来的节点身上还挂着 grad_fn,记录「我是被哪个算子算出来的」;叶子节点没有,为 None3。
对根调用 z.backward(),autograd 从 z 出发沿图反向溯源,把每个叶子的梯度累加进它的 .grad 属性4。
书里把这句跑成了数:x 取 2,w、b 随机、都开 requires_grad,backward 之后打印——w.grad 是 2.0(z 对 w 的导数就是 x 的值),b.grad 是 1.0(z 对 b 的导数恒为 1),而 x 无须求导、梯度为 None5。两个数字各就各位,这就是「自动求导」的全部观感。
两条容易踩的规矩,都源于「梯度是累加的」这一设计:非叶子节点(y、z)的梯度在 backward 之后立即清空,不看就没了;中间缓存(算好的中间结果)也一并清掉,想再 backward 一次要传 retain_graph=True,且第二次 backward 的结果会叠在第一次上,通常要先手动清零6。评估、测试阶段不想要任何梯度记录,用 with torch.no_grad() 包住代码块即可7。