首页 / 模块 1 · 线性代数与微积分 / 第 9 课(共 10 课)

矩阵求导

从零到前沿 ML 自学课程 · 阶段0:数学与工具基础 · 能力点:整层网络的梯度——矩阵求导与 ∂L/∂W 同形约定

读完这一课,你将能够

  • 说出本课全程使用的"分母布局"约定,并解释为什么 \(\partial L/\partial W\) 必须与 \(W\) 同形。
  • 对任意梯度表达式立刻做 shape check,仅凭形状判断它"不可能对"还是"可能对"。
  • 从第一性原理(逐元素或微分法)独立推出 \(\partial(x^\top Ax)/\partial x=(A+A^\top)x\) 和 \(\partial\|x\|^2/\partial x=2x\),不靠背表。
  • 背下并解释线性层三大梯度 \(\partial L/\partial W=\delta x^\top\)、\(\partial L/\partial b=\delta\)、\(\partial L/\partial x=W^\top\delta\),说清那个外积为什么自动与 \(W\) 同形。
  • 推出 softmax+交叉熵的 \(\partial L/\partial z=\hat y-y\),并用中心差分 numgrad 数值验证任意解析梯度。

上一课我们用计算图,把损失对每个标量的偏导一个一个地反向传播出来。那很扎实,但当一层网络有成千上万个权重时,逐元素写偏导既慢又容易错。这一课我们把同样的事情向量化:直接对整个向量、整个矩阵求导,让一行公式 \(\partial L/\partial W=\delta\,x^\top\) 替代成千上万行链式法则。

一句话动机:反向传播的本质是"损失对参数的梯度"。参数是矩阵,所以我们需要"对矩阵求导"的语言。学会它,你看任何深度学习公式都会从"天书"变成"一眼看穿形状"。

1. 先把"布局"这件事一次说清

"矩阵求导"之所以让初学者头疼,90% 的痛苦来自一个根本没说清的约定:导数排成什么形状。同一个 \(\partial(a^\top x)/\partial x\),有的书得到行向量,有的书得到列向量——它们都没错,只是布局约定(layout convention)不同。

有两种约定:

要点:本课与全程的约定

我们全程使用分母布局。最重要的一条直接记住:

\[ \boxed{\ \frac{\partial L}{\partial W}\ \text{与}\ W\ \textbf{同形}\ } \]

即:损失是标量,那么"标量对矩阵的导数"就是一个与该矩阵一模一样大小的矩阵,第 \((i,j)\) 个元素是 \(\partial L/\partial W_{ij}\)。这就是 PyTorch 里 W.grad 永远和 W 同形的原因。

全程总原则(记住这一句就不会乱):分子是标量(即我们绝大多数时候要算的"标量损失对参数"),一律用分母布局,结果与分母同形唯一的例外向量对向量的雅可比——它沿用上一课的分子布局(\(m\times n\))。本课要算的几乎都是"标量对参数",所以分母布局是绝对的主角;那个唯一的例外只在 §2.2 露一次面,下面会专门点出。

1.1 四类导数的形状总览

把"谁对谁求导"分成四类。设标量 \(a\)、列向量 \(x\in\mathbb{R}^n\)、\(y\in\mathbb{R}^m\)、矩阵 \(W\in\mathbb{R}^{m\times n}\)。在分母布局下:

四类导数在分母布局下的输出形状速查表2行3列网格:行为分子(标量L、向量y),列为分母(标量a、向量x、矩阵W),每格用形状示意输出维度。分母布局 denominator layout形状跟随分母分子 \ 分母标量 a向量 x∈ R^n矩阵 W∈ R^(m×n)标量 L向量 y∈ R^m标量∈ RR^n 列向量∇ₓLm×n 方块与 W 同形R^m 列向量n×m 方块雅可比转置 Jᵀ高阶张量本课略
四类导数的形状速查表:标量/向量 对 标量/向量/矩阵,在分母布局下各自的输出形状。
分子\分母对标量 \(a\)对向量 \(x\in\mathbb{R}^n\)对矩阵 \(W\in\mathbb{R}^{m\times n}\)
标量 \(L\)标量列向量 \(\in\mathbb{R}^{n}\)(梯度 \(\nabla_x L\))矩阵 \(\in\mathbb{R}^{m\times n}\)(与 \(W\) 同形)
向量 \(y\in\mathbb{R}^m\)列向量 \(\in\mathbb{R}^{m}\)矩阵 \(\in\mathbb{R}^{n\times m}\)(雅可比的转置)(高阶张量,本课不展开)

注意第二行第二列:\(\partial y/\partial x\)。上一课我们把雅可比(Jacobian)\(J\) 定义成 \(m\times n\)(\(J_{ij}=\partial y_i/\partial x_j\),分子布局)。在分母布局下,\(\partial y/\partial x\) 是它的转置 \(J^\top\in\mathbb{R}^{n\times m}\)。这正是上面"总原则"里说的唯一例外——也是两套约定唯一容易"打架"的地方,下面会专门点出。

易错:别背公式,要做形状检查(shape check)

