BM25 公式拆解
BM25 是一个排序打分算法:给它一批文档和一个查询词,它为每篇文档算出一个相关度分数,分数高的排在前面 —— 这个分数就是搜索引擎或 RAG 候选召回的排序依据。几乎所有搜索引擎和 RAG 系统的关键词检索底下都跑它。
BM25 总览 给出了完整公式和三层直觉(稀有度 / 饱和 / 长度归一化)。这篇按 idf、饱和、长度归一化三项拆开,对每一项指出它在代码里的位置,再说明它为什么长成这个形式。
最小示例
BM25 的整体骨架是这样:
for t in query:
idf = rarity(t) # 第 3 节:idf 项
sat = saturation(tf, k1) # 第 4 节:饱和项
norm = length_norm(dl, avgdl, b) # 第 5 节:长度项
score += idf * sat / norm
三个部件彼此独立。下面三节把 rarity / saturation / length_norm 各自拆回 BM25 公式里对应的那项,最后一节再合起来得到可运行的 Python 实现。
BM25 公式
这是 per-term 打分再求和:对 query 里的每个词 算一个贡献值,把贡献值累加得到文档分。单项贡献 = idf 乘以饱和 tf,而文档长度归一化嵌在饱和项的分母里,不独立出现。三个部件彼此独立,下面一节一项单独拆:
- idf 项:词在 collection 里有多稀有,只和
N、df有关 - 饱和项:同一个词多次出现贡献边际递减,由 控制
- 长度项:长文档整体打折,嵌在饱和项分母里,由 控制
idf 项
代码里就是这一行:
idf = log(1 + (N - n + 0.5) / (n + 0.5))
N 是总文档数,n 是含词 t 的文档数。在 toy corpus 上打印每个 query 词的 idf:
稀有的词值大,常见的词值小 —— 这是经典 idf 的直觉。
去掉 1 + 那个偏移(Lucene / rank_bm25 加的防负数保护),剩下的 是 RSJ weight 在”无 relevance 信息”假设下的自然退化。下面把这条推导走完。
PRP
If retrieved documents are ordered by decreasing probability of relevance on the data available, then the system’s effectiveness is the best that can be obtained for the data.
按”相关概率”降序排文档,在单用户 / 单 query 下是期望损失最小的策略(Bayes Optimal Decision Rule)。后面所有推导都只盯一件事:估每篇文档的相关概率。
BIM · 从整篇概率到 per-term 求和
PRP 让我们按 降序排文档,但这个概率直接估不了 —— 文档空间太大,标注数据太薄,根本没法对某个具体的 组合给出可靠估计。
Binary Independence Model 做三个简化让它能估:
- 文档记成二值词出现向量(出现 / 不出现),丢掉词频
- 给定 时,各词的出现彼此独立
- 只看 query 里出现过的词,query 外的词假设和 relevance 无关
有了这三条,整篇相关概率就能写成每个命中词的 log odds ratio 之和。
下面 6 步每一步都是保序变换 —— 只改公式形式,不改任何两个文档之间的相对排序。严格说:若变换前文档 的分数低于 ,变换后仍然低于 。高等数学里两个最常见的保序操作是:
- 单调递增函数(比如 ): 等价于 ,所以取 log 前后排名不变
- 乘以或加上一个对所有候选文档相同的常数:整排平移不改相对位置
这 6 步就用这两类操作,把 逐步化成只含 query 命中词的 per-term 求和。
记号:
- :文档的二值词出现向量, 表示词 在文档里出现过
- :query 的同类二值向量
- :query 固定下的保序等价,两侧对所有候选文档排序相同
每一步:
- (1) odds 和 P 是单调映射,排序不变
- (2) Bayes 展开后先验比只依赖 ,对所有候选是同一常数,排序时丢掉
- (3) 给定 ,各词出现彼此独立。这是条件独立(在 下),不是无条件独立 ——
neural和network无条件肯定相关 - (4) 只取 0/1,乘积按”出现 / 缺失”拆;、
- (5) 不在 query 里的词假设和 relevance 无关()→ 因子为 1 可丢;代数重排把缺失项归入查询常数后一并丢掉
- (6) 取 log,乘法变加法
结果:文档得分 = 每个命中 query 词的 log odds ratio 之和。这和 tf-idf 在倒排列表累加是同一类做法,但每项权重现在有了概率语义。
RSJ weight + 0.5 smoothing
、 怎么估?填一张 2×2 contingency table:
| Relevant | Non-relevant | Total | |
|---|---|---|---|
| 含 | |||
| 不含 | |||
| Total |
MLE 直接代入 (6) 式:
小样本下分母容易为 0, —— 某词在 20 个相关文档里碰巧没命中,就被赋 ,含它的文档全被排死。
一个经典的修正:4 个中心 cell 各加 0.5,得到 Robertson-Spärck Jones weight:
加 0.5 不是拍脑袋 —— 这个修正能被证明最小化 log-odds 估计的偏差。
退化到 idf
现实里很少有现成的 relevance judgments。没有 judgment,代入 + 无偏先验 :
时近似 —— 经典的 inverse document frequency。
idf 最早只是一个经验直觉 —— 越稀有越有鉴别力,没有理论支撑。二十多年后,RSJ 模型在无 judgment 的极端假设下自动退化到同一个公式。直觉被事后追认。
这也顺便解释了为什么 idf 的各种变种(带 0.5、带 、不同偏移)在不同 collection 上表现有差:每个变种对应对 和 的不同先验假设。
Ordering Principle: 从哪来
回看公式 log(1 + (N - n + 0.5) / (n + 0.5)),分子里那个 有来历。
RSJ 的原始工作把 relevance weighting 做成了一个四选一问题:
Independence Assumption
- I1:词在相关文档中独立 + 在全集合中独立
- I2:词在相关文档中独立 + 在非相关文档中独立
Ordering Principle
- O1:文档得分只看 query 词在文档里出现的情况
- O2:文档得分同时看出现和缺失
组合出四个加权函数:
| O1 | O2 | |
|---|---|---|
| I1 | F1 | F3 |
| I2 | F2 | F4 |
F4(I2 + O2)就是上面推出来的 RSJ weight。在 Cranfield 和 Keen 两个标准 IR 测试集上的完整对比:
- I1 vs I2:差异很小,独立性的具体假设不太关键
- O1 vs O2:O2 持续优于 O1
对”应该被惩罚的词”(高频但在相关文档里几乎不出现,典型如 stopword),O2 给出的负权重比 O1 更极端 —— 它显式利用了”缺失”这个信号。现代 BM25 里的 就是 O2 的遗产。如果历史走岔选了 O1,BM25 的 idf 项可能只有分母,效果会差一截。
饱和项
代码里是这三行(暂时忽略长度部分):
num = tf[term] * (k1 + 1)
den = tf[term] + k1
score += idf * num / den
对应公式 。 时趋近 (上限), 时为 0,中间单调但边际递减。
为什么饱和
词在文档里第 1 次出现提供”这篇在讲这个”的强信号。第 2 次加强。到第 10 次,边际贡献接近 0。BM25 设计者观察到这点,把它编码进公式。
工程效果:再怎么堆同一个词,权重也不会超过 的上限。这挡住了”关键词堆砌”式 SEO —— 在 2000 年代对抗黑帽 SEO 时起过作用。
控制饱和速度
- 小(0.5)→ 快速饱和,第 2、3 次出现贡献都小
- 大(3.0)→ 慢饱和,高频词继续拉分
- 是 TREC 数据上 grid search 的经验甜点
这个饱和形式来自 2-Poisson 模型(词频服从双 Poisson 混合分布)的一阶近似。为什么是 而非 或 ,推导见 Robertson–Walker 1994(ACM,10 页)。
长度项
代码里是分母多出来的这部分:
den = tf[term] + k1 * (1 - b + b * dl / avgdl)
b * dl / avgdl 是长度归一化。它嵌在饱和项分母里,和饱和 tf 共享同一个分母。
为什么嵌在分母
独立乘一个长度因子相当于对整个 score 线性缩放 —— 不区分”同样一个 tf 在长文档里是被稀释”还是”这个词本来就是分布重心”。把它嵌进分母,只缩放饱和项的”分母增量”:
- 长文档():分母变大 → 同样 tf 拿到的分更少
- 短文档():分母变小 → 同样 tf 拿到的分更多
- :,退化为纯饱和项
控制归一化强度
- :完全不做长度归一化
- :按长度完全归一化(线性惩罚)
- :在更大规模实验上收敛到的经验值
保留了长文档的部分优势。理由:有些长文档覆盖更多内容(维基百科大词条 vs tweet),不该被简单当”啰嗦”。
参数与默认值
| 参数 | 默认 | 作用 |
|---|---|---|
| 1.2 | 饱和速度(TREC 数据上 都可) | |
| 0.75 | 长度归一化强度 | |
| 0 或省略 | query 端饱和,query 短时用不上 |
这些数字没有一阶原理支持,都是实证的。Elasticsearch / Lucene 的默认就是这组,在通用文本上表现不错,但专业领域(医学 / 法律 / 代码搜索)往往要自己调。
合成 · 完整 BM25
把三项装回一起:
打完分会看到 d1 > d2 > d3:代码里的 idf 是 idf 项在无 relevance judgment 假设下的退化式,num / den 的分子 tf * (k1 + 1) 是饱和形式,分母 tf + k1 * (1 - b + b * dl / avgdl) 嵌入了长度归一化。堆了三遍 bm25 的 d2 没赢 d1,因为它缺 parameters、同一个词堆叠有饱和、文档稍长也拿到长度惩罚 —— 三项各自在打分里起了作用。
本章总结
- idf 是直觉先到、理论事后追认:BIM 在 R=r=0 + 无偏先验下退化为经典 idf
- (N-n) 项来自 RSJ 的 O2 假设:“缺失也算证据”,让 stopword 的负权更极端
- 饱和形式 tf/(k₁+tf) 是 Harter 2-Poisson 模型的一阶近似
- 长度归一化嵌在饱和分母里、不独立乘:避免对整个 score 线性缩放
- k₁=1.2 / b=0.75 是 TREC 上的经验值,专业 collection 通常要自调
参考来源
本章推导主要依据几篇原文:RSJ weight 与 0.5 smoothing 来自 Robertson–Spärck Jones 1976 和 Cox 1970;PRP 来自 Robertson 1977;2-Poisson 来自 Harter 1975; 的默认值来自 Robertson–Walker 1994 和 Spärck Jones–Walker–Robertson 2000。完整引用(带 DOI 和简评)见 BM25 总览 § 参考资料。