跳到主要内容

创建日期:2026-09-26 | 最近更新:2026-09-26 外部资料均为逐字原文(BGE 模型卡 / arXiv 摘要页,抓取日期 2026-09-26,留档于 source/vector-similarity-lab/official-sources/ 与 source/rag-lab/);所有数字为本机实测(RTX 3090 / torch 2.10.0 / transformers 5.7.0 / bge-small-zh-v1.5,脚本与真实输出 source/vector-similarity-lab/run.log)。我自己的推断一律标注「推断」,未核实的一律标注「未能核实」。

向量相似度的原理:为什么余弦能测出语义相似

一句话:余弦值本身不含任何语义。它之所以能测语义,是因为有人在训练时把「语义相近」这件事,定义成了「余弦要大」——相似度函数就是损失函数。 剩下的部分(为什么用余弦而不是欧氏、为什么高维里方向够用)是几何问题,几何只负责让这件事在数值上可行,不负责让它成立。

入门篇说链路有五个环节,第 1 篇拆了重排,第 2 篇拆了切片——那两篇里反复出现「双塔的像不像」「相似度差 0.0001」这类读数,但都默认了一件事:向量之间的余弦,代表语义的接近程度。 这篇把这个默认拆开,回答三个层次的问题:

层次问题谁来回答
表示层语义凭什么能变成坐标?分布式假设(§2)
几何层坐标里的「近」凭什么用余弦量?度量选择 + 高维几何(§3、§4)
学习层余弦凭什么等于语义相近?训练目标(§5)——这是最关键的一层

三组实验,从纯数学一路做到真模型,全部可复现:bash source/vector-similarity-lab/run.sh。

0. 先给结论​

六条,后面每一条都有实测数据:

  1. 字面方法在最需要它的地方必然失效。 八对同义词,「番茄/西红柿」的直接共现次数 0、字符 bigram Jaccard 0.0000——重叠为零,无从下手。
  2. 只数共现、不做任何「理解」,同义词就会自己靠近。 共现矩阵 → PPMI → SVD,同义词对的余弦 0.8108,同话题不同词的 0.1254,完全无关的 0.0764——三个层级是自己分出来的,没写一行规则。
  3. 余弦、点积、欧氏在归一化后是同一把尺子(实测秩相关 1.0000);不归一化就分家(0.9063)。所以「向量库该用哪种度量」这个问题,在归一化之后问错了。
  4. 高维让「方向」装得下语义:512 维随机向量的两两余弦标准差只有 0.0441(理论值 1/512=0.04421/\sqrt{512} = 0.0442),几乎必然接近 90°。方向多到用不完,这是前提不是结论。
  5. 语义能力是训出来的,不是架构自带的。 同一个架构、同样的参数量、同样的分词器,只是权重没训过:它在低词面重叠的检索上只对 5/10;训练后 10/10。
  6. 但余弦高 ≠ 语义等价。 型号差一位的订单号,余弦 1.0000 vs 1.0000——一模一样;「支持自动续费」与「不支持自动续费」差 0.0118,排序随时可能翻。这四条边界在 §6,是这一篇最该记住的部分。

1. 起点:一句话为什么测不出另一句话​

拿两组句子,人一眼就知道在问同一件事:

A: 番茄怎么做好吃?
B: 西红柿的做法有哪些?

C: 苹果手机多少钱
D: 苹果多少钱一斤

正确的判断是 A≈B(同一件事)、C≁D(手机 ≠ 水果)。字面方法给出的恰好相反:A/B 重叠 0(判无关),C/D 重叠很高(判同一件事)——两处都错,而且错得对称。

先把这个失败量化。我用一个受控语料(构造方式见 distributional.py 顶部注释)建了 89 个词的词表,只统计直接共现(窗口 = 2):

同义词对 直接共现 字符 Jaccard bigram Jaccard
番茄 / 西红柿 0 0.0000 0.0000
土豆 / 马铃薯 0 0.0000 0.0000
电脑 / 计算机 0 0.0000 0.0000
手机 / 移动电话 0 0.0000 0.0000
医生 / 大夫 0 0.0000 0.0000
老师 / 教师 0 0.3333 0.0000
出租车 / 的士 0 0.0000 0.0000
地铁 / 地下铁 0 0.6667 0.0000

