开始前,会这些就够了
- 知道向量是一组有顺序的数
- 理解平方与平方根
遇到不熟悉的词,先看这 2 个解释
- 向量
- 按固定顺序排列的一组数,可以表示一个对象的多个特征。每个位置是一维;比较两个向量前,要确认这些位置的含义、单位和生成方式能够对应。
- 余弦相似度
- 比较两个非零向量方向是否接近的数值,由点积除以两个向量的长度得到,取值在负一到一之间。只把向量乘以正数、保持方向不变时,余弦相似度不变。
把一支箭头沿原方向拉长一倍,它仍指向同一个方向。余弦相似度关注的就是这种方向关系,所以两条长度不同的向量也可能得到相似度一。点积还会受到长度影响,同一对数据用不同打分方式,数值和排序都可能变化。
这里先不运行文本模型,而是自己写四个二维向量。只有看清计算究竟保留了什么,才不容易把检索分数误读成“有多大概率说的是同一件事”。
方向变了,还是只变长了?
- 余弦相似度
- 0.7071
- B 的坐标
- (1.41, 1.41)
A 固定为 (1, 0),B 是二维示意向量,不是文字的真实 embedding。余弦只比较非零向量的方向;相似度高不代表内容真实,也不保证适合任何检索任务。
点积看方向,也看长度
点积把对应位置相乘后相加。余弦则把点积再除以两个向量长度的乘积;长度在这里采用平方和开根号得到的欧氏长度。对于非零向量,这一步相当于先把箭头调整成单位长度,再比较方向,所以正比例放大不会改变余弦。
下面固定查询向量为 (1,2)。same 与它相同,scaled 只是加倍,turned 改了方向,opposite 指向反方向。程序同时打印点积与余弦,便于看到究竟是哪一项发生了变化。输入都是小数值的二维例子,计算没有训练任何语义模型。
import math
def scores(a, b):
if len(a) != len(b):
raise ValueError("different dimensions")
dot = sum(x * y for x, y in zip(a, b))
na = math.sqrt(sum(x * x for x in a))
nb = math.sqrt(sum(y * y for y in b))
if na == 0 or nb == 0:
raise ValueError("zero vector has no direction")
return dot, dot / (na * nb)
query = (1.0, 2.0)
candidates = {
"same": (1.0, 2.0),
"scaled": (2.0, 4.0),
"turned": (2.0, 1.0),
"opposite": (-1.0, -2.0),
}
for name, vector in candidates.items():
dot, cosine = scores(query, vector)
print(name, f"dot={dot:.1f}", f"cos={cosine:.3f}")
怎样运行:Python 3.10+;保存为 cosine_demo.py,在该目录运行 python cosine_demo.py。只用标准库,无需联网。
运行后对照结果
same dot=5.0 cos=1.000
scaled dot=10.0 cos=1.000
turned dot=4.0 cos=0.800
opposite dot=-5.0 cos=-1.000归一化保留方向,也放弃长度
same 与 scaled 的余弦相同,并不表示两组数字逐项相等,而是它们方向相同。若任务只想比较方向,这很有用;若模型训练时让长度也承载了有用信息,统一长度可能丢掉这部分信号。因此不能不看模型说明,就认定余弦总比点积好。
如果两个非零向量本来都已经归一化成单位长度,点积与余弦就相等。BGE 文档用其示例展示了这种关系,但实际使用仍应检查自己的模型和编码选项:是否归一化、查询与文档如何编码、检索端采用什么距离,都需要相互对应。算法名称相同不等于输入处理相同。
零向量没有方向,数学上的余弦分母为零。本地代码明确拒绝它;有些库会通过数值约定处理零长度或极小长度,这种返回值不应被解释成正常的几何方向。对非常大、非常小或高维的数据,还需考虑稳定计算与浮点误差,本例不提供通用数值库。
相似度分数不是事实概率
把文本编码成向量后,方向有多大语义价值,来自模型的训练目标和数据,而不是余弦公式自己理解了语言。相似度 0.8 不能直接读成“八成概率正确”或“八成内容相同”。某个阈值能否分开相关与不相关材料,需要用具体任务的已标注样本检查。
可以先准备几条同义、同主题但结论相反、只有关键词相同的文本,观察检索顺序是否符合目标。这个过程是在评价整套编码和打分方案,不是在证明余弦本身会判断真假。需要保留精确名称、版本或否定条件时,还要回到原文核对,不能只看一个高分就接受结论。
记住这三点
- 余弦比较非零向量的方向,正比例放大不会改变它。
- 两个向量都为单位长度时,点积与余弦数值相同。
- 文本相似度的阈值要在具体任务中验证,分数不是事实概率。
别把例子用过头
- 二维箭头只是计算直觉,不能据此判断真实文本模型的语义能力。
- 零向量、维度错误及浮点范围要单独处理,示例只面向小型有限数值输入。
自己试一小步
能否找出点积排序改变、余弦排序却不变的例子?
- 准备两个方向不同的非零候选,先计算它们与同一查询的两种分数。
- 只用一个正数放大其中一个候选,比较新旧排序。
- 把正数改为负数前先预测结果,再核对方向变化。
做到这些,就算完成
- 分别记录点积和余弦,没有只看同一列。
- 正比例缩放与反向缩放被分开讨论。
- 没有把实验分数解释成模型正确率。
查原始资料
想确认一个细节,先看这份资料对应的部分,不必从头读完所有文档。
- scikit-learn:cosine_similarity (新标签页)↗
余弦是归一化点积,以及 L2 归一化后与线性核的关系。
- BGE:向量相似度与点积 (新标签页)↗
点积同时包含长度和方向、余弦只保留方向,以及归一化示例。
找一份资料继续练
先选一项,按下面的范围继续学;不必从头读完整门课程。
MIT 18.06SC:线性代数 (新标签页)↗
只选正交投影部分,用两条二维箭头分别改变长度和方向,再比较点积的变化。
查看学习准备与访问说明 →