BM25 与 Lucene Similarity:公式如何落到 postings

原文发布于 quant67.com,转载请保留出处。

Postings 与 codec 把词项如何编码成可跳跃的 docID/freq 列表讲清了;查询侧还要回答:两个文档都包含关键词,凭什么 A 排在 B 前面? 这不是 UI 偏好,而是 Similarity 在 postings 上为每个 (query term, docID) 算出的分数如何累加。

本文是「全文检索引擎」系列第 6 篇。目标不是复述信息检索教材,而是钉住三条链:

  1. Robertson & Zaragoza (2009) 的 BM25 公式与参数 k1 k_1 k1、 bb b 各自抑制什么失效模式;
  2. Lucene BM25Similarity 如何把 avgdl、norms、IDF 接到 第 5 篇 的 postings 访问路径上;
  3. BM25 作为可解释基线,与学习排序(LTR)在生产里分工在哪里。

版本锚定 :Apache Lucene 9.x / 10.xorg.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 公式与参数语义

对查询 QQ Q、文档 DD D,设 f(q,D)f(q,D) f(q,D) 为词项 qq q 在 DD D 中的词频, ∣D∣|D| ∣D∣ 为文档长度(通常用该字段总词数), avgdl\mathrm{avgdl} avgdl 为索引内同字段平均文档长度, NN N 为文档总数, nq n_q nq 为包含 qq q 的文档数。Robertson & Zaragoza (2009) 给出的 BM25 项级打分可写为:
score(q,D)=IDF(q)⋅ f(q,D) (k1+1)f(q,D)+k1⋅(1−b+b⋅ ∣D∣avgdl ) \mathrm{score}(q, D) = \mathrm{IDF}(q) \cdot \frac{f(q,D)\,(k_1 + 1)}{f(q,D) + k_1 \cdot \left(1 - b + b \cdot \dfrac{|D|}{\mathrm{avgdl}}\right)} score(q,D)=IDF(q)⋅f(q,D)+k1⋅(1−b+b⋅avgdl∣D∣)f(q,D)(k1+1)

查询总分对 QQ Q 中各 qq q 求和(Lucene 默认对多词查询按词项分数相加,具体组合由 BooleanQuery 与协调因子决定,见 第 9 篇)。

IDF 在 Lucene BM25Similarity 中使用 Robertson-Spark Jones 风格平滑:
IDF(q)=ln⁡ (1+ N−nq+0.5 nq+0.5 ) \mathrm{IDF}(q) = \ln\left(1 + \frac{N - n_q + 0.5}{n_q + 0.5}\right) IDF(q)=ln(1+nq+0.5N−nq+0.5)

(自然对数;实现里对负 IDF 截断为 0。)

| 符号 / 参数 | 典型值(Lucene 默认) | 抑制什么 |
|--------------------------------------------------------------------------------------|--------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|---|----------------------|
| k1 k_1 k1 | 1.2 | TF 饱和速度:越大,高频词越晚「封顶」 |
| bb b | 0.75 | 长度归一强度: b=0b=0 b=0 忽略长度; b=1b=1 b=1 完全按 | D | /\\mathrm{avgdl} 惩罚 |
| avgdl\mathrm{avgdl} avgdl | 索引统计 | 把绝对长度变成相对平均长度的比值 |
| IDF\mathrm{IDF} IDF | 由 N,nq N, n_q N,nq 得 | 常见词降权、稀有词升权,但带平滑避免 nq=0 n_q=0 nq=0 爆炸 |

2.1 TF 饱和的曲线形状

固定 k1=1.2 k_1=1.2 k1=1.2、长度因子为 1 时,TF 贡献 f(k1+1) f+k1 \dfrac{f(k_1+1)}{f+k_1} f+k1f(k1+1) 随 ff f 单调增但渐近上限为 k1+1 k_1+1 k1+1 。这与 TF-IDF 里 TF 未饱和(或仅 tf \sqrt{\mathrm{tf}} tf 等弱饱和)形成鲜明对比------也是「长文堆砌关键词」在 BM25 下更难刷分的根因。

