Skip to content

BM25

五层读懂一个词。这次拆的是:BM25--关键词检索的事实工业标准,Elasticsearch / Lucene / OpenSearch 默认打分。


L1 · 一句话点破

BM25 是 TF-IDF 的概率论升级版:词频饱和(不无限增长)+ 文档长度归一化(长文档不再占便宜)+ 概率推导的 IDF。

三个改进合起来,让 1994 年提出的 BM25 至今仍是稀疏检索的默认打分函数。Lucene 6+、Elasticsearch、OpenSearch、Solr 全部默认 BM25。


L2 · 通俗类比

TF-IDF 的两个老毛病,BM25 都治了。

病 1:词频无上限。TF-IDF 里一个词出现 1000 次的权重是 10 次的 100 倍。但现实里,"苹果"出现 10 次已经说明这篇文档在讲苹果了,1000 次和 100 次的相关性差距根本没那么大--边际收益递减。

BM25 的药方:饱和函数。词频涨到一定程度就涨不动了,逼近一个上限。像充电池,前期涨得快,后期涨得慢,最后充满就停了。

病 2:长文档天然占便宜。一篇 10000 字的文档,"AI"出现 50 次;一篇 100 字的文档,"AI"出现 5 次。TF-IDF 算下来长文档远高,但相对密度长文档其实更低。除以文档长度只是粗略补救。

BM25 的药方:长度归一化参数 $b$。文档比平均长多少,就按比例压多少权重,且可调。

病 3:IDF 的概率意义不清。TF-IDF 的 IDF 是经验公式,BM25 从"二值独立检索模型(BIR)"的概率推导出 IDF,理论根基更扎实。

一句话:BM25 不是新发明,是把 TF-IDF 的经验直觉用概率论重新推导一遍,顺手修了两个毛病。


L3 · 正经定义

BM25 (Best Matching 25):对查询 $q$ 和文档 $d$,分数为

$$ \text{BM25}(q, d) = \sum_{t \in q} \text{IDF}(t) \cdot \frac{f(t, d) \cdot (k_1 + 1)}{f(t, d) + k_1 \cdot (1 - b + b \cdot \frac{|d|}{\text{avgdl}}))} $$

参数:

  • $f(t, d)$:词项 $t$ 在文档 $d$ 中的原始频次
  • $|d|$:文档 $d$ 的长度(词数)
  • $\text{avgdl}$:语料库平均文档长度
  • $k_1$:词频饱和参数,典型 $1.2 \sim 2.0$,Lucene 默认 1.2
  • $b$:长度归一化参数,$0 \le b \le 1$,Lucene 默认 0.75
  • $\text{IDF}(t)$:BM25 的 IDF,来自概率检索模型:

$$ \text{IDF}_{\text{BM25}}(t) = \log \frac{N - \text{df}(t) + 0.5}{\text{df}(t) + 0.5} + 1 $$

(+1 是 Lucene 改造,避免负值;理论原版可能为负,工程上常用 $\log(1 + \frac{N - \text{df} + 0.5}{\text{df} + 0.5})$ 避负)

伪代码

python
import math

def bm25_score(query, doc, df_map, N, avgdl, k1=1.2, b=0.75):
    """query, doc: 词列表"""
    tf = Counter(doc)
    doc_len = len(doc)
    score = 0.0
    for t in query:
        if t not in tf:
            continue
        f = tf[t]
        df = df_map.get(t, 0)
        idf = math.log((N - df + 0.5) / (df + 0.5) + 1)  # Lucene 变体
        # 词频饱和 + 长度归一化
        tf_norm = (f * (k1 + 1)) / (f + k1 * (1 - b + b * doc_len / avgdl))
        score += idf * tf_norm
    return score

关键性质

  1. $f \to \infty$ 时,$\frac{f(k_1+1)}{f + k_1(\dots)} \to k_1 + 1$(饱和到上限)
  2. $b = 0$:完全不做长度归一化,回到原始 TF
  3. $b = 1$:完全按文档长度反比缩放
  4. $k_1 = 0$:TF 项变成常数 1,BM25 退化为纯 IDF 求和

L4 · 原理深挖

4.1 概率检索模型:BM25 从哪推出

