PyTorch 入门。
自动求导 (1)
计算图 (1.1)
PyTorch 会记录下来 tensor 的计算过程,也就是保留计算图。
假如:
- 模型函数:$f_{\boldsymbol{\theta}}(\boldsymbol{x})$;$\boldsymbol{\theta} = \begin{bmatrix} \theta_1, \theta_2, \theta_3, \dots \theta_m \end{bmatrix}^T$,$m$ 个参数。
- 损失:MSE (Mean Square Error);
- 一个 Batch 包含 $B$ 条训练数据:$(\boldsymbol{x}^i, y^i)$,$i = 1, 2, \dots, B$;
那么,损失函数就是:
$$
L_{\boldsymbol{\theta}} = \frac{1}{B}\sum\limits_{i=1}^{B} (f_{\boldsymbol{\theta}}(\boldsymbol{x}^i) - y^i)^2
$$
注意:$L_{\boldsymbol{\theta}}$ 中只有 ${\boldsymbol{\theta}}$ 是未知数;$\boldsymbol{x}^i$ 和 $y^i$ 都是训练数据;
函数 $f$ 非常复杂,包含很多激活函数。而且对于深度学习,激活函数还会发生嵌套(展开的话,下一层嵌套上一层)。所以,想要把 $L_{\boldsymbol{\theta}}$ 展开成关于 $\theta_1, \theta_2, \dots, \theta_m$ 的独立项是不可能的,PyTorch 也根本不会做这样的尝试。那么 PyTorch 是怎么做的呢?
假设初始参数是 $\boldsymbol{\theta}^0$,PyTorch 会根据 $L_{\boldsymbol{\theta}}$ 一步一步地进行前向计算(forward pass),就像执行程序一样。每一步都是一个运算节点(记为 $p_i$,如乘法、加法、sigmoid、平方、求均值等)。计算完成之后,PyTorch 就记录了:
$$
L^0 = p_1(p_2(p_3(… p_N(\boldsymbol{\theta}^0))))
$$
这就是计算图。
链式法则 (1.2)
求嵌套函数导数,使用链式法则。
如果:
$$
y = f_1(f_2(f_3(x)))
$$
那么:
$$
\frac{dy}{dx}
=
f_1’(f_2(f_3(x)))
\cdot
f_2’(f_3(x))
\cdot
f_3’(x)
$$
每一层只需要知道自己的局部导数,再乘起来即可。
把这个规则套用到计算图 $L^0 = p_1(p_2(p_3(\dots p_N(\boldsymbol{\theta}^0))))$ 上,对于参数 $\theta_k$,有:
$$
\frac{\partial L}{\partial \theta_k}
=
\frac{\partial p_1}{\partial p_2}
\cdot
\frac{\partial p_2}{\partial p_3}
\cdot
\cdots
\cdot
\frac{\partial p_{N-1}}{\partial p_N}
\cdot
\frac{\partial p_N}{\partial \theta_k}
$$
其中每个因子 $\frac{\partial p_i}{\partial p_{i+1}}$ 都是局部导数,只需知道节点 $p_i$ 是什么运算就能直接算出来(例如:加法节点导数为 1;乘法节点导数为另一个因子;sigmoid 导数为 $p_i(1-p_i)$;平方节点导数为 $2x$)。
PyTorch 就是把这些局部导数逐层累乘,从而得到 $\frac{\partial L}{\partial \theta_k}$。
PyTorch 从 $L^0$ 出发,沿计算图反向逐层计算局部导数并累乘,最终得到 $L$ 对 $\theta_k$ 的偏导数 $\frac{\partial L}{\partial \theta_k}$,存入 $\theta_k$ 的 .grad。
对 $\boldsymbol{\theta}$ 的每个分量求偏导数,就得到 Error Surface 上 $\boldsymbol{\theta}^0$ 处的梯度。
举例 (1.3)
网络结构 (1.3.1)