八对同义词,直接共现全部是 0。字符级方法只抓到「老师/教师」共享的一个「师」字、「地铁/地下铁」共享的两个字——那是碰巧,不是能力;碰到「番茄/西红柿」这种真正的同义词,立刻归零。bigram 更是一个都抓不到。

语义不在字面上。 那它在哪?

2. 语义从哪来:分布式假设​

语言学里有一条老假设——一个词的意思,由它经常一起出现的那些词决定。它的经典表述通常挂在 Harris (1954) 和 Firth (1957) 名下。

⚠️ 核对声明:这两份原始文献我没能拿到——T&F 对 Harris 1954 返回 HTTP 403,ACL Anthology 无该条目。所以上面这一句是转述,不是逐字引用,请以二手文献为准。本节真正逐字可查的一手来源是下面两条。

这条假设为什么在计算上可行,word2vec 的摘要给了一句(逐字,arXiv:1301.3781):

We observe large improvements in accuracy at much lower computational cost, i.e. it takes less than a day to learn high quality word vectors from a 1.6 billion words data set. Furthermore, we show that these vectors provide state-of-the-art performance on our test set for measuring syntactic and semantic word similarities.

而「只靠共现能不能让同义词靠近」这件事,不需要信任何论文——跑一遍就知道。

2.1 实测:只数共现,让同义词自己靠近​

distributional.py 做四件事,一行「理解」都没有:

语料的构造是刻意的(这是整个实验的关键):同一对同义词永不共现,但上下文槽位完全相同;同组内不同对(番茄/土豆)的上下文只部分重叠。

先看「二阶共现」——它们自己不见面,但它们的邻居是不是同一批:

番茄 的邻居: 一下、凉拌、怎么、沙拉、炒蛋、维生素、蔬菜、鸡蛋
西红柿 的邻居: 凉拌、多少、怎么、炒蛋、维生素、蔬菜、问题、鸡蛋
重合: 凉拌、怎么、炒蛋、维生素、蔬菜、鸡蛋
邻居集合 Jaccard = 0.600

电脑 的邻居: 一下、内存、处理器、屏幕、怎么、硬盘、程序、配置
计算机 的邻居: 内存、处理器、屏幕、怎么、硬盘、程序、配置、问题
重合: 内存、处理器、屏幕、怎么、硬盘、程序、配置
邻居集合 Jaccard = 0.778

直接共现 0 次,邻居重合 0.6~0.78。 关系不在它们之间,在它们各自与世界的连接方式里。

共现矩阵是 89×89 = 7921 格,非零元只有 1004 个(稀疏度 87.32%)。做 PPMI 加权再 SVD 压到 32 维之后:

层级例子SVD 余弦均值
同义番茄 / 西红柿0.8108
同话题、不同事物番茄 / 土豆0.1254
完全无关番茄 / 电脑0.0764

三个层级是它自己分出来的。 逐对看也一样(电脑/计算机 0.9288、土豆/马铃薯 0.7835、番茄/西红柿 0.5599)——都远高于无关词对的 0.0462~0.1445。

这里有个值得说的细节:番茄/西红柿 的 0.56 反而比 电脑/计算机 的 0.93 低。原因是这对词分到的上下文只有 6 个,而语料里撒了大量噪声词(问题、情况、可能……),6 个信号被稀释得更厉害。信号密度决定余弦绝对值,不决定排序。

还有一个副产品,先记下来,§6.4 会用到。看 土豆 的最近邻:

土豆 马铃薯(0.783) 可能(0.721) 种植(0.344) 当然(0.336) 淀粉(0.327)

可能 一个纯噪声词,排到了第二位(0.721)。它跟谁都像——这就是 hubness 的雏形,后面真模型上还会再见到。

3. 三把尺子:点积、欧氏、余弦​

语义进了坐标,接下来是几何。对 a,b∈Rd\mathbf{a}, \mathbf{b} \in \mathbb{R}^d,三个最常见的度量:

a⋅b=∑i=1daibi∥a−b∥2=∑i=1d(ai−bi)2cos⁡(a,b)=a⋅b∥a∥2 ∥b∥2\mathbf{a} \cdot \mathbf{b} = \sum_{i=1}^{d} a_i b_i \qquad \lVert \mathbf{a} - \mathbf{b} \rVert_2 = \sqrt{\sum_{i=1}^{d} (a_i - b_i)^2} \qquad \cos(\mathbf{a}, \mathbf{b}) = \frac{\mathbf{a} \cdot \mathbf{b}}{\lVert \mathbf{a} \rVert_2 \, \lVert \mathbf{b} \rVert_2}

