文字也有角度?搜索引擎与向量相似度
想象一个场景:用户搜索"苏州园林",系统要从一堆景点介绍中找出最相关的。
有四条候选文本:
-
A:"拙政园,苏州的古典园林之首"
-
B:"留园,苏州四大名园之一,苏州著名景点"
-
C:"颐和园,北京皇家园林"
-
D:"西湖,杭州著名景点"
直觉告诉我们:A 最相关(直接提到"苏州"和"园林"),D 完全不相关。但 B 和 C 呢?哪个更相关?
机器怎么判断这种"相关程度"?怎么量化"最像"?
让我们从最简单的方法开始,一步步找到答案。
先试试最简单的方法:数共同词
查询"苏州园林"有两个关键词:苏州、园林。
| 文本 | 包含"苏州"? | 包含"园林"? | 共同词数 |
|---|---|---|---|
| A | ✓ | ✓ | 2 |
| B | ✓ | ✗ | 1 |
| C | ✗ | ✓ | 1 |
| D | ✗ | ✗ | 0 |
看起来不错——A 最相关,D 无关。但 B 和 C 都是 1 个共同词,怎么排序?
-
B(留园):有"苏州",但没有"园林"(用的是"名园")
-
C(颐和园):有"园林",但没有"苏州"(用的是"北京")
简单的计数无法区分这种差异。我们需要一种更精细的方法。
把文本变成向量
从"计数"到"向量":为什么要这样做?
数共同词的方法有个根本问题:它把文本看作一个"集合",只关心"有什么词",不关心"每个词有多重要"。
但直觉告诉我们,如果一条文本反复提到"苏州",说明它更专注于这个话题。我们应该把这种"强度"考虑进去。
怎么做到?我们需要一种新的表示方式——向量。
向量不仅能记录"有什么词",还能记录"每个词出现了多少次"。更重要的是,一旦文本变成了向量,我们就可以将搜索词和文档都向量化,然后比较两个向量的"方向"是否相同、内容是否相似,以此判断是不是搜索目标文档。
具体怎么做?
先把四条文本分词:
-
A:拙政园 / 苏州 / 古典 / 园林 / 之首
-
B:留园 / 苏州 / 四大 / 名园 / 之一 / 苏州 / 著名 / 景点
-
C:颐和园 / 北京 / 皇家 / 园林
-
D:西湖 / 杭州 / 著名 / 景点
收集所有不重复的词,构成一个词典:
拙政园、苏州、古典、园林、之首、留园、四大、名园、之一、颐和园、北京、皇家、西湖、杭州、著名、景点
共 16 个词。每个词就是一个维度。
现在,把每条文本表示成一个 16 维向量——某个词出现几次,对应位置就填几(查询关键词"苏州园林"用 Q 标记):
注:向量的每个分量代表对应词的词频。比如 B 中"苏州"出现 2 次,所以第 2 维是 2。词频高,说明这个话题对文本更重要。
| 文本 | 拙政园 | 苏州 | 古典 | 园林 | 之首 | 留园 | 四大 | 名园 | 之一 | 颐和园 | 北京 | 皇家 | 西湖 | 杭州 | 著名 | 景点 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Q | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| A | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| B | 0 | 2 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
| C | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
| D | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 |
以查询 Q 为例:分词后得到"苏州"和"园林"两个词。在 16 维词典中,"苏州"是第 2 维,"园林"是第 4 维,所以 Q 的向量是:
每条文本都变成了空间中的一个点。Q、A、B、C、D 都是 16 维空间中的向量。
向量的两个属性:大小和方向
向量有两个属性:
-
大小(长度):向量的"模",记作 ,代表向量的"强度"
-
方向:向量在空间中的指向
在文本场景中:
-
大小:向量的长度大致代表文档的"信息量"——长文档的向量更长,短文档的向量更短
-
方向:向量的方向代表文档的"主题分布"——如果两篇文档的词频比例相似,它们的方向就相近
这就是向量化的好处:它不仅记录了"有什么词",还记录了"每个词有多重要"。
怎么衡量两个向量的相似度?
第一步:从二维空间的"方向"开始
16 维空间太抽象了,我们先从熟悉的二维空间说起。
在二维平面上,一个向量就是一个箭头——从原点出发,指向某个方向。比如 就是从原点指向坐标 的箭头。
怎么直观理解向量的"方向"? 把向量看成直角三角形的斜边——从原点出发,过向量顶点向 X 轴做垂线,就形成了一个直角三角形。
向量 :底 = 1,高 = 2,斜率 = 2
如果我们把词频比例看作向量的方向比例,如果两段文本提及了同一个词,那么在这个词的维度上,它们应当有着一致的分量。如果它们提及的所有关键词的词频一致,是不是就可以直接理解为向量的方向一致了?
既然在二维平面上,我们可以用直角三角形来表示向量,那么两个直角三角形如果相似(对应边成比例),它们的方向自然相同。反过来,方向相同的两个向量,对应的三角形也必然相似。
第二步:回忆一下相似三角形
两个三角形相似,意味着:形状相同,大小可以不同。
看几个例子:
-
和 :B 的每个分量都是 A 的 2 倍——方向完全相同,只是长度翻倍。
-
和 :C 的每个分量都是 A 的 3 倍——方向相同,长度变成 3 倍。
-
和 :长度相同,但方向完全不同——形状不一样。
"相似"的核心就是方向一致——就像相似三角形一样,形状相同,大小无所谓。
把向量看成直角三角形的斜边——从原点出发,过向量顶点向 X 轴做垂线:
-
向量 :底 = 1,高 = 2,斜率 = 2
-
向量 :底 = 2,高 = 4,斜率 = 2
两个向量的斜率相等——这意味着它们与 X 轴的夹角相等。对于两个直角三角形,一个锐角相等,再加上直角也相等,根据 AA 定理(两角对应相等),它们就是相似三角形。