输入层:$x_1$, $x_2$(2 个 feature)
第一层:3 个 sigmoid 神经元
第二层:2 个 sigmoid 神经元
输出层:1 个线性神经元,输出值为 $\hat{y}$
参数定义 (1.3.2)
第一层(3 个神经元,每个接收 2 个输入):
| 参数 | 含义 |
|---|---|
| $w_{11}, w_{12}, b_1$ | 第 1 个神经元的 2 个权重 + 1 个 bias |
| $w_{21}, w_{22}, b_2$ | 第 2 个神经元的 2 个权重 + 1 个 bias |
| $w_{31}, w_{32}, b_3$ | 第 3 个神经元的 2 个权重 + 1 个 bias |
小计:$3 \times 3 = 9$ 个参数。
第二层(2 个神经元,每个接收 3 个输入):
| 参数 | 含义 |
|---|---|
| $u_{11}, u_{12}, u_{13}, d_1$ | 第 1 个神经元的 3 个权重 + 1 个 bias |
| $u_{21}, u_{22}, u_{23}, d_2$ | 第 2 个神经元的 3 个权重 + 1 个 bias |
小计:$2 \times 4 = 8$ 个参数。
输出层(1 个神经元,接收 2 个输入):
| 参数 | 含义 |
|---|---|
| $v_1, v_2, b$ | 2 个权重 + 1 个 bias |
小计:$3$ 个参数。
参数总数:
$$
9 + 8 + 3 = 20
$$
模型函数 (1.3.3)
第一层:
$$
\begin{aligned}
z_1 &= w_{11}x_1 + w_{12}x_2 + b_1 \\
z_2 &= w_{21}x_1 + w_{22}x_2 + b_2 \\
z_3 &= w_{31}x_1 + w_{32}x_2 + b_3
\end{aligned}
$$
$$
\begin{aligned}
h_1 &= \sigma(z_1) \\
h_2 &= \sigma(z_2) \\
h_3 &= \sigma(z_3)
\end{aligned}
$$
其中 $\sigma(z) = \frac{1}{1+e^{-z}}$。
第二层:
$$
\begin{aligned}
t_1 &= u_{11}h_1 + u_{12}h_2 + u_{13}h_3 + d_1 \\
t_2 &= u_{21}h_1 + u_{22}h_2 + u_{23}h_3 + d_2
\end{aligned}
$$
$$
\begin{aligned}
g_1 &= \sigma(t_1) \\
g_2 &= \sigma(t_2)
\end{aligned}
$$
输出层:
$$
\hat{y} = v_1 g_1 + v_2 g_2 + b
$$
损失函数 (1.3.4)
假如一个 Batch 包含 $B$ 条训练数据:$(\boldsymbol{x}^i, y^i)$,$i = 1, 2, \dots, B$;损失用 MSE(均方误差):
$$
L_{\boldsymbol{\theta}} = \frac{1}{B}\sum\limits_{i=1}^{B} (\hat{y}^i - y^i)^2
$$
损失函数 $L$ 是一个关于 $\boldsymbol{\theta}$ 的复杂嵌套函数,其中:
${\boldsymbol{\theta}} = \begin{bmatrix} w_{11},w_{12},\dots,w_{32},b_1,b_2,b_3,u_{11},u_{12},\dots,u_{23},d_1,d_2,v_1,v_2,b \end{bmatrix}^T$
$\hat{y}^i$ 是第 $i$ 条样本的预测值。对任意一条样本,把 $\hat{y}$ 完全展开:
$$
\begin{aligned}
\hat{y} = & b + \\
& v_1 \sigma(u_{11}\sigma(w_{11}x_1 + w_{12}x_2 + b_1) + u_{12}\sigma(w_{21}x_1 + w_{22}x_2 + b_2) + u_{13}\sigma(w_{31}x_1 + w_{32}x_2 + b_3) + d_1) + \\
& v_2 \sigma(u_{21}\sigma(w_{11}x_1 + w_{12}x_2 + b_1) + u_{22}\sigma(w_{21}x_1 + w_{22}x_2 + b_2) + u_{23}\sigma(w_{31}x_1 + w_{32}x_2 + b_3) + d_2)
\end{aligned}
$$
前向计算(forward pass)(1.3.5)
假设一个 batch 有 2 条训练数据:
| 样本 | $x_1$ | $x_2$ | $y$ |
|---|---|---|---|
| 1 | 3.0 | 2.0 | 1.5 |
| 2 | 0.5 | 1.2 | 0.9 |
假设初始参数$\boldsymbol{\theta}$:
| 参数 | 初始值 | 参数 | 初始值 | 参数 | 初始值 |
|---|---|---|---|---|---|
| $w_{11}$ | 0.3 | $u_{11}$ | 0.7 | $v_1$ | 0.6 |
| $w_{12}$ | 0.6 | $u_{12}$ | 0.2 | $v_2$ | 0.8 |
| $b_1$ | 0.2 | $u_{13}$ | 0.8 | $b$ | 0.3 |
| $w_{21}$ | 0.4 | $d_1$ | 0.2 | ||
| $w_{22}$ | 0.7 | $u_{21}$ | 0.5 | ||
| $b_2$ | 0.5 | $u_{22}$ | 0.4 | ||
| $w_{31}$ | 0.8 | $u_{23}$ | 0.9 | ||
| $w_{32}$ | 0.1 | $d_2$ | 0.5 | ||
| $b_3$ | 0.9 |
样本1
第一层:
$$
\begin{aligned}
z_1 &= w_{11}x_1 + w_{12}x_2 + b_1 = 0.3 \cdot 3.0 + 0.6 \cdot 2.0 + 0.2 = 0.9 + 1.2 + 0.2 = 2.3 \\
z_2 &= w_{21}x_1 + w_{22}x_2 + b_2 = 0.4 \cdot 3.0 + 0.7 \cdot 2.0 + 0.5 = 1.2 + 1.4 + 0.5 = 3.1 \\
z_3 &= w_{31}x_1 + w_{32}x_2 + b_3 = 0.8 \cdot 3.0 + 0.1 \cdot 2.0 + 0.9 = 2.4 + 0.2 + 0.9 = 3.5
\end{aligned}
$$
$$
\begin{aligned}
h_1 &= \sigma(z_1) = \frac{1}{1+e^{-2.3}} = 0.9089 \\
h_2 &= \sigma(z_2) = \frac{1}{1+e^{-3.1}} = 0.9569 \\
h_3 &= \sigma(z_3) = \frac{1}{1+e^{-3.5}} = 0.9707
\end{aligned}
$$
第二层:
$$
\begin{aligned}
t_1 &= u_{11}h_1 + u_{12}h_2 + u_{13}h_3 + d_1 = 0.7 \cdot 0.9089 + 0.2 \cdot 0.9569 + 0.8 \cdot 0.9707 + 0.2 = 1.8042 \\
t_2 &= u_{21}h_1 + u_{22}h_2 + u_{23}h_3 + d_2 = 0.5 \cdot 0.9089 + 0.4 \cdot 0.9569 + 0.9 \cdot 0.9707 + 0.5 = 2.2108
\end{aligned}
$$
$$
\begin{aligned}
g_1 &= \sigma(t_1) = \frac{1}{1+e^{-1.8042}} = 0.8587 \\
g_2 &= \sigma(t_2) = \frac{1}{1+e^{-2.2108}} = 0.9012
\end{aligned}
$$
输出层:
$$
\hat{y}^1 = v_1 g_1 + v_2 g_2 + b = 0.6 \cdot 0.8587 + 0.8 \cdot 0.9012 + 0.3 = 1.5362
$$
样本2
第一层:
$$
\begin{aligned}
z_1 &= w_{11}x_1 + w_{12}x_2 + b_1 = 0.3 \cdot 0.5 + 0.6 \cdot 1.2 + 0.2 = 0.15 + 0.72 + 0.2 = 1.07 \\
z_2 &= w_{21}x_1 + w_{22}x_2 + b_2 = 0.4 \cdot 0.5 + 0.7 \cdot 1.2 + 0.5 = 0.20 + 0.84 + 0.5 = 1.54 \\
z_3 &= w_{31}x_1 + w_{32}x_2 + b_3 = 0.8 \cdot 0.5 + 0.1 \cdot 1.2 + 0.9 = 0.40 + 0.12 + 0.9 = 1.42
\end{aligned}
$$
$$
\begin{aligned}
h_1 &= \sigma(z_1) = \frac{1}{1+e^{-1.07}} = 0.7446 \\
h_2 &= \sigma(z_2) = \frac{1}{1+e^{-1.54}} = 0.8235 \\
h_3 &= \sigma(z_3) = \frac{1}{1+e^{-1.42}} = 0.8053
\end{aligned}
$$
第二层:
$$
\begin{aligned}
t_1 &= u_{11}h_1 + u_{12}h_2 + u_{13}h_3 + d_1 = 0.7 \cdot 0.7446 + 0.2 \cdot 0.8235 + 0.8 \cdot 0.8053 + 0.2 = 1.5302 \\
t_2 &= u_{21}h_1 + u_{22}h_2 + u_{23}h_3 + d_2 = 0.5 \cdot 0.7446 + 0.4 \cdot 0.8235 + 0.9 \cdot 0.8053 + 0.5 = 1.9265
\end{aligned}
$$
$$
\begin{aligned}
g_1 &= \sigma(t_1) = \frac{1}{1+e^{-1.5302}} = 0.8220 \\
g_2 &= \sigma(t_2) = \frac{1}{1+e^{-1.9265}} = 0.8729
\end{aligned}
$$
输出层:
$$
\hat{y}^2 = v_1 g_1 + v_2 g_2 + b = 0.6 \cdot 0.8220 + 0.8 \cdot 0.8729 + 0.3 = 1.4915
$$
损失
$$
\begin{aligned}
L & = \frac{1}{2} \left[(\hat{y}^1 - y^1)^2 + (\hat{y}^2 - y^2)^2\right] \\
& = \frac{1}{2} \left[(1.5362 - 1.5)^2 + (1.4915 - 0.9)^2\right] \\
& = \frac{1}{2}(0.001310 + 0.349872) \\
& = 0.1756
\end{aligned}
$$
计算图 (1.3.6)
以 $w_{11}$ 为例构建计算图,其它参数类似。
- 样本1:第一层
$$
\begin{aligned}
z_1 &= w_{11}x_1 + w_{12}x_2 + b_1 = 3.0 \cdot w_{11} + 1.4 \\
z_2 &= w_{21}x_1 + w_{22}x_2 + b_2 = 3.1 \\
z_3 &= w_{31}x_1 + w_{32}x_2 + b_3 = 3.5
\end{aligned}
$$
把 $z_1$ 记为 $p_1(w_{11}) = 3.0 \cdot w_{11} + 1.4$
$$
\begin{aligned}
h_1 &= \sigma(z_1)\\
h_2 &= \sigma(z_2) = 0.9569 \\
h_3 &= \sigma(z_3) = 0.9707
\end{aligned}
$$
把 $h_1$ 记为 $p_2(w_{11}) = \sigma(p_1(w_{11}))$
- 样本1:第二层
$$
\begin{aligned}
t_1 &= u_{11}h_1 + u_{12}h_2 + u_{13}h_3 + d_1 = 0.7 \cdot h_1 + 1.1679 \\
t_2 &= u_{21}h_1 + u_{22}h_2 + u_{23}h_3 + d_2 = 0.5 \cdot h_1 + 1.7564
\end{aligned}
$$
把 $t_1$ 记为 $p_3(w_{11}) = 0.7 \cdot p_2(w_{11}) + 1.1679$
把 $t_2$ 记为 $p_4(w_{11}) = 0.5 \cdot p_2(w_{11}) + 1.7564$
$$
\begin{aligned}
g_1 &= \sigma(t_1) \\
g_2 &= \sigma(t_2)
\end{aligned}
$$
把 $g_1$ 记为 $p_5(w_{11}) = \sigma(p_3(w_{11}))$
把 $g_2$ 记为 $p_6(w_{11}) = \sigma(p_4(w_{11}))$
- 样本1:输出层
$$
\hat{y}^1 = v_1 g_1 + v_2 g_2 + b = 0.6 \cdot g_1 + 0.8 \cdot g_2 + 0.3
$$
把 $\hat{y}^1$ 记为 $p_7(w_{11}) = 0.6 \cdot p_5(w_{11}) + 0.8 \cdot p_6(w_{11}) + 0.3$
- 样本2:第一层
$$
\begin{aligned}
z_1 &= w_{11}x_1 + w_{12}x_2 + b_1 = 0.5 \cdot w_{11} + 0.92 \\
z_2 &= w_{21}x_1 + w_{22}x_2 + b_2 = 1.54 \\
z_3 &= w_{31}x_1 + w_{32}x_2 + b_3 = 1.42
\end{aligned}
$$
把 $z_1$ 记为 $p_8(w_{11}) = 0.5 \cdot w_{11} + 0.92$
$$
\begin{aligned}
h_1 &= \sigma(z_1) \\
h_2 &= \sigma(z_2) = 0.8235 \\
h_3 &= \sigma(z_3) = 0.8053
\end{aligned}
$$
把 $h_1$ 记为 $p_9(w_{11}) = \sigma(p_8(w_{11}))$
- 样本2:第二层
$$
\begin{aligned}
t_1 &= u_{11}h_1 + u_{12}h_2 + u_{13}h_3 + d_1 = 0.7 \cdot h_1 + 1.0089 \\
t_2 &= u_{21}h_1 + u_{22}h_2 + u_{23}h_3 + d_2 = 0.5 \cdot h_1 + 1.5542
\end{aligned}
$$
把 $t_1$ 记为 $p_{10}(w_{11}) = 0.7 \cdot p_9(w_{11}) + 1.0089$
把 $t_2$ 记为 $p_{11}(w_{11}) = 0.5 \cdot p_9(w_{11}) + 1.5542$
$$
\begin{aligned}
g_1 &= \sigma(t_1) \\
g_2 &= \sigma(t_2)
\end{aligned}
$$
把 $g_1$ 记为 $p_{12}(w_{11}) = \sigma(p_{10}(w_{11}))$
把 $g_2$ 记为 $p_{13}(w_{11}) = \sigma(p_{11}(w_{11}))$
- 样本2:输出层
$$
\hat{y}^2 = v_1 g_1 + v_2 g_2 + b = 0.6 \cdot g_1 + 0.8 \cdot g_2 + 0.3
$$
把 $\hat{y}^2$ 记为 $p_{14}(w_{11}) = 0.6 \cdot p_{12}(w_{11}) + 0.8 \cdot p_{13}(w_{11}) + 0.3$
- 损失
$$
L = \frac{1}{2}\left[(\hat{y}^1 - y^1)^2 + (\hat{y}^2 - y^2)^2\right]
$$
把 $(\hat{y}^1 - y^1)^2$ 记为 $p_{15}(w_{11}) = (p_7(w_{11}) - 1.5)^2$
把 $(\hat{y}^2 - y^2)^2$ 记为 $p_{16}(w_{11}) = (p_{14}(w_{11}) - 0.9)^2$
把 $L$ 记为 $p_{17}(w_{11}) = \frac{1}{2}(p_{15}(w_{11}) + p_{16}(w_{11}))$
至此,$L = p_{17}(w_{11})$ 是一个关于 $w_{11}$ 的嵌套函数,即 1.1 中 $L^0 = p_1(p_2(\dots p_N(\boldsymbol{\theta}^0)))$ 的具体形态(沿 $w_{11}$ 的路径共 17 个运算节点)。代入 $w_{11}=0.3$ 验证:$p_7(0.3)=1.5362$、$p_{14}(0.3)=1.4915$、$p_{17}(0.3)=0.1756$,与 1.3.5 一致。
机器视角
重要说明:上文是盯着一个参数 ($w_{11}$) 从“人脑视角”看到的计算图。实际上:
- PyTorch 存的是所有参数共享的一张运算 DAG,不是每个参数一条链。
- 一次 forward 建一张图,节点是原子运算(mul、add、sigmoid、平方、求均值…),边是 tensor。而本小节为了突出展示 $w_{11}$ 相关的链条,把其它参数的运算都省略了,直接替换成了它们的结果。20 个参数共用这张图。
- 对 $w_{11}$ 的链是 backward 时沿图反向走出来的,不是事先存好的。
- PyTorch 不做符号代入/合并。前面展示的 $p_1(w_{11}) = 3.0 \cdot w_{11} + 1.4$ 是把其它参数和子表达式折叠成常数了,这是为了突出 $w_{11}$ 相关的链条。PyTorch 里这会保持为多个独立节点:$mul(w_{11}, 3.0)$、$mul(0.6, 2.0)$、$add$、$add$;常数 $1.4$、$1.1679$ 等在图里是各自子图的计算结果,不会被合并成一个字面量。
- 每个节点只存:运算类型 + 输入/输出 tensor 的引用(部分节点额外保存 backward 需要的中间值,如 sigmoid 存自己的输出、乘法存另一个因子)。局部导数是 backward 时用它现算的。
- backward 的走法:从 $L$ 出发,反向拓扑遍历这张共享 DAG,每个节点把自己的局部导数乘进上游传来的梯度;在分叉点(如 $h_1$ 同时喂给 $t_1$ 和 $t_2$)把多条路径的贡献相加——这正好对应 $p_2$ 出现在 $p_3$ 和 $p_4$ 两处。
总之,PyTorch 的“机器视角”是存原始运算 DAG + 数值 tensor,反向时逐节点累乘累加,从不生成 $p_i(w_{11})$ 这种符号式单变量函数。