BM25 属于 Robertson-Spärck Jones 二值独立检索模型(BIR) 的延伸。核心假设:

给定查询 $q$ 和文档 $d$,定义相关性 $R \in {0, 1}$。词项 $t$ 出现在相关文档的概率 $P(t \mid R=1) = p_t$,出现在不相关文档的概率 $P(t \mid R=0) = u_t$。

由贝叶斯和 odds 比推导,每个查询词项 $t$ 对相关性的对数贡献为:

$$ \log \frac{p_t (1 - u_t)}{u_t (1 - p_t)} $$

用 $\text{df}(t)/N$ 估计 $u_t$,假设 $p_t = 0.5$(相关文档中词出现的概率均匀),化简得到:

$$ \log \frac{0.5}{\text{df}(t)/N} + \log \frac{1 - \text{df}(t)/N}{0.5} = \log \frac{N - \text{df}(t) + 0.5}{\text{df}(t) + 0.5} $$

这就是 BM25 IDF 的来历--不是拍脑袋,是从概率检索模型推出来的。$+0.5$ 是 $p_t, u_t$ 的贝叶斯平滑(先验)。

4.2 词频饱和的 $k_1$ 参数

TF-IDF 用 $1 + \log f$ 压词频,BM25 用双曲饱和函数

$$ \text{TF}_{\text{BM25}}(f) = \frac{f(k_1+1)}{f + k_1} $$

(先忽略长度归一化项)

直观:

$f$$k_1=1.2$ 时 TF$k_1=2.0$ 时 TF
000
11.091.50
51.933.21
102.104.29
502.214.81
$\infty$2.20 (= $k_1+1$)5.00 (= $k_1+1$)

无论词频多大,TF 项最多涨到 $k_1 + 1$,不会无限增长。

$k_1$ 调参经验

  • $k_1 = 0$:忽略 TF,只用 IDF(适用于短查询、关键词命中即相关)
  • $k_1 = 1.2$(默认):通用场景,饱和较早
  • $k_1 = 2.0$:饱和较慢,更看重词频差异(适用于长文档、专业语料)
  • $k_1$ 越大,TF 项越线性(饱和越慢)

工程经验:多数场景 $k_1 \in [1.0, 2.0]$,调它影响不大;$b$ 影响更大。

4.3 长度归一化的 $b$ 参数

完整 TF 项:

$$ \text{TF}_{\text{BM25}}(f, |d|) = \frac{f(k_1+1)}{f + k_1\left(1 - b + b \cdot \frac{|d|}{\text{avgdl}}\right)} $$

分母里的 $(1 - b + b \cdot |d|/\text{avgdl})$ 是长度归一化因子:

  • $b = 0$:因子恒为 1,完全不归一化。长文档天然 TF 大,BM25 退化为饱和版 TF-IDF。
  • $b = 1$:因子 $= |d|/\text{avgdl}$,完全按长度反比缩放。长文档被压得最狠。
  • $b = 0.75$(默认):折中,长文档被压但不到完全反比。

$b$ 调参经验

  • 短文档语料(推文、标题):$b$ 调小($0.3 \sim 0.5$),因为短文档长度差异多源于噪声,不应惩罚。
  • 长文档语料(论文、网页全文):$b$ 保持 $0.75$ 甚至调高,长文档确实稀释了关键词。
  • 全部同长(如截断到固定长度的片段):$b$ 不起作用,可设 0。

4.4 IDF 的负值问题

理论版 IDF:

$$ \text{IDF}(t) = \log \frac{N - \text{df}(t) + 0.5}{\text{df}(t) + 0.5} $$

当 $\text{df}(t) > N/2$(超过一半文档包含 $t$),分子小于分母,IDF 为负。这意味着"普遍的词反而惩罚分数"--理论上有道理(普遍词更可能在不相关文档出现),但工程上有问题:

  1. 负分数会拉低整篇文档总分,导致一篇其实相关的文档因为含几个常见词被排到后面。
  2. 完全匹配查询的文档可能因某个高频词得负分,反直觉。

不同实现的应对:

实现处理方式
Lucene / Elasticsearch$\log(1 + \frac{N - \text{df} + 0.5}{\text{df} + 0.5})$,恒正
原版 BM25 论文允许负值,截断到 0
_rank_bm25 (Python 库)默认 Lucene 变体