它们的差别只在「长度算不算数」。 一个能一眼看穿的例子(geometry.py E1):

a = [1, 0] b = [10, 0] c = [0, 1]

cos dot L2
(a,b) 1.0000 10.0000 9.0000 ← 方向相同,长度差 9 倍
(a,c) 0.0000 0.0000 1.4142 ← 正交
(b,c) 0.0000 0.0000 10.0499 ← 正交,但 b 很长 → L2 比 (a,b) 还大

(b,c) 是正交的(该判无关),但它的 L2 距离 10.05 比 (a,b)(方向完全相同)的 9.00 还大。长度污染了欧氏距离。

3.1 归一化之后,三把尺子合成一把​

如果先把向量归一化(∥a∥=∥b∥=1\lVert \mathbf{a} \rVert = \lVert \mathbf{b} \rVert = 1),有一条约等式:

∥a−b∥22=(a−b)⋅(a−b)=∥a∥2+∥b∥2−2 a⋅b=2−2cos⁡(a,b)\lVert \mathbf{a} - \mathbf{b} \rVert_2^2 = (\mathbf{a} - \mathbf{b}) \cdot (\mathbf{a} - \mathbf{b}) = \lVert \mathbf{a} \rVert^2 + \lVert \mathbf{b} \rVert^2 - 2\,\mathbf{a} \cdot \mathbf{b} = 2 - 2\cos(\mathbf{a}, \mathbf{b})

也就是 cos⁡=1−∥a−b∥22/2\cos = 1 - \lVert \mathbf{a} - \mathbf{b} \rVert_2^2 / 2 —— 余弦和欧氏是同一个量的仿射变换,排序必然一致。

实测(geometry.py E2,300 组随机单位向量):恒等式两边最大绝对误差 8.882e-16(浮点精度内),余弦与欧氏的 Pearson 相关 -0.999594。

真模型上同样成立(bench_similarity.py E4,用真实的 BGE 向量做检索排序):

度量组合Spearman 秩相关
归一化后:cos vs 欧氏1.0000
归一化后:cos vs 内积1.0000
未归一化:cos vs 内积0.9673
未归一化:cos vs 欧氏0.9063

1.0000 意味着两种度量给出的名次完全相同。而真实 embedding 未归一化时的模长并不一致(实测 min 6.047 / 中位 6.810 / max 7.358,极差 1.22 倍),这点差异就足以让排序分家到 0.9063。

三个工程结论(推断,但由上面的实测直接支撑): ① 只要你归一化了,「向量库支持 cosine 还是 L2」这个选型问题不存在——排序一样; ② 但索引类型仍然有别:内积有专门的 MIPS 索引(如 FAISS 的 IndexFlatIP),归一化后可以直接吃; ③ 反过来说,忘了归一化是最隐蔽的一类 bug——它不报错,只是结果悄悄变差,和第 2 篇里「pooling 用错不报错」是同一类坑。

4. 高维几何:为什么「方向」装得下语义​

余弦只用了方向。问题是:方向够用吗? 一个 512 维的向量,能区分多少个不同的意思而不互相干扰?

答案是:多到用不完。原因是高维空间里随机两个方向几乎必然垂直。

E[cos⁡]=0,Std[cos⁡]≈1d\mathbb{E}[\cos] = 0, \qquad \mathrm{Std}[\cos] \approx \frac{1}{\sqrt{d}}

实测(geometry.py E3,每维独立取标准正态,3000 个向量、采样 20 万对):

维度 ddcos 均值cos 标准差理论 1/d1/\sqrt{d}∣cos⁡∣>0.3\lvert\cos\rvert \gt 0.3 占比
2-0.00090.70770.707180.556%
100.00020.31610.316237.021%
500.00040.14130.14143.259%
100-0.00000.09990.10000.242%
5120.00020.04410.04420.000%
7680.00000.03620.03610.000%
1536-0.00000.02550.02550.000%

