同样的词,不同的意思:向量检索分得清辖域吗?
我的出发直觉是一段数学。"For all δ there exists ε"(对每个 δ 都存在一个 ε)和 "there exists ε for all δ"(存在一个对所有 δ 都成立的 ε)用的词是一样的,意思却不一样。量词顺序一换,命题就变了:一个方向是每个 δ 各配一个 ε,另一个方向是一个 ε 要对所有 δ 成立。词几乎完全相同,意思不是。
这就引出一个关于检索增强系统底层机制的问题:向量检索分得清这两者吗?如果一本教材里同时有正确表述和它那个辖域互换的孪生表述,用户问的是其中一个,检索器会返回正确的那一个,还是把两个都交出来,把区分的活推给下游?
我进去时的预测很直白:几个最先进的 embedding,乘上几种距离,都会失败。这篇文章报告两次验证这个预测的实验——一次小探针,和一次扩到四十组最小对的规模化版本。预测对了一半,而错的那一半才是更有意思的部分。
设计:最小对与 WIN 规则
实验由最小对构成:两句话共享几乎全部的词,只在辖域上不同——量词顺序、否定位置、或者连接词结合方式——而真值条件确实不同。每组对子都带一个区分场景:一个具体的情形,在其中一个孪生句为真、另一个为假。构造不出这种场景的对子就不是对子,只是同一句话写了两遍。
每对的每一侧各配一条查询,写成该侧读法的意译。判定规则严格且成对:只有当正确段的分数高于它那个辖域互换的孪生段时,这条查询才算 WIN。正确段排第一不是必需的,赢过孪生段才是。这就是全部测试,其余一切——干扰段、其他段、绝对分数——都只是背景。
有一个微妙之处关系到量词换位的弱读一侧。弱读("每个病人都有某个医生")在逻辑上被强读("某个医生治疗所有病人")蕴含,所以弱读一侧的 plain 意译无法排除强读世界。弱读一侧的查询因此用了混合场景——不同病人配不同医生——这是唯一能把读者钉在弱读上的写法。
待测方法:
- 手写 BM25(k1 = 1.5,b = 0.75),作为关键词对照。
- 三个双塔模型,均值池化:MiniLM-L6-v2、bge-base-en-v1.5、e5-base-v2(e5 按规范加 query:/passage: 前缀)。
- 四种距离——cosine、dot、欧氏、曼哈顿——各在原始向量和 L2 归一化向量上都算一遍。
- 交叉编码重排器 ms-marco-MiniLM-L-6-v2,对全语料打分。这是关键对照:双塔模型永远不会同时看到查询和段落,但交叉编码器是同时读两边的,所以如果有什么东西能看见辖域,应该是它。
全部在 CPU 上跑(torch 2.14.1,transformers 5.20.2)。
ε-δ 对子,细算
我最初的例子是 δ 和 ε 朝另一个方向互换。实验里的 ε-δ 对子是同一类量词顺序互换,只是变量处在标准连续性定义里的角色上:逐点(∀ε∃δ)对均匀(∃δ∀ε)。
- E1(逐点):"For every epsilon > 0, there exists a delta > 0 such that for all x, ..."
- E2(互换):"There exists a delta > 0 such that for every epsilon > 0, for all x, ..."
这两句话作为词袋是完全相同的。下面是第一次实验在它们上面的得分(归一化 cosine,交叉编码器除外):
| 方法 | Q_E1:正确段 vs 孪生段 | Q_E2:正确段 vs 孪生段 |
|---|---|---|
| MiniLM-L6-v2 | 0.570 vs 0.585,LOSE | 0.482 vs 0.465,WIN |
| bge-base-en-v1.5 | 0.824 vs 0.815,WIN(+0.009) | 0.794 vs 0.789,WIN(+0.005) |
| e5-base-v2 | 0.872 vs 0.866,WIN | 0.857 vs 0.860,LOSE(−0.003) |
| 交叉编码器 | 第 1 名 vs 孪生第 3 名,WIN | 第 3 名 vs 孪生第 2 名,LOSE |
纯词袋对照 BM25 在这对上两次都是精确平局(6.929 = 6.929 和 7.876 = 7.876)——词袋相同,平局是必然的。Embedding 比平局好一点点:赢出 0.005 到 0.015。这个 margin 是整个结果形态的第一条线索。顺序信息确实有一点进了向量,只是不够强,也不可靠。
实验一:探针
第一次跑用了 14 段:4 组最小对加 6 段干扰(极限定义、导数定义、数列收敛、三段论、可导推出连续、开集定义)。4 组对子是上面的 ε-δ 对、1 组命题逻辑对("It is not true that both Alice and Bob passed",¬(A∧B),对 "Alice did not pass, and Bob passed",(¬A)∧B),和 2 组一阶逻辑对(每个学生喜欢某个老师,对某个被所有学生喜欢的老师;以及母亲版的同一互换)。8 条查询,每侧一条。
| 方法 | 得分 | 丢掉的查询 |
|---|---|---|
| BM25 | 6/8 | Q_E1、Q_E2(精确平局) |
| MiniLM-L6-v2 | 6/8 | Q_E1(0.570 vs 0.585)、Q_F1(0.736 vs 0.749) |
| bge-base-en-v1.5 | 7/8 | Q_F2(0.933 vs 0.936,差 0.003) |
| e5-base-v2 | 7/8 | Q_E2(0.857 vs 0.860,差 0.003) |
| 交叉编码器 | 6/8 | Q_E2(1.486 vs 1.569)、Q_F2(4.160 vs 4.327) |
关于距离,有两个事实在这次跑里出现,并在规模化后依然成立。在归一化向量上,cosine、dot、欧氏三者的排名完全相同——这是数学恒等,不是实验巧合。而原始向量的 dot 是最不稳的度量(MiniLM 原始 dot 这次只有 4/8),因为它把向量长度也算进去了。不要用原始 dot。
探针的 verdict:我的预测对了一半。没有全军覆没——bge 和 e5 都是 7/8。但命题逻辑那组是所有方法都赢的一组,也是孪生句表面差别最大的一组,所以它证明的东西最少。真正的试金石是硬对子,即词袋相同或近相同的那些——在那里,每个模型至少丢一组,赢的 margin 全在 0.003 到 0.015 之间,连同时读问句和段落的交叉编码器都丢了两组。不过探针太小了,不能停在这里。8 条查询只是一个 hint,不是结果。
实验二:规模化
第二次跑把第一次的 14 段和 8 条查询原样保留,新增 40 组最小对:20 组命题逻辑对(否定辖域、连接词结合、蕴含辖域、only-if 对 if、if-and-only-if、相容或对异或、unless、逆否命题),15 组一阶量词换位对,覆盖不同领域(病人和医生、城市和市长、问题和解法、员工和经理、歌和歌手、锁和钥匙、课程和老师、书和作者、狗和主人、学生和家教、村庄和水井、队伍和教练、文章和编辑、花园和园丁、电影和导演),以及 5 组否定-量词对("not every" 对 "no","some did not" 对 "none did")。一共 94 段、88 条查询。跑完 exit 0,约四分钟。
各方法结果
| 方法 | 新对(80) | 全部(88) |
|---|---|---|
| BM25 | 44/80 = 0.550 | 50/88 = 0.568 |
| 交叉编码器 | 53/80 = 0.662 | 59/88 = 0.670 |
| MiniLM-L6-v2,归一化 cosine | 52/80 = 0.650 | 58/88 = 0.659 |
| MiniLM-L6-v2,归一化 dot | 52/80 = 0.650 | 58/88 = 0.659 |
| MiniLM-L6-v2,归一化欧氏 | 52/80 = 0.650 | 58/88 = 0.659 |
| MiniLM-L6-v2,归一化曼哈顿 | 51/80 = 0.637 | 57/88 = 0.648 |
| MiniLM-L6-v2,原始 cosine | 52/80 = 0.650 | 58/88 = 0.659 |
| MiniLM-L6-v2,原始 dot | 43/80 = 0.537 | 47/88 = 0.534 |
| MiniLM-L6-v2,原始欧氏 | 54/80 = 0.675 | 60/88 = 0.682 |
| MiniLM-L6-v2,原始曼哈顿 | 57/80 = 0.713 | 63/88 = 0.716 |
| bge-base-en-v1.5,归一化 cosine | 62/80 = 0.775 | 69/88 = 0.784 |
| bge-base-en-v1.5,归一化 dot | 62/80 = 0.775 | 69/88 = 0.784 |
| bge-base-en-v1.5,归一化欧氏 | 62/80 = 0.775 | 69/88 = 0.784 |
| bge-base-en-v1.5,归一化曼哈顿 | 62/80 = 0.775 | 69/88 = 0.784 |
| bge-base-en-v1.5,原始 cosine | 62/80 = 0.775 | 69/88 = 0.784 |
| bge-base-en-v1.5,原始 dot | 61/80 = 0.762 | 68/88 = 0.773 |
| bge-base-en-v1.5,原始欧氏 | 62/80 = 0.775 | 69/88 = 0.784 |
| bge-base-en-v1.5,原始曼哈顿 | 62/80 = 0.775 | 69/88 = 0.784 |
| e5-base-v2,归一化 cosine | 65/80 = 0.812 | 72/88 = 0.818 |
| e5-base-v2,归一化 dot | 65/80 = 0.812 | 72/88 = 0.818 |
| e5-base-v2,归一化欧氏 | 65/80 = 0.812 | 72/88 = 0.818 |
| e5-base-v2,归一化曼哈顿 | 64/80 = 0.800 | 71/88 = 0.807 |
| e5-base-v2,原始 cosine | 65/80 = 0.812 | 72/88 = 0.818 |
| e5-base-v2,原始 dot | 57/80 = 0.713 | 65/88 = 0.739 |
| e5-base-v2,原始欧氏 | 63/80 = 0.787 | 70/88 = 0.795 |
| e5-base-v2,原始曼哈顿 | 62/80 = 0.775 | 69/88 = 0.784 |
最好的双塔模型是 e5,新对 65/80(81.2%),然后是 bge 62/80(77.5%),MiniLM 52/80(65.0%)。而同时读问句和段落的交叉编码器只有 53/80(66.2%)——低于 bge 和 e5。它在第一次跑里的中间表现不是小样本偶然偏高,规模化后它掉到了本该被它打败的双塔模型之下。
按类型拆分(headline 方法,归一化 cosine)
| 方法 | 命题逻辑(40) | 量词换位(30) | 否定-量词(10) | 新对(80) |
|---|---|---|---|---|
| BM25 | 23/40 | 14/30 | 7/10 | 44/80 |
| MiniLM-L6-v2 | 26/40 | 19/30 | 7/10 | 52/80 |
| bge-base-en-v1.5 | 28/40 | 26/30 | 8/10 | 62/80 |
| e5-base-v2 | 32/40 | 24/30 | 9/10 | 65/80 |
| 交叉编码器 | 26/40 | 20/30 | 7/10 | 53/80 |
否定-量词对最容易(各方法 70–90%),命题逻辑居中(65–80%),量词换位最硬且 margin 最小。这个排序说得通:"no" 和 "not every" 本身就是强词汇信号,而纯量词换位在表面上几乎什么都没改。
赢的幅度有多大?Margin 分析
赢了不等于赢得结实,还得看 margin。下面是(正确段分数 − 孪生段分数)在归一化 cosine 下的分布;交叉编码器那列是 logit 差,量纲不同,只在本列内可比。
| 方法 | 类型 | 均值 | 中位数 | abs margin < 0.015 的占比 | 窄胜(赢了且 abs margin < 0.015) |
|---|---|---|---|---|---|
| MiniLM-L6-v2 | 命题逻辑 | +0.0099 | +0.0120 | 0.300 | 8/26 |
| MiniLM-L6-v2 | 量词换位 | +0.0186 | +0.0140 | 0.333 | 5/19 |
| MiniLM-L6-v2 | 否定-量词 | +0.0593 | +0.0760 | 0.000 | 0/7 |
| bge-base-en-v1.5 | 命题逻辑 | +0.0175 | +0.0260 | 0.175 | 5/28 |
| bge-base-en-v1.5 | 量词换位 | +0.0207 | +0.0250 | 0.300 | 6/26 |
| bge-base-en-v1.5 | 否定-量词 | +0.0736 | +0.1040 | 0.200 | 1/8 |
| e5-base-v2 | 命题逻辑 | +0.0133 | +0.0120 | 0.425 | 14/32 |
| e5-base-v2 | 量词换位 | +0.0070 | +0.0090 | 0.733 | 17/24 |
| e5-base-v2 | 否定-量词 | +0.0314 | +0.0330 | 0.100 | 1/9 |
| 交叉编码器 | 命题逻辑 | +0.0764 | +0.3070 | 0.025 | 0/26 |
| 交叉编码器 | 量词换位 | +0.3081 | +0.4910 | 0.000 | 0/20 |
| 交叉编码器 | 否定-量词 | +0.6653 | +0.8830 | 0.000 | 0/7 |
看 e5 的换位行:73.3% 的换位 margin 落在 ±0.015 内,24 场胜里 17 场是窄胜。最硬对子上的最好模型,赢也是靠噪声赢的。bge 在换位上稳一些(均值 +0.021,中位数 +0.025),但仍有 30% 的 margin 落在窄带里。否定-量词对的 margin 大得多(bge 均值 +0.074),因为区分词本身就是强信号。
交叉编码器没有进这张图,因为它的分数是 logit,量纲不同,0.015 这条带对它不适用。
召回没问题,精确排序才是失败
| 方法 | 孪生段进前 3(新对) | 孪生段进前 3(换位对) | 正确段排第 1(新对) |
|---|---|---|---|
| MiniLM-L6-v2 | 0.925 | 1.000 | 0.625 |
| bge-base-en-v1.5 | 0.975 | 1.000 | 0.762 |
| e5-base-v2 | 0.950 | 1.000 | 0.800 |
| 交叉编码器 | 0.963 | 1.000 | 0.637 |
| BM25 | 0.750 | 0.700 | 0.475 |
在量词换位对上,孪生段进前 3 的概率是百分之百——三个双塔模型和交叉编码器全是如此。检索一定会把两个互相矛盾的版本都交出来。所以失败不是正确段落缺席,而是错误段落就贴在旁边,而两者之间的排序是抛硬币。任何一个取 top-k 再往下推理的系统,拿到手的都是矛盾材料,还得自己在下游把它分清楚。
还有两个更精确的事实。第一,弱读比强读难:在换位对上,弱读(∀∃)查询对强读(∃∀)查询的胜率,MiniLM 是 7/15 对 12/15,bge 是 11/15 对 15/15(bge 强读全赢),e5 是 13/15 对 11/15。模型往更短、更"标准"的强读句子上漂。第二,难度是集中的,不是摊开的:80 条新查询里有 8 条被三个 embedding 全丢(P01_a、P02_a、P04_b、P07_a、P14_a、P20_b、F11_a、N04_b)——全是弱读或带否定的弱读形态——而 46 条被三个模型全赢。
BM25 在换位对上值得单独写一行:14/30,平均 margin −0.69。词袋重合度在这里不只是帮不上忙,它系统性地偏向错误的孪生段。
第一次跑被精确复现
规模化没有改变实验的最强证据:第一次跑的 8 条查询,放进 94 段语料里重打分,五个 headline 方法的 WIN/LOSE 逐条一致。
| 查询 | BM25 | MiniLM | bge | e5 | 交叉编码器 |
|---|---|---|---|---|---|
| Q_E1 | LOSE | LOSE | WIN | WIN | WIN |
| Q_E2 | LOSE | WIN | WIN | LOSE | LOSE |
| Q_P1 | WIN | WIN | WIN | WIN | WIN |
| Q_P2 | WIN | WIN | WIN | WIN | WIN |
| Q_F1 | WIN | LOSE | WIN | WIN | WIN |
| Q_F2 | WIN | WIN | LOSE | WIN | LOSE |
| Q_F3 | WIN | WIN | WIN | WIN | WIN |
| Q_F4 | WIN | WIN | WIN | WIN | WIN |
协议没有漂移,第一次跑的数字也不是小语料假象。方向是稳定的。
这意味着什么
诚实的总结不是"检索失败了",而是更窄、也更有用的一句:检索在召回上成功了,在最后一步上失败了。在最硬的对子上——词相同、只有辖域不同的那些——检索器可靠地把两个版本都返回,然后用一个比噪声还小的 margin 给它们排序。读 top-k 结果的语言模型拿到的是两段互相矛盾的材料,而排序里没有任何信号告诉它哪一段回答了问题。
这就定位了修复点。它不在召回——把网撒得更宽、加更多段落、取更多候选,都分不开本来就已经并排坐在顶部的孪生段。它必须在排序或验证那一步:一个真正拿候选段落和问题的辖域去对的步骤——哪个量词约束哪个变元、否定落在哪里——并拿答案去核对,而不是信任一个从来就没被训练去看辖域的相似度分数。在这样的步骤出现之前,任何精确材料——数学、法律、合同、规格——用这种方式检索都带一个安静的风险:那段看起来对、意思却相反的段落,永远只差一个排名。
必须留在后面的 caveat
- 查询是为实验写的意译,不是真实用户敲出来的问法。换措辞,结果可能动。
- 段落是单句。真实教材块是 512 token 的长段,辖域信号可能被周围文本稀释,也可能被上下文加强——没测。
- 只测了英文。量词辖域标记方式不同的语言没测。
- 88 条查询仍然是探针规模,不是 benchmark——但第一次跑在更大语料里的精确复现说明方向是稳定的。
可复现
可跑的脚本(exp.py、exp2.py)、原始输出(exp-output.txt、exp2-output.txt),以及带逻辑形式和区分场景的完整对子清单,都在 quant-order-exp 目录里。第二次跑建成时,第一次跑的三个文件保持字节原样;第二次跑是独立的脚本和独立的记录,不是对第一次的修改。
我开始时的问题,现在有了一个更锋利的答案。向量检索看不见辖域。它看见的是和辖域相关的东西——多到能比词袋平局赢出百分之一分,少到在词相同、意思不同的地方没法信任。