工程经验:用 Lucene 变体(恒正),几乎没人想看到负分数。

4.5 BM25 的变体家族

BM25 是一个家族,原版 BM25 是最常用,但有诸多变体:

  • BM25F:字段加权 BM25。文档有多个字段(标题、正文、标签),每个字段分别算 TF,再加权求和。Elasticsearch 用 multi_match + 字段 boost 实现。
  • BM25+(Lv & Zhai 2011):解决 $f=0$ 时 BM25 完全不贡献的问题(即使词在文档不出现,理论上也应有微小负贡献)。加常数 $\delta$: $$ \text{TF}_{\text{BM25+}} = \frac{f(k_1+1)}{f + k_1} + \delta $$
  • BM25L(Lv & Zhai 2011):修正 BM25 在短文档上的偏置,用 $\frac{f + k_1}{1 - b + b \cdot |d|/\text{avgdl}}$ 重新缩放。
  • BM25-Adpt:自适应调 $k_1$,按语料统计自动选最优值。

实际生产中,多数场景就用原版 BM25 + 手调 $k_1, b$,变体用得少。

4.6 BM25 vs 现代 Embedding 检索

维度BM25Dense Retrieval
匹配方式字面(词形一致)语义(向量近邻)
训练不需要需要训练数据
资源CPU 即可,毫秒级需 GPU 推理编码
精确匹配强(专有名词、ID、代码)中(可能漏精确词)
语义召回
长尾词强(只要索引中有)弱(训练时没见过)
可解释性强(分数可拆解到词项)弱(黑盒)
索引大小紧凑(倒排索引)大(每文档一个稠密向量)

工程结论:两者互补。现代 RAG 系统标配混合检索(hybrid search),BM25 召回精确匹配 + Dense 召回语义匹配,再用 RRF 或加权融合合并。详见后续 hybrid-search 词条。

4.7 BM25 在工程实现中的优化

优化 1:词频预编码。Lucene 把 $\text{norm}(t, d) = 1/\sqrt{|d|/b \cdot \text{avgdl}}$ 编码到一个字节(8-bit 量化),存索引里,检索时直接查表,不用实时算。代价:损失精度,但实测影响不大。

优化 2:跳表(skip list)。倒排索引的 posting list 排序后,加跳表加速 AND 查询的合并。Lucene 的 postings 默认带 skip list。

优化 3:WAND / MaxScore 剪枝。检索时按 IDF 排序词项,对每个候选文档算上界分数,低于当前 top-k 最小分数的直接跳过。这是 Lucene / Elasticsearch 在大规模语料上能秒级返回的关键。

优化 4:分片并行。Elasticsearch 把索引分到多 shard,每个 shard 独立打分,最后 merge top-k。线性扩展。


L5 · 沿革与坑

5.1 历史脉络

  • 1960s–1970s:Maron-Kuhns(1960)、Robertson-Spärck Jones(1976)奠定概率检索模型基础。
  • 1980s:Robertson 等在伦敦城市大学研发 Okapi 检索系统,BM 系列在 TREC 评测中表现优异。
  • 1994:Robertson & Walker 在 TREC-3 正式提出 BM25,作为 Okapi 系统的打分函数。
  • 1990s–2000s:BM25 在 TREC、CLEF 等评测中持续是强基线,击败多数学习排序方法。
  • 2010:Trotman 等综述论文 "Efficiency and Effectiveness of BM25" 确认 BM25 工业地位。
  • 2016:Lucene 6 把默认 ClassicSimilarity(TF-IDF)切到 BM25Similarity,BM25 成为 Elasticsearch / Solr 的事实默认。
  • 2020+:Dense retrieval 兴起,但 BM25 仍是稀疏检索最强基线,混合检索成为 RAG 标配。

5.2 BM25 调参的常见坑

坑 1:照搬默认参数,不验证

Lucene 默认 $k_1=1.2, b=0.75$ 是通用经验值,不一定适合你的语料。短文本(标题、推文)建议 $b \approx 0.3$;长文档(论文、网页)建议 $b \approx 0.85$。务必用评估集调参。

坑 2:忘了分词器对 BM25 的影响

