为什么 AI 论文全是天书?因为每一个数学符号,其实都是一句人话
AI 论文里的 ∑、∂、∇、𝔼、argmax 为什么越看越像天书?其实它们不是新的知识,而是一整句话被压缩后的缩写。从数学符号的诞生历史,到 GPT、Transformer、PyTorch 代码,一篇文章教你读懂任何公式。
"一个好的记号,能把大脑从不必要的劳动中解放出来,让它去思考更高级的问题。" —— 怀特海
"在 1591 年之前,人类甚至写不出'一般的方程'这句话。"
上一篇(Chapter 0 / 0.5)讲的是为什么是这个公式 —— 为什么反复出现平方、\(1/r^2\)、\(\log\)。 这一篇讲更前置、也更卡人的一步:你拿到一个陌生公式,怎么把它读下去。
很多人说"我数学不好",其实说的是:"我看到 \(\sum\)、\(\partial\)、\(\nabla\)、\(\mathbb{E}\)、\(\arg\max\)、\(\otimes\) 就自动关机了。" 这不是智力问题,是没人告诉你这些符号是缩写,而缩写的全称从来没被展开过。
🕰️ 0.6.1 先看一件震撼的事实:数学在 1600 年前,是用大白话写的
数学史学家 Nesselmann(1842)把代数分成三个阶段:
| 阶段 | 时代 | 长什么样 |
|---|---|---|
| 文辞代数(rhetorical) | 巴比伦 ~ 花拉子米(820) | 全是句子,一个符号都没有 |
| 简写代数(syncopated) | 丢番图(~250) | 开始用缩写字母,但仍是"速记" |
| 符号代数(symbolic) | 韦达(1591)→ 笛卡尔(1637) | 可以写出"任意的方程" |
花拉子米(al-Khwārizmī, 820,al-Jabr —— "algebra" 一词的来源)解一元二次方程时,原文大意是:
"一个平方,与十个它的根,等于三十九迪拉姆。取根数目之半,得五;自乘之,得二十五;加于三十九,得六十四;取其方根,得八;减去根数目之半,得三 —— 此即所求之根。"
翻译成今天的符号,全文就是一行:
一整段话 → 一行符号。 你现在感受到的"公式好难",本质是信息密度难:一行公式压缩了一整段人话,而没人教你解压。
而且这些符号是被一个一个发明出来的,每一个背后都有一个具体的人、一个具体的偷懒动机:
| 符号 | 发明者 / 年份 | 发明动机(真实的) |
|---|---|---|
| \(+\;-\) | 维德曼 1489 | 仓库账本上的盈余/亏空记号,先是商业符号,后被数学借用 |
| \(\sqrt{\;}\) | 鲁道夫 1525 | 拉丁文 radix(根)的手写体 r 拉长 |
| \(=\) | 雷科德 1557 | "我写腻了 is equal to",取两条等长平行线 —— "再没有两样东西比它们更相等" |
| \(\times\) | 奥特雷德 1631 | 莱布尼茨嫌它像字母 x,坚持用 \(\cdot\)(今天两派仍并存) |
| \(<\;>\) | 哈里奥特 1631(遗著) | 开口朝大数 |
| 未知数 \(x,y,z\) | 笛卡尔 1637 | 韦达 1591 用元音表未知、辅音表已知;笛卡尔改成字母表末尾=未知,开头=已知 |
| 指数 \(x^3\) | 笛卡尔 1637 | 此前要写 cubus,或画三个 x |
| \(\infty\) | 沃利斯 1655 | 罗马数字里表"很多"的符号变体 |
| \(\int\) | 莱布尼茨 1675.10.29 | 拉长的 s = summa(和)—— 积分号从诞生第一天起就在喊:"我是求和" |
| \(dx\) | 莱布尼茨 1675.11.11 | differentia(差)的首字母 —— 差分的 d |
| \(\dot{x}\) | 牛顿 1665 | "流数",强调对时间的变化率 |
| \(f(x)\) | 欧拉 1734 | 第一次把"函数"当成可命名的对象 |
| \(\sum\) | 欧拉 1755 | 希腊大写 Sigma = Sum |
| \(\pi\) | 琼斯 1706 / 欧拉 1748 推广 | περιφέρεια(周长)首字母 |
| \(e\) | 欧拉 1727 | 很可能只是 exponential,或 a,b,c,d 之后的下一个可用字母 |
| \(i\) | 欧拉 1777 / 高斯 1801 推广 | imaginarius |
| \(n!\) | 克拉普 1808 | 为了排版方便(此前写成 \(\lfloor\!\underline{n}\) 这类怪东西) |
| \(\prod\) | 高斯 1812 | 大写 Pi = Product |
| \(\partial\) | 勒让德 1786 / 雅可比 1841 推广 | "圆 d",用来区分"只对其中一个变量偷偷动一下" |
| \(\lim\) | 吕利耶 1786;箭头 \(\to\) 由哈代 ~1908 | 此前只能写"无限趋近于" |
| \(\varepsilon,\delta\) | 柯西 1821(\(\varepsilon\) = erreur 误差)→ 魏尔斯特拉斯 1860s 严格化 | 把"无穷小"这个哲学争议翻译成可验证的不等式 |
| 矩阵 | 西尔维斯特 1850 命名,凯莱 1858 成为代数对象 | 先有"一堆数排成方阵"的用法,两千年后才有名字 |
| \(\in\;\cup\;\cap\;\subset\) | 皮亚诺 1888–1889 | \(\in\) 取自希腊语 ἐστί("是")的 ε |
| \(\exists\) | 皮亚诺 1897 | 反写的 Exists |
| \(\forall\) | 根岑 1935 | 倒写的 All |
| \(\nabla\) | 哈密顿 1837,泰特命名 "nabla" | 形似一种希伯来竖琴;吉布斯/亥维赛把它推成向量分析标配 |
| \(\aleph\) | 康托尔 1893 | 希伯来字母第一个,标记无穷的"大小" |
| 求和约定 | 爱因斯坦 1916 | "重复指标就求和" —— 他自嘲这是他"数学史上最大的贡献" |
| $\langle\psi | \phi\rangle$ | 狄拉克 1939 |
看这张表要看出的东西:没有一个符号是"从天上掉下来的规定"。每一个都是某个人嫌麻烦,于是把一段话压成一个记号。 所以读符号的正确姿势永远是:把它解压回那句人话。
🧭 0.6.2 读公式六步法:像读代码一样读公式
数学公式和一段代码是同构的。程序员读陌生代码有一套本能流程 —— 读公式用同一套。
Step 1 · 先做 shape check:每个符号是什么"类型"?
在读含义之前,先读类型:标量?向量?矩阵?函数?算子?集合?分布?
import torch
# 读公式的第一件事不是"理解",是"标注类型"
x = torch.randn(4) # 输入:向量, shape (4,) —— 小写粗体 → 向量
W = torch.randn(3, 4) # 权重:矩阵, shape (3, 4) —— 大写 → 矩阵
b = torch.randn(3) # 偏置:向量, shape (3,)
sigma = torch.sigmoid # σ:函数 R→R,逐元素作用 —— 希腊字母这里是"函数"不是"标准差"
y = sigma(W @ x + b) # (3,4)@(4,) -> (3,) ; +(3,) -> (3,) ; σ 不改 shape
print(y.shape) # torch.Size([3])
类型对不上,公式一定读错了。 这一步能过滤掉 80% 的误读 —— 就像编译器帮你抓住的那 80% 的 bug。
Step 2 · 找"主动词":从外向内读,不要从左向右读
汉语句子先找谓语,公式先找最外层的运算。这才是这个公式"在干什么"。
从外往里剥:
最外层: 负号 + 平均 → "我要最小化某个平均量"
第二层: ∑_i 遍历 N 个样本 → "这个量是逐样本算完再平均的"
第三层: ∑_c 遍历 C 个类别 → "每个样本内部再对类别求和"
最内层: y·log p̂ → "只有真实类别 (y=1) 那一项活下来"
到这里公式已经读完了:交叉熵 = 对每个样本,取真实类别的 \(-\log\) 预测概率,再平均。 一行代码:
loss = -(y_onehot * p_hat.log()).sum(dim=1).mean() # 逐符号对应,一一映射
由外向内是关键。从左向右读 \(-\frac{1}{N}\sum\sum y\log\hat p\),你会淹死在下标里; 先看清"这是一个平均",剩下的都是细节。
Step 3 · 分清哑变量与自由变量 —— 这是最大的困惑源
\(i\) / \(k\) 是哑变量(bound variable):它只在 \(\sum\) 内部存在,出了求和号就没有意义。 \(n\) 和 \(a\) 是自由变量:它们从外面传进来。
这就是 lambda 的参数,改名不改语义 —— λ 演算里叫 α-变换(呼应 Chapter 0.9):
# 哑变量 = 函数的局部参数,改名毫无影响
f = lambda a, n: sum(a[i] for i in range(n)) # i 是哑的
g = lambda a, n: sum(a[k] for k in range(n)) # k 是哑的,f 和 g 完全相同
凡是带"绑定器"的符号,其紧跟的变量都是哑的:
| 绑定器 | 哑变量 | 编程对应 |
|---|---|---|
| \(\sum_{i}\)、\(\prod_{i}\) | \(i\) | for i in ... 的循环变量 |
| \(\int f(x)\,dx\) | \(x\) | 积分变量 = 被积函数的形参 |
| \(\forall x\)、\(\exists x\) | \(x\) | all(... for x in ...) |
| \(\max_{\theta}\)、\(\arg\min_{\theta}\) | \(\theta\) | 优化循环里的可变参数 |
| \(\{x : P(x)\}\) | \(x\) | 列表推导 [x for x in S if P(x)] |
| \(\lambda x.\,M\) | \(x\) | lambda x: M |
一旦你会分辨"哪个字母是临时工、哪个是从外面传进来的",公式的作用域就清晰了 —— 和读代码找变量作用域是同一个动作。
Step 4 · 量纲/维度检查:加号两边必须是同类东西
- 加号两边必须同量纲、同 shape。 \(E = mc^2 + v\) 一眼就是错的(能量 + 速度)。
- \(\exp\)、\(\log\)、\(\sin\) 的宗量必须无量纲。 所以 \(e^{-t/\tau}\) 里必然有个时间常数 \(\tau\) 来把 \(t\) 除成纯数 —— 看到 \(e^{-x/\lambda}\),你立刻知道 \(\lambda\) 和 \(x\) 同量纲。
- 比值和平方要跟着量纲走。 \(\mathrm{Var}[X]\) 的量纲是 \(X\) 的平方,所以才需要开根号变成标准差,才能跟 \(X\) 画在同一张图上。
# 用量纲反推陌生公式里某个符号的身份:高斯密度里的 σ 一定和 x 同量纲
# 因为 exp 的宗量 (x-μ)²/(2σ²) 必须无量纲
# 又因为整个密度要积分成 1(无量纲),前面的 1/(σ√(2π)) 必须是 1/量纲(x)
这一步不需要理解公式就能做,却能倒推出符号的身份。 物理学家用它猜公式(呼应 Chapter 0.7),你可以用它读公式。
Step 5 · 代极端值:\(n=0,1\);\(\sigma\to0,\infty\);\(T\to 0\)
公式是抽象的,抽象的东西要审问(呼应 Chapter 0.95 的代入法):
import torch
def softmax_T(z, T):
return torch.softmax(z / T, dim=-1)
z = torch.tensor([2.0, 1.0, 0.1])
print(softmax_T(z, 1.0)) # tensor([0.6590, 0.2424, 0.0986]) 正常
print(softmax_T(z, 0.01)) # tensor([1.0000e+00, 3.78e-44, 0.0000e+00]) T→0 退化成 argmax(一位独大)
print(softmax_T(z, 100.0)) # tensor([0.3366, 0.3332, 0.3302]) T→∞ 退化成均匀分布
三个极端一试,\(T\) 这个符号的身份立刻水落石出:它是"决策锐度"的旋钮。 这比读十行文字描述都快。任何含参数的公式,都先把参数推到两个极端看它退化成什么。
Step 6 · 写成代码跑一遍 —— 唯一的验收标准
能把公式写成一个不用任何数学库的 for 循环,才算真读懂了。
xs = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0]
mu = sum(xs) / len(xs) # 𝔼[X] → 平均
var = sum((x - mu) ** 2 for x in xs) / len(xs) # 𝔼[(X-μ)²] → 平方后再平均
print(mu, var) # 5.0 4.0
# 对拍:库函数必须给出一样的答案,否则是你读错了公式
import torch
t = torch.tensor(xs)
print(t.mean().item(), t.var(unbiased=False).item()) # 5.0 4.0 ✓
"手写 for 循环 vs 库函数对拍" 是检验"我是不是真看懂这个符号"的黄金标准 —— 它无法自欺欺人。
📖 0.6.3 符号 → 代码 总字典
把这张表贴在显示器旁边。读公式时逐符号查,读三个月,就不用查了。
求和 / 求积 / 积分 —— "循环"家族
| 符号 | 读作 | 代码 | 注意 |
|---|---|---|---|
| \(\sum_{i=1}^{n} a_i\) | 对 i 从 1 到 n 求和 | sum(a[i] for i in range(n)) / a.sum() |
数学下标从 1,代码从 0 |
| \(\sum_{i \ne j}\) | 跳过对角 | mask = ~torch.eye(n,dtype=bool) |
极易漏掉的条件 |
| \(\prod_i a_i\) | 求积 | a.prod() |
数值上常改成 a.log().sum().exp() 防下溢 |
| \(\int_a^b f(x)dx\) | 求和的连续版 | torch.trapz(f(x), x) |
\(\int\) 就是拉长的 s = summa |
| \(\oint\) | 沿闭合回路积分 | 首尾相接的 trapz |
环路 → 守恒量、绕数 |
| \(\iint,\ \iiint\) | 多重积分 | 嵌套循环 / meshgrid |
对应多重 for |
| \(\mathbb{E}[X]\) | X 的期望 | x.mean()(蒙特卡洛) |
期望 = 概率加权求和,采样后就是平均 |
| \(\mathbb{E}_{x\sim p}[f(x)]\) | 在分布 p 下的期望 | f(p.sample((N,))).mean() |
下标写的是从哪个分布采样 |
微分 —— "变化率"家族
| 符号 | 读作 | 代码 | 含义差别 |
|---|---|---|---|
| \(\dot{x}\) | 牛顿点记法 | grad(x, t) |
专指对时间求导 |
| \(\frac{dy}{dx}\) | 莱布尼茨记法 | torch.autograd.grad(y, x) |
强调"两个微分的比值",可以约分(换元法的心理支柱) |
| \(\frac{\partial f}{\partial x}\) | 偏导,"圆 d" | 多元函数中对单个参数求导 | 其它变量按住不动 |
| \(f'(x)\) | 拉格朗日撇号 | 同上 | 撇号也可能表示"另一个变量",看上下文! |
| \(\nabla f\) | 梯度(nabla / del) | torch.autograd.grad(f, x) → 向量 |
标量→向量,指向上升最快方向 |
| \(\nabla \cdot \mathbf{F}\) | 散度 | sum(∂F_i/∂x_i) → 标量 |
"这里是源还是汇" |
| \(\nabla \times \mathbf{F}\) | 旋度 | 反对称组合 → 向量 | "这里在打转吗" |
| \(\nabla^2,\ \Delta\) | 拉普拉斯算子 | 二阶导之和 | 热传导、扩散、图拉普拉斯的核心 |
| \(J\) | 雅可比矩阵 | torch.func.jacrev(f)(x) |
shape = (输出维, 输入维) |
| \(H\) | 海森矩阵 | torch.func.hessian(f)(x) |
二阶导,曲率 = 牛顿法的地基 |
| \(\delta\)(变分) | 泛函的"微分" | 最优控制 / 拉格朗日力学 | 和 Kronecker \(\delta\)、Dirac \(\delta\) 完全不是一个东西 |
import torch
x = torch.tensor([1.0, 2.0], requires_grad=True)
f = (x ** 2).sum() # f = x₁² + x₂²
g, = torch.autograd.grad(f, x) # ∇f = (2x₁, 2x₂)
print(g) # tensor([2., 4.])
# ∇ 这个符号在代码里的全部内容,就是这一行:把标量变成和输入同 shape 的向量
线性代数 —— "shape 流"家族
| 符号 | 读作 | 代码 | shape 变化 |
|---|---|---|---|
| \(A^{\mathsf T}\) | 转置 | A.T |
(m,n) → (n,m) |
| \(A^{-1}\) | 逆 | torch.linalg.inv(A) |
实战中几乎总该换成 solve |
| \(A^{+}\) | 伪逆 | torch.linalg.pinv(A) |
最小二乘的闭式解 |
| \(\langle u, v\rangle\)、\(u\cdot v\)、\(u^{\mathsf T}v\) | 内积 | u @ v |
(n,),(n,) → 标量 |
| \(u \otimes v\)、\(uv^{\mathsf T}\) | 外积 / 张量积 | torch.outer(u,v) |
(m,),(n,) → (m,n) |
| \(A \odot B\) | 逐元素积(Hadamard) | A * B |
shape 不变 |
| \(\|x\|_2\) | L2 范数 | x.norm() |
向量 → 标量(长度) |
| \(\|x\|_1\) | L1 范数 | x.abs().sum() |
稀疏正则 |
| \(\det A\) | 行列式 | torch.linalg.det(A) |
矩阵 → 标量(有向体积) |
| \(\operatorname{tr} A\) | 迹 | A.trace() |
特征值之和 |
| \(\lambda_i,\ v_i\) | 特征值 / 特征向量 | torch.linalg.eig(A) |
"被这个矩阵作用后只缩放不转向"的方向 |
| \(A_{ij}B_{jk}\)(爱因斯坦约定) | 重复指标即求和 | torch.einsum('ij,jk->ik', A, B) |
einsum 就是爱因斯坦 1916 的记号本身 |
\(\otimes\) 和 \(\odot\) 这类符号最容易糊 —— 别背名字,背 shape: 外积让维度变多,Hadamard 让维度不变,内积让维度消失。
逻辑 / 集合 —— "断言"家族
| 符号 | 读作 | 代码 |
|---|---|---|
| \(\forall x \in S,\ P(x)\) | 对所有 | all(P(x) for x in S) |
| \(\exists x \in S,\ P(x)\) | 存在 | any(P(x) for x in S) |
| \(x \in S\) | 属于 | x in S |
| \(A \subset B\) | 子集 | A <= B(Python set) |
| \(A \cup B,\ A\cap B\) | 并、交 | A | B、A & B |
| \(\Rightarrow,\ \iff\) | 蕴含、当且仅当 | not p or q、p == q |
| \(:=\)、\(\triangleq\) | 定义为(不是等式) | = 赋值 —— 左边是新名字,不用去"解"它 |
| \(\propto\) | 正比于 | 差一个常数因子;贝叶斯里表示"归一化常数省略了" |
| \(\sim\) | 服从分布 / 渐近于 | x = dist.sample();或 \(n! \sim \sqrt{2\pi n}(n/e)^n\) |
| \(\sup,\ \inf\) | 上确界、下确界 | max/min,但可能取不到(开区间) |
| \(\arg\max_x f(x)\) | 使 f 最大的那个 x | x[f(x).argmax()] —— 返回位置,不是返回值 |
\(\max\) 与 \(\arg\max\) 的区别,是初学者最常见的翻车点: \(\max_x f(x)\) 返回最高的分数,\(\arg\max_x f(x)\) 返回取得最高分的那个选手。 一个是
f(x).max(),一个是f(x).argmax()。
⚠️ 0.6.4 一词多义地图:同一个符号,五种意思
这是"符号看不懂"最真实的原因 —— 不是符号太多,是符号太少,被反复重用。 希腊字母只有 24 个,而数学分支有上百个。
| 符号 | 可能的含义(靠上下文消歧) |
|---|---|
| \(\sigma\) | 标准差 / 激活函数(sigmoid)/ 奇异值 / 置换 / 表面电荷密度 / 应力 |
| \(\Sigma\) | 求和号 / 协方差矩阵 / 字母表(形式语言) |
| \(\lambda\) | 特征值 / 正则化系数 / 波长 / 泊松参数 / λ 演算的抽象 / 学习率衰减 |
| \(\delta\) | Kronecker \(\delta_{ij}\)(相等取 1)/ Dirac \(\delta(x)\)(广义函数)/ 变分 \(\delta J\) / ε-δ 里的小量 / 反向传播的误差项 |
| \(\varepsilon\) | 任意小量 / 噪声项 / 强化学习探索率 / 数值稳定项 1e-8 / 介电常数 |
| \(\mu\) | 均值 / 测度 / 磁导率 / 动量系数 / 微(\(10^{-6}\)) |
| \(\pi\) | 圆周率 / 策略函数(RL)/ 置换 / 平稳分布 |
| \(\theta\) | 角度 / 模型参数(ML 中最常见) |
| \(\rho\) | 密度 / 相关系数 / 谱半径 / 密度矩阵 |
| \(\|\cdot\|\) | 绝对值 / 复数模 / 集合势 / 行列式($ |
| \(*\) | 乘法 / 卷积 \(f*g\) / 共轭 \(z^*\) / 对偶空间 \(V^*\) / 最优解 \(x^*\) |
| \('\) | 导数 / "另一个"变量(\(x'\))/ 转置(部分统计文献) |
| \(\top\) | 转置 / 逻辑真 / 格的顶元素 |
| \(\nabla\) | 梯度 / 散度(配 \(\cdot\))/ 旋度(配 \(\times\))/ 联络(微分几何) |
| \(\cdot\) | 乘法 / 内积 / 占位符(\(f(\cdot)\) 表示"这里放一个参数") |
唯一的消歧规则:符号本身没有意义,"符号 + 领域 + 位置"才有意义。
具体三招: 1. 看它出现在什么运算里:\(\Sigma\) 后面跟下标 \(i=1\) 就是求和,出现在 \(\mathcal{N}(\mu,\Sigma)\) 里就是协方差矩阵。 2. 看它的 shape:\(\delta\) 带两个下标(\(\delta_{ij}\))是 Kronecker,带括号(\(\delta(x)\))是 Dirac。 3. 看这一页的第一次出现:论文的规范是首次出现处必须定义。看不懂就往回找定义 —— 找不到的话,那是作者的错,不是你的。
🔬 0.6.5 实战解剖:三个公式,逐符号拆开
按本项目的规矩:现象 → 模拟 → 拆解 → 公式(Chapter 0.7),不是反过来。
案例一:高斯分布 —— 从"高尔顿板"到符号
先看现象:一堆小球从上往下掉,每层随机左右偏一次,落到底部形成一堆。
import torch, matplotlib.pyplot as plt
n_balls, n_rows = 20000, 32
steps = (torch.randint(0, 2, (n_balls, n_rows)) * 2 - 1) # 每层 ±1,公平的硬币
pos = steps.sum(dim=1).float() # 累积左右偏移
plt.hist(pos.numpy(), bins=40, density=True, alpha=.6, label="高尔顿板(模拟)")
mu, sigma = pos.mean(), pos.std()
xs = torch.linspace(pos.min(), pos.max(), 300)
pdf = 1/(sigma*torch.sqrt(torch.tensor(2*torch.pi))) * torch.exp(-(xs-mu)**2/(2*sigma**2))
plt.plot(xs, pdf, lw=2, label="高斯公式")
plt.legend(); plt.show()
先跑出那个钟形,再回头解剖公式:
| 片段 | 它在干什么 | 对应代码 |
|---|---|---|
| \(x - \mu\) | 距离中心多远 | xs - mu |
| \((\cdot)^2\) | 左右对称 + 可微(呼应 0.5.1) | ** 2 |
| \(/\sigma^2\) | 用"自然尺度"去量,使宗量无量纲 | / sigma**2 |
| \(\exp(-\cdot)\) | 让远处衰减得极快,且永不为零 | torch.exp |
| \(\frac{1}{\sigma\sqrt{2\pi}}\) | 让总面积 = 1,\(\sqrt{2\pi}\) 是被逼出来的常数 | 归一化因子 |
\(\sqrt{2\pi}\) 不是审美,是积分 \(\int e^{-x^2/2}dx=\sqrt{2\pi}\) 逼出来的账单(那个极坐标换元技巧,正是 Chapter 0.3 的 gaussian_integral_polar.py)。
案例二:注意力公式 —— 现代 AI 里最"吓人"的一行
用六步法,只跟 shape 走,两分钟读完:
import torch
n, d_k, d_v = 5, 8, 8 # 5 个 token
Q = torch.randn(n, d_k) # Query:我在找什么
K = torch.randn(n, d_k) # Key :我是什么
V = torch.randn(n, d_v) # Value:我能提供什么
scores = Q @ K.T # (n,d_k)@(d_k,n) -> (n,n) ← 每对 token 的内积=相似度
scores = scores / d_k ** 0.5 # 除 √d_k:内积随维度增长 ~√d_k,不缩放 softmax 会饱和
w = torch.softmax(scores, dim=-1) # (n,n),每行求和为 1 ← 变成"注意力权重分配"
out = w @ V # (n,n)@(n,d_v) -> (n,d_v) ← 按权重把别人的信息加权平均
print(scores.shape, w.shape, out.shape) # (5,5) (5,5) (5,8)
print(w.sum(dim=-1)) # tensor([1., 1., 1., 1., 1.]) ✓ 归一化确认
逐符号翻译成人话:
| 符号 | 人话 | 历史血统 |
|---|---|---|
| \(QK^{\mathsf T}\) | 每个查询和每个键的内积 = 相似度 | 内积 = 投影 = 欧几里得的"夹角"(0.5.4) |
| \(\sqrt{d_k}\) | 尺度校正,避免 softmax 饱和 | 随机向量内积的标准差 \(\propto\sqrt{d}\) —— 中心极限定理 |
| \(\mathrm{softmax}\) | 把任意分数变成概率分布 | 玻尔兹曼分布(1877)改名换姓 |
| \(\cdot\, V\) | 按权重加权平均 | 期望 \(\mathbb{E}\) 的离散形式 |
结论:这一行公式的全部内容是 —— "算相似度 → 归一化成权重 → 加权平均"。 三个动作,每个都有两百年历史。吓人的只是符号密度,不是思想密度。
案例三:链式法则 —— 一个 \(\partial\) 引发的百年国耻
莱布尼茨记法的天才之处:它长得就像可以约分(\(\frac{\partial L}{\partial \hat y}\cdot\frac{\partial \hat y}{\partial z}\) 中间的 \(\partial\hat y\) "消掉")。 这个视觉暗示不是严格证明,但它让人敢往下推 —— 这就是好符号的全部价值。
import torch
w = torch.tensor(2.0, requires_grad=True)
x = torch.tensor(3.0)
z = w * x # ∂z/∂w = x = 3
y_hat = torch.sigmoid(z) # ∂ŷ/∂z = ŷ(1-ŷ)
L = (y_hat - 1.0) ** 2 # ∂L/∂ŷ = 2(ŷ-1)
L.backward() # ← 这一行就是那个连乘
manual = 2*(y_hat-1) * y_hat*(1-y_hat) * x # 逐符号手抄公式
print(w.grad.item(), manual.item()) # 两者相等 ✓ —— 公式读对了
而牛顿的 \(\dot{x}\) 写不出这种链式约分。 英国数学界出于民族情绪坚持点记法一百年,直到 1812 年剑桥的巴贝奇、赫歇尔、皮考克成立"分析学会",打出那句著名口号:
"推行纯粹的 d 主义,反对这所大学的 点时代。" (the principles of pure D-ism in opposition to the Dot-age of the University —— 一句双关:D-ism / deism(自然神论),Dot-age / dotage(老糊涂))
一个符号的选择,让一个国家的数学落后了一个世纪。 这是"符号即思维外骨骼"最贵的一次学费。
🧠 0.6.6 三条习惯:把"看不懂"变成"查得到"
- 看到陌生符号,先问"它是什么类型",而不是"它是什么意思"。 标量/向量/矩阵/函数/算子/集合 —— 类型定了,能做什么运算就定了一半。这是 shape check,不是理解力问题。
- 看到复杂公式,先找最外层运算,由外向内剥。 一个公式只有一个"主动词"。找到它,你就知道这个公式产出什么;剩下的括号都是它的参数。
- 看到任何公式,最后一定要写成能跑的代码,并跟库函数对拍。
手写
for循环 = 唯一无法自欺欺人的验收。跑通那一刻,符号就从"要背"变成了"我造过"。
再补一条对付一词多义的:符号无意义,符号 + 领域才有意义。 别问"\(\sigma\) 是什么",要问"在这一页里,\(\sigma\) 是什么"。
🎨 0.6.7 可视化与代码:ch00_97_reading_formulas/
| 脚本 | 做什么 |
|---|---|
symbol_timeline.py |
数学符号发明编年史时间轴(1489 → 1939),标注发明者与动机 |
rhetorical_to_symbolic.py |
花拉子米原文 ↔ 现代符号 ↔ Python 代码,三栏同步高亮动画 |
shape_flow_attention.py |
注意力公式的 shape 流动图:\((n,d)\to(n,n)\to(n,d_v)\) 逐步可视化 |
bound_variable_alpha.py |
哑变量改名(α-变换)动画:\(\sum_i\) 与 \(\sum_k\) 是同一段代码 |
gaussian_dissection.py |
高尔顿板模拟 → 逐片段点亮高斯密度公式的每个因子 |
dot_vs_leibniz.py |
点记法 vs \(d/dx\) 记法推链式法则,并排展示"能不能约分" |
sigma_disambiguation.py |
一词多义交互面板:同一个 \(\sigma\) 在 6 个语境中的不同身份 |
einsum_as_einstein.py |
爱因斯坦求和约定 ↔ torch.einsum 的逐指标对照动画 |
formula_to_forloop.py |
输入一个公式,输出手写 for 循环 + 库函数对拍结果 |
本文小结:数学符号不是门槛,是压缩包。每一个都由某个具体的人,为了少写几个字,在某一年发明出来 —— 莱布尼茨 1675 年 10 月 29 日写下的那个拉长的 s,至今仍在每一篇论文里喊着"我是求和"。
看不懂公式,从来不是因为你笨,而是因为没人给你解压密码。密码就三条:先读类型,由外向内,写成代码。
下期预告:Chapter 0.7 —— 学物理就是学数学,为什么大脑只记得住"有现实意义的事"。