本课最重要的方法论不是某个公式,而是一个习惯:写下任何梯度后,立刻检查它的形状是否等于"分母"的形状。如果你算出的 \(\partial L/\partial W\) 不是 \(m\times n\),那一定是某处转置错了。形状对不上的式子,连验证都不用——它一定是错的。后面每个公式我都会带你做一次 shape check。

2. 从第一性原理推导四个必备公式

我们不背表,而是从定义推。所有推导只用一件事:标量对向量的导数,第 \(k\) 个分量就是对 \(x_k\) 的偏导。

2.1 线性型 \(\partial(a^\top x)/\partial x = a\)

设 \(f(x)=a^\top x=\sum_{k=1}^n a_k x_k\),这里 \(a\) 是常向量。对第 \(i\) 个分量求偏导:

\[ \frac{\partial f}{\partial x_i}=\frac{\partial}{\partial x_i}\sum_{k} a_k x_k = a_i . \]

把所有 \(i\) 堆成一个列向量(分母布局),得到

\[ \boxed{\ \frac{\partial (a^\top x)}{\partial x}=a\ }\in\mathbb{R}^n . \]

Shape check:分母是 \(x\in\mathbb{R}^n\),结果应是 \(\mathbb{R}^n\) 列向量——而 \(a\in\mathbb{R}^n\),对上了。这就是标量对向量求导最基本的"砖块",相当于一元微积分里的 \((ax)'=a\)。

2.2 矩阵乘向量 \(\partial(Wx)/\partial x\)

这里 \(y=Wx\) 是向量对向量——也就是 §1 里说的那个唯一例外。先别急着记结论:实践中我们几乎从不单独用这个导数,谁该转置最终由"形状必须对上"自动决定(见 §4),所以下面的方框只是为了和上一课的雅可比对照,不必背

第 \(i\) 个输出 \(y_i=\sum_k W_{ik}x_k\),所以 \(\partial y_i/\partial x_j=W_{ij}\)。雅可比(分子布局,\(m\times n\))正是 \(W\) 本身:

\[ J=\frac{\partial y}{\partial x}\Big|_{\text{分子布局}}=W . \]

而在分母布局下,\(\partial y/\partial x\) 是它的转置:

\[ \frac{\partial (Wx)}{\partial x}\Big|_{\text{分母布局}}=W^\top\in\mathbb{R}^{n\times m}.\qquad(\textit{不必记忆,仅供对照}) \]

Shape check:分母 \(x\in\mathbb{R}^n\) 占行(\(n\)),分子 \(y\in\mathbb{R}^m\) 占列(\(m\)),所以是 \(n\times m\)——而 \(W^\top\) 正是 \(n\times m\)。

不用死记是 \(W\) 还是 \(W^\top\)。实践中我们几乎从不单独用这个向量对向量的导数,而是把它放进链式法则里求 \(\partial L/\partial x\)(标量对向量)。那时谁转置由"形状必须对上"自动决定,见 §4。这也呼应本课的方法论:shape check 比记公式更重要

2.3 二次型 \(\partial(x^\top A x)/\partial x=(A+A^\top)x\)

设 \(f(x)=x^\top A x=\sum_{i}\sum_{j} x_i A_{ij} x_j\),\(A\) 是常矩阵。要对 \(x_k\) 求偏导,注意 \(x_k\) 出现在两处:作为外层下标 \(i=k\),以及作为内层下标 \(j=k\)。用乘积法则:

\[ \frac{\partial f}{\partial x_k}=\underbrace{\sum_{j} A_{kj}x_j}_{i=k\ \text{那一项}}+\underbrace{\sum_{i} x_i A_{ik}}_{j=k\ \text{那一项}} . \]

第一个和是 \((Ax)_k\);第二个和是 \(\sum_i A_{ik}x_i=(A^\top x)_k\)。合起来:

\[ \boxed{\ \frac{\partial (x^\top A x)}{\partial x}=(A+A^\top)x\ }. \]

当 \(A\) 对称(\(A=A^\top\),机器学习里协方差矩阵、海森矩阵都对称)时简化为

\[ \frac{\partial (x^\top A x)}{\partial x}=2Ax . \]

Shape check:\((A+A^\top)\in\mathbb{R}^{n\times n}\) 乘 \(x\in\mathbb{R}^n\) 得 \(\mathbb{R}^n\),与分母 \(x\) 同形。

2.4 平方范数 \(\partial\|x\|^2/\partial x=2x\)

这是 §2.3 的特例:\(\|x\|^2=x^\top x=x^\top I x\),取 \(A=I\)(对称),立刻得

\[ \boxed{\ \frac{\partial\|x\|^2}{\partial x}=2Ix=2x\ }. \]

它是一元 \((x^2)'=2x\) 的向量版本,也是 L2 正则、最小二乘损失里最常见的梯度。

四个必备求导公式与形状契约 线性型、矩阵乘向量、二次型、平方范数四个求导公式,每个结果的形状都等于分母 x 的形状。 四个必备求导公式 · 结果形状 = 分母 x 的形状 ① 线性型 ∂(aᵀx)/∂x = a x Rⁿ a Rⁿ a 与 x 同为 Rⁿ 竖条 输出 ≅ 分母 x ② 矩阵乘向量 ∂(Wx)/∂x = Wᵀ (分母布局) W m×n 转置 ᵀ Wᵀ n 行 m 列 行数 = dim x = n ≅ 分母 x 维度 ③ 二次型 ∂(xᵀAx)/∂x = (A+Aᵀ)x A + Aᵀ n×n n×n A+Aᵀ × x Rⁿ ≅ x A 对称 → = 2Ax ④ 平方范数 ∂‖x‖²/∂x = 2x x Rⁿ ×2 2x Rⁿ 同向放大 2 倍 输出 ≅ 分母 x 统一契约:每个导数结果的形状都与分母 x 相同
四个必备求导公式与各自的形状契约:线性型、矩阵乘向量、二次型、平方范数。

3. 微分法:一招通杀的 \(dL=\operatorname{tr}(G^\top dX)\Rightarrow \partial L/\partial X=G\)

逐元素求偏导对复杂表达式会越来越累。有一种更优雅、更不易错的工具——微分法(differential method)。它的核心要用到迹(trace),所以先花一句话把迹讲清楚。

先认识迹(trace)。对一个方阵 \(M\),迹 \(\operatorname{tr}(M)\) 就是它对角线元素之和:\(\operatorname{tr}(M)=\sum_i M_{ii}\)。我们只需要两个关键事实:(1) 标量看成 \(1\times1\) 矩阵时,迹就等于它自己(所以任何标量都可以"套上一个迹"而不改变值);(2) 循环性 \(\operatorname{tr}(AB)=\operatorname{tr}(BA)\)(更一般地 \(\operatorname{tr}(ABC)=\operatorname{tr}(BCA)\)),只要乘积合法、结果是方阵即可。下面 §4.1 把"标量等于自己的迹"再用循环性把 \(dX\) 凑到末尾,全靠这两条。

有了迹,微分法的核心是一条"识别定理":

要点:微分识别公式

对标量 \(L\) 与矩阵变量 \(X\in\mathbb{R}^{m\times n}\),若能把 \(L\) 的全微分写成

\[ dL=\operatorname{tr}\!\big(G^\top\,dX\big), \]

则(在分母布局下)

\[ \frac{\partial L}{\partial X}=G,\qquad G\in\mathbb{R}^{m\times n}\ \text{与}\ X\ \text{同形}. \]

为什么成立?把迹展开:\(\operatorname{tr}(G^\top dX)=\sum_{i,j}G_{ij}\,dX_{ij}\)。而由全微分定义 \(dL=\sum_{i,j}\dfrac{\partial L}{\partial X_{ij}}dX_{ij}\)。两式对每个独立的 \(dX_{ij}\) 都成立,逐项比较系数即得 \(G_{ij}=\partial L/\partial X_{ij}\),也就是 \(G=\partial L/\partial X\)。向量是 \(n\times 1\) 矩阵的特例,公式照用:\(dL=g^\top dx\Rightarrow \partial L/\partial x=g\)。

三条够用的运算法则(\(d\) 像普通微分一样线性,且服从乘积法则):

小试身手(用微分法重做平方范数):\(L=\|x\|^2=x^\top x\)。两边微分,用乘积法则:

\[ dL=(dx)^\top x+x^\top dx=x^\top dx+x^\top dx=2x^\top dx=(2x)^\top dx . \]

对照 \(dL=g^\top dx\),立得 \(g=\partial L/\partial x=2x\)。和 §2.4 一致,但没碰任何下标。微分法的威力在矩阵情形(下一节)才真正显现。

4. 一层线性层的全部梯度

这是反向传播的"原子层"。设

\[ y=Wx+b,\qquad W\in\mathbb{R}^{m\times n},\ x\in\mathbb{R}^{n},\ b,y\in\mathbb{R}^{m}, \]

后面接一个标量损失 \(L(y)\)。我们已经(在上一课或本层下游)拿到 上游梯度

\[ \delta\ :=\ \frac{\partial L}{\partial y}\in\mathbb{R}^{m}\quad(\text{与 }y\text{ 同形}). \]

目标:求 \(\partial L/\partial W,\ \partial L/\partial b,\ \partial L/\partial x\)。

4.1 用微分法一次推完

这里我们分别对每个变量求偏导:求 \(\partial L/\partial W\) 时,把 \(b\)、\(x\) 视作常数(即令 \(db=0,\ dx=0\),注意 \(x\) 是该层的输入、\(b\) 是参数,它们并非恒为常量,只是在对 \(W\) 求偏导这一语境里暂时当常数)。于是 \(y=Wx+b\) 的微分只剩 \(dy=(dW)x\)。又 \(dL=\delta^\top dy\)(这是 \(\delta\) 的定义,标量对向量)。代入:

\[ dL=\delta^\top (dW)\,x . \]

这是个标量,等于自己的迹(标量看成 \(1\times1\) 矩阵),再用循环性把 \(dW\) 凑到末尾:

\[ dL=\operatorname{tr}\big(\delta^\top (dW)\,x\big)=\operatorname{tr}\big(x\,\delta^\top\,dW\big)=\operatorname{tr}\big((\delta x^\top)^\top dW\big). \]

对照识别公式 \(dL=\operatorname{tr}(G^\top dW)\),读出 \(G=\delta x^\top\):

\[ \boxed{\ \frac{\partial L}{\partial W}=\delta\,x^\top=\frac{\partial L}{\partial y}\,x^\top\ }. \]

同理,对 \(b\) 求偏导时把 \(W\)、\(x\) 当常数,\(dy=db\Rightarrow dL=\delta^\top db\),得 \(\partial L/\partial b=\delta\);对 \(x\) 求偏导时把 \(W\)、\(b\) 当常数,\(dy=W\,dx\Rightarrow dL=\delta^\top W\,dx=(W^\top\delta)^\top dx\),得 \(\partial L/\partial x=W^\top\delta\)。汇总:

要点:线性层三大梯度(背下来)

\[ \frac{\partial L}{\partial W}=\delta\,x^\top,\qquad \frac{\partial L}{\partial b}=\delta,\qquad \frac{\partial L}{\partial x}=W^\top\delta, \quad\text{其中 }\delta=\frac{\partial L}{\partial y}. \]

4.2 外积结构与 shape check

\(\partial L/\partial W=\delta x^\top\) 是一个外积(outer product):列向量 \(\delta\in\mathbb{R}^{m\times 1}\) 乘行向量 \(x^\top\in\mathbb{R}^{1\times n}\),结果 \(m\times n\)——恰好与 \(W\) 同形。这个"上游梯度 ⊗ 本层输入"的外积,是所有全连接层反向传播的统一模板。

外积 ∂L/∂W = δ xᵀ:(m×1) 列向量乘 (1×n) 行向量得到 m×n 矩阵,与 W 同形外积 ∂L/∂W = δ xᵀ行向量 xᵀ (1×n) 本层输入转置x₁x₂xⱼxₙδ₁δᵢδₘ列向量 δ(m×1)=∂L/∂y×∂L/∂W (m×n)(∂L/∂W)ᵢⱼ= δᵢ · xⱼ结果 m×n 与 W 同形 ✓前向用 W:y = W x反向 ∂L/∂x = Wᵀ δ 用 Wᵀ(n×m),∂L/∂W = δ xᵀ 用外积
分母布局下 ∂L/∂W=δxᵀ 的外积形状契约:(m×1) 列向量乘 (1×n) 行向量得到与 W 同形的 m×n 矩阵。

再核对另外两个:\(\partial L/\partial b=\delta\in\mathbb{R}^m\) 与 \(b\) 同形 ✓;\(\partial L/\partial x=W^\top\delta\),\(W^\top\) 是 \(n\times m\) 乘 \(\delta\) 的 \(m\times1\) 得 \(n\times1\),与 \(x\) 同形 ✓。三个梯度的形状全部"自动对上"——这正是分母布局的好处。注意 §2.2 那个"该用 \(W\) 还是 \(W^\top\)"的纠结,在这里被 shape check 自动解决了:要让结果与 \(x\) 同形,就只能是 \(W^\top\delta\)。

ML 和 ML 的联系

注意 \(\partial L/\partial x=W^\top\delta\) 里出现的 \(W^\top\):前向用 \(W\),反向用 \(W^\top\)。反向传播本质上是"把上游梯度沿转置权重传回去"。把多层串起来,\(\partial L/\partial x\) 就是这一层算出的、要喂给上一层的新 \(\delta\)——这就是下一课手写两层网络时反传循环的全部秘密。

另外,真实训练用 mini-batch,数据矩阵 \(X\in\mathbb{R}^{B\times n}\)(行=一个样本),前向写成 \(Y=XW^\top+\mathbf{1}b^\top\)。这里 \(\mathbf{1}\in\mathbb{R}^{B}\) 是全 1 列向量,\(\mathbf{1}b^\top\in\mathbb{R}^{B\times m}\) 表示"把偏置 \(b^\top\) 复制到每一行"——这正是 numpy 里 广播(broadcasting)的效果:把形状为 \(m\) 的 \(b\) 自动加到 \(B\times m\) 矩阵的每一行上(代码里直接写 X @ W.T + b 即可)。此时 \(\partial L/\partial W=\Delta^\top X\)(\(\Delta\) 是 \(B\times m\) 的上游梯度),相当于把 batch 内每个样本的外积 \(\delta_i x_i^\top\) 求和。本课先吃透单样本,batch 版只是把外积换成矩阵乘求和(练习 3 会动手验证)。

5. softmax + 交叉熵:那个"好得不像话"的梯度

分类网络最后一层几乎总是 softmax 把打分(logits)\(z\in\mathbb{R}^K\) 变成概率,再用交叉熵 cross-entropy 与真标签比对。神奇的是,两者复合后梯度极其简洁:\(\partial L/\partial z=\hat y-y\)。我们来推它——而且照本课一贯的做法,softmax 的雅可比也从定义亲手推,不直接抛结论。

记 softmax 输出 \(\hat y_k=\dfrac{e^{z_k}}{\sum_{j}e^{z_j}}\),真标签是 one-hot 向量 \(y\)(正确类 \(c\) 处为 1,其余 0)。交叉熵损失

\[ L=-\sum_{k}y_k\log\hat y_k=-\log\hat y_c . \]

第一步:softmax 的偏导。设 \(S=\sum_j e^{z_j}\),则 \(\hat y_k=e^{z_k}/S\)。用商法则对 \(z_i\) 求导(注意 \(\partial S/\partial z_i=e^{z_i}\),且分子 \(e^{z_k}\) 只在 \(k=i\) 时才依赖 \(z_i\)),分两种情况:

\[ i=k:\quad \frac{\partial \hat y_k}{\partial z_k}=\frac{e^{z_k}S-e^{z_k}e^{z_k}}{S^2}=\frac{e^{z_k}}{S}\Big(1-\frac{e^{z_k}}{S}\Big)=\hat y_k(1-\hat y_k), \] \[ i\neq k:\quad \frac{\partial \hat y_k}{\partial z_i}=\frac{0\cdot S-e^{z_k}e^{z_i}}{S^2}=-\frac{e^{z_k}}{S}\frac{e^{z_i}}{S}=-\hat y_k\,\hat y_i . \]

两种情况可以合写成一条(用 Kronecker 记号 \(\delta_{ki}\):\(k=i\) 取 1,否则 0):

\[ \frac{\partial \hat y_k}{\partial z_i}=\hat y_k(\delta_{ki}-\hat y_i). \]

第二步:链式法则。先用 \(L=-\sum_k y_k\log\hat y_k\),\(\partial L/\partial\hat y_k=-y_k/\hat y_k\),于是

\[ \frac{\partial L}{\partial z_i}=\sum_k\frac{\partial L}{\partial\hat y_k}\frac{\partial \hat y_k}{\partial z_i} =\sum_k\left(-\frac{y_k}{\hat y_k}\right)\hat y_k(\delta_{ki}-\hat y_i) =-\sum_k y_k(\delta_{ki}-\hat y_i). \]

拆成两个和:\(-\sum_k y_k\delta_{ki}=-y_i\)(只有 \(k=i\) 那项留下);而第二项里 \(\hat y_i\) 不含求和指标 \(k\),可提到求和号外面,得 \(\sum_k y_k\hat y_i=\hat y_i\sum_k y_k=\hat y_i\)(因为 one-hot 的 \(\sum_k y_k=1\))。所以

\[ \frac{\partial L}{\partial z_i}=-y_i+\hat y_i=\hat y_i-y_i . \]

堆成向量:

\[ \boxed{\ \frac{\partial L}{\partial z}=\hat y-y\ }. \]
softmax+交叉熵计算图:梯度汇成 ŷ−ysoftmax + 交叉熵 计算图logits z∈R³210softmaxŷ∈R³ (Σ=1).665.245.090y (one-hot)100交叉熵CE标量 L≈0.408exp 与 log 相消⇒ 梯度极简反向梯度∂L/∂z = ŷ − y= [.665, .245, .090] − [1, 0, 0]≈ [−.335, .245, .090 ]分量和 ≈ 0
softmax+交叉熵的小计算图:logits z 经 softmax 得 ŷ,与 one-hot 标签 y 算交叉熵 L,反向梯度汇成简洁的 ŷ−y。

为什么这么干净?softmax 把指数"曲率"引进来,交叉熵又取了对数"抵消"掉指数——一拉一拽正好对消,只剩下最朴素的"预测概率减真实概率"。这不是巧合:对数似然 + 指数族分布天生配对。结果的直觉也极好:预测对了(\(\hat y\approx y\))梯度近 0;错得越离谱,梯度越大,正好推着模型往正确方向走。

Shape check:\(\hat y,y\in\mathbb{R}^K\),差还是 \(\mathbb{R}^K\),与分母 \(z\) 同形 ✓。在实现里,这个 \(\hat y-y\) 就是喂给上一层(输出线性层)的 \(\delta\),直接套 §4 的三大梯度。

6. 例题

例题 1:完整推导 \(\partial(x^\top A x)/\partial x\),并用具体数验证

取 \(A=\begin{bmatrix}1&2\\0&3\end{bmatrix}\)(故意非对称,看 \(A+A^\top\) 是否真有必要),\(x=\begin{bmatrix}1\\2\end{bmatrix}\)。

(a) 直接展开成标量函数:

\[ f=x^\top Ax = x_1^2 + 2x_1x_2 + 0\cdot x_2x_1 + 3x_2^2 = x_1^2+2x_1x_2+3x_2^2 . \]

(b) 逐元素偏导:

\[ \frac{\partial f}{\partial x_1}=2x_1+2x_2,\qquad \frac{\partial f}{\partial x_2}=2x_1+6x_2 . \]

代入 \(x=(1,2)\):\(\partial f/\partial x_1=2+4=6\),\(\partial f/\partial x_2=2+12=14\)。所以梯度 \(=\begin{bmatrix}6\\14\end{bmatrix}\)。

(c) 用公式 \((A+A^\top)x\) 核对:

\[ A+A^\top=\begin{bmatrix}1&2\\0&3\end{bmatrix}+\begin{bmatrix}1&0\\2&3\end{bmatrix}=\begin{bmatrix}2&2\\2&6\end{bmatrix},\quad (A+A^\top)x=\begin{bmatrix}2&2\\2&6\end{bmatrix}\begin{bmatrix}1\\2\end{bmatrix}=\begin{bmatrix}6\\14\end{bmatrix}.✓ \]

两条路殊途同归。注意若误用 \(2Ax=\begin{bmatrix}2&4\\0&6\end{bmatrix}x=\begin{bmatrix}10\\12\end{bmatrix}\),就错了——只有 \(A\) 对称时才能简化为 \(2Ax\)。

例题 2:推导线性层 \(\partial L/\partial W\) 并做维度校验

设 \(m=2,n=3\),\(W\in\mathbb{R}^{2\times3}\),\(x=\begin{bmatrix}1\\0\\-1\end{bmatrix}\in\mathbb{R}^3\),上游梯度 \(\delta=\dfrac{\partial L}{\partial y}=\begin{bmatrix}0.5\\-2\end{bmatrix}\in\mathbb{R}^2\)。

(a) 套公式(外积):

\[ \frac{\partial L}{\partial W}=\delta x^\top=\begin{bmatrix}0.5\\-2\end{bmatrix}\begin{bmatrix}1&0&-1\end{bmatrix} =\begin{bmatrix}0.5 & 0 & -0.5\\ -2 & 0 & 2\end{bmatrix}. \]

(b) 维度校验:\(\delta\) 是 \(2\times1\),\(x^\top\) 是 \(1\times3\),外积是 \(2\times3\)——与 \(W\) 同形 ✓。

(c) 抽一个元素手验:按定义 \(\partial L/\partial W_{ij}=\delta_i x_j\)(因为 \(y_i=\sum_j W_{ij}x_j+b_i\),\(\partial y_i/\partial W_{ij}=x_j\),再乘上游 \(\delta_i\))。取 \((i,j)=(2,3)\):\(\delta_2 x_3=(-2)\times(-1)=2\),与矩阵右下角一致 ✓。

(d) 顺手求另外两个:\(\partial L/\partial b=\delta=(0.5,-2)^\top\);\(\partial L/\partial x=W^\top\delta\in\mathbb{R}^3\)(需要具体 \(W\) 才能算数值,但形状已确定)。

例题 3:softmax+CE 的 \(\hat y-y\)(具体数)

设三分类 logits \(z=\begin{bmatrix}2\\1\\0\end{bmatrix}\),真标签是第 0 类,即 \(y=\begin{bmatrix}1\\0\\0\end{bmatrix}\)。

(a) softmax:\(e^2\approx7.389,\ e^1\approx2.718,\ e^0=1\),和 \(S\approx11.107\)。

\[ \hat y\approx\begin{bmatrix}7.389/11.107\\2.718/11.107\\1/11.107\end{bmatrix}=\begin{bmatrix}0.665\\0.245\\0.090\end{bmatrix}. \]

(b) 梯度 \(\hat y-y\):

\[ \frac{\partial L}{\partial z}=\hat y-y\approx\begin{bmatrix}0.665-1\\0.245-0\\0.090-0\end{bmatrix}=\begin{bmatrix}-0.335\\0.245\\0.090\end{bmatrix}. \]

解读:正确类(第 0 类)梯度为负——梯度下降会增大 \(z_0\)(沿负梯度方向更新即 \(z_0\) 上升),让模型更自信;其余两类梯度为正,会被压低。三个分量之和 \(\approx0\)(因为 \(\sum\hat y=\sum y=1\)),这是 softmax 梯度的固有性质。

7. 代码:用数值梯度验证我们的公式

口说无凭。验证矩阵求导公式最可靠的办法是有限差分数值梯度(finite-difference numerical gradient):用 \(\dfrac{\partial L}{\partial \theta_i}\approx\dfrac{L(\theta+\epsilon e_i)-L(\theta-\epsilon e_i)}{2\epsilon}\)(中心差分,误差 \(O(\epsilon^2)\))逐个分量逼近,再和解析公式比相对误差。下面三段一次性验证 §2.3、§4、§5 的全部公式,相对误差都应 \(<10^{-6}\)。

import numpy as np
rng = np.random.default_rng(0)
eps = 1e-6

def numgrad(f, theta):
    """对标量函数 f(theta) 在 theta 处做中心差分数值梯度(theta 任意形状)。"""
    g = np.zeros_like(theta, dtype=float)
    it = np.nditer(theta, flags=['multi_index'])
    for _ in it:
        idx = it.multi_index
        old = theta[idx]
        theta[idx] = old + eps; fp = f(theta)
        theta[idx] = old - eps; fm = f(theta)
        theta[idx] = old
        g[idx] = (fp - fm) / (2*eps)
    return g

def relerr(a, b):
    return np.linalg.norm(a-b) / (np.linalg.norm(a)+np.linalg.norm(b) + 1e-12)

# ---- 验证 1: d(x^T A x)/dx = (A + A^T) x (A 故意非对称)----
n = 4
A = rng.standard_normal((n, n))
x = rng.standard_normal(n)
f1 = lambda x: x @ A @ x
g_analytic = (A + A.T) @ x
g_numeric  = numgrad(f1, x.copy())
print("[1] x^T A x   相对误差 =", relerr(g_analytic, g_numeric))

# ---- 验证 2: 线性层 y=Wx+b, L=0.5||y-t||^2 ----
m, n = 3, 5
W = rng.standard_normal((m, n)); b = rng.standard_normal(m)
xv = rng.standard_normal(n);     t = rng.standard_normal(m)
def L2(W, b, xv):
    y = W @ xv + b
    return 0.5*np.sum((y - t)**2)
y = W @ xv + b
delta = (y - t)              # = dL/dy
dW = np.outer(delta, xv)     # = delta x^T  (m x n)
db = delta
dx = W.T @ delta             # (n,)
print("[2a] dL/dW    相对误差 =", relerr(dW, numgrad(lambda W: L2(W,b,xv), W.copy())))
print("[2b] dL/db    相对误差 =", relerr(db, numgrad(lambda b: L2(W,b,xv), b.copy())))
print("[2c] dL/dx    相对误差 =", relerr(dx, numgrad(lambda xv: L2(W,b,xv), xv.copy())))
print("     形状检查 dW.shape == W.shape :", dW.shape == W.shape)

# ---- 验证 3: softmax + 交叉熵, dL/dz = yhat - y ----
K = 5
z = rng.standard_normal(K)
y_oh = np.zeros(K); y_oh[2] = 1.0          # 正确类 = 第 2 类
def softmax(z):
    z = z - z.max()                         # 数值稳定
    e = np.exp(z); return e / e.sum()
def ce(z):
    p = softmax(z); return -np.sum(y_oh * np.log(p))
yhat = softmax(z)
g3 = yhat - y_oh
print("[3] softmax+CE 相对误差 =", relerr(g3, numgrad(ce, z.copy())))

运行后五个相对误差应当都在 \(10^{-9}\sim10^{-6}\) 量级,形状检查输出 True。这就是工程上验证任何反向传播实现是否正确的"金标准"——下一课写完两层网络,我们也会用同样的 numgrad 给它做体检。

易错:数值梯度的 \(\epsilon\) 别取太小,也别在不可导点测

很多人以为 \(\epsilon\) 越小越准——错。\(\epsilon\) 太小(如 \(10^{-12}\))会被浮点舍入误差吞掉,相对误差反而变大。中心差分的甜区是 \(\epsilon\approx10^{-5}\sim10^{-6}\)。

另外,要避免在不可导点附近测——例如 ReLU 在 \(0\)、绝对值 \(|x|\) 在 \(0\)、\(\max\) 在两分支相等处:这些点的解析梯度与有限差分会对不上(差分跨过了"拐点")。本课的 softmax 不在此列:它处处可导,代码里的 z.max() 只是减一个常数做数值稳定(softmax 平移不变),不影响可导性,所以那段验证的相对误差稳稳落在 \(10^{-10}\) 量级。

调一调,观察现象

下面三个小实验都只用 numpy 和 print,几秒钟跑完。改一个数、看一个现象,把本课的公式从"看懂"变成"亲眼见过"。

实验 1:对称还是不对称?看 \(2Ax\) 何时会骗你

改什么:用一个非对称的 \(A\),分别用正确公式 \((A+A^\top)x\) 和"偷懒公式"\(2Ax\) 算梯度。
预期现象:两者给出不同的结果(\([6,14]\) vs \([10,12]\));但当你把 \(A\) 换成它的对称化 \(A_{\text{sym}}=\tfrac12(A+A^\top)\) 后,\(2A_{\text{sym}}x\) 又和 \((A+A^\top)x\) 重新相等(都回到 \([6,14]\))。
为什么:只有 \(A=A^\top\) 时 \((A+A^\top)x\) 才简化为 \(2Ax\);非对称的 \(A\) 偷用 \(2Ax\) 就会错。这正是 §2.3、例题1 里强调的坑。

import numpy as np
A = np.array([[1.,2.],[0.,3.]])   # 故意非对称
x = np.array([1.,2.])
print("正确 (A+A^T)x =", (A + A.T) @ x)   # [ 6. 14.]
print("偷懒 2Ax       =", 2 * A @ x)       # [10. 12.]  —— 不一样!
As = 0.5*(A + A.T)                          # 对称化
print("对称化后 2*As*x =", 2 * As @ x)      # [ 6. 14.]  —— 又对上了

实验 2:梯度 \(\delta x^\top\) 是个外积——它的秩永远是 1

改什么:用 §4 的外积公式算 \(\partial L/\partial W=\delta x^\top\),打印它的形状和矩阵的秩。
预期现象:形状是 \((2,3)\),与 \(W\) 同形;而矩阵的秩永远是 1,无论 \(\delta\)、\(x\) 取什么非零值。
为什么:外积"列向量 × 行向量"得到的矩阵每一列都是 \(\delta\) 的倍数(倍数是 \(x\) 的各分量),所以列空间是一维的,秩恒为 1。这就是单样本反传梯度的固有结构(batch 版才靠求和把秩提上去)。

import numpy as np
delta = np.array([0.5, -2.0])     # m=2 上游梯度
xv    = np.array([1., 0., -1.])   # n=3 本层输入
dW = np.outer(delta, xv)          # delta x^T
print("dW =\n", dW)
print("dW.shape =", dW.shape, " 应与 W 同形 (2,3)")
print("rank(dW) =", np.linalg.matrix_rank(dW))   # 永远是 1

实验 3:softmax+CE 梯度——分量和恒为 0,越自信梯度越小

改什么:把 logits 从 \([2,1,0]\) 调成更"自信"的 \([5,1,0]\)(真标签都是第 0 类),各算一次 \(\hat y-y\)。
预期现象:两种情况梯度分量之和都恰好为 0;而更自信那组的梯度幅度明显更小(正确类从 \(-0.335\) 缩到 \(-0.024\))。
为什么:因为 \(\sum\hat y=\sum y=1\),差的和必为 0(§5 的固有性质);预测越接近真标签 \(\hat y\to y\),梯度 \(\hat y-y\to 0\),正是"预测对了梯度近 0、错得越离谱梯度越大"的直觉。

import numpy as np
def softmax(z):
    z = z - z.max(); e = np.exp(z); return e/e.sum()
y = np.array([1.,0.,0.])               # 真标签=第0类
for z in ([2.,1.,0.], [5.,1.,0.]):
    g = softmax(np.array(z)) - y
    print("logits", z, "-> grad", np.round(g,3),
          " sum =", round(g.sum() + 0.0, 12))   # 两组都打印 0.0

动手练习

  1. 对称化二次型。证明对任意 \(A\),\(x^\top A x=x^\top A_{\text{sym}}x\),其中 \(A_{\text{sym}}=\tfrac12(A+A^\top)\)。由此说明为何梯度公式里只出现 \(A+A^\top\)("反对称部分对二次型无贡献")。再用 numpy 取一个随机非对称 \(A\),数值验证 \(x^\top A x=x^\top A_{\text{sym}}x\)。
    import numpy as np
    rng = np.random.default_rng(1)
    A = rng.standard_normal((4,4)); x = rng.standard_normal(4)
    As = 0.5*(A + A.T)
    print("两种二次型之差 =", x@A@x - x@As@x)   # 应≈0
    
  2. L2 正则的梯度。设 \(L(W)=\tfrac{\lambda}{2}\|W\|_F^2=\tfrac{\lambda}{2}\sum_{ij}W_{ij}^2\)(Frobenius 范数平方)。手推 \(\partial L/\partial W\),并解释它为什么导致"权重衰减 weight decay"。用 numgrad 验证你的解析式。(提示:答案是 \(\lambda W\),与 \(W\) 同形。)
  3. batch 版线性层。把 §4 推广到 mini-batch:\(Y=XW^\top+\mathbf{1}b^\top\),\(X\in\mathbb{R}^{B\times n}\)(行=样本),其中 \(\mathbf{1}\in\mathbb{R}^B\) 是全 1 列向量、\(\mathbf{1}b^\top\in\mathbb{R}^{B\times m}\) 把偏置复制到每一行(即 numpy 的广播)。上游 \(\Delta=\partial L/\partial Y\in\mathbb{R}^{B\times m}\)。证明 \(\partial L/\partial W=\Delta^\top X\)、\(\partial L/\partial b=\Delta^\top\mathbf{1}\)(按样本求和)、\(\partial L/\partial X=\Delta W\)。用下面骨架做数值验证。
    import numpy as np
    rng = np.random.default_rng(2)
    B, n, m = 8, 5, 3
    X = rng.standard_normal((B, n)); W = rng.standard_normal((m, n)); b = rng.standard_normal(m)
    T = rng.standard_normal((B, m))
    ones = np.ones(B)               # 全 1 列向量 1
    def Lb(W, b, X):
        Y = X @ W.T + np.outer(ones, b)   # = X W^T + 1 b^T (广播写法等价于 X @ W.T + b)
        return 0.5*np.sum((Y - T)**2)
    Y = X @ W.T + np.outer(ones, b)
    Delta = (Y - T)                 # B x m
    dW = Delta.T @ X                # m x n  —— 你来核对维度
    db = Delta.T @ ones             # m      (= 按样本求和 Delta.sum(axis=0))
    # TODO: 写一个 numgrad 验证 dW, db(可复用正文里的 numgrad)
    print("dW.shape =", dW.shape, " 应为", W.shape)
    
  4. softmax 雅可比。不带交叉熵,直接写出 softmax 的雅可比 \(J_{ki}=\partial\hat y_k/\partial z_i=\hat y_k(\delta_{ki}-\hat y_i)\) 的矩阵形式 \(J=\operatorname{diag}(\hat y)-\hat y\hat y^\top\)。用 numpy 构造它,并验证 \(J\mathbf{1}=0\)(每行和为 0,因为概率归一)。
  5. 从微分法推 \(\partial L/\partial x=W^\top\delta\)。仿照 §4.1,对 \(y=Wx\) 取 \(dy=W\,dx\),代入 \(dL=\delta^\top dy\),整理成 \(dL=g^\top dx\) 读出 \(g\)。写下每一步,确认 \(g=W^\top\delta\) 且形状为 \(\mathbb{R}^n\)。

掌握自检

下一课,我们把第 1 模块的所有零件——向量、矩阵变换、链式法则、计算图、以及本课的矩阵求导——拼到一起,从零手写并训练一个两层神经网络,亲眼看着损失曲线降下来。