土法炼钢兴趣小组的算法知识备份

【全文检索引擎】BM25 与 Similarity:公式如何落到 Lucene

文章导航

分类入口
databasestorage
标签入口
#lucene#bm25#similarity#tf-idf#norms#ranking#elasticsearch#full-text-search

目录

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

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

  1. Robertson & Zaragoza (2009) 的 BM25 公式与参数 \(k_1\)\(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 语料上很快失效:

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|\) 为文档长度(通常用该字段总词数),\(\mathrm{avgdl}\) 为索引内同字段平均文档长度,\(N\) 为文档总数,\(n_q\) 为包含 \(q\) 的文档数。Robertson & Zaragoza (2009) 给出的 BM25 项级打分可写为:

\[ \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)} \]

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

IDF 在 Lucene BM25Similarity 中使用 Robertson-Spark Jones 风格平滑:

\[ \mathrm{IDF}(q) = \ln\left(1 + \frac{N - n_q + 0.5}{n_q + 0.5}\right) \]

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

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

2.1 TF 饱和的曲线形状

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

flowchart LR
  subgraph tf ["TF leg"]
    f["freq from postings"]
    sat["saturating fn<br/>k1=1.2"]
    f --> sat
  end
  subgraph len ["Length leg"]
    dl["doc length |D|"]
    norm["norms byte"]
    avg["avgdl stats"]
    dl --> norm
    avg --> sat
    norm --> sat
  end
  subgraph idf ["IDF leg"]
    N["total docs N"]
    nq["doc freq nq"]
    idf["ln smoothed IDF"]
    N --> idf
    nq --> idf
  end
  sat --> sum["term score"]
  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 读取 \(N\)、总词数等,计算:

\[ \mathrm{avgdl} = \frac{\text{sumTotalTermFreq}}{\text{docCount}} \]

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

3.3 与 explain 对齐

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

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

四、与 TF-IDF 的工程差异

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

维度 Classic / TF-IDF 路径 BM25 默认路径
TF \(\sqrt{\mathrm{tf}}\) 或未饱和线性 显式饱和,上限由 \(k_1\) 控制
长度 lengthNorm 乘子 \(b\)\(\mathrm{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/n_q)\)。Lucene 实现用 \((N-n_q+0.5)/(n_q+0.5)\) 平滑;与教科书简化式数值不同,explain 里应对 implementation 而非背错公式。
  2. 「调大 \(k_1\) 一定提升召回」\(k_1\) 只改 TF 饱和形状;极端大 \(k_1\) 接近线性 TF,重新引入堆砌关键词偏置。
  3. 「similarity 只影响排序,不影响倒排」。Similarity 不改 postings 编码,但 norms 在索引时写入;改 \(b\) 或换 Similarity 类通常需 reindex 才能一致生效(除非仅 IDF 腿且 norms 未变)。

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

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

flowchart TB
  q["User query"]
  recall["Recall layer<br/>BM25 / Boolean / kNN"]
  cand["Candidate docIDs<br/>top N per shard"]
  feat["Feature extraction<br/>BM25 score, boosts, DV"]
  ltr["LTR model optional"]
  out["Final ranking"]
  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. 跨语言 / 多字段\(\mathrm{avgdl}\)\(b\) 是否应字段级定制,仍依赖域内评测而非单一全局默认。
  3. LLM 重排 成本与延迟下,BM25 粗排窗口该多大,缺少与硬件无关的普适公式。

5.1 Elasticsearch 侧如何改 BM25

Elasticsearch mapping 允许字段级 similarity

{
  "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 结论)

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

6.1 与 RAG 稀疏召回的衔接

llm-infra 第 17 篇 在应用层组合 BM25 与向量召回。引擎侧应明确:

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

BM25 不能保证语义等价(「汽车」vs「车辆」),这是 学习排序或向量路 存在的理由,不是 BM25 实现缺陷。


七、小结

三句话小结

  1. BM25 用 饱和 TF + \(\mathrm{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 parameterssimilaritynormsExplain API

站内

  1. Postings codec查询执行系列目录

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

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-07-15 · database / storage

【全文检索引擎】混合检索边界:BM25 与 dense_vector

只谈 Elasticsearch 8.x / Lucene 上 BM25 与 kNN 的组合接口与段内共存代价,ANN 算法与专用向量引擎细节外链 vector-engine 与 db-frontier/08;指出倒排与向量同 Segment 生命周期尚未统一的开放问题。

2026-07-15 · database / storage

【全文检索引擎】Lucene · BM25 · Segment · Elasticsearch NRT

补齐搜索架构叙事与 RAG/向量引擎之间的全文检索内核层:以 Lucene 9.x/10.x 拆解 Analyzer、FST、postings、BM25 与 IndexWriter,并以 Elasticsearch 8.x 拆解分片、refresh/translog 与查询路径;OpenSearch 对照与选型收束。


By .