标准差精确贴着 1/d1/\sqrt{d}。到了 BGE 用的 512 维,随机两两余弦几乎全部落在 ±0.13\pm 0.13(3σ)以内——随便挑两个方向,它们天然互不干扰。

这就是「用方向表示语义」在数值上可行的原因——不是因为方向有什么魔力,而是因为高维空间里方向实在太多了。

高维还有第二个后果,而且它是负面的:

维度 dd最近距离最远距离最远 / 最近
20.0754.82264.66×
102.0957.9733.80×
10011.87817.3551.46×
51229.50935.0661.19×
153651.94557.9201.12×

(geometry.py E4,1 个 query + 2000 个随机点)

二维里最远的点比最近的点远 65 倍;1536 维里只差 12%。所有点都挤在一个薄球壳上——这叫距离集中。它解释了为什么高维检索里「最近邻」不再特别近,也解释了为什么相似度阈值天然不可靠(§6.5)。

注:这里用的是无结构的随机点。真实 embedding 有训练出来的结构,对比度会好得多(实测 BGE 在 30 个跨话题句子上的余弦 p5=0.2869、p95=0.5336,确实拉开了)。但「维度越高越集中」这个趋势不会消失,只是被结构抵消了一部分。

5. 最关键的一层:相似度函数就是损失函数​

到这里,几何只保证了「余弦可以量方向差」。它没有保证「方向差 = 语义差」。 这一步是训练干的。

对比学习(contrastive learning)的目标函数通常是 InfoNCE:

L=−log⁡exp⁡ ⁣(sim(q,k+)/τ)∑iexp⁡ ⁣(sim(q,ki)/τ)\mathcal{L} = -\log \frac{\exp\!\big(\mathrm{sim}(\mathbf{q}, \mathbf{k}^+) / \tau\big)} {\sum_{i} \exp\!\big(\mathrm{sim}(\mathbf{q}, \mathbf{k}_i) / \tau\big)}

其中 sim\mathrm{sim} 就是余弦,k+\mathbf{k}^+ 是正样本(语义相同),ki\mathbf{k}_i 里包含正样本和一堆负样本,τ\tau 是温度。

盯住这个式子看:损失函数里出现的那个 sim\mathrm{sim},就是推理时你用来排序的那个 sim\mathrm{sim}。 训练在做的唯一一件事,是把 sim(q,k+)\mathrm{sim}(\mathbf{q}, \mathbf{k}^+) 往上推、把 sim(q,k−)\mathrm{sim}(\mathbf{q}, \mathbf{k}^-) 往下压。所以:

「余弦大 = 语义近」不是自然规律,是训练目标的定义。 这不是我从官方文档里读到的表述,是我对这个损失函数的分析(推断)——但它可以直接验证:把训练拿掉,看余弦还灵不灵。

5.1 实测:把训练拿掉​

对照组不是随便找个模型,而是同一个架构、同样的层数与隐藏维度、同一个分词器,只是权重没训过(AutoConfig.from_pretrained() + AutoModel.from_config())。BGE 的配置:512 维 / 4 层 / 21128 词表 / 24.0M 参数。

检索任务(10 条语料,每条配两个问法,都指同一件事):

模型高词面重叠 query低词面重叠 query
随机初始化(没训过)10/105/10
训练后 BGE10/1010/10

两组 query 与 gold 文档的字符 bigram Jaccard 均值:高重叠组 0.2681,低重叠组 0.0070——低重叠组几乎不共用任何字。

[随机初始化(没训过)]
✗ 第一天上班要准备什么材料 → 食堂早餐供应时间是七点到九点。
✗ 硬盘快满了系统会通知吗 → 每周五下午三点召开部门例会。
✗ 大家多久碰一次头 → 年假需要在休假前五个工作日提交申请。
[训练后 BGE]
✓ 第一天上班要准备什么材料 → 新员工入职当天需要携带身份证和学历证明原件。
✓ 硬盘快满了系统会通知吗 → 服务器磁盘占用超过百分之九十会触发告警。
✓ 大家多久碰一次头 → 每周五下午三点召开部门例会。

结论很清楚:词面重叠高时,两条路都能走(未训练的表示靠字符/词的重复就能蒙对);词面重叠一撤,只剩语义一条路,未训练的表示立刻垮掉一半。 语义检索能力不是 Transformer 架构自带的,是训练目标塞进去的。

