Skip to content
BaiRuic
Go back

BM25 与向量检索的混合搜索实践

目录

在构建基于大语言模型或知识库的检索系统时,如果只使用向量检索(Dense Retrieval),经常会遇到一个普遍现象:搜索“如何减少模型微调显存”这类研究方向时,向量模型能准确召回语义相关的文本;但当搜索特定的算法代号或方法名(例如 FlashAttention-2、QLoRA 或 RoPE)时,排在前面的往往是泛泛讨论注意力优化或量化技术的长篇内容,精确包含该算法代号的文档反而排在靠后位置。

这一现象源于两种算法特性的差异:向量检索依靠高维空间的几何距离衡量相似度,更关注整段文本的宏观含义,容易稀释专有符号与低频方法名的信息权重;而传统的 BM25 算法直接统计词项的命中频率与稀有度,具备稳定的字面匹配能力,但无法识别近义表述。将两者结合的双路混合搜索(Hybrid Search),成为解决该矛盾的标准方案。

场景背景
Zotero 是一款开源文献管理软件,科研人员通常用它收集、整理与检索海量学术论文。在扩展其检索能力时,最核心的检索对象是论文的标题与摘要(Abstract)。本文以 Zotero 论文库的摘要检索为切入点,探讨 BM25 与向量检索的互补特性、双路召回的打分融合机制,以及具体的工程实现。


一、 文献检索中的两类查询诉求

在为 Zotero 扩展文献检索功能时,通常需要同时应对科研人员的两类核心诉求:

  1. 精确词法匹配(Exact Match):查询特定的算法缩写、模型版本、数据集名称或专有术语(如 FlashAttention-2、LLaMA-3、HumanEval)。用户期望检索结果严格包含该特定词项,不需要过度泛化。
  2. 研究意图匹配(Semantic Match):查询研究方向、解决的技术问题或技术路线(如“利用低秩矩阵减少微调参数量”)。用户的输入往往缺少固定的专有名词,用词可能与论文作者在摘要中的表述完全不同。
维度词法检索(BM25)语义检索(Dense Embedding)
匹配机制倒排索引匹配词项,基于统计概率打分预训练模型编码,基于向量夹角打分
优势场景算法代号、模型版本号、作者姓名、具体术语跨词汇意图匹配、近义词表达、跨语言检索
局限场景无法识别近义词与学术概念变体低频专有名词容易在向量空间中向高频概念漂移
计算开销CPU 字符串与数值统计,资源开销低依赖模型推理,向量索引常驻内存开销较高

两类查询诉求在单一路线的检索系统中各具盲区:向量检索容易产生语义漂移(Semantic Drift),即将具体的专有代号泛化为大类概念;BM25 则存在词汇不匹配(Vocabulary Mismatch)问题,一旦用词不一致就无法召回。


二、 BM25 算法的计算机制

BM25(Best Matching 25)是传统倒排索引中最常用的相关性打分算法。它在词频-逆文档频率(TF-IDF,Term Frequency-Inverse Document Frequency)算法的基础上,通过引入词频饱和度与文档长度归一化来平衡不同篇幅文本的得分。

BM25 计算查询语句 QQ 与论文摘要 DD 的相关性得分公式如下:

Score(D,Q)=∑i=1nIDF(qi)⋅f(qi,D)⋅(k1+1)f(qi,D)+k1⋅(1−b+b⋅∣D∣avgdl)Score(D, Q) = \sum_{i=1}^{n} IDF(q_i) \cdot \frac{f(q_i, D) \cdot (k_1 + 1)}{f(q_i, D) + k_1 \cdot \left( 1 - b + b \cdot \frac{|D|}{\text{avgdl}} \right)}

公式通过如下三个独立的项控制相关性打分。

1. 逆文档频率(IDF):用来量化词汇的稀有程度

在机器学习相关的学术文献中,“learning”、“model”、“attention”等高频词在绝大多数摘要中都会出现,对确定特定论文缺乏区分度;而“LoRA”、“FlashAttention”等词项仅在特定论文中出现,命中时具有很高的判别权重。

BM25 采用的 IDF 计算公式如下:

IDF(qi)=ln⁡(N−n(qi)+0.5n(qi)+0.5+1)IDF(q_i) = \ln \left( \frac{N - n(q_i) + 0.5}{n(q_i) + 0.5} + 1 \right)

  • NN 是 Zotero 库中的文献总数;
  • n(qi)n(q_i) 是包含查询词 qiq_i 的文献篇数。

包含该词项的文献数 n(qi)n(q_i) 越少,该词的 IDF(qi)IDF(q_i) 值越高;若某词在全库绝大多数摘要中出现,IDFIDF 的贡献趋向于 0。

从直觉上看,IDF 本质上是在衡量一个词的信息量:

