系列 《算法工程师的数学:读公式不卡壳的最小集》 第 2 / 9 篇
上一篇:向量、矩阵与形状——一个 token 过一层要算多少下一篇:正交与旋转、特征值与 SVD——从 RoPE 到 LoRA
上一篇说一个 token 是一个向量。这一篇回答一个自然的问题:两个向量怎么比较?1“这个 token 该看那个 token 多少”(attention)、”这段文本和查询有多相关”(检索)、”这两张图和这两句话配不配”(CLIP)、”量化后的权重离原来差多远”(压缩)、”参数是不是太大了”(正则化)——五个看起来不同的问题,用的是同一套只有三个词的语言:内积、范数、余弦相似度。
全篇的核心问题是:
两个向量”像不像”有几种算法?2 各在哪里用?3 为什么 attention 用内积、检索用余弦、量化误差用 Frobenius 范数?4
一、总览
1. 三个词一张图
先把符号认全(后面各章逐个展开):
| 词 | 公式 | 怎么念 | 一句话 | 保留了什么 |
|---|---|---|---|---|
| 内积 | \(\langle a, b \rangle = \sum_i a_i b_i\) | 尖括号读”a 与 b 的内积”,\(\sum_i\) 是”对所有位置 \(i\) 求和” | 对应位置相乘、再全部加起来 | 方向和大小都在里面 |
| 范数 | \(\lVert a \rVert = \sqrt{\langle a, a \rangle} = \sqrt{\sum_i a_i^2}\) | 双竖线读”a 的范数”,就是 a 的长度 | 向量自己跟自己做内积再开平方根 | 只有大小 |
| 余弦 | \(\cos\theta = \dfrac{\langle a, b \rangle}{\lVert a \rVert\, \lVert b \rVert}\) | 夹角 θ 的余弦 | 内积除掉两个长度 | 只有方向,取值 \(-1 \ldots 1\) |
三者由一条等式连着:\(\langle a, b \rangle = \lVert a \rVert\, \lVert b \rVert \cos\theta\)——内积 = a 的长度 × b 的长度 × 夹角余弦。图里的例子 \(a = (1, 2, 3)\)、\(b = (4, -1, 2)\):内积 8,两个长度 \(\sqrt{14} \approx 3.74\) 与 \(\sqrt{21} \approx 4.58\),余弦 \(8 / (3.74 \times 4.58) \approx 0.47\),夹角约 62°。
2. 本文的章节安排
| 章 | 主题 | 内容 |
|---|---|---|
| 二 | 内积 | 定义、几何含义、attention score 就是内积、矩阵形式 \(QK^T\) |
| 三 | 范数 | \(L_2\)、\(L_1\)、Frobenius;长度、单位向量、归一化 |
| 四 | 余弦相似度 |
|
| 五 | 范数作为正则化项 | weight decay 的 \(\frac{\lambda}{2}\lVert W \rVert_F^2\);\(L_1\) 为什么稀疏 |
| 六 | 范数作为误差度量 | 量化误差;GPTQ 为什么最小化 \(\lVert WX - \hat W X \rVert_F\) 而不是 \(\lVert W - \hat W \rVert_F\) |
| 七 | 本文小结 | |
| 八 | 自测 | 五道题 |
二、内积
1. 定义
两个同维向量 \(a, b \in \mathbb{R}^d\) 的内积(inner product,也叫点积 dot product)是对应位置相乘再求和:
\[\langle a, b \rangle = a^T b = \sum_{i=1}^{d} a_i b_i\]\(a^T b\) 这个写法用上一篇的形状规则读:\(a^T\) 是 \([1, d]\),\(b\) 是 \([d, 1]\),乘起来是 \([1, 1]\)——一个标量。所以内积是矩阵乘法的最小情形:一行乘一列。
手算一个:\(a = (1, 2, 3)\),\(b = (4, -1, 2)\),\(\langle a, b \rangle = 1 \cdot 4 + 2 \cdot (-1) + 3 \cdot 2 = 8\)。
2. 几何含义
内积等于两个向量的长度乘以夹角的余弦:
\[\langle a, b \rangle = \lVert a \rVert\, \lVert b \rVert \cos\theta\](\(\lVert a \rVert\) 是 \(a\) 的长度,下一章定义。)这条等式给了内积三个直觉:
- 同向为正、垂直为零、反向为负:\(\theta = 0\) 时 \(\cos\theta = 1\),内积最大;\(\theta = 90°\) 时为 0;\(\theta = 180°\) 时为负。
- 它是”投影”:\(\langle a, b \rangle / \lVert b \rVert\) 是 \(a\) 在 \(b\) 方向上的投影长度——”\(a\) 有多少成分沿着 \(b\)“。
- 它同时含方向与大小:把 \(a\) 放大两倍,内积也放大两倍。这一点在第四章区分”内积”与”余弦”时是关键。
3. attention score 就是内积
Transformer 的 attention 里,每个 token 有一个 query 向量 \(q\) 和一个 key 向量 \(k\)(都是 \(d_h = 128\) 维),”第 \(i\) 个 token 该看第 \(j\) 个 token 多少”的原始分数是
\[s_{ij} = q_i^T k_j\]——就是内积。\(q_i\) 与 \(k_j\) 方向越一致、长度越大,分数越高。写成矩阵形式:\(Q \in \mathbb{R}^{T \times d_h}\) 每行一个 query,\(K \in \mathbb{R}^{T \times d_h}\) 每行一个 key,则
\[S = QK^T \in \mathbb{R}^{T \times T}, \qquad S_{ij} = q_i^T k_j\]用形状规则:\([T, d_h] \times [d_h, T] \to [T, T]\),一张 \(T \times T\) 的表,第 \(i\) 行是第 \(i\) 个 token 对所有 token 的分数。上一篇第三章说”矩阵乘法的第三种看法是相似度表”,说的就是它。
\(S\) 随后除以 \(\sqrt{d_h}\) 再过 softmax 变成概率。为什么要除:内积是 \(d_h\) 项之和,\(d_h\) 越大、和的典型大小越大(每一项随机正负时,和的量级约为 \(\sqrt{d_h}\) 倍的单项),\(d_h = 128\) 时分数会比 \(d_h = 1\) 时大约 11 倍,直接过 softmax 就会一头独大;除以 \(\sqrt{d_h}\) 正好把这个增长抵消——这就是 “scaled dot-product attention” 里 scaled 的含义。第四篇用方差把这句话算清楚,第五篇解释 softmax。
这里只要记住:attention 的”相关性”是内积,所以它同时受方向与长度影响。这一点在训练大模型时会出问题:训练中 \(q, k\) 的长度可能越来越大(哪怕方向没变),内积就跟着变大,softmax 变得极端,梯度不稳、loss 出现尖峰。QK-norm 的做法是在算内积之前先把每个 \(q\)、每个 \(k\) 各自归一化(除以自己的长度,通常用 RMSNorm 实现,第三章讲归一化),再乘一个可学习的缩放系数——于是分数只由方向决定,长度失控的那条路被堵死;缩放系数则把”分数该多大”这件事交给一个专门的参数去学,而不是让它随长度漂。去掉长度会不会丢信息?丢的正是那部分不该影响”相关性”的信息;实践里(ViT-22B、Gemma 3、Qwen3、OLMo 2 都用了它)QK-norm 让训练更稳而质量不降。
三、范数
1. \(L_2\) 范数:长度
向量的长度(\(L_2\) 范数,欧几里得范数)是它与自己内积的平方根:
\[\lVert a \rVert_2 = \sqrt{\langle a, a \rangle} = \sqrt{\sum_i a_i^2}\]二维时就是勾股定理:\((3, 4)\) 的长度是 \(\sqrt{9 + 16} = 5\)。不带下标的 \(\lVert a \rVert\) 默认指 \(L_2\)。
长度为 1 的向量叫单位向量;任何非零向量除以自己的长度就变成单位向量,这个操作叫归一化(normalize):\(\hat a = a / \lVert a \rVert\)。它保留方向、抹掉大小。
2. \(L_1\) 范数:绝对值之和
\[\lVert a \rVert_1 = \sum_i \lvert a_i \rvert\]\((3, -4)\) 的 \(L_1\) 范数是 7,\(L_2\) 范数是 5。\(L_1\) 在第五章出现:Lasso 是在线性回归的 loss 里加一项 \(\lambda \lVert w \rVert_1\) 作惩罚的做法(L2 经典机器学习系列第二篇),特点是会把很多参数压成恰好为零,第五章讲为什么。
一般地,\(L_p\) 范数是 \((\sum_i \lvert a_i \rvert^p)^{1/p}\);\(p \to \infty\) 时趋向最大绝对值 \(\max_i \lvert a_i \rvert\),叫 \(L_\infty\) 范数——量化里”最大绝对值决定缩放因子”用的就是它。
3. Frobenius 范数:矩阵的”长度”
矩阵没有天然的”长度”,最常用的定义是把它拉直成一个长向量再取 \(L_2\) 范数:
\[\lVert W \rVert_F = \sqrt{\sum_{i, j} W_{ij}^2}\]一个 \(4096 \times 4096\) 的矩阵,Frobenius 范数就是它 1677 万个元素的平方和开根号。weight decay(第五章)与量化误差(第六章)都用它。
另一个矩阵范数——谱范数(矩阵能把一个单位向量最多拉长多少倍)——在 L3 讲梯度消失与爆炸时出现,那里再定义。
4. 三种范数怎么选
同一个向量 \(a = (3, -4, 0)\) 在三种范数下的读数不同,各自回答一个不同的问题:
| 范数 | 公式 | \(a = (3, -4, 0)\) | 它在回答什么 | 在哪里用 |
|---|---|---|---|---|
| \(L_2\) | \(\sqrt{\sum a_i^2}\) | 5 | “这个向量有多长” | 归一化、余弦、weight decay、量化的均方误差 |
| \(L_1\) | \(\sum \lvert a_i \rvert\) | 7 | “所有分量的总量有多大”——每个分量按同样的权重计入 | Lasso 正则化(第五章:它会把小分量压到 0,产生稀疏) |
| \(L_\infty\) | \(\max \lvert a_i \rvert\) | 4 | “最极端的那个分量有多大” | 量化时决定缩放因子(最大绝对值要能放进 int8 的 127) |
三者总是 \(L_\infty \le L_2 \le L_1\)。选哪个看你在意什么:在意整体长度用 \(L_2\);希望惩罚”非零分量的个数”用 \(L_1\);只关心最坏的那一个用 \(L_\infty\)。矩阵版的 Frobenius 范数是 \(L_2\) 的推广。
四、余弦相似度
1. 定义
把第二章的几何等式反过来解出 \(\cos\theta\):
\[\cos\theta = \frac{\langle a, b \rangle}{\lVert a \rVert\, \lVert b \rVert}\]这就是余弦相似度:内积除掉两个长度,只剩方向。取值在 \([-1, 1]\)——1 是同向,0 是垂直,\(-1\) 是反向;与向量长度无关,\(a\) 放大十倍余弦不变。
它也等于两个归一化向量的内积:\(\cos\theta = \hat a^T \hat b\)。这个等价在工程上很重要(下一节)。
2. embedding 检索为什么用余弦
embedding 是把一段文本变成一个向量(比如 1024 维),使得含义相近的文本向量相近。检索时把查询也变成向量,在库里找最相近的。用什么度量”相近”?
用内积的问题是:内积受长度影响,一段很长、embedding 模值很大的文本会对所有查询都得高分。用余弦只比方向,”这段文本讲的是什么”由方向表示,”它多长、多啰嗦”由长度表示,检索只关心前者。
工程上的做法是存之前先归一化:库里每个向量都除以自己的长度,查询也归一化,然后余弦相似度就变成了单纯的内积 \(\hat q^T \hat v\)——一次矩阵乘法 \([1, 1024] \times [1024, N]\) 算出对全库 \(N\) 条的分数。向量数据库里的”内积检索”与”余弦检索”在归一化之后是同一件事。
3. CLIP 的图文匹配
CLIP(Contrastive Language–Image Pre-training,OpenAI 2021)是一个把图片和文字放进同一个向量空间的模型,余弦相似度是它的全部接口:
- 它是什么:两个编码器。图像编码器把一张图变成一个 512 维向量,文本编码器把一句话(”一只趴在沙发上的橘猫”)也变成一个 512 维向量。训练时喂成对的图和说明文字,目标只有一句:配对的图文余弦高,不配对的余弦低。
- 训好后怎么用:给一张图和几句候选描述(”一只猫” / “一条狗” / “一辆车”),分别算图向量与每句话向量的余弦,挑最高的——不用再训练就能分类(零样本分类);反过来,给一句话在图库里找余弦最高的图,就是以文搜图。
- 为什么用余弦不用内积:理由同上一节——图像编码器与文本编码器是两个不同的网络,输出向量的长度没有理由可比,只有方向有意义,所以先各自归一化再算内积。
- 去哪读细节:L7 多模态系列的第一篇展开 CLIP 的对比学习 loss——把一批图文两两算余弦、除以一个温度、过 softmax + 交叉熵,用的正是本系列第五、六篇讲的东西。
4. 一个数字感受高维
在高维空间里,随机的两个向量几乎总是接近垂直。取两个 \(d = 1024\) 维、每个分量独立随机的向量,它们余弦相似度的标准差约为 \(1 / \sqrt{d} = 1 / 32 \approx 0.03\)——绝大多数随机向量对的余弦在 \(\pm 0.1\) 以内。所以对随机向量来说 0.3 的余弦已经远在噪声之外;但学出来的 embedding 不是随机向量——它们有各向异性(anisotropy),任意两段无关文本的余弦常在 0.3–0.6,各模型的”基线”不同,0.3 或 0.8 是不是”相关”要用该模型自己的分布校准(先算一批无关对的余弦看均值与方差),不能拿随机向量的 \(1/\sqrt{d}\) 当阈值,也不要用二维平面的直觉(0.5 才算”有点像”)去读这些数字。
五、范数作为正则化项
1. weight decay
训练神经网络,就是反复调整参数让 loss(损失:一个衡量”模型在训练数据上答得有多差”的数,第五篇给出它的定义)变小。只盯着这一个数会出问题:参数可以长得很大、把训练数据的每个细节都”背”下来,换一批没见过的数据就答不对——这叫过拟合。最常用的一个对策是在 loss 上再加一项参数的范数:
\[\mathcal{L}_{\text{total}} = \mathcal{L}_{\text{data}} + \frac{\lambda}{2} \lVert W \rVert_F^2\]\(\lambda\) 是一个小常数(LLM 预训练常用 0.1),\(\lVert W \rVert_F^2\) 是所有参数的平方和。这项的作用是”不许参数太大”:优化器在降低数据 loss 的同时也要压小参数。对它求导(第七篇)得到 \(\lambda W\)——每一步把参数往零缩一点,所以叫 weight decay(权重衰减)。这类”给参数加约束、换取在新数据上更稳”的手段统称正则化,weight decay 是其中最常用的一种;L2 经典机器学习系列讲它为什么有效,L3 讲 AdamW 里它与 \(L_2\) 正则的微妙区别。
2. \(L_1\) 为什么产生稀疏
把上面的 \(\lVert W \rVert_F^2\) 换成 \(\lVert w \rVert_1 = \sum_i \lvert w_i \rvert\),就是 Lasso。它的特点是会把很多参数压成恰好为零(稀疏),而 \(L_2\) 只会把参数压小但不为零。一个直觉:
参数 w 从 0.1 往 0 走时,惩罚项的下降量:
L2: w² 从 0.01 → 0,只降 0.01——越接近 0 越没动力继续压
L1: |w| 从 0.1 → 0,降 0.1——一直有恒定的动力压到 0
\(L_1\) 的惩罚在零点附近不变小,所以能把参数推到零并停在零。稀疏在特征选择(L2 系列)与模型剪枝里有用;LLM 本身很少用 \(L_1\)。
六、范数作为误差度量
1. 量化误差
量化(quantization)是把 16 位浮点的权重 \(W\) 用更少的位(比如 4 位整数)近似成 \(\hat W\),以省显存、加快推理。近似有误差,怎么度量?最直接的是权重本身的差:
\[\lVert W - \hat W \rVert_F\]——原矩阵与量化矩阵逐元素差的平方和开根号。逐元素独立地把每个数四舍五入到最近的量化格点(round-to-nearest)就是在最小化它。
2. GPTQ 最小化的是另一个东西
GPTQ 一类方法不最小化 \(\lVert W - \hat W \rVert_F\),而最小化
\[\lVert WX - \hat W X \rVert_F\]——\(X\) 是一批真实输入(校准数据),比较的是权重作用在输入上的输出差多远,而不是权重本身差多远。为什么这更对?因为模型最终输出的是 \(WX\) 不是 \(W\):如果输入 \(X\) 的某些维度总是接近零,那些维度上权重量化得再差也不影响输出;反过来输入里数值特别大的维度(LLM 里确实存在,叫 outlier 通道),对应的权重一点误差就会被放大。\(\lVert WX - \hat W X \rVert_F\) 自动按输入的分布给不同权重不同的”重要性”。
两个目标之间的关系可以写成一行:\(\lVert (W - \hat W) X \rVert_F^2 = \text{tr}\big((W - \hat W) X X^T (W - \hat W)^T\big)\),中间那个 \(XX^T\)(输入的二阶统计量,Hessian 的近似)就是”重要性”矩阵。L6 高效推理与压缩系列讲 GPTQ 怎么利用它逐列量化并补偿误差;这里只要读懂:量化不是逼近权重本身,而是逼近权重作用在输入上的结果。
3. 同一个词的三个身份
到这里,”范数”在三个地方出现,身份不同:
| 身份 | 公式 | 它量的是什么 | 一个例子 | 在哪里 |
|---|---|---|---|---|
| 长度 / 归一化 | \(\lVert a \rVert_2\),\(\hat a = a / \lVert a \rVert\) | 一个向量有多长;除掉它只剩方向 | \(a = (3, 4)\),\(\lVert a \rVert = 5\),\(\hat a = (0.6, 0.8)\) | 余弦相似度、QK-norm、LayerNorm / RMSNorm 的分母 |
| 正则化项 | \(\frac{\lambda}{2} \lVert W \rVert_F^2\),\(\lVert w \rVert_1\) | 参数本身有多大——加进 loss 里,告诉优化器”别让参数长太大” | 一个 \(4096 \times 4096\) 的权重,元素典型大小 0.02,\(\lVert W \rVert_F^2 \approx 1677 \text{万} \times 0.02^2 \approx 6700\) | weight decay、Lasso |
| 误差度量 | \(\lVert W - \hat W \rVert_F\),\(\lVert WX - \hat W X \rVert_F\) | 两个东西的差有多大——原件与近似件之间的距离 | int8 量化一行 4096 个权重,每个误差约 \(\pm 0.0002\),\(\lVert w - \hat w \rVert_2 \approx 0.0002 \times \sqrt{4096} \approx 0.013\) | 量化误差、低秩近似误差(下一篇) |
同一个符号,三种身份的区别在量的是谁:第一行量一个向量自身,目的是把它变成单位长度;第二行量参数,它出现在 loss 里、参与求导、影响训练往哪走;第三行量一个差,它是评价指标,告诉你近似得好不好。读论文时看到 \(\lVert \cdot \rVert\),先问”括号里是一个量、一个参数、还是一个差”,身份就清楚了;再看下标(2、1、F)知道用的是哪种范数。
七、本文小结
- 内积 \(\langle a, b \rangle = \sum_i a_i b_i = \lVert a \rVert \lVert b \rVert \cos\theta\):矩阵乘法的最小情形,同时含方向与大小;attention 的 score \(QK^T\) 是一张内积表。
- 范数:\(L_2\) 是长度、\(L_1\) 是绝对值之和、Frobenius 是矩阵拉直后的 \(L_2\);除以长度叫归一化,只留方向。
- 余弦相似度 \(= \langle a, b \rangle / (\lVert a \rVert \lVert b \rVert) = \hat a^T \hat b\):只比方向;embedding 检索与 CLIP 用它,归一化后退化为内积;高维里随机向量的余弦在 \(\pm 1/\sqrt{d}\) 量级;学到的 embedding 有各向异性,阈值要按具体模型的无关对分布校准。
- 范数作正则化项:weight decay 是 \(\frac{\lambda}{2}\lVert W \rVert_F^2\),导数 \(\lambda W\) 每步把参数往零缩;\(L_1\) 因为零点附近惩罚不变小而产生稀疏。
- 范数作误差度量:量化误差不该看 \(\lVert W - \hat W \rVert_F\) 而该看 \(\lVert WX - \hat W X \rVert_F\)——逼近的是权重作用在输入上的结果,输入的二阶统计量 \(XX^T\) 给每个权重不同的重要性。
八、自测
-
\(a = (1, 0, -1)\),\(b = (2, 2, 2)\):内积是多少?两者的夹角是多少度?
答案
\(1 \cdot 2 + 0 + (-1) \cdot 2 = 0\);垂直,90°。
-
\((3, -4)\) 的 \(L_1\)、\(L_2\)、\(L_\infty\) 范数各是多少?
答案
7、5、4。
-
attention 里 \(Q, K \in \mathbb{R}^{2048 \times 128}\),\(QK^T\) 是什么形状、多少 FLOPs?把 \(q\) 全部放大两倍,score 怎么变?余弦相似度怎么变?
答案
\([2048, 2048]\),\(2 \times 2048 \times 2048 \times 128 \approx 1.07\) G;score 放大两倍;余弦不变。
-
库里有 100 万条 1024 维、已归一化的 embedding,一次查询的余弦检索是一次什么形状的矩阵乘法,多少 FLOPs?
答案
\([1, 1024] \times [1024, 10^6]\),约 \(2 \times 10^9\) FLOPs。
-
为什么”把每个权重四舍五入到最近的格点”不是最小化 \(\lVert WX - \hat W X \rVert_F\) 的最优解?举一个 \(X\) 的例子说明。
答案
若 \(X\) 的第 1 维恒为 100、第 2 维恒为 0.01,则 \(W\) 第 1 列的误差被放大 100 倍、第 2 列几乎不影响输出;最优解应该把第 1 列量得更准(哪怕第 2 列量得更差),逐元素四舍五入做不到这种取舍。
下一篇讲矩阵的两种”好性质”:正交(保持内积——RoPE 为什么能编码相对位置)与低秩(少数方向解释全部——LoRA 为什么能用半个百分点的参数微调)。
-
用三个词:内积(同时含方向与大小)、范数(只量大小)、余弦相似度(只比方向,等于内积除以两个长度)。attention、检索、CLIP、量化误差、正则化五个问题用的都是这三样。详见第二至四章。 ↩
-
内积用在 attention 的 \(QK^T\) 与 CLIP 的图文匹配;范数用在正则化(weight decay 是 \(L_2\) 范数的平方)与误差度量(量化、蒸馏);余弦用在检索与 embedding 相似度。详见第四至六章。 ↩
-
attention 用内积是因为 \(QK^T\) 就是一张内积表——矩阵乘法一次算完,而且长度信息保留了下来(q、k 的范数会影响 logit 的尖锐度,QK-norm 一类方法正是要去控制它——所以”长度 = 想看多少”只是一种解读,不是设计);检索用余弦是因为库里向量长度不一、只关心方向,归一化之后余弦退化为内积,仍是一次矩阵乘(第四章);量化误差用 Frobenius 范数是因为要度量整个矩阵(拉直后的 \(L_2\)),而且该量的是 \(\lVert WX - \hat W X \rVert_F\)——权重作用在输入上的结果——而不是权重本身的差(第六章)。 ↩
系列 《算法工程师的数学:读公式不卡壳的最小集》 第 2 / 9 篇
上一篇:向量、矩阵与形状——一个 token 过一层要算多少下一篇:正交与旋转、特征值与 SVD——从 RoPE 到 LoRA
本文由 arganzheng 创作,采用 CC BY 4.0 许可协议。在保留原文作者、署名以及完整原文链接(https://arganzheng.life/inner-product-norms-and-cosine-similarity.html)的前提下,欢迎各种形式的转载、翻译或商业引用。
COMMENTS
评论存放在 GitHub Discussions, 用 GitHub 账号登录即可发表,支持 Markdown。 想针对正文某句话说?选中那段文字,点浮出的「评论」即可划线评论;觉得哪里写错了,发表时勾上「同时提交 Issue」。 有人回复你时 GitHub 会按你的通知设置发邮件,不用守在这里。