这一点官方也明说了。BGE 模型卡(逐字):

If you pre-train bge on your data, the pre-trained model cannot be directly used to calculate similarity, and it must be fine-tuned with contrastive learning before computing similarity.

5.2 训练在几何上留下了什么痕迹​

同一批 30 个句子,三种表示的两两余弦分布(435 个句对):

表示均值标准差p5p50p95minmax
纯随机高斯向量(理论)-0.00060.043499-0.0734-0.00160.0702-0.14470.1246
随机初始化 BERT0.99740.0004410.99650.99740.99800.99590.9983
训练后 BGE0.39900.0766690.28690.39500.53360.20680.6752

这张表信息量很大,三行读三次:

  • 随机高斯向量:均值 0、标准差 0.043499,正好贴着 1/512=0.04421/\sqrt{512} = 0.0442 —— 这是 §4 说的「理想空间」,各向同性。
  • 随机初始化 BERT:均值 0.9974、标准差 0.000441。没训过的 Transformer,输出几乎全部指向同一个方向——任何两句话的余弦都是 0.99 以上,包括完全无关的(q↔无关 实测 0.9952)。这个空间里余弦零信息量。
  • 训练后 BGE:均值 0.3990、标准差 0.076669。训练把均值从 0.9974 压到 0.3990,同时把有意义的差异撑开了 174 倍(0.000441 → 0.076669)。

「未训练表示的各向异性」不是我的发现。BERT-flow 的摘要(逐字,arXiv:2011.05864):

We find that BERT always induces a non-smooth anisotropic semantic space of sentences, which harms its performance of semantic similarity.

Ethayarajh 对上下文化词向量的测量(逐字,arXiv:1909.00512):

For one, we find that the contextualized representations of all words are not isotropic in any layer of the contextualizing model.

5.3 温度这个参数,为什么会把分布压窄​

BGE 模型卡(逐字):

Since we finetune the models by contrastive learning with a temperature of 0.01, the similarity distribution of the current BGE model is about in the interval [0.6, 1]. So a similarity score greater than 0.5 does not indicate that the two sentences are similar.

注意官方把两件事放在了一句话里:温度 0.01 → 分布落在 [0.6, 1]。官方只说了现象,没解释机制。下面是我的解释(推断):

温度 τ\tau 很小的时候,softmax 里的 exp⁡(⋅/τ)\exp(\cdot/\tau) 会极度放大差异,只有最难的负样本还在贡献梯度。模型被迫把「本来就很像、只是细节不同」的那些负样本也推开——比如同一话题下的不同句子。结果就是:同一话题的句子全被挤进一个很窄的锥里,而它们之间的相对差异被保留了下来。

用纯几何模拟这个「公共方向分量」(geometry.py E5,xi=μ+σεix_i = \mu + \sigma \varepsilon_i,768 维):

σ\sigmaσ2d\sigma^2 d实测余弦均值实测标准差理论 1/(1+σ2d)1/(1+\sigma^2 d)
2.003072.00.00030.03600.0003
0.107.70.11540.03530.1152
0.051.90.34300.03010.3425
0.030.70.59080.02010.5913
0.020.30.76600.01180.7650

公共分量越强,基线越高、标准差越小——分布被压窄。σ=0.02\sigma = 0.02 时全体的余弦已经挤在 0.71~0.81 之间了。

这就是为什么绝对阈值不能用,也是官方紧接着那句的意思(逐字):

For downstream tasks, such as passage retrieval or semantic similarity, what matters is the relative order of the scores, not the absolute value. If you need to filter similar sentences based on a similarity threshold, please select an appropriate similarity threshold based on the similarity distribution on your data (such as 0.8, 0.85, or even 0.9).

一个必须说清的版本落差:模型卡里的 [0.6, 1] 描述的是旧版 BGE;同一条 FAQ 的开头就是「Suggest to use bge v1.5, which alleviates the issue of the similarity distribution」。 我实测 bge-small-zh-v1.5 在 30 个跨话题句子上:均值 0.3990、p5~p95 = 0.2869~0.5336——分布确实被拉开了,比 [0.6, 1] 低了一大截。(口径不同不能直接比:模型卡没说它的样本构成。但 v1.5 把分布拉开这件事,实测与官方说法方向一致。) 而同话题的句子依然很高:§6.2 里「支持自动续费」与「不支持自动续费」的余弦是 0.9335 / 0.9216。拉开的是跨话题的基线,不是同话题内部的细粒度。 这个区别在选阈值时必须心里有数。

