原文发布于 quant67.com,转载请保留出处。
Postings 与 codec 把词项如何编码成可跳跃的 docID/freq 列表讲清了;查询侧还要回答:两个文档都包含关键词,凭什么 A 排在 B 前面? 这不是 UI 偏好,而是 Similarity 在 postings 上为每个 (query term, docID) 算出的分数如何累加。
本文是「全文检索引擎」系列第 6 篇。目标不是复述信息检索教材,而是钉住三条链:
- Robertson & Zaragoza (2009) 的 BM25 公式与参数 k1、 b 各自抑制什么失效模式;
- Lucene
BM25Similarity如何把avgdl、norms、IDF 接到 第 5 篇 的 postings 访问路径上; - BM25 作为可解释基线,与学习排序(LTR)在生产里分工在哪里。
版本锚定 :Apache Lucene 9.x / 10.x (
org.apache.lucene.search.similarities.BM25Similarity);Elasticsearch 8.x 默认similarity: BM25。公式以 Robertson & Zaragoza, The Probabilistic Relevance Framework: BM25 and Beyond , FnTIR 2009 为准。
一、问题从哪来:TF 线性增长为何在工程上失效
经典向量空间模型里,词频 TF 常线性进入打分:同一词出现越多,文档越「相关」。这在短查询、长短文档混杂的 Web 语料上很快失效:
- 词频饱和:第 10 次出现「database」带来的边际相关性远低于第 1 次;
- 长度偏置:长文档天然更容易累积高 TF,把真正聚焦主题的短文挤下去。
BM25(Best Matching 25)来自 Robertson & Walker 等在 Okapi 系统上的概率相关框架;Robertson & Zaragoza (2009) 把它整理为 PRF 教程中的标准写法,并讨论 Beyond BM25 的扩展。Lucene 自 6.0 起把默认 Similarity 从 TF-IDF 切到 BM25------这不是「新算法噱头」,而是把饱和 TF + 长度归一写进默认路径。
二、BM25 公式与参数语义
对查询 Q、文档 D,设 f(q,D) 为词项 q 在 D 中的词频, ∣D∣ 为文档长度(通常用该字段总词数), avgdl 为索引内同字段平均文档长度, N 为文档总数, nq 为包含 q 的文档数。Robertson & Zaragoza (2009) 给出的 BM25 项级打分可写为:
score(q,D)=IDF(q)⋅f(q,D)+k1⋅(1−b+b⋅avgdl∣D∣)f(q,D)(k1+1)
查询总分对 Q 中各 q 求和(Lucene 默认对多词查询按词项分数相加,具体组合由 BooleanQuery 与协调因子决定,见 第 9 篇)。
IDF 在 Lucene BM25Similarity 中使用 Robertson-Spark Jones 风格平滑:
IDF(q)=ln(1+nq+0.5N−nq+0.5)
(自然对数;实现里对负 IDF 截断为 0。)
| 符号 / 参数 | 典型值(Lucene 默认) | 抑制什么 |
|--------------------------------------------------------------------------------------|--------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|---|----------------------|
| k1 | 1.2 | TF 饱和速度:越大,高频词越晚「封顶」 |
| b | 0.75 | 长度归一强度: b=0 忽略长度; b=1 完全按 | D | /\\mathrm{avgdl} 惩罚 |
| avgdl | 索引统计 | 把绝对长度变成相对平均长度的比值 |
| IDF | 由 N,nq 得 | 常见词降权、稀有词升权,但带平滑避免 nq=0 爆炸 |
2.1 TF 饱和的曲线形状
固定 k1=1.2、长度因子为 1 时,TF 贡献 f+k1f(k1+1) 随 f 单调增但渐近上限为 k1+1 。这与 TF-IDF 里 TF 未饱和(或仅 tf 等弱饱和)形成鲜明对比------也是「长文堆砌关键词」在 BM25 下更难刷分的根因。
三、Lucene 落点:BM25Similarity、norms 与 avgdl
3.1 索引期:norms 编码字段长度
Lucene 在索引带 IndexOptions 的文本字段时,默认写入 norms (每文档每字段一字节),编码该字段长度信息供打分使用。BM25Similarity 在 computeNorm 中把 token 计数映射为可被 $b$ 使用的长度因子------查询期不再扫描全文数词 ,只需读 norms 与 postings 里的 freq。
这与 第 2 篇 的字段正交开关一致:omitNorms=true 会去掉长度归一(所有文档长度因子视为 1),适合长度分布极均匀或刻意不做长度惩罚的场景。
3.2 查询期:CollectionStatistics 与 avgdl
打开 IndexSearcher 时,BM25Similarity 从 CollectionStatistics 读取 N、总词数等,计算:
avgdl=docCountsumTotalTermFreq
(对应该字段;多段索引时由 reader 聚合段统计。)每个 (term, doc) 的打分在 Scorer 里按需计算:从 postings 取 freq,从 norms 解码长度,再套上一节的公式。
3.3 与 explain 对齐
Elasticsearch _explain 与 Lucene Explanation 会展开 idf 、tf 、fieldNorm 等子项------排障时应对照公式逐项核对,而不是只看最终 _score。本系列不在此粘贴未在本机执行的 explain 输出;读者可在单节点用官方文档中的 explain API 自行核对。
| 组件 | 索引期 | 查询期 |
|---|---|---|
postings freq |
写入 | 读取 f(q,D) |
| norms | 编码 ∥D∥ | 解码长度因子 |
| 词典 docFreq | 写入 nq | 计算 IDF |
| 段/索引统计 | --- | N、 avgdl |
四、与 TF-IDF 的工程差异
Lucene 仍提供 ClassicSimilarity(传统 TF-IDF 风格)供显式切换;Elasticsearch mapping 里也可设 similarity: boolean 等。对照 BM25,工程上应记住:
| 维度 | Classic / TF-IDF 路径 | BM25 默认路径 |
|---|---|---|
| TF | 常 tf 或未饱和线性 | 显式饱和,上限由 k1 控制 |
| 长度 | lengthNorm 乘子 |
b 与 avgdl 进入 TF 分母 |
| 稀有词 | IDF | 同族 IDF 平滑,但 TF 腿不同 |
| 默认 | 历史默认 | Lucene 6+ / ES 现代默认 |
争论(有文献支撑) :BM25 在 TREC 类 ad-hoc 检索上长期作为强基线(Robertson & Zaragoza 2009 综述);但在域内排序 (电商、招聘)上,手工特征 + 学习排序常能压过纯 BM25------前提是标注数据、特征管线与 serving 成本可承受。BM25 的价值在于:零训练、可解释、与 postings 紧密耦合、延迟可预测。
4.1 常见误解
- 「BM25 的 IDF 就是 ln(N/nq)」 。Lucene 实现用 (N−nq+0.5)/(nq+0.5) 平滑;与教科书简化式数值不同,explain 里应对 implementation 而非背错公式。
- 「调大 k1 一定提升召回」 。 k1 只改 TF 饱和形状;极端大 k1 接近线性 TF,重新引入堆砌关键词偏置。
- 「similarity 只影响排序,不影响倒排」 。Similarity 不改 postings 编码,但 norms 在索引时写入 ;改 b 或换 Similarity 类通常需 reindex 才能一致生效(除非仅 IDF 腿且 norms 未变)。
五、与学习排序的工程边界
学习排序(Learning to Rank, LTR)把相关性变成监督学习:LambdaMART、RankNet 等在特征向量上训练,再在生产对候选文档重排。代表工作包括 Burges (2010) 对 LTR 的综述,以及后续将 BM25 分数、字段匹配、点击信号等作为特征的工业实践。
工程分界(本系列立场,非学术终局判断):
| 层 | 典型实现 | BM25 角色 |
|---|---|---|
| 召回 | match / dis_max / 混合 kNN |
主力或强特征;决定候选集是否漏 doc |
| 粗排 | 分片内 Top-K | BM25 分数直接截断 |
| 精排 | ES rescore、外部 LTR |
BM25 常作特征之一,非唯一分数 |
| RAG 稀疏路 | llm-infra 召回 | BM25 零训练、易复现;语义缺口由向量路补 |
工程间隙 :论文里的 LTR 常在固定候选集上测 NDCG;生产里 候选集由 BM25 先砍到 Top-N ,LTR 再精排------BM25 参数恶化会造成功能性漏召回,精排模型救不回来。Elasticsearch 的 sltr 插件与外部 rerank 服务把模型 serving 挪出 Lucene 核心;Lucene Similarity 接口仍是嵌入式场景的扩展点 ,但大多数 ES 用户不会改 Java Similarity,而是改 boost、function_score 或二次 rescore。
开放问题:
- 稠密检索 + BM25 融合 的统一打分函数尚无社区共识(见本系列 第 15 篇)。
- 跨语言 / 多字段 下 avgdl 与 b 是否应字段级定制,仍依赖域内评测而非单一全局默认。
- LLM 重排 成本与延迟下,BM25 粗排窗口该多大,缺少与硬件无关的普适公式。
5.1 Elasticsearch 侧如何改 BM25
Elasticsearch mapping 允许字段级 similarity:
json
{
"mappings": {
"properties": {
"title": {
"type": "text",
"similarity": "BM25",
"index_options": "positions"
}
}
}
}
BM25 类型可配 k1、b(8.x Reference Similarity )。修改后 仅影响后续索引 的 norms 与统计;已有段仍按旧参数写入的 norms 解释。全索引统一需 reindex 或 rollover 新索引------这与「只改查询 DSL」完全不同。
bool 查询里的 boost 乘在 Lucene 子句 Weight 上,不改变 IDF 公式本身 ;function_score 可把 BM25 分与 DocValues 字段组合(见 第 10 篇),已超出纯 Similarity 范畴。
六、调参与失效模式(无 benchmark 结论)
下列判断来自机制推导与官方文档,非本机吞吐排名:
- 新闻 / 日志 :文档长度方差大,保持默认 b=0.75 通常合理;极短字段(标题)可单独字段并视情况
omitNorms或降低 b。 - 词典字段(SKU、ISBN):长度几乎恒定,norms 收益低;更应依赖精确词项与 boost。
- 多语言混合索引 : avgdl 对「中文长文 + 英文摘要」同字段混合时会失真------优先 分字段 而非只调 k1。
6.1 与向量引擎 / RAG 的分工
llm-infra 第 17 篇 在应用层组合 BM25 与向量召回;向量检索引擎系列 专讲 ANN 索引与 Segment 生命周期。本系列第 6 篇只钉 BM25 在 Lucene 内的落点,三者分工如下:
| 环节 | BM25(引擎层) | 向量 ANN | RAG 应用层 |
|---|---|---|---|
| 召回依据 | 词项共现、IDF | embedding 几何近邻 | chunk 边界、metadata 过滤 |
| 必用场景 | SKU、法规号、日志 keyword | paraphrase、跨语言语义 | prompt 窗口、citation |
| 调试入口 | _explain、Analyzer |
efSearch、索引度量 | 融合权重、rerank 延迟 |
| 典型漏召回 | 分词未产出 query term | 域外向量、错误 embedding 模型 | chunk 切断实体 |
引擎侧应明确:
| 环节 | BM25 作用 | 常见坑 |
|---|---|---|
| chunk 索引 | 按 chunk 字段算 avgdl | 父文档过长导致 chunk norms 失真 |
| hybrid | BM25 与 kNN 分数量纲不同 | 需 rank_window_size / RRF 等融合,非直接相加(见 第 15 篇) |
| explain | 调试漏召回 | 先看是否 未索引到词项(Analyzer),再看 IDF/TF |
BM25 不能保证语义等价(「汽车」vs「车辆」),这是 向量路或 LTR 存在的理由,不是 BM25 实现缺陷;反之,向量路不能保证 必须出现某 token 的合规检索------RAG 生产链路应默认 双路召回 + 显式融合,而非单路替代。
七、小结
三句话小结
- BM25 用 饱和 TF + avgdl 长度归一 + 平滑 IDF 解决线性 TF 与长文偏置,公式以 Robertson & Zaragoza (2009) 为准,Lucene 在
BM25Similarity中落到 postings/norms/段统计。 - norms 索引期写入、avgdl 查询期聚合------改 Similarity 往往意味着 reindex,不能只当查询侧开关。
- 生产里 BM25 仍是 召回与可解释基线;LTR/LLM 精排在候选集之上工作,不能替代 postings 相交与截断层。
下一篇看写入路径如何把新文档变成可搜段:IndexWriter 与 NRT。
参考资料
论文 / 教程
- Robertson, S. & Zaragoza, H., The Probabilistic Relevance Framework: BM25 and Beyond , Foundations and Trends in Information Retrieval, 2009(BM25 公式与 PRF 谱系)。
- Robertson, S. & Walker, S., Some Simple Effective Approximations to the 2-Poisson Model for Probabilistic Weighted Retrieval , SIGIR 1994(Okapi/BM25 源头之一)。
- Burges, C. J. C., From RankNet to LambdaRank to LambdaMART: An Overview , Microsoft Technical Report 2010(LTR 工程特征常含 BM25 分)。
规范 / 源码
- Apache Lucene 9.x/10.x Javadoc:
org.apache.lucene.search.similarities.BM25Similarity、ClassicSimilarity、Similarity。 - Elasticsearch 8.x Reference:Mapping parameters ---
similarity、norms;Explain API。
站内
返回 系列目录 | 上一篇:Postings codec | 下一篇:IndexWriter NRT