Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

B. 数学符号与常用推导

公式读不下去时,不一定是数学太难。有时只是同一个字母在两页里换了工作:上一页的 TT 是温度,下一页又成了序列长度。

查符号时,先看当前章节给它的定义,再看形状和单位。下面是一份常用约定,也保留几个容易混淆的地方。

1. 符号不是密码:先把它读成一句话

写法可以怎样读
x∈Rx\in\mathbb Rxx 是一个实数
x∈Rd\mathbf x\in\mathbb R^d一个有 dd 个实数分量的向量
X∈Rn×dX\in\mathbb R^{n\times d}nn 行、dd 列的矩阵
xix_i、XijX_{ij}第 ii 个分量;第 ii 行第 jj 列
wTx\mathbf w^{\mathsf T}\mathbf x对应分量相乘再相加
ATA^{\mathsf T}矩阵转置,交换行列
∥x∥2\|\mathbf x\|_2向量的欧几里得长度
y^\hat y预测或估计出来的量
θ⋆\theta^\star某个约定目标下的最优参数
∑i=1nai\sum_{i=1}^n a_i从第 1 项到第 nn 项相加
∏i=1nai\prod_{i=1}^n a_i从第 1 项到第 nn 项相乘
arg⁡min⁡θL(θ)\arg\min_\theta L(\theta)让损失最小的参数,而不是最小损失值
∂L/∂wj\partial L/\partial w_j只改变第 jj 个参数时,损失怎样变化
∇wL\nabla_{\mathbf w}L把各个参数的偏导数排成向量
P(A∣B)P(A\mid B)已知 BB 时,AA 的条件概率
X∼pX\sim p随机变量 XX 服从分布 pp
E[X]\mathbb E[X]、Var⁡(X)\operatorname{Var}(X)期望与方差
1[A]\mathbf 1[A]条件成立为 1,否则为 0
∝\propto、≈\approx成比例;近似相等

数学下标常从 1 开始,Python 下标从 0 开始,所以“第一个类别”的编号在代码里往往是 0。这个差别在交叉熵目标中尤其值得检查。

本书通常用 nn 表示样本数,dd 表示特征或表示维度,BB 表示批量大小,KK 表示类别数。序列长度、训练步数、温度等含义需要看局部定义。Transformer 中的 Q,K,VQ,K,V 又分别是查询、键、值,不能机械地把每一个 KK 都解释成类别数。

上标也要看语境:ATA^{\mathsf T} 是转置,x2x^2 是平方,而 h(l)h^{(l)} 常表示第 ll 层的表示。后者不是乘方。

2. 用形状检查一个式子有没有接对

设每行是一条样本,输入 XX 的形状为 n×dn\times d,权重 WW 的形状为 d×hd\times h。那么:

XW∈Rn×h XW\in\mathbb R^{n\times h}

中间两个长度 dd 对上,输出保留样本数 nn 和输出宽度 hh。若再加一个长度为 hh 的偏置,程序通常沿每行广播。

第一章的单输出模型可以写成:

y^=Xw+b1 \hat{\mathbf y}=X\mathbf w+b\mathbf 1

1\mathbf 1 是长度为 nn 的全 1 向量,说明每条样本都加同一个偏置。纸上的列向量在 NumPy 中可能用 (n,) 保存;转换为 (n,1) 时要让目标也遵守同一约定,见附录 A。

单位也能帮忙检查。若输入是页数,输出是秒,权重就是秒/页,偏置是秒。把秒数平方后取平均,MSE 的单位是秒的平方;RMSE 再开平方,才回到秒。

3. 均方误差的梯度:把链式法则展开一次

设第 ii 条样本的残差为 rir_i:

ri=wTxi+b−yiL=1n∑i=1nri2 \begin{aligned} r_i&=\mathbf w^{\mathsf T}\mathbf x_i+b-y_i\\ L&=\frac1n\sum_{i=1}^n r_i^2 \end{aligned}

对第 jj 个权重,先对平方求导,再乘上残差对该权重的变化率:

∂ri2∂wj=2rixij∂L∂wj=2n∑i=1nrixij∂L∂b=2n∑i=1nri \begin{aligned} \frac{\partial r_i^2}{\partial w_j} &=2r_i x_{ij}\\ \frac{\partial L}{\partial w_j} &=\frac2n\sum_{i=1}^n r_i x_{ij}\\ \frac{\partial L}{\partial b} &=\frac2n\sum_{i=1}^n r_i \end{aligned}

在打印例子里,三行输入是 [10,2]、[4,6]、[8,0],残差是 −2、2、−1。第一个权重的梯度于是为:

∂L∂w1=23[−2⋅10+2⋅4−1⋅8]=−403 \begin{aligned} \frac{\partial L}{\partial w_1} &=\frac23[-2\cdot10+2\cdot4-1\cdot8]\\ &=-\frac{40}{3} \end{aligned}

它为负,表示在当前点稍微增大第一个权重,损失会下降。实际更新还要乘学习率,不能把局部变化率当成“大步走到哪里都有效”的承诺。

把各分量合起来,便得到常见的矩阵写法:

∇wL=2nXTr \nabla_{\mathbf w}L=\frac2n X^{\mathsf T}\mathbf r

XTX^{\mathsf T} 是 d×nd\times n,乘长度为 nn 的残差,得到长度为 dd 的梯度。这个形状检查也解释了为什么这里需要转置。

有些资料把平方损失定义成前面带 1/(2n)1/(2n)。那样梯度中的 2 会消掉。两个版本并不矛盾,但比较公式或学习率时要先看损失定义。

4. softmax 与交叉熵:概率和梯度接在一起

给一条样本的类别分数 z1,…,zKz_1,\ldots,z_K,softmax 定义为:

pj=exp⁡(zj)∑k=1Kexp⁡(zk) p_j=\frac{\exp(z_j)}{\sum_{k=1}^K\exp(z_k)}

所有分数一起加一个常数,概率不变。因此实际计算时可以先减去最大分数 mm:

pj=exp⁡(zj−m)∑kexp⁡(zk−m) p_j=\frac{\exp(z_j-m)} {\sum_k\exp(z_k-m)}

这样最大的指数项是 1,避免直接计算 exp⁡(1000)\exp(1000) 之类的大数。它不是随意剪掉概率,而是同一个公式的等价写法。

若真实类别是 yy,单条交叉熵可改写为:

ℓ=−log⁡py=log⁡∑kexp⁡(zk)−zy \begin{aligned} \ell&=-\log p_y\\ &=\log\sum_k\exp(z_k)-z_y \end{aligned}

对任意分数 zjz_j 求导,第一项给出 pjp_j,第二项只在 j=yj=y 时给出 1,于是:

∂ℓ∂zj=pj−1[j=y] \frac{\partial\ell}{\partial z_j} =p_j-\mathbf1[j=y]

正确类别的概率如果还很小,它对应的梯度就明显为负;梯度下降会倾向于抬高这个分数。其他类别的梯度为正,分数倾向于降低。这里说的是对分数的局部梯度,网络参数还要继续沿计算图求导。

本书没有特别说明时,log⁡\log 指自然对数。交叉熵因此以 nat 计量,困惑度是 exp⁡(L)\exp(L);若改用以 2 为底的对数,对应的单位与反变换也要一起改变。

5. 期望与方差:平均什么,按什么权重

随机变量取离散值 xix_i,各自概率为 pip_i,且概率和为 1。期望是:

μ=E[X]=∑ipixi \mu=\mathbb E[X]=\sum_i p_i x_i

方差看的是相对期望的平方偏差:

Var⁡(X)=E[(X−μ)2]=E[X2]−μ2 \begin{aligned} \operatorname{Var}(X) &=\mathbb E[(X-\mu)^2]\\ &=\mathbb E[X^2]-\mu^2 \end{aligned}

第二行来自展开平方,再用 E[X]=μ\mathbb E[X]=\mu。例如取 0、1、2 的概率分别是 1/4、1/2、1/4,期望为 1,方差为 1/2。