6. 边界:余弦高 ≠ 语义等价​

上面全在讲「为什么能行」。这一节讲什么时候不行——四条,条条有实测。

下面的用例编号([7]、[10]…)对应 run.log 里 E1 的用例清单,每一步都能回查。

6.1 精确串:差一位型号,余弦一模一样​

用例 [10]
q: 订单 A20260926 的状态
gold: 订单 B20260926 的状态 cos = 1.0000
干扰: 订单 A20260927 的状态 cos = 1.0000
分差 = 0.0000 ✗

余弦到小数点后四位完全相同。 这不是模型小——这是稠密向量表示方式的固有边界:它擅长「说的是不是同一件事」,不擅长「到底是哪一天、哪个数」。和第 2 篇表格那节的实测(不同日期不同价格,相似度差 0.0001)是同一个现象。

怎么办:这类查询交给 BM25 或结构化过滤,别指望换更大的 embedding 模型。这正是第 4 篇里 B/C 两类 query 上 BM25 反超稠密向量的原因。

6.2 否定:差一个「不」字,只差 0.0118​

用例 [7]
q: 系统支持自动续费吗
gold: 系统不支持自动续费 cos = 0.9335 ✓
干扰: 系统支持手动续费吗 cos = 0.9216
分差 = 0.0118

语义正好相反的两句,余弦 0.93。 判对了(0.9335 > 0.9216),但分差只有 0.0118——这个量级的差距,换一批数据、换一个分词、甚至换个 pooling 方式就可能翻过来。

三组否定用例里,训练后的模型分差分别是 0.0118 / 0.1629 / 0.0472,并不是稳定的「否定必被识破」,而是「看运气」。同一组里未训练的模型分差是 0.0004 量级——等于没有信号。

6.3 同义改写:也会有输的时候​

A 类(同义改写)四组用例,训练后的 BGE 只判对 2/4,平均分差 0.0093:

# 类别 q↔gold q↔干扰 q↔无关 分差 判对?
0 A 同义改写 0.6797 0.6588 0.2186 0.0209 ✓
1 A 同义改写 0.7429 0.8120 0.2217 -0.0692 ✗ ← 输在哪?
2 A 同义改写 0.8127 0.7103 0.2882 0.1024 ✓
3 A 同义改写 0.6747 0.6915 0.3039 -0.0168 ✗

输的那条是用例 [1]:

q: 电脑开不了机怎么办
gold: 计算机无法启动怎么处理 cos = 0.7429
干扰: 手机开不了机怎么办 cos = 0.8120 ← 干扰赢了

干扰项和 query 共用了整个句式模板(「X 开不了机怎么办」),只换了主体。 双塔模型能抓住「开不了机」这件事,却压不住「电脑 vs 手机」这个主体差异。这和第 1 篇里「双塔把相关与不相关的分差压在 0.06~0.17」是同一个结构——双塔的「像不像」不等于「答不答得上」。

对比一下 B 类(词面重叠高但语义不同):平均分差 0.1904、3/3 全对。同一批用例里,双塔区分「词面像但语义不同」的能力,明显强于区分「语义同但细节不同」。

6.4 hubness:有的句子跟谁都像​

同一个空间里,不同句子的「社交能力」不一样(bench_similarity.py E5,30 个句子,各自对另外 29 个求平均余弦):

最高 3 个(hub,跟谁都像):
0.4480 医生建议每天保证七到八小时的睡眠时间。
0.4472 数据库主从延迟超过十秒需要人工介入排查。
0.4417 这段代码的时间复杂度是 O(n log n)。
最低 3 个(反 hub):
0.3528 这台相机的传感器尺寸是全画幅的。
0.3479 番茄炒蛋要先放鸡蛋还是先放番茄。
0.3142 这家店的招牌菜是红烧肉和清蒸鲈鱼。

全体均值 0.3990 极差 0.1338 最高 / 最低 = 1.43×

「余弦 0.8」对 hub 句和反 hub 句的含义是不一样的。 固定阈值过滤会系统性地偏向 hub 句——它们更容易越过任何门槛。§2 的玩具实验里那个 可能 就是同一个现象(纯噪声词排到最近邻第二位,0.721)。

