点积:相似度的度量衡
你在购物 App 里输入「买完不想要了怎么办」,系统从一万条帮助文档里捞出《退货流程》排在第一——尽管你一个字都没提「退货」。这背后发生的事:你的问题被变成向量,一万条文档早已各自是向量,然后系统做了一万次同一种运算,挑出得分最高的。那种运算就是点积。它把「像不像」这个人类直觉,压缩成一次乘加;它是向量检索、推荐系统、注意力机制共同的心跳。这一章把这台度量衡拆开:代数的脸、几何的魂,以及它为什么配得上「AI 第一运算」的名号。
代数的脸:乘起来,加起来
点积(dot product,也叫内积)的计算规则再朴素不过:两个同维向量,对应分量相乘,再全部加起来,输出一个数:
比如 (2, 1) · (1, 3) = 2×1 + 1×3 = 5。就这样,没有了。输入两支箭,输出一个数——请把「输出是标量」这件事刻在脑子里,它是新手最常摔的跤(向量加法输出向量,点积输出数字,两者气质完全不同)。
顺便认三个记号,它们在论文里指同一件事:v·w(点记号)、vᵀw(转置记号,把 v 横过来当 1×n 矩阵乘 n×1 的 w,第 07 章后你会觉得这写法理所当然)、⟨v, w⟩(尖括号,数学系偏爱)。深度学习论文里 QKᵀ 的那个转置,就是在批量摆点积的姿势。
几何的魂:长度乘长度,再乘夹角的余弦
神奇之处在于,这套「乘起来加起来」的机械流程,算出来的数有一个完全几何的解释:
其中 |v| 表示 v 的长度。为什么逐分量乘加会算出夹角的余弦?给一个够用的直觉证明:先验证最简单的情形——标准基向量之间,î·î = 1(同向,cos 0° = 1,长度都是 1),î·ĵ = 0(垂直,cos 90° = 0)。再利用点积对加法和数乘都「诚实」(线性)这一性质,把任意向量拆成 î、ĵ 的组合展开——交叉项 î·ĵ 全部归零,活下来的正好是 v₁w₁ + v₂w₂。代数定义与几何定义在「线性 + 基向量正交」这两根柱子上会师。
这条等式让点积的正负号立刻有了人话翻译。固定两支箭的长度,点积的值全由夹角决定:
- 点积 > 0:夹角小于 90°,两支箭「大体同向」——相似;
- 点积 = 0:恰好 90°,互相垂直——不相干。线代黑话叫正交(orthogonal),这个词在 AI 文献里出场率极高,「两个特征正交」「梯度方向正交」说的都是「点积为零、互不掺和」;
- 点积 < 0:夹角大于 90°,「大体反向」——相斥。
是时候亲手转一转这台仪表了:
建议的实验:① 抓着 v 绕一圈,盯住读数里 v·w 的符号在哪两个位置翻转——正是与 w 垂直的两个方向;② 把 v 拖到和 w 完全同向,再对比两者长度差异对点积的影响:同样的「方向相似」,长箭头会把分数刷得更高——这个「长度偏置」一会儿要专门算账;③ 观察紫色的投影箭头,它始终是「v 在 w 方向上的影子」。
点积的性格档案
作为一种运算,点积的「性格」值得备案,后面章节会反复引用这几条:
- 对称:v·w = w·v。谁在前谁在后无所谓——注意这一点矩阵乘法不继承,第 07 章会看到 AB ≠ BA 的现场。
- 对两边都线性:(au + bv)·w = a(u·w) + b(v·w)。「先组合再打分 = 先打分再组合」,上一章的配方思维可以穿透点积。这条性质有个立即可用的推论:想知道一份线性组合和某模板的匹配度,可以先算好各原料的匹配度再按配方加权——很多推荐系统的「实时打分」就靠这条性质把计算搬到离线。
- 自己点自己 = 长度的平方:v·v = v₁² + v₂² + ⋯ = |v|²。夹角为 0,cos = 1,剩下长度乘长度。这是点积和「长度」之间的脐带,下一章的 L2 范数直接从这里出生。
- 可以为负、没有上限:点积不是概率也不是百分比,别把 v·w = 27 读成「27% 相似」。要落到 [−1, 1] 区间,得用一会儿的余弦版本。
投影:把一支箭摁到另一支的方向上
Demo 里那支紫色小箭头值得一个正式定义。想象正午的太阳垂直照向 w 所在的直线,v 投下的影子就是投影:
分子是点积,分母把 w 的长度影响除干净,整体是「w 方向上放多长」的配方系数,再乘 w 本身。由此得到一个漂亮的分解:任何向量都可以拆成「沿 w 的分量 + 垂直于 w 的分量」,前者是影子,后者是 v 减去影子。这个「按方向拆账」的动作,是第 14 章最小二乘、第 18 章 PCA 的核心零件——PCA 说白了就是找一批方向,让数据投影上去时影子尽量长。
投影还给了点积第三种读法:v·w = (v 在 w 方向的影子长) × (w 的长度)。两种几何读法(夹角版、影子版)和一种代数读法(乘加版),说的是同一个数。多重视角不是负担,是杠杆:论文里哪种读法能让公式变直觉,就切换到哪种。
神经元:一台点积机器
现在换上 AI 的眼镜重看点积,第一个震撼是:神经网络里的「神经元」,本体就是一次点积加一个弯。一个神经元持有一组权重 w(它的「性格」),接到输入 x 后,计算:
用本章的语言翻译:神经元在问「输入 x 和我心里的模板 w 有多像?」。像,点积大,神经元兴奋;不像,点积小甚至为负,神经元沉默。一层网络有几千个神经元,就是几千个模板同时在对输入做匹配打分——这就是「特征检测」的全部数学。经典视觉网络第一层的模板学出来是各种朝向的边缘和条纹;往深走,模板越来越抽象。「深度学习在提取特征」这句话的微观真相,是海量点积在做模板匹配。
把镜头拉远,点积在 AI 里至少有四个身份:① 检索:RAG 系统把知识库切块、embedding 进向量数据库,查询时算点积挑 top-k——你和 ChatGPT 聊公司内部文档时,背后是几百万次点积;② 推荐:用户向量点积物品向量 = 预测偏好,这一手撑起了千亿广告市场;③ 注意力:Transformer 的注意力分数就是 Q 和 K 的点积(第 22 章拆解),「哪个词该看哪个词」是点积说了算;④ 神经元本体:如上所述。一种运算,四个要害岗位——这就是为什么本书把点积放在第一卷,而 GPU 厂商把「乘加单元」堆满整块芯片。
从点积到卷积:让模板滑起来
模板匹配的思想再往前推半步,就得到另一个如雷贯耳的词:卷积。图像处理里的卷积核(kernel),就是一小块模板,比如一个 3×3 的「竖直边缘检测器」:左列全是 −1,中列 0,右列 +1。把它摊平成 9 维向量,盖在图片左上角的 3×3 像素块上做一次点积,得到一个分数:这块像素「多像一条竖直边缘」。然后把模板向右滑一格,再点积;滑遍全图,得到一张「边缘分数地图」。这就是卷积——没有新数学,只是同一支模板向量,和图片的每个局部窗口挨个做点积。
你写过的每个图片滤镜都在做这件事:锐化核、模糊核、浮雕核,只是模板里的数字不同。卷积神经网络(CNN)的贡献,是把「模板里该填什么数字」交给训练去学,并且一层叠一层:第一层学出边缘模板,第二层拿边缘分数图当输入、学出「拐角」「圆弧」模板,层层抽象。第 01 章说「一切皆矩阵乘」在卷积这里也成立:工程实现常把所有滑动窗口摊平堆成大矩阵(im2col),让卷积彻底变成一次矩阵乘法,好喂给 GPU。
十亿次点积的工程账
说点积是「AI 第一运算」,可以用算力账本验证。一次 n 维点积 = n 次乘法 + n−1 次加法 ≈ 2n 次浮点运算。一个向量数据库存了一亿条 1536 维的 embedding,一次朴素的全库检索就是 1 亿 × 3072 ≈ 3000 亿次运算——单查询!所以真实系统从不硬算:它们用近似最近邻索引(HNSW、IVF 之类)把「必须点积的候选」从一亿砍到几千,再精算点积排序。索引的原理超出本书范围,但值得记住这个格局:算法工程的很大一部分,是在想办法少做点积;硬件工程的很大一部分,是在想办法把点积做得更快。一进一出,都是围着这颗心跳转。
余弦相似度:给点积卸掉长度偏置
Demo 实验 ② 暴露了原始点积的一个毛病:它同时反映「方向像不像」和「箭头长不长」。检索场景里这会闹笑话——一篇又长又啰嗦的文档,embedding 可能天生偏长,靠长度就能刷高分。解决办法简单粗暴:把两支向量都除以自己的长度(归一化),再做点积。得到的就是余弦相似度:
它只看方向,无视长短:1 是完全同向,0 是不相干,−1 是完全对立。向量数据库(Faiss、Milvus、pgvector……)默认的相似度量多半就是它,或者等价做法「先把所有向量归一化,再用普通点积」。工程上后者更常见,因为归一化可以离线做一次,在线查询就只剩纯点积,GPU 跑起来最顺。
「余弦相似度永远比点积好」——不成立。长度有时携带信息:在一些 embedding 方案里,向量长度和「模型对这个概念的置信度/词频」相关,粗暴归一化等于把这条信息扔了。注意力机制用的就是未归一化的点积(只除了个 √d,第 22 章讲为什么),因为它希望「强烈的 key」真的拿到更多注意力。选度量前先问:长度在我的场景里是信号还是噪声?
你写过的传感器代码里就有点积。判断手机是不是平放在桌上:取加速度计的重力向量 g,和设备 z 轴单位向量做点积——其实就是取 g 的 z 分量,平放时 |g·ẑ| ≈ 9.8,竖着拿时 ≈ 0。SensorManager.getAngleChange、光照法线贴图、PathMeasure 的切线方向判断,底下全是点积在算「两个方向的一致程度」。物理引擎判断「碰撞后反弹还是滑走」,也是法线点积速度的符号说了算。
写成代码:一个 @ 走天下
import numpy as np q = np.array([0.2, 0.8, -0.3]) # 查询向量 docs = np.array([[0.1, 0.9, -0.2], # 三条文档向量,每行一条 [-0.7, 0.1, 0.5], [0.3, 0.6, -0.4]]) scores = docs @ q # 一次算出全部点积:shape (3,) # 余弦版:先归一化再点积 qn = q / np.linalg.norm(q) dn = docs / np.linalg.norm(docs, axis=1, keepdims=True) cos = dn @ qn best = np.argmax(cos) # 最像的那条文档
留意 docs @ q 这一行:把一批向量摞成矩阵,一次矩阵乘向量,等于批量点积。这再次预告了卷二的主旋律——矩阵乘法没有新魔法,它只是把点积排成方阵批发。你在第 07 章会亲手数出来:C 的每一格,就是一行点一列。
从分数到决策:点积的下游一步
点积产出的是一堆裸分数,系统拿到分数后通常再走一步,把它变成决策或概率。认识两种最常见的下游动作,能帮你把本章接到真实系统上:
argmax:要最像的那个。检索、推荐的排序场景,直接按分数排序取 top-k。刚才代码里的 np.argmax(cos) 就是它。简单,但「赢家通吃」——分数 0.71 和 0.70 的两条文档,命运天差地别。
softmax:按分数分配预算。把一组分数 s₁…sₙ 变成一组非负、和为 1 的权重:每个分数先取指数 e^s,再除以总和。分数高的拿大头,分数低的拿零头,但人人有份。上一章说过「和为 1 的非负权重 = 凸组合配方 = 概率分布」——softmax 就是把点积分数翻译成配方的翻译官。Transformer 注意力的三部曲「点积算分 → softmax 分配 → 加权求和」,前两步你现在已经全部认识了。softmax 还有个「温度」旋钮:分数先除以温度 T 再取指数,T 小则贫富分化(几乎 argmax),T 大则平均主义——你调用 LLM API 时拧的那个 temperature,数学上就是它。
高维空间的冷知识:人人几乎垂直
最后送一个反直觉的高维事实,它对理解 embedding 空间意外地重要。在二维平面随机扔两支箭,夹角均匀分布,「接近垂直」没什么特别。但在 4096 维空间里随机扔两支箭,它们的夹角以极高概率挤在 90° 附近——余弦相似度集中在 0 的一个窄带里。维度越高,随机向量之间越「互不相干」。
给个具体数字帮你建立体感:n 维空间里两支随机单位向量的余弦相似度,均值为 0,标准差约 1/√n。n = 2 时标准差 0.7,夹角天马行空;n = 4096 时标准差只有 0.016——也就是说,随机两支箭的余弦几乎必然落在 ±0.05 以内,想「碰巧」达到 0.3 的相似度,概率低到可以忽略。所以 embedding 检索里 0.3 的余弦是实打实的信号。
这件事有两个推论。好消息:高维空间里「几乎正交」的方向多到用不完,所以一个 4096 维的 embedding 空间塞得下几十万个互不干扰的概念方向——这是「小向量装大知识」的几何原因,可解释性研究里叫「叠加」(superposition)。坏消息:如果你算出两个 embedding 的余弦相似度是 0.3,别急着说「不太像」——在高维背景下,0.3 可能已经远超随机基线、是强信号了。高维相似度要和「随机能到多少」比,而不是和 1 比。
① (1, 2) · (4, −2) = ?这两支箭什么关系?
② 在 Demo 里让 v·w ≈ 0 但两支箭都不短——有几种摆法?
③ 神经元权重 w = (2, −1),输入 (1, 1) 和 (−1, 1),哪个更让它「兴奋」?
④ 为什么向量数据库常常「先归一化、存起来,查询时只做点积」而不是每次算完整的余弦公式?
⑤ 4096 维里随机两支箭的余弦大概率接近多少?这对「相似度阈值该设多少」有什么提醒?
小结
点积用一串乘加算出「长度 × 长度 × 夹角余弦」,正负零分别对应同向、垂直、反向;除以长度得到只认方向的余弦相似度;投影把向量按方向拆账;让模板滑遍全图就是卷积;把一批分数交给 softmax,就得到注意力式的配方。它是神经元的本体、检索与推荐的心跳、注意力分数的来源——AI 世界的硬通货,也是这块星球上被计算次数最多的数学运算,没有之一。但这一章一直在偷用一个没正式定义的词:「长度」。|v| 到底怎么算?除了直尺量的长度还有别的量法吗?为什么正则化偏爱某种「长度」?下一章把这笔账补上——范数,以及用它定义的「距离」;顺路揭开一个面试经典:为什么 L1 正则化能把权重逼成稀疏,而 L2 只会把它们拽小。