概率分布的方差和有限样本的方差估计不是同一个定义。对独立同分布、方差有限且数量大于 1 的样本,用样本均值估计总体均值时,常见的无偏样本方差除以 n−1n-1;如果只把当前数组当作一个经验分布来描述,则通常除以 nn。不能只看函数名叫 var,还要检查自由度参数。数字很大但差异很小时,直接计算“平方均值减均值平方”还可能损失精度,程序通常采用更稳定的算法。

6. 用计算核对推导

完整脚本只依赖 Python 标准库。它计算稳定概率,并用中心差分核对交叉熵梯度。运行:

python3 src/examples/math_reference.py

实际输出:

稳定 softmax: [0.244728, 0.665241, 0.090031]
真实类别为 1 时的交叉熵:0.407606
交叉熵梯度: [0.244728, -0.334759, 0.090031]
中心差分核对:通过
打印例子的权重梯度: [-13.333333, 5.333333]
离散例子的期望:1.000,方差:0.500

代码里的真实类别编号 1 指第二个分数。数值核对能发现不少下标和系数错误,但有限个输入上的检查不能代替一般证明。

展开完整代码
"""附录数学核对:稳定概率、均方误差梯度、离散期望;仅标准库。"""

from math import exp, log


def logsumexp(values):
    largest = max(values)
    return largest + log(sum(exp(v - largest) for v in values))


def cross_entropy(logits, label):
    return logsumexp(logits) - logits[label]


def softmax(logits):
    shifted = [exp(z - max(logits)) for z in logits]
    total = sum(shifted)
    return [v / total for v in shifted]


def mse(weights, bias, inputs, targets):
    return sum((sum(a * b for a, b in zip(row, weights)) + bias - y) ** 2
               for row, y in zip(inputs, targets)) / len(inputs)


def main():
    z = [1000.0, 1001.0, 999.0]
    p = softmax(z)
    gradient = [v - int(j == 1) for j, v in enumerate(p)]
    eps = 1e-4
    finite = []
    for j in range(len(z)):
        plus, minus = z.copy(), z.copy()
        plus[j] += eps
        minus[j] -= eps
        finite.append((cross_entropy(plus, 1) - cross_entropy(minus, 1)) / (2 * eps))
    assert max(abs(a - b) for a, b in zip(gradient, finite)) < 1e-8
    outcomes, probabilities = [0, 1, 2], [0.25, 0.5, 0.25]
    mean = sum(x * q for x, q in zip(outcomes, probabilities))
    variance = sum((x - mean) ** 2 * q for x, q in zip(outcomes, probabilities))
    x, w, y = [[10, 2], [4, 6], [8, 0]], [3.0, 5.0], [62, 60, 45]
    residual = [sum(a * b for a, b in zip(row, w)) + 20 - t for row, t in zip(x, y)]
    dw = [2 * sum(row[j] * e for row, e in zip(x, residual)) / len(x) for j in range(2)]
    print("稳定 softmax:", [round(v, 6) for v in p])
    print(f"真实类别为 1 时的交叉熵:{cross_entropy(z, 1):.6f}")
    print("交叉熵梯度:", [round(v, 6) for v in gradient])
    print("中心差分核对:通过")
    print("打印例子的权重梯度:", [round(v, 6) for v in dw])
    print(f"离散例子的期望:{mean:.3f},方差:{variance:.3f}")


if __name__ == "__main__":
    main()

自测

  1. 把 LL 从平均平方误差改成平方误差总和,梯度改变多少?
  2. 给全部 logits 加 100,交叉熵和它对各 logits 的梯度是否改变?
  3. 为什么本页交叉熵梯度的各分量加起来为 0?
  4. 随机变量总是等于 5,它的期望和方差各是多少?
答案
  1. 乘以样本数 nn。比较训练时也要检查批量大小与损失归约方式。
  2. 数学上不变;稳定实现更容易在浮点计算中保留这个性质。
  3. softmax 概率之和是 1,真实类别指示量之和也是 1。这也对应共同平移分数不会改变损失。
  4. 期望为 5,方差为 0。“随机变量”这个名称不要求每次一定出现不同值。

需要更慢的推导时,回到第一章、第二章、第三章和第五章。这些基础关系也可对照 《Deep Learning》第 2、3、6 章阅读。