写这个系列的第一篇,我想先把求导记号钉死。深度学习论文里梯度公式满天飞,但"标量对矩阵求导"这件事在不同教材里有两种互相转置的约定(分子布局与分母布局),不先统一,后面推 RoPE、推 DPO 时每一步都要额外心算一次转置,非常痛苦。
布局约定:让梯度长得像参数
本文全系列采用一个朴素原则:标量函数 $f$ 对矩阵 $X\in\mathbb{R}^{m\times n}$ 的梯度,形状与 $X$ 相同,即
$$ \frac{\partial f}{\partial X}\in\mathbb{R}^{m\times n},\qquad \left[\frac{\partial f}{\partial X}\right]_{ij}=\frac{\partial f}{\partial X_{ij}}. $$
这个约定的好处是更新公式不用想:$X \leftarrow X - \eta\, \partial f/\partial X$,形状天然对齐。它就是"分母布局"(denominator layout)的自然推广。
微分法:比逐元素求导省一个数量级的脑力
逐元素求 $\partial f/\partial X_{ij}$ 再拼回矩阵,对稍微复杂的表达式就是灾难。工程上更顺手的是微分法。核心工具是一个内积恒等式:对矩阵空间定义内积 $\langle A,B\rangle = \mathrm{tr}(A^\top B)$,那么只要能把 $f$ 的微分写成
$$ df = \mathrm{tr}(G^\top\, dX), $$
根据 Riesz 表示,$G$ 就是我们要的梯度 $\partial f/\partial X$。推导变成纯粹的代数变形:把 $df$ 算出来,利用 $\mathrm{tr}(A^\top B)=\mathrm{tr}(B^\top A)$、$\mathrm{tr}(ABC)=\mathrm{tr}(CAB)$ 这些恒等式把 $dX$ 挪到右边,剩下的就是梯度。
拿最常见的二次型练手。设 $f(X)=\|AX-B\|_F^2$,$A$ 是常数矩阵。令 $E=AX-B$,则 $f=\mathrm{tr}(E^\top E)$,于是
$$ df = \mathrm{tr}(dE^\top E + E^\top dE) = 2\,\mathrm{tr}(E^\top dE) = 2\,\mathrm{tr}(E^\top A\,dX) = \mathrm{tr}\big((2A^\top E)^\top dX\big), $$
直接读出 $\dfrac{\partial f}{\partial X} = 2A^\top(AX-B)$。全程没有碰任何一个下标,这就是微分法的价值。
一次完整的反向传播手推
看一个最小但完整的例子:两层线性网络加交叉熵。输入 $x\in\mathbb{R}^{d}$,第一层 $h = \sigma(W_1 x + b_1)$($\sigma$ 取 ReLU),第二层 logits $z = W_2 h + b_2$,softmax 后 $p = \mathrm{softmax}(z)$,损失 $\ell = -\log p_y$,$y$ 是真实类别。
先推最后一层,因为 $p - y$ 这个结果是整个深度学习里出镜率最高的梯度。把交叉熵展开:$\ell = -\log \dfrac{e^{z_y}}{\sum_j e^{z_j}} = \log\sum_j e^{z_j} - z_y$。对 $z_i$ 求导:
$$ \frac{\partial \ell}{\partial z_i} = \frac{e^{z_i}}{\sum_j e^{z_j}} - \mathbb{1}[i=y] = p_i - \mathbb{1}[i=y]. $$
写成向量就是 $\delta_2 := \partial\ell/\partial z = p - e_y$($e_y$ 是 one-hot)。这个结果值得拆开看一眼,因为它不是巧合。交叉熵对 softmax 输出的梯度是 $\partial\ell/\partial p = -e_y \oslash p$($\oslash$ 为逐元素除,即第 $y$ 个分量是 $-1/p_y$,其余为零),而 softmax 的雅可比是 $J = \mathrm{diag}(p) - pp^\top$。两者相乘的第 $i$ 个分量:
$$ [J^\top(-e_y\oslash p)]_i = p_i\cdot\left(-\frac{\mathbb{1}[i=y]}{p_y}\right) - p_i\sum_j p_j\cdot\left(-\frac{\mathbb{1}[j=y]}{p_y}\right) = -\mathbb{1}[i=y] + p_i. $$
第一项来自雅可比的对角部分,第二项来自 $pp^\top$ 部分——求和里只有 $j=y$ 一项非零,恰好等于 $p_i \cdot 1$。两个各自都不漂亮的梯度相乘,负号与归一化性质相互抵消,剩下 $p - e_y$。这种"指数族分布 + 对数损失"的梯度净化现象在广义线性模型里是普遍规律,交叉熵-softmax 只是它最出名的一个实例。
接着沿计算图反向。$z = W_2 h + b_2$,由微分法 $d\ell = \delta_2^\top dz = \delta_2^\top(dW_2\, h + W_2\, dh + db_2)$。整理:
$$ d\ell = \mathrm{tr}\big((\delta_2 h^\top)^\top dW_2\big) + (W_2^\top\delta_2)^\top dh + \delta_2^\top db_2, $$
于是
$$ \frac{\partial \ell}{\partial W_2} = \delta_2 h^\top,\quad \frac{\partial \ell}{\partial b_2} = \delta_2,\quad \frac{\partial \ell}{\partial h} = W_2^\top \delta_2. $$
注意 $\partial\ell/\partial W_2$ 是外积——"误差向量 × 输入向量",这是所有线性层梯度的统一形态。继续往下:$\delta_1 = \sigma'(W_1x+b_1)\odot (W_2^\top\delta_2)$($\odot$ 是逐元素乘,ReLU 的导数就是 0/1 掩码),然后同样的外积结构给出 $\partial\ell/\partial W_1 = \delta_1 x^\top$。
反向传播的全部内容就是这条链:每层只需知道"上游传来的误差 $\delta$"和"本层的输入",梯度 = 误差 ⊗ 输入,传下去的误差 = 权重转置 × 误差(再过激活的导数)。框架里的 autograd 做的就是把这条链对任意计算图自动化。
微分法的常用工具清单
把前面用到的恒等式集中列一下,这是微分法的全部弹药:
$$ \mathrm{tr}(A^\top B) = \mathrm{tr}(B^\top A),\qquad \mathrm{tr}(ABC) = \mathrm{tr}(BCA) = \mathrm{tr}(CAB),\qquad \mathrm{tr}(A) = \mathrm{tr}(A^\top), $$
外加两条微分规则:$d(XY) = dX\cdot Y + X\cdot dY$(乘积法则对矩阵成立,但顺序不能换),$d\,\mathrm{tr}(X) = \mathrm{tr}(dX)$。所有矩阵梯度推导都是这几条的组合。再练一个稍难的:$f = \mathrm{tr}(X^\top A X)$,$A$ 是常数矩阵。展开微分:
$$ df = \mathrm{tr}(dX^\top A X + X^\top A\, dX) = \mathrm{tr}\big((AX)^\top dX\big) + \mathrm{tr}(X^\top A\, dX) = \mathrm{tr}\big((AX + A^\top X)^\top dX\big), $$
得 $\partial f/\partial X = AX + A^\top X$;当 $A$ 对称时退化为 $2AX$。第一步用乘积法则,第二步把 $\mathrm{tr}(dX^\top AX)$ 转置变形,第三步合并——三步全是清单里恒等式的直接套用。熟练之后这类推导比逐元素求导快得多,而且不容易错。
数值梯度检验:手推之后的验收
手推完梯度,必须做数值验证再写进训练代码,这是一个能救命的习惯。方法朴素:对标量损失 $\ell(\theta)$,用中心差分逐维估计
$$ \hat g_i = \frac{\ell(\theta + h e_i) - \ell(\theta - h e_i)}{2h},\qquad h\approx 10^{-5}, $$
然后比较相对误差
$$ \frac{\|\hat g - g\|_2}{\|\hat g\|_2 + \|g\|_2}. $$
两个注意点:一是必须用 float64 做这个检查,float32 的截断误差会淹没差分信号;二是中心差分本身精度是 $O(h^2)$,相对误差降到 $10^{-7}$ 以下才算通过,单边差分($O(h)$)只能到 $10^{-4}$ 左右,别拿它当验收标准。我自己的经验是:矩阵求导的错误九成是转置放错位置,而这类错误在标量测试里暴露得最快——因为转置错位的梯度形状常常"碰巧"也对,只有数值对不上才能抓出来。
反向传播从不实例化雅可比:VJP 视角
把上面的链抽象一步。设某层 $y=f(x)$,上游传来 $\bar y=\partial\ell/\partial y$,链式法则要求 $\bar x = J_f^\top \bar y$,其中 $J_f=\partial f/\partial x$ 是雅可比。关键在于:反向传播要的从来不是 $J_f$ 本身,而是它转置后左乘一个向量的结果——向量-雅可比积(VJP)。线性层 $y=Wx$ 的雅可比是 $m\times n$ 矩阵,VJP 就是 $W^\top\bar y$,一次矩阵-向量乘;逐元素激活的雅可比是对角阵,VJP 退化成 $\odot$;连看着最吓人的 softmax($J=\mathrm{diag}(p)-pp^\top$)也不需要物化 $J$,展开 $J^\top\bar y$ 得到 $p\odot\bar y - p\,\langle p,\bar y\rangle$,$O(n)$ 收场。每层的反向代价与同层前向同阶,全程没有一个稠密雅可比被实例化——这是反向传播能 scale 到百亿参数的结构性原因。
这个效率有理论保证:Baur–Strassen(1983)证明,标量函数的全部偏导可以在不超过单次求值常数倍(约 3~5 倍)的算术操作内算完,反向传播就是该定理的构造性实现。真正的代价在显存而不在 FLOPs:反向要用前向的中间值(上面推导里的 $h$、$z$、$p$ 都得留着),所以训练显存的大头是激活而不是参数。梯度检查点(gradient checkpointing)正是在这条边界上做交易——只存少数锚点的激活,反向时把中间段重算一遍前向,激活显存从 $O(L)$ 层降到 $O(\sqrt{L})$ 层,代价是约三成的额外计算。看懂"VJP 近乎免费、中间量存储收费"这个不对称,训练侧一半显存优化手段的动机就都有了着落。
为什么必须是 reverse mode
最后回答一个动机问题:自动微分有前向和反向两种模式,为什么深度学习只用反向?算笔账。设函数 $f:\mathbb{R}^n\to\mathbb{R}^m$,前向模式每算一列雅可比要一次完整前向,代价 $\sim n$ 次前向;反向模式一次反向得到一整行,代价 $\sim m$ 次反向。训练损失是标量,$m=1$,参数 $n$ 动辄上亿——前向模式要跑一亿次前向,反向模式只跑一次。这个不对称是标量损失带来的,也是"反向传播"这个名字存在的根本原因。
有了这套记号,后面推 LayerNorm 的梯度、推注意力里 $Q,K,V$ 的回传,都只是微分法的重复应用。回头审视整个推导链,真正需要记忆的结论只有三个:梯度的形状等于参数的形状(布局约定),$df=\mathrm{tr}(G^\top dX)$ 读出 $G$(微分法),误差 ⊗ 输入(线性层梯度的统一形态)。其余全是代数变形。下一篇我们用同样的工具处理概率侧的问题:softmax 的温度参数到底对分布做了什么。