1. BM25 解决的到底是什么问题?![]()
全文检索首先需要回答两个不同的问题:
哪些文档匹配查询词?
匹配的文档应该如何排序?
倒排索引解决第一个问题。对于查询词database,搜索引擎可以直接找到包含该词的文档列表。
BM25 主要解决第二个问题:同样包含database的文档,哪一篇更相关?
直觉上,一个合理的词项相关性模型至少应考虑三件事:
查询词在当前文档中出现得越多,文档通常越相关;
一个词在整个语料库中越少见,它越有区分度;
同样出现三次,短文档中的三次通常比超长文档中的三次更重要。
BM25 正是把这三种信号组合起来:
TF(Term Frequency):词频信号,表示查询词在当前文档中出现了多少次。
IDF(Inverse Document Frequency):稀有度信号,表示一个查询词在整个语料库中有多稀有。
字段长度归一化:长文档天然更容易包含查询词,不能仅因为篇幅长,就获得更高分数。
不过 BM25 并不是语义模型。它不知道“数据库”和“DBMS”语义相近,也不理解句子的真实含义。它做的是一种非常高效、可解释、面向词项匹配的相关性估计。
2. BM25 公式拆解
对于查询 中的每个词项 ,文档 的 BM25 得分通常写成:
其中:
符号 | 含义 |
|---|---|
查询词 在文档 中的出现次数,即 term frequency | |
当前文档该字段的 token 数 | |
avgdl | 所有文档该字段的平均 token 数 |
文档总数 | |
包含词项 的文档数,即 document frequency | |
控制词频饱和速度 | |
控制字段长度归一化强度 |
Tantivy 0.26.1 中固定使用:
const K1: Score = 1.2; const B: Score = 0.75;这也是常见的默认参数组合。
2.1 IDF:越少见的词,区分度越高
Tantivy 使用的 IDF 为:
当一个词几乎出现在所有文档中时,它没有多少区分能力,IDF 较低。
当一个词只出现在少量文档中时,它更可能代表用户真正关心的主题,IDF 较高。
例如,在技术文档库中:
the、is、data可能很常见;MVCC、DiskANN、fieldnorm可能更稀有。
因此,后者通常能给文档带来更高的相关性增量。
公式中的+0.5和外层1+是平滑处理,使 IDF 在极端数据分布下仍保持稳定且为正。
2.2 TF 不是线性增长,而是逐渐饱和
假设当前字段长度正好等于平均长度,即:
BM25 的词频部分可以简化为:
使用 Tantivy 的 :
词频 | TF 部分 |
|---|---|
1 | 1.000 |
2 | 1.375 |
4 | 1.692 |
10 | 1.964 |
趋近无穷 | 2.200 |
第一次命中很重要;从一次增加到两次仍然有明显收益;但从一百次增加到一百零一次,几乎不会改变排序。
这就是“词频饱和”。
如果词频完全线性增长,一篇反复堆砌关键词的文档会轻易压过正常文档。BM25 用 抑制了这种行为。
2.3 字段长度归一化
BM25 的长度归一化项为:
当 时,完全不考虑字段长度。
当 时,完整地按照字段长度相对平均长度进行归一化。
Tantivy 使用 ,意味着长度是重要因素,但不会完全主导评分。
假设查询词在文档中都出现三次,平均字段长度为 100:
当前字段长度 | 不含 IDF 的 TF 得分 |
|---|---|
50 | 1.760 |
100 | 1.571 |
200 | 1.294 |
同样出现三次,短字段中的命中更集中,因此得分更高。
这里有一个容易被忽略的关键点:
BM25 归一化的是“字段经过 analyzer 后得到的 token 数”,不是原始字符串长度,也不是整篇文档所有字段长度的总和。
分词器、停用词过滤、N-gram 参数都会改变 token 数,因此也会间接改变 BM25 分数。
3. 落地 BM25 需要哪些数据?
把公式翻译成存储引擎语言,需要四类数据:
范围 | 数据 | Tantivy 中的来源 |
|---|---|---|
当前文档、当前词项 | 词项出现次数 ,即 term frequency | postings 中记录的 term frequency |
当前文档、当前字段 | 字段长度 ,即该字段分词后的 token 数 | fieldnorm / |
全局字段统计 | 该字段的总 token 数,以及索引文档总数 | 各 segment 的倒排索引元数据 |
当前查询词的全局统计 | 包含该词项的文档数 ,即 document frequency | term dictionary 中 |
BM25 看起来只是一个公式,但它要求索引在写入阶段提前保存:
每个 term 在每个文档中的频率;
每个文档字段的长度;
每个 term 的文档频率;
每个字段的全局 token 统计。
如果索引只保存“这个 term 出现在哪些文档中”,而不保存频率和字段长度,那么就无法完整计算 BM25。