如果一个词在全库几千篇论文里随处可见(例如“model”或“algorithm”),那它出现在这篇论文里并不能说明文章的独特性,其信息量接近于零,系统赋予它的权重就会极低; 反之,如果一个词在整整一万篇论文里总共只有两三篇提到过(例如“FlashAttention-2”或“Mamba”),一旦用户的搜索词命中它,几乎可以断定这篇论文就是用户想要寻找的目标,系统就会赋予极高的加分。

简而言之:越是普遍出现的词越不具备区分度,越是稀有独特的词判别价值越高。

2. 词频饱和度(Term Saturation):限制高频词过度加分

在标准 TF-IDF 算法中,词频(TF)对分数的贡献是线性的。某篇摘要若提及同一个算法名 10 次,其词频得分就是提及 1 次的 10 倍。这会导致内容较长、重复提及词项的摘要获得不合理的加分。

BM25 引入参数 k1k_1(通常取 1.2 到 2.0)来调节词频饱和度:

f(qi,D)⋅(k1+1)f(qi,D)+k1\frac{f(q_i, D) \cdot (k_1 + 1)}{f(q_i, D) + k_1}

当词项在摘要中的出现次数 f(qi,D)f(q_i, D) 从 0 增加到 2 次时,得分上升明显;当出现次数继续上升时,得分曲线斜率逐渐减小,渐进趋于上限值 (k1+1)(k_1 + 1)。该项限制了单一关键词重复出现所带来的得分放大。

我们可以用具体数值来对比标准线性 TF 与 BM25 的差异。假定参数 k1=1.5k_1 = 1.5(此时分母饱和上限为 1.5+1=2.51.5 + 1 = 2.5),不同出现次数下的词频得分对比如下:

关键词在摘要中出现次数 (tftf)传统线性 TF 得分BM25 词频得分项 (tf⋅2.5tf+1.5\frac{tf \cdot 2.5}{tf + 1.5})
1 次1.01.00
5 次5.01.92
20 次20.02.33
100 次100.02.46

从 1 次增加到 5 次,BM25 得分上涨了将近一倍;但从 20 次暴增到 100 次,得分仅微涨 0.13,永远不会突破 2.5 的天花板。这意味着即便某篇论文摘要为了博取排名而反复堆砌某个词项,它的得分很快就会饱和,无法以此压过其他正常文献。

3. 长度归一化:平衡不同篇幅的摘要

不同期刊与会议对摘要的字数要求不同,长度从 100 词到 400 词不等。长摘要因字数基数大,包含查询词的概率高于短摘要。BM25 引入长度归一化项消除该偏差:

K=k1⋅(1−b+b⋅∣D∣avgdl)K = k_1 \cdot \left( 1 - b + b \cdot \frac{|D|}{\text{avgdl}} \right)

  • ∣D∣|D| 为当前论文摘要的词数;
  • avgdl\text{avgdl} 为库中所有摘要的平均词数;
  • bb 是长度调节参数(通常取 0.75)。

当摘要长度大于平均长度(∣D∣>avgdl|D| > \text{avgdl})时,分母增大,对词频贡献施加抑制;当摘要长度较短时,分母减小,对短小精炼的摘要给予补偿。b=0b=0 时完全关闭长度调节,b=1b=1 时实施完全的长度比例惩罚。

从直觉上可以这样理解:一篇仅有 80 词的短摘要如果提到了 2 次“LoRA”,说明整篇论文的核心就在研究 LoRA,其关键词的信息密度极高;而一篇 400 词的长摘要如果也提到了 2 次“LoRA”,很可能只是在介绍相关工作(Related Work)或对比基线时顺带提了一句。BM25 通过计算摘要长度与平均长度的比值(∣D∣/avgdl|D|/\text{avgdl}),本质上是在度量“单位篇幅内的关键词密度”,防止长篇摘要单纯依靠篇幅优势占据高位。

4. 词法检索局限:词汇不匹配

BM25 的计算仅基于字面切词后的倒排列表。在实际的文献检索场景中,最直接的局限体现在跨语言检索上:

国内科研人员经常习惯输入中文进行检索,例如搜索“低显存模型微调”或“注意力机制加速”。然而,主流顶会(如 NeurIPS、ICML)的绝大多数论文标题与摘要通篇都是英文(例如 LoRA: Low-Rank Adaptation of Large Language Models)。

BM25 依靠纯字面匹配。用户输入的中文词汇和英文文献摘要没有任何相同的单词或标记,此时无论两者的学术研究方向多么契合,BM25 算出的相关性得分一律为 0,相关文献根本不会出现在检索结果中。这就是词汇不匹配(Vocabulary Mismatch)在真实文献管理中暴露的缺陷。

三、 向量检索的几何匹配机制