图:向量 和 分别构成两个直角三角形。小三角形(蓝)套在大三角形(红)里面,斜率都是 2——这就是相似三角形。斜率相等 = 方向相同 = 向量相似。
第三步:怎么判断"方向一致"?
最直接的方式:算它们之间的夹角。
-
夹角 = 0°:方向完全一致
-
夹角 = 90°:各走各的路,毫无关联
-
夹角 = 180°:方向完全相反
这就是余弦相似度的核心思想——用夹角的余弦值来衡量方向的接近程度:
-
分子是点积
-
分母是两个向量长度的乘积:用来归一化,消除长度影响
为什么用余弦,而不是其他函数?
| 函数 | 范围 | 0° 时 | 90° 时 | 适合做相似度? |
|---|---|---|---|---|
| cos | [-1, 1] | 1(最相似) | 0(不相关) | ✅ 完美 |
| sin | [-1, 1] | 0 | 1 | ❌ 反直觉 |
| tan | (-∞, +∞) | 0 | ∞ | ❌ 无界 |
| 点积 | [0, +∞) | - | - | ❌ 受长度影响 |
余弦是唯一一个同时满足以下条件的函数:
-
有界:值域 [-1, 1],适合做相似度
-
直觉一致:0° 时最大(最相似),90° 时为 0(不相关)
-
消除长度影响:通过除以长度乘积,只看方向
第四步:那"距离"为什么不行?
直接用欧氏距离不行吗?两个点越近,不就越相似吗?
看这个例子:
算一下距离:
A 到 B 的距离 = A 到 C 的距离。 但 B 指向右下方,C 指向上方——方向完全不同!
两个向量可以距离相等,但方向完全不同。 如果我们想衡量的是"说的是不是同一件事",那距离就不是一个好的指标。

图:,但 和 的方向完全不同。
第五步:推广到高维
好消息是:数学公式完全一样。
16 维空间中,计算方式完全一样——只是维度更多,求和的项更多。
公式不需要改,直觉也不需要改。 高维空间只是"画不出来"而已,数学上没有任何区别。