反向传播(backward)(1.3.7)
人类视角:从外向内手算 $\partial L/\partial w_{11}$
forward 是“由内向外算每个节点的值”,backward 是“由外向内传每个节点的导数”。记 $\bar{p}_i \triangleq \partial L/\partial p_i$,即“流进节点 $p_i$ 的梯度”。每个运算节点的局部导数规则见 1.2;若某节点的输出同时喂给多个下游节点,则把各条贡献相加。
- 起点:$\bar{p}_{17} = 1$($L$ 对自身求导)。
- $p_{17}=\frac{1}{2}(p_{15}+p_{16})$,加法节点系数为 $\frac{1}{2}$:
$$\bar{p}{15} = \tfrac{1}{2}, \qquad \bar{p}{16} = \tfrac{1}{2}$$
- 平方节点 $p_{15}=(p_7-1.5)^2$,局部导数 $2(p_7-1.5)$,与上游的 $\tfrac{1}{2}$ 相乘正好是残差 $(\hat{y}^1-y^1)$:
$$\bar{p}7 = 1.5362-1.5 = 0.0362, \qquad \bar{p}{14} = 1.4915-0.9 = 0.5915$$
- 输出层加法节点 $p_7 = 0.6,p_5+0.8,p_6+0.3$,系数即“另一个因子”:
$$\bar{p}_5 = 0.0362\cdot0.6 = 0.02172, \qquad \bar{p}_6 = 0.0362\cdot0.8 = 0.02896$$
$$\bar{p}{12} = 0.5915\cdot0.6 = 0.3549, \qquad \bar{p}{13} = 0.5915\cdot0.8 = 0.4732$$
- sigmoid 节点,局部导数 = 输出·(1−输出)(即 1.2 的 $p_i(1-p_i)$):
$$\bar{p}_3 = 0.02172\cdot(0.8587\cdot0.1413) = 0.02172\cdot0.1213 = 0.002635$$
$$\bar{p}_4 = 0.02896\cdot(0.9012\cdot0.0988) = 0.02896\cdot0.0890 = 0.002579$$
$$\bar{p}_{10} = 0.3549\cdot(0.8220\cdot0.1780) = 0.3549\cdot0.1463 = 0.051928$$
$$\bar{p}_{11} = 0.4732\cdot(0.8729\cdot0.1271) = 0.4732\cdot0.1109 = 0.052499$$
- 第二层加法节点。注意 $h_1$ 同时喂给 $t_1$ 和 $t_2$,所以 $p_2$/$p_9$ 处要把两条贡献相加(分叉点链式法则的具体形态):
$$\bar{p}_2 = 0.002635\cdot0.7 + 0.002579\cdot0.5 = 0.001845+0.001289 = 0.003134$$
$$\bar{p}_9 = 0.051928\cdot0.7 + 0.052499\cdot0.5 = 0.036350+0.026250 = 0.062599$$
- 第一层 sigmoid 节点:
$$\bar{p}_1 = 0.003134\cdot(0.9089\cdot0.0911) = 0.003134\cdot0.0828 = 0.000260$$
$$\bar{p}_8 = 0.062599\cdot(0.7446\cdot0.2554) = 0.062599\cdot0.1902 = 0.011905$$
- 叶子 $w_{11}$:它同时进入 $p_1$(系数 $x_1=3.0$)与 $p_8$(系数 $x_1=0.5$),两条路径相加:
$$\frac{\partial L}{\partial w_{11}} = 0.000260\cdot3.0 + 0.011905\cdot0.5 = 0.000779+0.005952 = 0.006731 \approx 0.0067$$
脚本验证:数值差分与全精度反向均得 $0.006731$,与手算一致。
两个观察:
- 两条样本的贡献为 $0.000779$ 对 $0.005952$:样本 2 的残差($0.5915$)是样本 1($0.0362$)的十几倍,梯度主要由误差大的样本驱动——“误差越大、修正越狠”。
- 梯度沿链从 $0.5915$ 一路缩到 $0.0067$:每层 sigmoid 的局部导数不超过 $0.25$,层数越深梯度越小——梯度消失的原型。
机器视角
上面是“盯住一个参数写出符号链”。PyTorch 的 backward() 做的是同一份计算,只是记账方式换成在共享 DAG 上走一遍:
- 从 $L$ 带梯度 $1.0$ 出发,按反向拓扑序遍历;每个节点用 forward 时保存的中间值(sigmoid 存自己的输出、乘法存另一个因子、平方存输入)现算局部导数,乘进上游传来的梯度再发出去——数值正是上小节那些 $\bar{p}_i$。
- 分叉点处相加;叶子参数处把梯度累加进
.grad属性。注意是累加(+=)而不是覆盖,所以每轮 backward 前要optimizer.zero_grad()清掉旧梯度,否则多步梯度会叠在一起。 - 走一遍同时得到全部 20 个参数的梯度。人类视角手算的只是 $w_{11}$ 一个,机器视角一次全给(脚本验证值,保留 4 位小数):
| 参数 | $\partial L/\partial$ | 参数 | $\partial L/\partial$ |
|---|---|---|---|
| $w_{11}$ | 0.0067 | $u_{11}$ | 0.0411 |
| $w_{12}$ | 0.0148 | $u_{12}$ | 0.0453 |
| $b_1$ | 0.0122 | $u_{13}$ | 0.0444 |
| $w_{21}$ | 0.0025 | $d_1$ | 0.0546 |
| $w_{22}$ | 0.0056 | $u_{21}$ | 0.0414 |
| $b_2$ | 0.0046 | $u_{22}$ | 0.0457 |
| $w_{31}$ | 0.0073 | $u_{23}$ | 0.0448 |
| $w_{32}$ | 0.0170 | $d_2$ | 0.0551 |
| $b_3$ | 0.0140 | $v_1$ | 0.5173 |
| $v_2$ | 0.5489 | ||
| $b$ | 0.6277 |
- 两种视角对照:数学是同一份链式法则,数值是同一份局部导数;人类视角适合理解,机器视角适合执行。梯度到手,下一步就是梯度下降 $\boldsymbol{\theta} \leftarrow \boldsymbol{\theta} - \eta,\partial L/\partial\boldsymbol{\theta}$(见下一节)。