flowchart LR subgraph tf [&#34;TF leg&#34;] f[&#34;freq from postings&#34;] sat[&#34;saturating fn<br/>k1=1.2&#34;] f --> sat end subgraph len [&#34;Length leg&#34;] dl[&#34;doc length |D|&#34;] norm[&#34;norms byte&#34;] avg[&#34;avgdl stats&#34;] dl --> norm avg --> sat norm --> sat end subgraph idf [&#34;IDF leg&#34;] N[&#34;total docs N&#34;] nq[&#34;doc freq nq&#34;] idf[&#34;ln smoothed IDF&#34;] N --> idf nq --> idf end sat --> sum[&#34;term score&#34;] idf --> sum

三、Lucene 落点:BM25Similarity、norms 与 avgdl

3.1 索引期:norms 编码字段长度

Lucene 在索引带 IndexOptions 的文本字段时,默认写入 norms (每文档每字段一字节),编码该字段长度信息供打分使用。BM25SimilaritycomputeNorm 中把 token 计数映射为可被 $b$ 使用的长度因子------查询期不再扫描全文数词 ,只需读 norms 与 postings 里的 freq

这与 第 2 篇 的字段正交开关一致:omitNorms=true 会去掉长度归一(所有文档长度因子视为 1),适合长度分布极均匀或刻意不做长度惩罚的场景。

3.2 查询期:CollectionStatistics 与 avgdl

打开 IndexSearcher 时,BM25SimilarityCollectionStatistics 读取 NN N、总词数等,计算:
avgdl=sumTotalTermFreqdocCount \mathrm{avgdl} = \frac{\text{sumTotalTermFreq}}{\text{docCount}} avgdl=docCountsumTotalTermFreq

(对应该字段;多段索引时由 reader 聚合段统计。)每个 (term, doc) 的打分在 Scorer 里按需计算:从 postings 取 freq,从 norms 解码长度,再套上一节的公式。

3.3 与 explain 对齐

Elasticsearch _explain 与 Lucene Explanation 会展开 idftffieldNorm 等子项------排障时应对照公式逐项核对,而不是只看最终 _score。本系列不在此粘贴未在本机执行的 explain 输出;读者可在单节点用官方文档中的 explain API 自行核对。

组件 索引期 查询期
postings freq 写入 读取 f(q,D)f(q,D) f(q,D)
norms 编码 ∥D∥\|D\| ∥D∥ 解码长度因子
词典 docFreq 写入 nq n_q nq 计算 IDF
段/索引统计 --- NN N、 avgdl\mathrm{avgdl} avgdl

四、与 TF-IDF 的工程差异

Lucene 仍提供 ClassicSimilarity(传统 TF-IDF 风格)供显式切换;Elasticsearch mapping 里也可设 similarity: boolean 等。对照 BM25,工程上应记住:

维度 Classic / TF-IDF 路径 BM25 默认路径
TF tf \sqrt{\mathrm{tf}} tf 或未饱和线性 显式饱和,上限由 k1 k_1 k1 控制
长度 lengthNorm 乘子 bb b 与 avgdl\mathrm{avgdl} avgdl 进入 TF 分母
稀有词 IDF 同族 IDF 平滑,但 TF 腿不同
默认 历史默认 Lucene 6+ / ES 现代默认

争论(有文献支撑) :BM25 在 TREC 类 ad-hoc 检索上长期作为强基线(Robertson & Zaragoza 2009 综述);但在域内排序 (电商、招聘)上,手工特征 + 学习排序常能压过纯 BM25------前提是标注数据、特征管线与 serving 成本可承受。BM25 的价值在于:零训练、可解释、与 postings 紧密耦合、延迟可预测

4.1 常见误解

  1. 「BM25 的 IDF 就是 ln⁡(N/nq)\ln(N/n_q) ln(N/nq)」 。Lucene 实现用 (N−nq+0.5)/(nq+0.5) (N-n_q+0.5)/(n_q+0.5) (N−nq+0.5)/(nq+0.5) 平滑;与教科书简化式数值不同,explain 里应对 implementation 而非背错公式。
  2. 「调大 k1 k_1 k1 一定提升召回」 k1 k_1 k1 只改 TF 饱和形状;极端大 k1 k_1 k1 接近线性 TF,重新引入堆砌关键词偏置。
  3. 「similarity 只影响排序,不影响倒排」 。Similarity 不改 postings 编码,但 norms 在索引时写入 ;改 bb b 或换 Similarity 类通常需 reindex 才能一致生效(除非仅 IDF 腿且 norms 未变)。

五、与学习排序的工程边界

学习排序(Learning to Rank, LTR)把相关性变成监督学习:LambdaMART、RankNet 等在特征向量上训练,再在生产对候选文档重排。代表工作包括 Burges (2010) 对 LTR 的综述,以及后续将 BM25 分数、字段匹配、点击信号等作为特征的工业实践。

flowchart TB q[&#34;User query&#34;] recall[&#34;Recall layer<br/>BM25 / Boolean / kNN&#34;] cand[&#34;Candidate docIDs<br/>top N per shard&#34;] feat[&#34;Feature extraction<br/>BM25 score, boosts, DV&#34;] ltr[&#34;LTR model optional&#34;] out[&#34;Final ranking&#34;] q --> recall --> cand --> feat feat --> out feat --> ltr --> out

工程分界(本系列立场,非学术终局判断):

典型实现 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。

开放问题

  1. 稠密检索 + BM25 融合 的统一打分函数尚无社区共识(见本系列 第 15 篇)。
  2. 跨语言 / 多字段 avgdl\mathrm{avgdl} avgdl 与 bb b 是否应字段级定制,仍依赖域内评测而非单一全局默认。
  3. LLM 重排 成本与延迟下,BM25 粗排窗口该多大,缺少与硬件无关的普适公式。

5.1 Elasticsearch 侧如何改 BM25

Elasticsearch mapping 允许字段级 similarity

json 复制代码
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "similarity": "BM25",
        "index_options": "positions"
      }
    }
  }
}