6.5 所以:不要用绝对阈值​

把 §5.3 的分布压缩、§6.4 的 hubness、§4 的距离集中放在一起,结论只有一个:

这不是我的私货,是官方 FAQ 的原文(§5.3 已逐字引用):what matters is the relative order of the scores, not the absolute value.

7. 速查​

问题答案依据
用余弦还是欧氏?归一化后等价,随便用;不归一化就不等价§3.1 实测 Spearman 1.0000 vs 0.9063
用余弦还是内积?归一化后等价(且内积更快、有 MIPS 索引)§3.1
要不要归一化?要。忘了不报错,只是结果悄悄变差§3.1
相似度 0.5 算不算相关?这个问题问错了——看相对顺序,不看绝对值BGE 模型卡逐字
阈值怎么定?在自己数据上按分位数选(p95 之类),别照搬常数§5.3 + §6.4
语义是架构带来的吗?不是。同架构未训练 → 低重叠检索 5/10§5.1
为什么高维能表示语义?随机方向近似正交,方向多到用不完§4,标准差 ≈ 1/d1/\sqrt{d}
余弦能分日期/型号吗?不能,差一位是 1.0000 vs 1.0000§6.1
余弦能分否定吗?常常不能,实测分差 0.0118§6.2
相似度不行怎么办?① 换度量不解决问题 ② 上 cross-encoder 重排(第 1 篇) ③ 精确串走 BM25/结构化(第 4 篇)§6.1~§6.3

8. 来源与核对​

本轮抓取(2026-09-26,curl -sS -L <url>,原文留档 source/vector-similarity-lab/official-sources/):

引用来源存档HTTP
「less than a day to learn … 1.6 billion words」「syntactic and semantic word similarities」arXiv:1301.3781 摘要页(word2vec)arxiv-1301.3781.txt200
「BERT always induces a non-smooth anisotropic semantic space」arXiv:2011.05864 摘要页(BERT-flow)arxiv-2011.05864.txt200
「representations of all words are not isotropic in any layer」arXiv:1909.00512 摘要页(Ethayarajh)arxiv-1909.00512.txt200
「dropout acts as minimal data augmentation … representation collapse」arXiv:2104.08821 摘要页(SimCSE)arxiv-2104.08821.txt200
「C-Pack / C-TEM 中文 embedding 模型族」arXiv:2309.07597 摘要页(BGE)arxiv-2309.07597.txt200

沿用已有留档(抓取日期 2026-09-23,原文见 source/rag-lab/official-bge-small-zh-v1.5.md):

引用位置
「contrastive learning with a temperature of 0.01 … interval [0.6, 1]」「a similarity score greater than 0.5 does not indicate …」模型卡 FAQ 第 2 条
「what matters is the relative order of the scores, not the absolute value.」模型卡 FAQ 第 2 条
「the pre-trained model cannot be directly used to calculate similarity, and it must be fine-tuned with contrastive learning」模型卡 FAQ(微调建议)
「Suggest to use bge v1.5, which alleviates the issue of the similarity distribution.」模型卡 FAQ 第 2 条 / 更新日志

未能核实(如实标注):

项情况
Harris (1954) Distributional Structure 原文T&F 返回 HTTP 403,ACL Anthology 无该条目 → §2 开头对分布式假设的表述是转述,非逐字
Firth (1957)「You shall know a word by the company it keeps」未找到一手可访问原文 → 未在正文中作为逐字引用使用
BGE 模型卡 [0.6, 1] 的样本口径模型卡未说明这组分布是在什么句子上测的 → §5.3 已注明「口径不同不能直接比」

实测脚本与真实输出:source/vector-similarity-lab/ geometry.py(§3.1 / §4 / §5.3 的几何数字)、distributional.py(§1 / §2)、bench_similarity.py(§5.1 / §5.2 / §6),三份真实输出合并存于 run.log。

复现环境:python 3.10(~/anaconda3/envs/xin)、torch 2.10.0+cu126、transformers 5.7.0、numpy 2.2.5、BAAI/bge-small-zh-v1.5(512 维 / 4 层 / 24.0M 参数)、NVIDIA RTX 3090。跑法 bash source/vector-similarity-lab/run.sh,随机种子固定为 20260926。