BM25信息检索算法简介
BM25信息检索算法简介
BM25(Best Matching 25)是信息检索领域最经典的关键词排序算法之一,Elasticsearch 默认排序即采用 BM25。在 RAG 场景中,它常与向量检索组合做混合检索,弥补语义检索在专有名词、条款编号等字面匹配上的不足。本文介绍 BM25 的定位、相关性计算原理、流程示例及在 RAG 中的典型用法。
目录
- 1. BM25 是什么
- 2. 与 TF-IDF、机器学习模型的区别
- 3. 相关性如何计算
- 4. 超参数 k1 与 b
- 5. 检索流程示例
- 6. 数值示例(简化)
- 7. 在 RAG 中的用法
- 8. 优缺点与适用场景
- 9. 小结
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 | 没有。可调超参数是 k1、b 等 |
| 「25」与 TF-IDF 或 IDF 的某个常数有关 | 无关,纯属历史命名 |
| 必须按 25 配置才能正确运行 | 不必,使用引擎默认的 k1、b 即可 |
因此也常写作 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-IDF | BM25 |
|---|---|---|
| 词频(TF) | 线性增加,易被关键词堆砌刷分 | 饱和曲线,出现 10 次与 100 次差距不大 |
| 文档长度 | 常需额外处理 | 内置长度归一化 |
| 工程落地 | 教学、简单检索 | Elasticsearch / Lucene 默认排序 |
2.2 BM25 vs 机器学习检索(Embedding)
| 对比 | BM25 | Embedding 向量检索 |
|---|---|---|
| 是否有训练 | 无,公式固定 | 有,从数据学习表示 |
| 是否理解语义 | 否,只看词面匹配 | 是,「老跳闸」≈「频繁动作」也能召回 |
| 输入 | 词频、文档长度、语料统计 | 文本 → 向量 |
| 输出 | BM25 相关分 | 余弦相似度等 |
| 成本 | CPU 即可,零 GPU | 需 embedding 模型推理 |
| 强项 | 专有名词、编号、条款号 | 同义词、口语化、语义改写 |
3. 相关性如何计算
3.1 核心直觉
对查询 Q 中的每个词 q,计算该词对文档 D 的贡献分,再将所有词的贡献相加,得到文档总分。
每个词 q 的贡献由三部分决定:
词频 TF(饱和)
词在文档里出现越多,分越高,但不会无限上涨——避免长文档靠堆砌关键词刷分。逆文档频率 IDF(稀有度)
全库很多文档都有的词(如「的」「规定」)权重低;只有少数文档才有的词(如「继电保护」「GB/T 1234」)权重高。文档长度归一化
同样词频下,短文档得分更高;长文档不会因词多而天然占优。
3.2 公式
对查询中的每个词 q,文档 D 的 BM25 相关分:
符号说明:
| 符号 | 含义 |
|---|---|
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 公式里有两个可调超参数:k1 和 b。它们不来自训练,而是人工设定(或由搜索引擎提供默认值)。理解二者作用,是调 BM25 排序效果的关键。
4.1 在公式中的位置
回顾单条 query 词 q 对文档 D 的 TF 贡献部分:
k1出现在分子分母,控制词频f(q,D)的饱和速度——词出现 1 次 vs 5 次 vs 20 次,加分差多少。b出现在分母的长度因子里,控制文档长度\|D\|对得分的惩罚强度——长文档是否因「词多」而被压分。
4.2 默认值与常见范围
| 参数 | 典型默认值 | 常见调参范围 | 说明 |
|---|---|---|---|
k1 | 1.2(Elasticsearch / Lucene) | 0 ~ 3.0 | 文献中常见 1.2 ~ 2.0 |
b | 0.75 | 0 ~ 1.0 | 0 = 不做长度归一化;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 场景)
先用默认值
Elasticsearch:k1=1.2, b=0.75;自研 BM25 库:k1=1.5, b=0.75是常见起点。- 结合分块策略
- 若已做固定长度分块且长度接近,
b往往不必大动,优先调k1。 - 若按章节/条款切分,短条款与长附录并存,可尝试 略调高
b(如 0.8~0.9),避免长附录 chunk 压制短条款。
- 若已做固定长度分块且长度接近,
- 用评测集网格搜索
准备一批带标注的(query, 相关文档),在小范围内扫参,例如:k1 ∈ {0.6, 1.2, 1.5, 2.0}b ∈ {0.0, 0.5, 0.75, 1.0}
看 Recall@K、MRR 或业务命中率,不要凭感觉单点改。
- 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"
}
}
}
}
- 与混合检索的关系
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 混合检索 |
| LangChain | BM25Retriever + EnsembleRetriever 组合向量检索 |
7.2 分数融合注意
两路分数量纲不同,通常需 各自归一化 后再加权,或采用 RRF(Reciprocal Rank Fusion) 按排名融合。融合权重与数据分布强相关,应在自有数据上 A/B 测试,不宜直接照搬他人参数。
8. 优缺点与适用场景
8.1 优点
- 对专有名词、编号、条款号、英文缩写等字面匹配强
- 可解释:能明确看到哪些词命中、贡献多少分
- 成熟、快:CPU 即可,无需 GPU,零额外模型成本
- 与向量检索互补,混合后召回更稳
8.2 局限
- 同义词、改写能力弱(「老跳闸」vs「频繁动作」可能对不上)
- 依赖分词质量(中文需 jieba 等 + 领域词典)
- 融合策略与权重需自行调优
8.3 适用场景
| 场景 | 是否推荐 BM25 |
|---|---|
| 政策法规、标准条款、需精准匹配编号 | 强烈推荐 |
| 口语化、同义表述多的问答 | 需配合向量检索 |
| 纯语义相似、无共同关键词 | BM25 单独效果有限 |
| 成本敏感、无 GPU | BM25 侧零模型成本,适合作为基线 |
9. 小结
| 要点 | 说明 |
|---|---|
| 定位 | 经典 IR 排序公式,不是模型,不是机器学习 |
| 名称 | Best Matching 25 中「25」为 Okapi 项目方案编号,非公式参数 |
| 核心思想 | 词频(饱和)+ IDF(稀有度)+ 文档长度归一化 |
| 超参数 | k1 控词频饱和,b 控长度惩罚;默认约 1.2 / 0.75,宜在评测集上网格搜索 |
| 工程依赖 | 倒排索引 + 分词;Elasticsearch 默认即 BM25 |
| RAG 角色 | 稀疏检索,与向量检索做混合,提高字面命中类 query 的召回与排序稳定性 |
一句话:BM25 = 基于词频、稀有度与文档长度的经典关键词排序算法;在 RAG 里它是检索层的「字面匹配专家」,与向量的「语义匹配专家」搭档使用,而非二选一。