图:三维空间中,向量 和 分别构成两个直角三角形。小三角形(蓝)套在大三角形(红)里面,,比例相等 → 方向相同。
小结
| 度量方式 | 受长度影响? | 适合场景 |
|---|---|---|
| 欧氏距离 | 是 | 长度有意义时 |
| 余弦相似度 | 否 | 只关心方向时 |
在文本场景中,我们关心的是"说的是不是同一件事",而不是"篇幅是不是一样长"。所以余弦相似度更合适。
| 余弦值 | 含义 |
|---|---|
| 1 | 方向完全一致(说的是同一件事) |
| 0 | 方向完全垂直(毫无关联) |
| -1 | 方向完全相反(文本中几乎不会出现) |
对于文本场景,词频都是非负的,所以余弦值的实际范围是 [0, 1]。
动手算一下
让我们用上面的例子,实际计算查询与四条文本的余弦相似度。
查询 vs A(拙政园)
查询向量:
A 向量:
第一步:点积
第二步:各自的长度
第三步:余弦相似度
其他文本的计算结果
用同样的方法计算 B、C、D:
| 文本 | 点积 | 向量长度 | 余弦相似度 |
|---|---|---|---|
| A(拙政园) | 2 | 2.236 | 0.632 |
| B(留园) | 2 | 3.162 | 0.447 |
| C(颐和园) | 1 | 2 | 0.354 |
| D(西湖) | 0 | 2 | 0 |
结果汇总
| 文本 | 匹配词 | 余弦相似度 | 解读 |
|---|---|---|---|
| A(拙政园) | 苏州 + 园林 | 0.632 | 最相关——两个词都匹配 |
| B(留园) | 苏州(×2) | 0.447 | 次相关——"苏州"出现两次 |
| C(颐和园) | 园林 | 0.354 | 较弱——只匹配"园林" |
| D(西湖) | 无 | 0 | 完全不相关 |
这个梯度基本符合直觉:A 最相关,B 次之("苏州"出现两次),C 再次之,D 无关。
回头看:B 和 C 为什么能区分了?
之前朴素计数无法区分 B 和 C——它们都只包含 1 个查询关键词。但余弦相似度给出了不同的分数:
为什么 B 比 C 高?因为 B 中"苏州"出现了两次。余弦相似度不仅看"匹配了什么",还看"匹配的词在文本中的权重"。B 反复提到"苏州",说明它更专注于这个话题。
反复提到的关键词,在向量中权重更高。 这就是余弦相似度相比朴素计数的优势——它不仅看"有没有这个词",还看"这个词有多重要"。
一个极端的例子:重复字符串
为了更直观地理解余弦相似度"只看方向,不看长度"的特性,我们来看一个极端的例子。
假设有两条文本:
-
文本 X:"苏州拙政园"
-
文本 Y:"苏州拙政园苏州拙政园苏州拙政园"
Y 是 X 的重复 3 次。假设词典只有两个词:苏州、拙政园。
-
X 的向量:
-
Y 的向量:
计算余弦相似度:
余弦相似度 = 1,完全相似!
余弦不关心你说了多少,只关心你在说什么。 一篇 50 字的简介和一篇 5000 字的攻略,如果词频分布相同,余弦相似度就是 1。
从短文本到长文档
上面的例子用了 4 条短文本和 16 个词的词典。实际搜索场景中:
-
词典可能有 10 万个词
-
每篇文档可能有 几百到几千字
-
向量是 10 万维的,绝大多数位置是 0(稀疏向量)
但核心思想完全一样:
-
分词 → 构建词典
-
统计词频 → 得到每篇文档的向量
-
计算查询向量和每篇文档向量的余弦相似度
-
取最高的 K 个返回
这就是向量空间模型(Vector Space Model,VSM)——由 Gerard Salton 在 1970 年代提出,是现代信息检索的基石。
向量空间模型的核心贡献:把"文本相似度"问题,转化成了"向量夹角"问题。
实际工程中的两个改进
上面的例子中,向量的每个维度是简单的词频计数。但在实际工程中,通常会做两个改进:
改进一:强度不是简单的词频计数
我们前面用的向量,每个维度的值是简单的词频计数——某个词出现几次,就填几。
但在实际工程中,向量的每个维度通常不是简单的词频,而是 TF-IDF 或 BM25 的权重。这些算法会给"高频但无意义的词"降权,给"低频但有区分度的词"加权——这正是我们在上一篇中讨论的内容。
也就是说,向量的"方向"不仅取决于"有什么词",还取决于"每个词有多重要"。
改进二:用 Embedding 代替词袋向量
词袋向量有个根本性问题:它只能识别"用词相同"的文本,无法理解语义。
举个例子:查询"苏州园林",候选文本是"江南古典名园"。
-
分词后:苏州 / 园林 vs 江南 / 古典 / 名园
-
没有任何一个词相同
-
在词袋模型中,余弦相似度 = 0
但直觉告诉我们,这两者说的是同一类东西。词袋模型无法捕捉这种语义关联。
现代搜索引擎引入 Embedding(词嵌入/句嵌入)来解决这个问题。Embedding 用神经网络把文本映射到一个语义空间——在这个空间中,意思相近的文本,向量方向相近,不需要字面匹配。
我们用实际的 Embedding 模型来验证一下。以 BAAI/bge-m3 为例,它会把每个文本映射为一个 1024 维的向量。
查询"苏州园林",对比两个候选:
| 候选 | 余弦相似度 |
|---|---|
| 江南古典名园 | 0.4955 |
| 北京故宫博物院 | 0.4392 |
具体计算过程(以"苏州园林" vs "江南古典名园"为例):
-
"苏州园林"的向量(前 5 维):
-
"江南古典名园"的向量(前 5 维):
-
点积 =
-
两个向量的模长均为 (模型默认做了 L2 归一化)
-
余弦相似度 =
在词袋模型中,"苏州园林"和"江南古典名园"的相似度是 0(没有共同词)。但在 Embedding 空间中,相似度是 0.4955——Embedding 捕捉到了它们在语义上的关联。
这就是 Embedding 的核心优势:它能理解"说的是不是同一件事",而不仅仅是"用的是不是同一个词"。
写在最后
回到开头的场景:用户搜索"苏州园林",系统要从一堆景点介绍中找出最相关的。
现在我们知道,搜索引擎是怎么做的:
-
把查询和文档都变成向量
-
用余弦相似度计算两个向量的方向是否一致
-
取最相似的那几个返回
从"数共同词"到"余弦相似度"——这就是搜索引擎的向量思维。
至于词袋向量的局限,以及 Embedding 如何解决这个问题,我们已经在"实际工程中的两个改进"中讨论过了。后续文章会详细介绍。