文章

BM25信息检索算法简介

BM25信息检索算法简介

BM25信息检索算法简介

BM25(Best Matching 25)是信息检索领域最经典的关键词排序算法之一,Elasticsearch 默认排序即采用 BM25。在 RAG 场景中,它常与向量检索组合做混合检索,弥补语义检索在专有名词、条款编号等字面匹配上的不足。本文介绍 BM25 的定位、相关性计算原理、流程示例及在 RAG 中的典型用法。


目录


1. BM25 是什么

BM25 全称 Best Matching 25,属于 Okapi BM25 算法家族,是经典 信息检索(Information Retrieval, IR) 中的 排序公式(scoring function)

1.1 名称由来:BM 与 25

名称拆解:

部分含义
BM(Best Matching)上世纪 80~90 年代,伦敦 City University 的 Okapi 信息检索项目中,Robertson、Walker 等人提出的一系列概率检索排序函数,统称 Best Match 系列
25该系列中的第 25 号 / 第 25 版方案。团队在 Okapi 上做了多轮实验(BM1、BM2、…),BM25 是其中效果较好、后来成为事实标准的一版,编号沿用至今

需要澄清的常见误解:

误解实际
公式里有个参数要设为 25没有。可调超参数是 k1b
「25」与 TF-IDF 或 IDF 的某个常数有关无关,纯属历史命名
必须按 25 配置才能正确运行不必,使用引擎默认的 k1b 即可

因此也常写作 Okapi BM25:Okapi 是当年检索系统的项目名,BM25 是其中默认且最成功的排序组件。Elasticsearch、Lucene 等实现的即这一族公式(IDF 等细节可能略有差异)。

需要首先明确:

问题答案
BM25 是模型吗?不是。无神经网络、无训练过程
BM25 是机器学习算法吗?不是。属于写死的数学规则(启发式算法)
BM25 做什么?根据「词是否出现、出现几次、词有多稀有、文档有多长」给文档打一个相关分

可以把它理解成:一套固定的打分规则,输入是查询词与文档的统计特征,输出是一个标量分数,用于对候选文档排序。

在 RAG 链路里,BM25 通常承担 稀疏检索 / 关键词检索 角色,与 稠密检索(embedding 向量相似度) 互补,而非替代关系。


2. 与 TF-IDF、机器学习模型的区别

2.1 BM25 vs TF-IDF

BM25 可视为 TF-IDF 的改进版

维度TF-IDFBM25
词频(TF)线性增加,易被关键词堆砌刷分饱和曲线,出现 10 次与 100 次差距不大
文档长度常需额外处理内置长度归一化
工程落地教学、简单检索Elasticsearch / Lucene 默认排序

2.2 BM25 vs 机器学习检索(Embedding)

对比BM25Embedding 向量检索
是否有训练无,公式固定有,从数据学习表示
是否理解语义否,只看词面匹配是,「老跳闸」≈「频繁动作」也能召回
输入词频、文档长度、语料统计文本 → 向量
输出BM25 相关分余弦相似度等
成本CPU 即可,零 GPU需 embedding 模型推理
强项专有名词、编号、条款号同义词、口语化、语义改写

3. 相关性如何计算

3.1 核心直觉

对查询 Q 中的每个词 q,计算该词对文档 D 的贡献分,再将所有词的贡献相加,得到文档总分。

每个词 q 的贡献由三部分决定:

  1. 词频 TF(饱和)
    词在文档里出现越多,分越高,但不会无限上涨——避免长文档靠堆砌关键词刷分。

  2. 逆文档频率 IDF(稀有度)
    全库很多文档都有的词(如「的」「规定」)权重低;只有少数文档才有的词(如「继电保护」「GB/T 1234」)权重高。

  3. 文档长度归一化
    同样词频下,短文档得分更高;长文档不会因词多而天然占优。

3.2 公式

对查询中的每个词 q,文档 D 的 BM25 相关分:

\[\text{score}(D,Q) = \sum_{q \in Q} \text{IDF}(q) \cdot \frac{f(q,D) \cdot (k_1 + 1)}{f(q,D) + k_1 \cdot \left(1 - b + b \cdot \frac{|D|}{\text{avgdl}}\right)}\]

符号说明:

符号含义
f(q,D)q 在文档 D 中出现次数
\|D\|文档 D 的长度(词数)
avgdl语料库平均文档长度
k1词频饱和程度,控制「出现多次」的加分幅度(见 §4
b文档长度归一化强度,控制「长文档」的惩罚力度(见 §4
IDF(q)q 在全库的稀有程度

IDF 常见形式:

\[\text{IDF}(q) = \log \frac{N - n(q) + 0.5}{n(q) + 0.5}\]
  • N:语料总文档数
  • n(q):包含词 q 的文档数
  • q 的文档越少 → IDF 越大 → 该词越「值钱」

4. 超参数 k1 与 b

BM25 公式里有两个可调超参数k1b。它们不来自训练,而是人工设定(或由搜索引擎提供默认值)。理解二者作用,是调 BM25 排序效果的关键。

4.1 在公式中的位置

回顾单条 query 词 q 对文档 D 的 TF 贡献部分:

\[\frac{f(q,D) \cdot (k_1 + 1)}{f(q,D) + k_1 \cdot \left(1 - b + b \cdot \frac{|D|}{\text{avgdl}}\right)}\]
  • k1 出现在分子分母,控制词频 f(q,D)饱和速度——词出现 1 次 vs 5 次 vs 20 次,加分差多少。
  • b 出现在分母的长度因子里,控制文档长度 \|D\| 对得分的惩罚强度——长文档是否因「词多」而被压分。

4.2 默认值与常见范围

参数典型默认值常见调参范围说明
k11.2(Elasticsearch / Lucene)0 ~ 3.0文献中常见 1.2 ~ 2.0
b0.750 ~ 1.00 = 不做长度归一化;1 = 完全按长度比例惩罚

不同实现默认值可能略有差异;LangChain BM25Retriever、rank-bm25 等库也多为 k1=1.5, b=0.75 一类组合。优先查你所用引擎/库的文档

4.3 k1:词频饱和

直觉k1 决定「同一个词在文档里多出现几次,还能加多少分」。

调参方向效果典型现象
调高 k1词频影响变大,饱和变慢出现 3 次比 1 次明显更高分;重复出现关键词的长段落更容易排前
调低 k1词频影响变小,饱和变快出现 1 次和 3 次差距不大;更依赖 IDF(词是否稀有),不易被「堆砌关键词」刷分
k1 → 0词频项几乎失效只要命中该词,次数多少贡献接近;排序主要看 IDF 和长度因子

适用倾向

  • 调高:领域文档里重复术语即强信号(如规程中反复出现的设备名、标准号);希望「命中且多次出现」的 chunk 优先。
  • 调低:语料存在关键词堆砌或模板化重复;chunk 较短、词频差异不大,希望减少「多写几遍就排前面」的情况。

4.4 b:文档长度归一化

直觉b 决定「文档比语料平均长度长很多时,要被压多少分」。

长度因子为:

\[1 - b + b \cdot \frac{|D|}{\text{avgdl}}\]
  • 文档长度 \|D\| = avgdl 时,因子 = 1,无额外惩罚。
  • 文档长于平均长度时,分母变大 → 该词贡献降低
  • 文档短于平均长度时,分母变小 → 该词贡献升高
调参方向效果典型现象
调高 b(→ 1)长度惩罚更强长文档更难靠「词多」排前;短而精的 chunk 更有优势
调低 b(→ 0)长度惩罚更弱长短文档更「一视同仁」;长文档若关键词多,仍可能得高分
b = 0关闭长度归一化纯看词频 + IDF,长文档不再因长度被额外压分
b = 1完全\|D\|/avgdl 比例惩罚对超长文档压制最强

适用倾向

  • 调高 b:chunk 长度差异大(短 FAQ + 长章节混存);RAG 分块后希望短 chunk 不因长度吃亏(实际上 RAG 里常见是长 chunk 被压、短 chunk 受益)。
  • 调低 b:分块长度较均匀(如固定 512 token);或长文档中多次命中本就应排前,不希望长度惩罚过重。

4.5 二者如何配合理解

可以用一张简表对照:

组合排序倾向
k1 + b 偏好「短文档 + 关键词多次出现」
k1 + b 长文档若关键词重复多,仍可排前
k1 + b 偏好「短文档、命中即可」,不太看重复次数
k1 + b 最接近「有词就有分」,长度和次数都不敏感

4.6 调参建议(RAG 场景)

  1. 先用默认值
    Elasticsearch:k1=1.2, b=0.75;自研 BM25 库:k1=1.5, b=0.75 是常见起点。

  2. 结合分块策略
    • 若已做固定长度分块且长度接近,b 往往不必大动,优先调 k1
    • 若按章节/条款切分,短条款与长附录并存,可尝试 略调高 b(如 0.8~0.9),避免长附录 chunk 压制短条款。
  3. 用评测集网格搜索
    准备一批带标注的 (query, 相关文档),在小范围内扫参,例如:
    • k1 ∈ {0.6, 1.2, 1.5, 2.0}
    • b ∈ {0.0, 0.5, 0.75, 1.0}
      看 Recall@K、MRR 或业务命中率,不要凭感觉单点改
  4. Elasticsearch 配置示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
  "settings": {
    "index": {
      "similarity": {
        "my_bm25": {
          "type": "BM25",
          "k1": 1.2,
          "b": 0.75
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "content": {
        "type": "text",
        "similarity": "my_bm25"
      }
    }
  }
}
  1. 与混合检索的关系
    k1/b 只影响 BM25 这一路的排序;与向量路的融合权重、rerank 是独立的。调 BM25 超参时,建议在固定融合策略下对比,避免多个变量同时变化。

5. 检索流程示例

从用户 query 到排序输出的典型流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
┌─────────────────────────────────────────────────────────────┐
│  用户查询: "保护 老跳闸"                                      │
└───────────────────────────┬─────────────────────────────────┘
                            ▼
┌─────────────────────────────────────────────────────────────┐
│  Step 1: 分词 / 预处理                                        │
│  → ["保护", "老跳闸"]  (中文需分词;可去停用词)              │
└───────────────────────────┬─────────────────────────────────┘
                            ▼
┌─────────────────────────────────────────────────────────────┐
│  Step 2: 倒排索引检索(Inverted Index)                       │
│  "保护"   → [Doc1, Doc3, Doc7, ...]                          │
│  "老跳闸" → [Doc3, Doc9, ...]                                │
│  → 取并集得到候选文档集合                                     │
└───────────────────────────┬─────────────────────────────────┘
                            ▼
┌─────────────────────────────────────────────────────────────┐
│  Step 3: 对每个候选文档 D,逐词算 BM25 分并累加               │
│                                                              │
│  对词 "保护":                                                  │
│    f("保护", D) = 3                                           │
│    IDF("保护") = 较低(常见词)                               │
│    长度因子 = 根据 |D| 与 avgdl 调整                          │
│    → 得到 score_保护                                          │
│                                                              │
│  对词 "老跳闸":                                                │
│    f("老跳闸", D) = 1                                         │
│    IDF("老跳闸") = 较高(稀有词)                             │
│    → 得到 score_老跳闸                                        │
│                                                              │
│  总分 = score_保护 + score_老跳闸                             │
└───────────────────────────┬─────────────────────────────────┘
                            ▼
┌─────────────────────────────────────────────────────────────┐
│  Step 4: 按总分降序排序 → Top-K 文档                          │
│  Doc3: 8.2  ← 两词都命中,且长度适中                          │
│  Doc7: 5.1  ← 只命中 "保护"                                   │
│  Doc9: 4.6  ← 只命中 "老跳闸"                                 │
└─────────────────────────────────────────────────────────────┘

倒排索引是 BM25 能高效运行的基础:预先建立「词 → 包含该词的文档列表」映射,查询时不必遍历全库。


6. 数值示例(简化)

假设语料 1000 篇文档,平均长度 avgdl = 200 词,参数 k1=1.5, b=0.75

查询:「变压器 故障」

文档 A(短,150 词):「变压器发生故障,需立即停运。」

  • 「变压器」出现 1 次,全库 50 篇含该词 → IDF 中等
  • 「故障」出现 1 次,全库 20 篇含该词 → IDF 较高
  • 文档偏短 → 长度因子加分

文档 B(长,800 词):一篇长规程,「变压器」「故障」各出现 2 次

  • 词频略高,但被长度归一化拉低
  • 总分可能不如文档 A
1
2
3
4
5
6
                    文档 A (150词)          文档 B (800词)
词 "变压器" TF贡献     中等                   略高(2次)
词 "故障"   TF贡献     较高(IDF大)            略高
长度惩罚               小(短文档)            大(长文档)
─────────────────────────────────────────────────────
BM25 总分              7.8  ← 排第1           5.2  ← 排第2

要点:不是「出现次数多就一定赢」,而是 TF、IDF、文档长度三者共同决定最终得分。


7. 在 RAG 中的用法

RAG 实践中,BM25 几乎总是与向量检索组合,形成 混合检索(Hybrid Search)

1
2
3
4
5
6
7
8
9
10
用户问题
    │
    ├─→ BM25 路径(规则公式,无训练)
    │      分词 → 倒排索引 → BM25 打分 → 候选集 A
    │
    └─→ 向量路径(神经网络 embedding)
           编码 → 余弦相似度 → 候选集 B
    │
    ▼
  分数融合(加权 / RRF 等)→ rerank(可选)→ 送 LLM

7.1 典型实现

方案说明
Elasticsearch默认 BM25,开箱即用全文检索
PostgreSQL全文检索(tsvector)自实现 BM25 侧
Milvus / Qdrant原生支持向量 + BM25 混合检索
LangChainBM25Retriever + EnsembleRetriever 组合向量检索

7.2 分数融合注意

两路分数量纲不同,通常需 各自归一化 后再加权,或采用 RRF(Reciprocal Rank Fusion) 按排名融合。融合权重与数据分布强相关,应在自有数据上 A/B 测试,不宜直接照搬他人参数。


8. 优缺点与适用场景

8.1 优点

  • 专有名词、编号、条款号、英文缩写等字面匹配强
  • 可解释:能明确看到哪些词命中、贡献多少分
  • 成熟、快:CPU 即可,无需 GPU,零额外模型成本
  • 与向量检索互补,混合后召回更稳

8.2 局限

  • 同义词、改写能力弱(「老跳闸」vs「频繁动作」可能对不上)
  • 依赖分词质量(中文需 jieba 等 + 领域词典)
  • 融合策略与权重需自行调优

8.3 适用场景

场景是否推荐 BM25
政策法规、标准条款、需精准匹配编号强烈推荐
口语化、同义表述多的问答需配合向量检索
纯语义相似、无共同关键词BM25 单独效果有限
成本敏感、无 GPUBM25 侧零模型成本,适合作为基线

9. 小结

要点说明
定位经典 IR 排序公式,不是模型,不是机器学习
名称Best Matching 25 中「25」为 Okapi 项目方案编号,非公式参数
核心思想词频(饱和)+ IDF(稀有度)+ 文档长度归一化
超参数k1 控词频饱和,b 控长度惩罚;默认约 1.2 / 0.75,宜在评测集上网格搜索
工程依赖倒排索引 + 分词;Elasticsearch 默认即 BM25
RAG 角色稀疏检索,与向量检索做混合,提高字面命中类 query 的召回与排序稳定性

一句话:BM25 = 基于词频、稀有度与文档长度的经典关键词排序算法;在 RAG 里它是检索层的「字面匹配专家」,与向量的「语义匹配专家」搭档使用,而非二选一。

本文由作者按照 CC BY 4.0 进行授权