混合检索 + rerank 现在是检索管线的默认配置,默认到很少有人再问它一句为什么在这儿。九月中旬 arXiv 上挂了一篇 cs.CL 的论文,编号 2609.13856,干的事情特别土——搭一个企业客服的政策判定系统,把六种检索配置放在 2632 条留出查询上拉平了比。结果最朴素的那一档拿了第一:纯 FAISS 稠密检索,85.37%,凡是在它上面加东西的,分数全往下走。
这篇拆完,你手里会有一样东西:一个判断标准,能判断你自己的检索任务到底是不是"混合检索有收益"的那一类。这比记住一个 85.37% 有用。
先拆 RRF。它是那套默认配置里最关键、也最容易被整个当黑盒用掉的一环。
稠密检索和稀疏检索吵的不是同一件事。FAISS 那一路把 query 和文档都编码成向量,比的是语义距离。BM25 压根不知道语义是什么,它数的是词项重叠——一个词在文档里出现几次、在别的文档里稀不稀罕,乘起来算个分。两路的输出没法直接加。一边是内积算出来的某个实数,一边是 BM25 那套量纲完全不同的分数,放一起相加,数学上就没有意义。
RRF 是绕开这个问题的办法。说人话就是——它不看分数,只看名次。每一路检索器给一个排好序的列表,RRF 给每个文档算 1/(k + 它的名次),k 一般取 60,再把来自不同路的这个值加起来重新排一遍。
这里最容易被搞混的是:RRF 是排名层面的投票,不是分数层面的融合。它默认相信一件事——多路检索器如果都把一个文档排得比较靠前,这个文档大概率靠谱。
为什么这个假设在很多开放域任务上成立?因为稠密和稀疏的盲区恰好不重叠。稠密检索怕的是模型没见过的那种专有名词、产品编号、罕见缩写,向量空间里没有合适的邻居,它就抓瞎。BM25 反过来,字面命中一个罕见词一抓一个准,但它对同义改写完全无感。
我一开始想写"两个瘸子凑一块各补一条腿",写完觉得不严谨,应该反过来说:不是它们功能上互补,是两路检索器的失败模式在统计上接近独立,所以至少有一路答对的概率比单路高。互补是结果,不是原因。这两句话听着差不多,落到工程上差很远——前者会让你去找"能力互补"的两路,后者只要求失败模式不相关。
(跑题一句:那个 k=60 我也说不清是谁最早拍的,大概是个经验值,作用是把名次差异压平一点,免得一路的第 8 名被另一路的第 1 名彻底按死。打住,拉回来。)
再说交叉编码器重排。这一步的动机很正经:双塔检索有个结构性的毛病——query 和文档是分别编码的,编码 query 的时候它看不见文档,编码文档的时候也看不见 query,两边各算各的,最后才碰一次面算个余弦。信息真正交互的程度很浅。
交叉编码器把 query 和文档拼成一个序列一起喂进去,让它们在注意力层里从头到尾互相看一遍。准是准了,代价是文档向量没法预先算好——每来一个 query,每一篇候选文档都得跟它配一次对、跑一次完整前向。所以它一般只放在第一轮召回之后,只对 Top-N 做。
到这儿,常规解释就讲完了。稠密加稀疏、RRF 融合、再挂个交叉编码器重排,每一环都有正当理由。问题出在这个场景压根不是检索场景。
拆一下它到底在干什么。用户输入一句客服问题,比如"我的包裹三天没动了",系统要从六类里挑一个:Refund、Return、Shipping、Cancellation、Damaged Product,还有一个 Unknown。论文里明确说了,不做意图到政策类别的固定映射,类别是拿检索到的文档直接判出来的。也就是说,检索这一步的输出不是"给你一堆资料自己看",而是"直接决定答案是哪个"。
检索在这里的角色是分类的中介。这是关键。
一旦这么看,几个前提就变了。文档库小而且封闭,就那几篇政策文档、六个类别,来回说的就是那几种事。BM25 最值钱的能力——靠一个罕见词把目标文档从几万篇里捞出来——在这里没有用武之地,因为没有几万篇,也没有罕见词。类内措辞又高度相似,退款政策和退货政策,用词重叠得厉害,"refund"、"return"、"order"这几个词几篇里都高频出现,BM25 数出来两边都很高,它分不出这两篇讲的是两回事。字面匹配的优势在这类语料上直接变成噪声。
所以只有 BM25 的那档掉到 55.74%,比抛硬币好一点,一点不奇怪。
(这里我做个推断,论文没这么说——选 LLaMA 3.2 本地跑、Ollama 部署,这个选型本身就透露出一点倾向:这套系统对生成质量的期待不高,重点压在判定准不准上。本地小模型润色客服话术够用了,判定错一次,回复写得再漂亮也是错的。当然这只是我的读法。)
数字摆出来看。纯 FAISS 85.37%,2247 条判对。Weighted RRF 85.07%,几乎贴平,它是给两路检索器各配权重的那种融合,比等权的 Fair RRF 强不少。带交叉编码器的 RRF 83.24%,带交叉编码器的 Top-10 混合检索 81.88%。两个交叉编码器配置都掉了一到三个点,论文说它们延迟还更高。
这里有个容易看漏的地方。论文做了 McNemar 检验,结论是纯 FAISS 和 Weighted RRF 之间没有显著差异,但这两个都显著优于 Fair RRF 和两个交叉编码器配置。
McNemar 检验在干嘛?说人话就是——它只比较两个系统判定结果不一致的那些样本,数"甲对乙错"有多少条、"乙对甲错"有多少条,看这个不对称性够不够显著。两个系统都判对的样本被剔掉了,因为那些样本不携带区分信息。这比直接比总准确率靠谱,它排除了"数据太简单、两个系统都蒙对"的干扰。
所以"FAISS 和 Weighted RRF 无显著差异"翻译过来是:融合确实把 BM25 拖的后腿补回来了,Weighted RRF 追平了纯稠密。但追平不等于超过。融合在这一档的价值精确地等于零——它没帮你多判对一条,只是没让你少判对。
那交叉编码器为什么是负的?它不是在帮忙,是在添乱。
机制上想一层:交叉编码器训练时学的是什么?是"这两段文字讲的是不是同一件事",目标是相关性排序。但这里的标签不是相关性,是政策类别。一段退货政策文档,对一个退款查询来说字面高度相关——都讲订单、都讲处理流程、都提到了退——重排模型会给它一个很高的分。可答案是错的。重排模型越自信,说明它越觉得"这两段在讲一件事",而这个信号在类别判定任务里恰好是干扰项。
还有第二层伤害:重排会翻动 FAISS 已经排好的顺序。原本排对的文档,被重排按"看起来更像"的标准往下挪,正确答案就出局了。翻一次名次,可能就把唯一那篇正确的政策文档推出了窗口。而且这个挪动不免费,交叉编码器对每个 query-doc 对跑一次完整前向,Top-10 就是十倍的计算量。论文写得客客气气,说增加了处理时间而没有改善分类性能。说白了就是净亏。
打个比方——RRF 和重排可以想成两个审稿人。RRF 是让两个初审各交一份名单,合并的时候只按名次,不管他们打分打得多高多低。重排是再请一位终审,把初审筛过的稿子逐字读一遍,读得最细。这两个人在"哪篇稿子写得好"这个维度上是有用的。但如果你要的不是"哪篇好",而是"这篇该归到哪个栏目",那终审读得越细,越容易被文笔带偏——文字流畅、用词贴近,跟栏目标签是两回事。这个类比到这儿就该收了,它撑不到解释 Fair RRF 为什么比 Weighted RRF 差那么多,那是权重分配的问题,不是审稿人的问题。
Unknown 那段单独说一句。论文按类别拆开看,Shipping、Cancellation、Return 这几类表现都还可以,主要错误集中在 Unknown 查询上。这个现象不奇怪,机制上是一类老问题:闭集分类器遇到开集输入。Unknown 是没有对应政策的那类提问,但分类器没有"我不知道"这个出口,它必须在六个选项里挑一个,而每一个看起来都不完全对,于是只能挑一个最不像错的。这条留到下次展开,它牵扯的东西比这篇文章的范围大。
写到这里,我得承认一句:这篇论文本身没什么惊艳的东西。六个模块拼一拼,本地跑个 LLaMA 3.2,FAISS 加 BM25,全是现成件,工程上谈不上新意。它的价值不在方法,在它给了一个控制得比较干净的反例——有留出集,有 McNemar,六种配置横向拉平了比。
混合检索这套东西在开放域问答上确实有收益,那不是错觉,也别因为这一篇就把它扔了。但它有前提:文档库得大到 BM25 的精确匹配真能补上语义匹配的盲区,类别得开放到字面相似不代表答案相同。这两个前提不成立的时候,这套默认配置就从增益变成负担——重排尤其,它每加一层,就多一层把正确答案挪走的可能。
说到底就一句话:混合检索没有失效,它只是从来没被通用过。库有多大、类别是封闭还是开放,这两件事你自己量一量,那套配置在你手上是资产还是负债,答案自己就出来了。