向量检索不依赖词项在字面上的逐字对应,而是将文本映射到高维几何空间,依据空间中的几何距离评估语义相似度。

1. 摘要向量化与余弦相似度

系统使用基于 Transformer 的预训练 Embedding 模型(如 Qwen-3-Embedding 或 BGE 等)对论文摘要进行特征提取,输出固定维度的稠密向量(例如 768 或 1024 维浮点数数组)。

向量检索通常使用余弦相似度(Cosine Similarity)评估查询向量与文献向量的方向接近程度:

similarity(u,v)=u⋅v∥u∥∥v∥=∑i=1duivi∑i=1dui2∑i=1dvi2\text{similarity}(\mathbf{u}, \mathbf{v}) = \frac{\mathbf{u} \cdot \mathbf{v}}{\|\mathbf{u}\| \|\mathbf{v}\|} = \frac{\sum_{i=1}^{d} u_i v_i}{\sqrt{\sum_{i=1}^{d} u_i^2} \sqrt{\sum_{i=1}^{d} v_i^2}}

当向量经过 L2 归一化(模长 ∥u∥=1\|\mathbf{u}\| = 1)后,计算余弦相似度等价于计算点积(Dot Product)。

2. 向量检索在文献库中的表现与代价

向量检索最大的优势在于能够直接理解语义大意,跨越字面表达乃至语言的鸿沟。当用户用中文输入“降低显存消耗的微调技术”时,即使目标论文通篇只有英文 We propose LoRA, which freezes pre-trained weights to reduce memory usage,多语言向量模型也能识别出两者讨论的是同一个研究主题,将其投射到空间中相近的位置,成功召回该文献。

但在本地文献库的工程实现中,纯向量检索有三个非常具体的痛点:

  1. 搜不准具体的版本与代号:面对 FlashAttention-2、QLoRA、DeepSeek-V3 这种特定算法名称,分词器会将其拆碎为通用的字母片段。模型计算完向量后,往往只保留了“讨论注意力优化”的整体轮廓,无法精准区分用户需要的是 1 代还是 2 代,容易把同方向的其他论文排在更前的位置。
  2. 占用较多计算与内存资源:为成千上万篇论文生成高维向量需要消耗大量的 CPU/GPU 推理算力。同时,为了保证即时搜索的响应速度,基于图的近邻索引(如 HNSW)必须完整驻留在电脑内存中,几万条千维浮点数所占用的内存体积是原始文本的数倍,在轻量级的桌面客户端(如 Zotero 插件)中容易引起卡顿。
  3. 无法通过明确规则微调结果:在传统词法检索中,如果某个词搜不准,可以增加专有名词词典、配置同义词映射或调整特定字段权重,调试手段立竿见影。而向量检索属于连续空间的黑盒计算,若两篇论文计算出的空间距离不合理,无法单独针对某个维度进行手动干预,只能更换或重新微调整个模型。

四、 双路召回与结果融合策略

为了在 Zotero 文献库中同时支持精确代号查询与概念泛化查询,采用双路召回流水线:

                    ┌─── BM25 倒排索引 ───> 候选列表 A ┐
用户查询 (Query) ───┤                                  ├─── RRF 排名融合 ───> Top-K 候选 ───> [可选] Reranker 精排 ───> 最终排序
                    └─── Dense 向量索引 ──> 候选列表 B ┘

两路检索各自输出一组候选文献后,系统需要解决核心问题:两种算法的打分量纲完全不同,如何将两组结果合并为一个有序列表?

1. 分数异构问题

BM25 输出的得分是无上界的非负实数(通常在 0 到 30 以上浮动,受 Zotero 库内文档频率分布影响),而向量余弦相似度通常落在 [0,1][0, 1] 之间。

若直接将两个原始分数值相加,BM25 的数值会完全掩盖向量检索的贡献,导致检索退化为单纯的字面匹配。

2. 线性归一化加权(Min-Max Normalization)

一种处理方式是将两路检索各自输出的分数线性缩放至 [0,1][0, 1] 区间,然后按固定权重计算综合得分:

Snorm=S−SminSmax−SminS_{\text{norm}} = \frac{S - S_{\text{min}}}{S_{\text{max}} - S_{\text{min}}}

Shybrid=α⋅Snorm_bm25+(1−α)⋅Snorm_denseS_{\text{hybrid}} = \alpha \cdot S_{\text{norm\_bm25}} + (1 - \alpha) \cdot S_{\text{norm\_dense}}

这种方法实现直观,但在实际检索中对极值敏感。若某篇摘要中多次密集命中同一个查询词,导致 BM25 产生极高离群分,这会将该批次其他正常文献的归一化分数压缩至接近 0,扰乱整体排序。

