一个结果,很多输入
问题:把向量 x 的每个分量稍微改动,标量损失 L 会变多少?
定义:把所有偏导排成梯度 (n,)。
梯度与改变量做点积,预测损失变化。
你已经会标量求导,也会向量、矩阵和矩阵乘法。本课从这些知识出发,先理解梯度,再把第一节线性回归的每一行计算拆成你熟悉的标量式子。你不需要先学张量或 PyTorch;后面的雅可比、一般线性层与反向传播可以继续学习,也可以在后续课程中回看。
01 / 先理解为什么要求导
你熟悉的标量导数已经表达了这个意思。例如 f(w)=w²,在 w=3 时 f′(3)=6:把 w 增加很小的 0.01,f 大约增加 6×0.01=0.06。实际从 9 变成 9.0601;导数给的是当前位置附近的一阶近似。
问题:把向量 x 的每个分量稍微改动,标量损失 L 会变多少?
定义:把所有偏导排成梯度 (n,)。
梯度与改变量做点积,预测损失变化。
问题:输入向量 x 有一点变化时,输出向量 y 的每个分量怎样变?
定义:所有输出对所有输入的偏导组成雅可比矩阵 (m,n)。
雅可比像一个局部线性变换,把输入变化映射为输出变化。
问题:把权重矩阵 W 的每个格子稍微改动,损失会变多少?
定义:每格的偏导组成与 W 同形的梯度 (m,n)。
负梯度告诉优化器该怎样同时调整整张权重表。
02 / 一个标量如何随多个数变化
令 L(x₁,x₂)=x₁²+2x₂²。你会分别求两个标量偏导:
把它们按 x 的顺序排在一起,就是梯度:
假设准备把当前位置改成 x+Δx。梯度不必重新完整计算函数,就能预测损失大约变化 ∇L·Δx。把改动调大、再调小,比较预测和真实变化。
Δx=−η∇L,用学习率 η 控制步长。应用 / 读懂第一节的训练代码
下面这段训练代码看起来都是矩阵,其实每个梯度元素仍然是对一个标量参数求导。我们先用你会的标量求导算清楚,再把重复计算组织成向量或矩阵。
先看一个输入 x 和目标 y。可训练的参数是 w 和 b;求导时,数据 x、y 保持不变。
求 w 的偏导时,把 b 当成常数;求 b 的偏导时,把 w 当成常数。这就是把你熟悉的一元求导分别应用到两个参数上。这里的 ℓ 没有乘 1/2,因此导数保留系数 2。
第一节的数据是 x=[0,1,2,3,4]、y=[1,3,5,7,9]。每条样本都使用同一组 w、b。用 i 表示样本编号,从 0 到 4:
求和的导数等于各项导数相加;除以常数 5,求导后仍然除以 5。没有新的求导规则,只是把五条样本的贡献汇总起来。
这里只需要三个记号:(5,1) 表示 5 行 1 列;@ 表示矩阵乘法;.T 表示转置。X 是五个输入排成的列,error 是五个误差排成的列;W=[[w]] 是只有一个元素的矩阵,b=[b] 只有一个元素。
看到了吗?转置加矩阵乘法,就是一次算完上面那串“输入乘误差再相加”。它把已推导好的标量计算写得更紧凑,并不代替求导过程。
第一行是上面的 ∂L/∂w,结果装在 (1,1) 矩阵里,与 W 同形。第二行是 ∂L/∂b:固定输出列索引 0,遍历五个样本的行索引,对五个误差求平均,再乘 2,结果形状为 (1,),与 b 同形。len(X) 就是样本数 5。
| 计算 | w=0、b=0 时的结果 |
|---|---|
prediction = X @ W + b | 五个预测都是 0 |
error = prediction - y | 误差为 −1、−3、−5、−7、−9 |
loss = np.mean(error ** 2) | (1+9+25+49+81)/5 = 33 |
X.T @ error | 0×(−1)+1×(−3)+2×(−5)+3×(−7)+4×(−9) = −70 |
grad_W | 2/5 × (−70) = −28 |
grad_b | 2 × (−1−3−5−7−9)/5 = −10 |
学习率为 0.05,参数更新为 w=0−0.05×(−28)=1.4、b=0−0.05×(−10)=0.5。负梯度意味着:在当前位置,小幅增加这两个参数会降低损失。用更新后的参数重新计算,损失从 33 降到 3.61。
for step in range(100):
prediction = X @ W + b # 一起计算五个 wxᵢ+b
error = prediction - y # 五个误差 eᵢ
loss = np.mean(error ** 2) # 五个平方误差的平均值
grad_W = 2 / len(X) * X.T @ error # 汇总 2xᵢeᵢ,再除以样本数
grad_b = 2 * error.mean(axis=0) # 汇总 2eᵢ,再除以样本数
W -= learning_rate * grad_W # 每个权重减去它自己的梯度 × 学习率
b -= learning_rate * grad_b
每次循环都会用当前参数重新计算误差和梯度。上面 −28、−10 只属于初始参数,不会一直保持不变。
如果每条样本有两个特征,预测变成 pᵢ=xᵢ₀w₀+xᵢ₁w₁+b。分别对 w₀、w₁ 求导,其他参数固定:
Σᵢ 就是“把所有样本 i 对应的项相加”,N 是样本数。此时 X 为 (N,2),W 为 (2,1),error 为 (N,1)。X.T @ error 的两行分别是 Σᵢ xᵢ₀eᵢ 和 Σᵢ xᵢ₁eᵢ,所以同一行代码得到两个权重各自的梯度。
自测:不看表格,能否从 eᵢ² 推出 2xᵢeᵢ 和 2eᵢ,再解释转置、除以 N、mean 各自做了什么?可以的话,就进入第一节,学习更完整的形状、轴和广播。
03 / 多个输出对多个输入
雅可比矩阵的意义不是“把偏导摆好看”,而是用一个矩阵把输入的小变化换算成输出的小变化:Δy≈JΔx。第 j 列直接回答“只改变第 j 个输入时,所有输出如何变化”。
设 x=[x₁,x₂],y=Wx+b,其中 W 有 2 行 2 列:
展开后 y₁=x₁+2x₂,y₂=−x₁+3x₂+1。每行对应该输出对所有输入的偏导:
本例是线性函数,所以 Δy=JΔx 完全相等。非线性函数中它是当前位置附近的近似,而且雅可比会随输入变化。
04 / 训练真正需要的形式
矩阵 W 可以看成一张参数表。∂L/∂Wᵢⱼ 的定义是:只把第 i,j 个参数增加一个极小量,其余参数不动时,损失变化量与这个极小量的比值。把每一格的结果放回原位置,就得到矩阵梯度。
直接写“向量 y 对矩阵 W 求导”会得到三维对象,很难使用。训练最终有一个标量损失 L,因此通常直接求 ∂L/∂W:它与 W 同形,每一格就是对应权重的局部敏感度。
| 要求的梯度 | 公式 | 本例结果 | 形状 |
|---|---|---|---|
| ∂L/∂W | e xᵀ | [[-2,-1,1],[4,2,-2]] | (2,3) |
| ∂L/∂b | e | [-1,2] | (2,) |
| ∂L/∂x | Wᵀe | [0,3,-4] | (3,) |
第 i 个输出为 yᵢ=ΣⱼWᵢⱼxⱼ+bᵢ。损失对 yᵢ 的偏导是 eᵢ。
因此梯度矩阵第 i,j 格就是 eᵢxⱼ,把所有格子合起来便是 e xᵀ。
这正是 Wᵀe 的第 j 个分量。
权重 Wᵢⱼ 连接“输入 xⱼ”和“输出 yᵢ”。它的梯度同时需要两条信息:
eᵢ:第 i 个输出收到多大的上游梯度;xⱼ:这条连接当时接收到多大的输入。把每个输出信号与每个输入值两两相乘,刚好形成一个 (输出数, 输入数) 的表,这就是外积 e xᵀ。
选择一个权重并改变一点。矩阵梯度对应位置的数字乘改变量,会预测损失怎样变化。负梯度表示增加这个权重会让损失下降;正梯度表示增加它会让损失上升。
W 是 (2,3),所以 ∂L/∂W 也必须是 (2,3)。如果得到 (3,2),通常是外积次序或转置写反了。05 / 从一个样本到一批样本
PyTorch 把 nn.Linear(in,out).weight 存成 (out,in)。一批输入 X 的每行是一个样本,所以前向是:
设上游梯度 G=∂L/∂Z,它和 Z 同形。批次公式是:
| 梯度 | 公式 | 形状检查 | 含义 |
|---|---|---|---|
∂L/∂weight | G.T @ X | (out,B) @ (B,in) → (out,in) | 把每条样本的外积贡献加起来 |
∂L/∂b | G.sum(axis=0) | (B,out) → (out,) | 同一个偏置被一批中的所有样本共用 |
∂L/∂X | G @ weight | (B,out) @ (out,in) → (B,in) | 把梯度继续传向上一层 |
G 中已经包含除以批次大小的因子;如果损失使用求和,则没有这个因子。二者方向相同但大小不同。06 / 不建立巨大的导数矩阵
考虑 z=Wx+b、h=ReLU(z)、L=½‖h−t‖²。从右向左,每个节点只回答:“已经收到 ∂L/∂输出,怎样算 ∂L/∂输入?”
⊙ 表示逐元素乘法。虽然 ReLU 作为“向量对向量函数”有雅可比矩阵,但它是对角矩阵;实际程序直接做逐元素乘法,不会创建一张巨大的对角矩阵。
07 / 先理解结果,不要求背
softmax 的每个输出都依赖所有输入,因此它的雅可比不是单纯的逐元素导数。若 p=softmax(z):
与单个样本的交叉熵 L=−Σᵢyᵢlog pᵢ 合起来后,对 logits 的梯度会化简:
这不是“softmax 的导数就是 p−y”。p−y 是 softmax 与交叉熵组合后的结果。
08 / 手推之后让代码检查
如果把矩阵中某个元素 Wᵢⱼ 增加和减少一个很小的 ε,就能近似该位置的偏导:
打开或下载配套代码。它会核对向量梯度、雅可比、单样本与批次矩阵梯度,并和 PyTorch 自动求导比较。在项目根目录运行:
uv run python lessons/lesson-math-matrix-calculus.py
∂L/∂W、∂L/∂b 和 ∂L/∂x。09 / 检查是否真正理解