中文用 jieba / IK / HanLP 分词,结果天差地别。调分词器比调 BM25 参数影响大得多。代码、专业术语、混合语料尤其要调分词器。

坑 3:字段 boost 设过头

Elasticsearch 允许 title^10 这种字段加权。设过头会让标题命中文档碾压正文命中,反而漏召回。^2 ~ ^5 是常见区间,先用小值评估。

坑 4:BM25 跨 shard 不公平

Elasticsearch 默认每 shard 独立打分,shard 间的文档长度分布、IDF 统计可能不一致,导致同样文档在不同 shard 分数不同。解决:用 ?search_type=dfs_query_then_fetch 让 IDF 全局统计。代价:慢一点。

坑 5:拿 BM25 当语义检索用

"我要买手机"和"想购入智能电话"在 BM25 下完全 0 分(无共同词)。语义检索必须用 Dense Retrieval。BM25 只解决字面匹配,不要让它做不擅长的事。

坑 6:分词后未去掉停用词,IDF 失真

"的、是、和"这种词 df 几乎等于 N,IDF 趋近 0,理论上不贡献分数。但有些实现没显式停用词,依赖 IDF 自然压低--这没问题但浪费算力。建议显式去停用词,索引更小、查询更快。

坑 7:高频词的负 IDF 拉低总分

用原版 BM25(非 Lucene 变体)时,查询含高频词会拉低分数。要么用 Lucene 恒正变体,要么把 IDF 截断到 0,要么把高频词当停用词处理。

坑 8:BM25 分数没归一化,跨查询不可比

BM25 分数绝对值受查询长度、文档长度分布影响,不同查询的分数没有可比性。如果要跨查询比分数(如设阈值过滤),需先归一化(如除以最大可能分数)或用 z-score。

5.3 BM25 vs TF-IDF 关键差异

改进点TF-IDFBM25
TF 缩放对数 $1 + \log f$ 或开方 $\sqrt{f}$双曲饱和 $\frac{f(k_1+1)}{f + k_1(\dots)}$
长度归一化简单除以长度可调 $b$ 参数
IDF经验 $\log(N/\text{df})$概率 $\log\frac{N-\text{df}+0.5}{\text{df}+0.5}$
理论根基启发式概率检索模型
参数$k_1, b$ 可调
工业默认已退出Lucene 6+/ES/Solr/OpenSearch

核心改进:BM25 把 TF-IDF 的经验公式用概率论重写一遍,并加入可调参数,让经验直觉变成可证伪的概率模型。

5.4 何时仍该用 BM25

哪怕 Dense Retrieval 兴起,BM25 仍是这些场景的最优选择:

  1. 专有名词 / 代码 / ID 检索:精确匹配场景 Dense 反而漏召回
  2. 极低延迟:CPU 即可毫秒级,Dense 需 GPU
  3. 小语料:训练 Dense 不值,BM25 直接可用
  4. 长尾词:训练时没见过的词 Dense 容易失效
  5. 可解释性场景:法律、医疗需分数可追溯
  6. 混合检索的稀疏分支:现代 RAG 标配

工程结论:Dense Retrieval 没有取代 BM25,而是把 BM25 从"唯一答案"变成"必备组件之一"


速记卡

维度BM25
公式$\sum_t \text{IDF}(t) \cdot \frac{f(t,d)(k_1+1)}{f(t,d) + k_1(1 - b + b \cdot
IDF$\log \frac{N - \text{df}(t) + 0.5}{\text{df}(t) + 0.5}$(概率推导)
关键参数$k_1$(饱和),$b$(长度归一化)
默认值$k_1=1.2, b=0.75$(Lucene)
关键改进TF 饱和 + 长度归一化 + 概率 IDF
接班者Dense Retrieval(语义)+ Hybrid(混合)
工业地位Lucene / ES / Solr / OpenSearch 默认

一句话记忆:BM25 = 概率推导的 IDF × 词频饱和函数 × 可调长度归一化。把 TF-IDF 的经验直觉用概率论重新推导,顺手修了词频无界和长文档偏置两个毛病。


上一篇:TF-IDF -- 经典的词项权重方案,BM25 的思想前身。下一篇:KNN / ANN -- 从精确最近邻到近似最近邻,向量检索的基础。

内容采用 CC BY-SA 4.0,代码采用 MIT。