3. 倒数排名融合(RRF, Reciprocal Rank Fusion)

工业实践中更常用的方案是 RRF 算法。RRF 不直接使用算法输出的分数数值,仅依据文献在各自召回列表中的相对排名位置进行计算。

对于候选集合中的任意文献 dd,其综合得分定义如下:

RRF_Score(d)=∑m∈M1k+rankm(d)RRF\_Score(d) = \sum_{m \in M} \frac{1}{k + rank_m(d)}

  • MM 为参与融合的检索系统集合(即 BM25 列表与向量列表);
  • rankm(d)rank_m(d) 为文献 dd 在系统 mm 中的排名次序(从 1 开始计);
  • kk 为平滑常数(通常固定取 60)。

RRF 解决混合排序的实用性主要体现在以下三点:

  1. 不管底层分数差多大,只按排第几名算分:BM25 算出的分数可能是 25.8 分,向量余弦相似度算出来是 0.85,两者不是同一个单位,根本无法直接比大小。RRF 直接抛弃具体分数值,只看各自榜单的名次:不论哪边,第 1 名统一拿到 160+1≈0.01639\frac{1}{60+1} \approx 0.01639 分,第 2 名拿到 160+2≈0.01613\frac{1}{60+2} \approx 0.01613 分,彻底消除了不同算法打分尺度的差异。
  2. 两边都认可的论文,得分断层领先:如果一篇论文在 BM25 里排第 1,在向量检索里也排第 1,它的 RRF 分数就是两份相加(约 0.03280.0328);而仅在单侧排在前面的论文,分数通常只有它的一半左右。这能把“既精确命中了专有名词,整体语义又高度相关”的优质文献稳稳推上榜首。
  3. 开箱即用,不需要人工调参:传统的加权求和(如线性归一化)总要纠结 BM25 占 0.3 还是 0.7,文献库扩充或更换模型后权重往往需要重新调试。RRF 公式中的平滑常数 k=60k=60 是信息检索界通用的经验默认值,不需要反复调试浮点权重,在各种规模的文献库中都能保持稳定的排序质量。

4. 接入重排序模型(Cross-Encoder Reranker)

双路召回结合 RRF 的目标是提升召回率(Recall),确保潜在相关的论文进入候选集。在此之后,为了让排在最前面的几篇文献与查询最贴切,系统可以接入第二阶段:重排序(Reranking)。

为什么需要重排序?双塔(Bi-Encoder)与交叉塔(Cross-Encoder)的区别

要理解重排序的必要性,先要看第一阶段的向量检索为什么在细节判断上会有所欠缺:

  • 双塔模型(Bi-Encoder,初检向量检索所用结构):为了实现毫秒级搜索,文献库在入库时就把每篇论文的摘要独立计算成向量存入数据库。用户检索时,模型只给用户的 Query 算一个向量,随后做简单的向量点积。在整个计算过程中,Query 里的每一个词和 Abstract 里的每一个词从来没有在深度学习模型内部产生直接交互,它们之间细微的修饰关系与因果逻辑直接压缩进一个固定长度的全局向量,容易丢失局部关键细节。
  • 交叉编码器(Cross-Encoder,重排序模型所用结构):重排序模型把 [Query, Abstract] 拼接成一整段文本一起送入模型。模型内部每一层的 Transformer 都在计算全量交叉注意力(Cross-Attention),让查询中的每一个词与摘要中的每一个词逐字比对。它能准确识别出复杂的否定句、逻辑限定与前后文关系,判断精度显著高于双塔向量。

为什么不直接用 Cross-Encoder 检索整个文献库?

原因在于计算延迟与资源消耗。

双塔模型速度极快,因为数万篇文献的向量都提前离线算好了,查询时只进行简单的数学点积。而 Cross-Encoder 每次搜索都必须把用户的查询与每一篇待比对的文献拼在一起进行深度网络的前向推理。如果文献库中有 10,000 篇论文,用户发起一次查询就需要大模型执行 10,000 次推理,系统需要数秒甚至数分钟才能返回结果,在交互式检索中不可接受。

两阶段检索架构(Two-stage Pipeline)

工业检索标准方案是采用“粗排 + 精排”的两阶段流水线:

  1. 第一阶段(粗排 / 双路召回):利用 BM25 和双塔向量检索,在 10 毫秒内从全库数万篇文献中快速挑出排在前列的 20 到 50 篇候选论文,保证高召回率;
  2. 第二阶段(精排 / 重排序):只将这几十篇优质候选文献送入 Cross-Encoder(例如 bge-reranker-base)逐一打分精排。由于只进行几十次前向计算,响应时间通常控制在 50 毫秒以内。

这样既享受了 Cross-Encoder 细粒度注意力交互的高准确度,又把整体响应时间限制在交互友好的毫秒级。