BM25 类型可配 k1b(8.x Reference Similarity )。修改后 仅影响后续索引 的 norms 与统计;已有段仍按旧参数写入的 norms 解释。全索引统一需 reindex 或 rollover 新索引------这与「只改查询 DSL」完全不同。

bool 查询里的 boost 乘在 Lucene 子句 Weight 上,不改变 IDF 公式本身function_score 可把 BM25 分与 DocValues 字段组合(见 第 10 篇),已超出纯 Similarity 范畴。


六、调参与失效模式(无 benchmark 结论)

下列判断来自机制推导与官方文档,非本机吞吐排名

  • 新闻 / 日志 :文档长度方差大,保持默认 b=0.75b=0.75 b=0.75 通常合理;极短字段(标题)可单独字段并视情况 omitNorms 或降低 bb b。
  • 词典字段(SKU、ISBN):长度几乎恒定,norms 收益低;更应依赖精确词项与 boost。
  • 多语言混合索引 avgdl\mathrm{avgdl} avgdl 对「中文长文 + 英文摘要」同字段混合时会失真------优先 分字段 而非只调 k1 k_1 k1。

6.1 与向量引擎 / RAG 的分工

llm-infra 第 17 篇 在应用层组合 BM25 与向量召回;向量检索引擎系列 专讲 ANN 索引与 Segment 生命周期。本系列第 6 篇只钉 BM25 在 Lucene 内的落点,三者分工如下:

flowchart LR q[&#34;Query&#34;] --> sparse[&#34;BM25 on postings<br/>this series&#34;] q --> dense[&#34;kNN on dense_vector<br/>vector-engine&#34;] sparse --> fuse[&#34;RRF / rescore / LTR<br/>llm-infra 17&#34;] dense --> fuse fuse --> ctx[&#34;Context to LLM&#34;]
环节 BM25(引擎层) 向量 ANN RAG 应用层
召回依据 词项共现、IDF embedding 几何近邻 chunk 边界、metadata 过滤
必用场景 SKU、法规号、日志 keyword paraphrase、跨语言语义 prompt 窗口、citation
调试入口 _explain、Analyzer efSearch、索引度量 融合权重、rerank 延迟
典型漏召回 分词未产出 query term 域外向量、错误 embedding 模型 chunk 切断实体

引擎侧应明确:

环节 BM25 作用 常见坑
chunk 索引 按 chunk 字段算 avgdl\mathrm{avgdl} avgdl 父文档过长导致 chunk norms 失真
hybrid BM25 与 kNN 分数量纲不同 rank_window_size / RRF 等融合,非直接相加(见 第 15 篇
explain 调试漏召回 先看是否 未索引到词项(Analyzer),再看 IDF/TF

BM25 不能保证语义等价(「汽车」vs「车辆」),这是 向量路或 LTR 存在的理由,不是 BM25 实现缺陷;反之,向量路不能保证 必须出现某 token 的合规检索------RAG 生产链路应默认 双路召回 + 显式融合,而非单路替代。


七、小结

三句话小结

  1. BM25 用 饱和 TF + avgdl\mathrm{avgdl} avgdl 长度归一 + 平滑 IDF 解决线性 TF 与长文偏置,公式以 Robertson & Zaragoza (2009) 为准,Lucene 在 BM25Similarity 中落到 postings/norms/段统计。
  2. norms 索引期写入、avgdl 查询期聚合------改 Similarity 往往意味着 reindex,不能只当查询侧开关。
  3. 生产里 BM25 仍是 召回与可解释基线;LTR/LLM 精排在候选集之上工作,不能替代 postings 相交与截断层。

下一篇看写入路径如何把新文档变成可搜段:IndexWriter 与 NRT


参考资料

论文 / 教程

  1. Robertson, S. & Zaragoza, H., The Probabilistic Relevance Framework: BM25 and Beyond , Foundations and Trends in Information Retrieval, 2009(BM25 公式与 PRF 谱系)。
  2. Robertson, S. & Walker, S., Some Simple Effective Approximations to the 2-Poisson Model for Probabilistic Weighted Retrieval , SIGIR 1994(Okapi/BM25 源头之一)。
  3. Burges, C. J. C., From RankNet to LambdaRank to LambdaMART: An Overview , Microsoft Technical Report 2010(LTR 工程特征常含 BM25 分)。

规范 / 源码

  1. Apache Lucene 9.x/10.x Javadoc:org.apache.lucene.search.similarities.BM25SimilarityClassicSimilaritySimilarity
  2. Elasticsearch 8.x Reference:Mapping parameters --- similaritynormsExplain API

站内

  1. Postings codec混合检索查询执行系列目录
  2. 向量检索引擎RAG 工程化(应用层融合分工)。

返回 系列目录 | 上一篇:Postings codec | 下一篇:IndexWriter NRT

相关推荐
ltl42 分钟前
RocksDB Block Cache 与 Bloom Filter:读放大怎么裁
数据库
deli0070072 小时前
JSON 树形查看器:粘贴即解析,折叠展开一目了然
前端·数据库·json·ai编程
国科安芯2 小时前
轨道供电韧性:ASP4644四通道降压架构在低轨卫星平台电源管理中的适用性分析
运维·网络·数据库·嵌入式硬件·架构·状态模式·低轨卫星星座
破土士V3 小时前
MySQL基础知识集合
数据库·mysql
笃行3503 小时前
达梦 VS 金仓:数据库替换项目里,迁移工具的差距体现在哪一层
数据库
笃行3503 小时前
向量数据库之后,一份合同为什么在五个库里对不上
数据库
笃行3503 小时前
数据库迁移工具装在一台笔记本上的年代,该翻篇了
数据库
笃行3503 小时前
一张表底下几千个分片:金仓时序数据库的超表怎么工作
数据库
whcyhhh4 小时前
头歌实践教学平台:数据科学与大数据技术导论(七上)
大数据·数据库·python