BM25算法的概念、定性理解+公式定量理解、举例说明、一文详解。
“BM25 是检索系统的根基算法”,而不是简单的 API 细节问题。下面我会从零假设你“完全不理解 BM25”,一步一步把它讲透,并且所有概念都会和你实际写 ES / RAG / 搜索代码的场景强绑定**。
说明:算法严格名称是 BM25(Best Matching 25),这是一个家族算法的第 25 号变体。
一、BM25 解决的到底是什么问题?
一句话先定性:
BM25 是用来回答一个问题的:
“在一堆文本里,哪一篇最像我现在输入的关键词?”
它不是:
- 语义理解 ❌
- 推理 ❌
- 向量相似度 ❌
它是:
- 基于词项统计的相关性排序算法 ✅
你真实面对的工程问题
假设你有 3 篇文档:
Doc1
AI 合规 平台 支持 监管 报告 自动 生成
Doc2
合规 平台 合规 平台 合规 平台 合规 平台
Doc3
AI 技术 平台 支持 模型 推理
用户 query:
AI 合规 平台
你直觉会觉得:
👉 Doc1 > Doc2 > Doc3
BM25 的目标就是:
用“可解释的数学规则”算出这个顺序
二、BM25 的前身:为什么 TF-IDF 不够好?
在理解 BM25 前,必须先知道它改进了什么。
1️⃣ TF(词频)的问题
TF 的原始想法:
一个词在文档里出现越多,说明越相关
问题是:
| 文档 | “合规”出现次数 |
|---|---|
| Doc1 | 1 |
| Doc2 | 4 |
TF 会认为 Doc2 远强于 Doc1
但你作为人会觉得:
Doc2 只是重复刷词,没有更多信息
2️⃣ IDF(逆文档频率)的局限
IDF 解决的是:
“平台”这种词谁都有,权重应该低
但 IDF 只关心“全局”,不关心“当前文档结构”
3️⃣ 文档长度偏置问题
长文档天然有优势:
词多 → 命中概率高 → 分数高
但长 ≠ 相关。
👉 BM25 的核心目标
同时解决 3 件事:
- 词出现一次和出现十次,不应差十倍
- 长文档不能天然占便宜
- 稀有词比常见词更重要
三、BM25 的直觉版本(非常重要)
我先不给你公式,只给人类直觉版规则:
BM25 对每个 query 词做如下判断:
规则 1:这个词在文档里出现了吗?
- 没出现 → 贡献 0 分
- 出现了 → 进入下一步
规则 2:出现 1 次和 2 次差很多吗?
- 第一次出现:加分很大
- 第二次出现:还行
- 第 10 次出现:几乎没啥额外价值
👉 这叫“词频饱和”
规则 3:这是一个“稀有词”吗?
- “AI 合规” → 稀有 → 加分多
- “系统 平台” → 常见 → 加分少
规则 4:这篇文档是不是特别长?
- 很长 → 稍微打点折
- 很短 → 稍微给点优势
BM25 = 把上面 4 条规则数学化
四、现在给你 BM25 的完整公式(逐项拆)

我们拆成 4 块。
1️⃣ 外层求和:按“词”算分

意思是:
Query 里有几个词,就分别算几次,再加起来
Query:AI 合规 平台
→ 分别算 3 次
2️⃣ IDF(t):这个词值不值钱?

直觉解释:
| 情况 | IDF |
|---|---|
| 所有文档都有 | 接近 0 |
| 只有少数文档有 | 很大 |
👉 这一步解决“平台 / 系统”这种词的水分问题
3️⃣ 分子:词频 × 放大因子

- ( f(t, D) ):出现次数
- ( k_1 ):控制“饱和速度”
k1 ≈ 1.2(ES 默认)
4️⃣ 分母:BM25 的“灵魂”

这是最重要的一部分。
它做了两件事:
A. 控制 TF 不无限增长
当 ( f ) 越来越大时:

👉 再刷词也刷不动了
B. 文档长度归一化

- 文档比平均长 → 分母变大 → 分数下降
- 文档比平均短 → 分母变小 → 分数上升
b ≈ 0.75(惩罚强度)
五、用一个完整“算分例子”帮你对齐直觉
假设:
- avgdl = 100
- Doc1 长度 = 80
- Doc2 长度 = 300
- 查询词 “合规”
| 文档 | f(合规) |
|---|---|
| Doc1 | 1 |
| Doc2 | 5 |
直觉你觉得:
Doc2 不应该是 Doc1 的 5 倍
BM25 会算出:
- Doc1:接近满分
- Doc2:略高或相近,但不会成倍增长
六、BM25 的本质总结(非常重要)
BM25 本质不是“相似度”
而是“相关性打分规则”
它的判断逻辑是:
- 词是否匹配(Boolean 检索)
- 再按“信息量”给权重
- 再按“文本结构”做修正
七、为什么 BM25 在你现在的项目里“非常合理”?
结合你的背景(ES + 合规 / 文档 / RAG):
| 场景 | BM25 表现 |
|---|---|
| 法规条文 | 极强 |
| 技术文档 | 极强 |
| 精确术语 | 极强 |
| 长文档 | 稳定 |
| 召回阶段 | 一流 |
| 语义改写 | 不行 |
八、你下一步应该做什么(建议)
如果你现在 “理解了公式,但没形成肌肉记忆”,我建议你:
- 用
explain: true看真实 BM25 拆解 - 手算 1 个 query × 2 个文档
- 对比 BM25 vs 向量召回结果
如下BM25公式中的每个字母详细解释:
你这个追问非常到位,而且这是理解 BM25 是否“真正吃透”的分水岭。下面我会严格、完整、逐符号地解释 BM25 公式中出现的每一个字母、每一个常量、每一个下标,不跳步、不省略、不假设你“应该懂”。
一、BM25 完整公式(原样再贴一次)

]
下面开始 逐个符号解释。
二、最外层符号(对象级别)
1️⃣ ( Q ):Query(查询)
- 含义:用户输入的查询文本
- 在 ES 中:
"query": "AI 合规 平台" - 经过分词后:
![image]()
2️⃣ ( t \in Q ):Query 中的一个词项(term)
-
( t ):单个词项
-
例如:
- query =
"AI 合规 平台" - 分词后:
- query =

3️⃣ ( D ):Document(文档)
-
含义:索引中的一篇文档
-
ES 中:一条
_source -
可以是:
- 一段法规
- 一篇文章
- 一条知识片段
4️⃣ ( score(D, Q) )
-
含义:文档 D 对查询 Q 的相关性得分
-
ES 中:
hit['_score'] -
数值特性:
- 无上界
- 仅用于相对排序
三、求和符号相关(结构层)
5️⃣ ( ![image]()
)
-
含义:
对 Query 中的每一个词 t 分别算分,再加起来 -
说明:
- Query 越长,理论上贡献项越多
- 如果某个词在文档中不存在,该词贡献为 0
四、IDF 部分(全局统计量)
6️⃣ ( IDF(t) ):Inverse Document Frequency
衡量一个词“稀不稀有”
数学定义:

每个符号解释:
6.1️⃣ ( N )
-
含义:索引中 文档总数
-
示例:
- 索引里一共有 100 万篇文档
- ( N = 1{,}000{,}000 )
6.2️⃣ ( df(t) )(document frequency)
-
含义:
包含词项 t 的文档数量 -
示例:
- “平台” 出现在 80 万篇文档中
→ ( df(\text{平台}) = 800{,}000 )
- “平台” 出现在 80 万篇文档中
6.3️⃣ 常数 ( 0.5 )
-
含义:平滑项(smoothing)
-
作用:
-
防止:
- 分母为 0
- IDF 无限大
-
保证数值稳定
-
6.4️⃣ 对数 ( \log )
-
含义:
- 压缩数值范围
- 防止极稀有词权重爆炸
-
Lucene 中使用的是自然对数 ln
五、TF 相关部分(局部统计量)
7️⃣ ( f(t, D) ):Term Frequency
-
含义:
词项 t 在文档 D 中出现的次数 -
示例:
- 文档 D 中 “合规” 出现 3 次
- ( f(合规, D) = 3 )
8️⃣ ( k_1 ):TF 饱和控制参数
-
含义:
控制“词频增长到多少开始饱和” -
特点:
- 数值越大 → 词频影响越强
- 数值越小 → 更快进入饱和
-
Elasticsearch 默认:
![image]()
9️⃣ ( k1 + 1 )
-
含义:
用于把分子最大值归一化到 1 左右 -
工程意义:
- 保证当词频很大时,TF 部分不会超过预期
六、文档长度归一化部分(关键)
🔟 ( |D| ):文档长度
-
含义:
文档 D 中的总词项数 -
注意:
- 是分词后的 term 数
- 不是字符数,也不是字节数
1️⃣1️⃣ ( avgdl ):Average Document Length
- 含义:
整个索引中,文档长度的平均值 - 计算方式:
![image]()
1️⃣2️⃣ ![image]()
-
含义:
当前文档长度相对于平均文档长度的比例 -
解释:
-
1 → 文档偏长
- <1 → 文档偏短
-
1️⃣3️⃣ ( b ):长度惩罚系数
- 含义:
控制“文档长度”对分数的影响程度 - 取值范围:
![image]()
| b 值 | 含义 |
|---|---|
| 0 | 完全不考虑长度 |
| 1 | 完全按长度归一化 |
| 0.75 | 经验最优(ES 默认) |
1️⃣4️⃣ ![image]()
-
含义:
BM25 的长度归一化因子 -
作用:
- 长文档 → 分母变大 → 分数降低
- 短文档 → 分母变小 → 分数提高
七、整体结构的“逻辑拆解”
你现在可以把 BM25 看成:
score = ∑(
稀有度(IDF)
× 出现次数(有上限)
× 文档长度修正
)
八、把每个字母映射回你写的 ES 代码
| 数学符号 | ES / Lucene 中的来源 | ||
|---|---|---|---|
| Q | 用户 query | ||
| t | 分词后的 term | ||
| D | 一条 document | ||
| score(D,Q) | _score |
||
| f(t,D) | 倒排索引 term frequency | ||
| df(t) | 倒排索引统计 | ||
| N | 索引文档数 | ||
| D | term 数 | ||
| avgdl | segment 统计 | ||
| k1, b | BM25Similarity 参数 |
九、一个非常关键的认知(请记住)
BM25 不是“相似度函数”,
而是“基于概率的相关性估计公式”
所以:
- 分数不可跨 query 比较
- 只能在同一 query 内排序
追问:为什么文档率计算的分子是N-df(t),而不是N,(就好像IDF公式中通常介绍的那样)?
这是一个非常关键、也非常“理论味”的问题。你已经不在“怎么用 BM25”这个层面了,而是在问:
为什么 IDF 的分子不是 N,而是 ( N - df(t) )?
这个设计在概率和信息论上到底想表达什么?
下面我会从直觉 → 数学 → 历史演化 → 工程效果四个层面,把这个问题彻底讲清楚。
一、先给你结论(然后证明)
IDF 的分子之所以是 ( N - df(t) ),而不是 ( N ),是因为:
BM25 试图估计的是:
“一个词区分相关文档 vs 不相关文档的能力”
而不是简单地“这个词在全集里有多罕见”。
换句话说:
- ( df(t) ):包含 t 的文档数
- ( N - df(t) ):不包含 t 的文档数
👉 区分能力来自“正负样本的对比”,而不是只看正样本。
二、如果分子直接用 N,会发生什么?
假设你用一个“朴素(Navie) IDF”:

这其实就是经典 TF-IDF。
这个公式隐含的假设是:
“一个词的价值,只和它出现的频率有关。”
但它忽略了一个事实:
一个词的区分力,来自“它没出现在哪里”。
直觉反例
假设:
- N = 100
- 词 t 出现在 99 篇文档中
那它几乎没有区分能力。
但:

虽然很小,但并没有明确表达“几乎所有文档都有”。
三、BM25 的视角:区分“相关 vs 不相关”
BM25 来自 概率相关模型(Probabilistic Relevance Model)。
它的核心思想是:
一个词有多重要,取决于它是否能区分:
- 相关文档
- 非相关文档
在这个模型里,世界被分成两类文档:
| 类别 | 是否包含词 t |
|---|---|
| 相关文档 | 常包含 |
| 非相关文档 | 很少包含 |
于是问题变成:
词 t 出现在相关文档中的概率
vs
出现在不相关文档中的概率
概率对比的自然形式是“比值”

但问题来了
在检索前:
- 我们不知道哪些文档是 relevant
- 所以用 全局统计量 近似
于是:

四、为什么分子是 ( N - df(t) )(核心推导)
BM25 使用的是 log odds(对数几率) 形式:

出现在分子的根本原因。
五、加入 +0.5 是干什么的?
最终 BM25 用的是:

原因:
- 防止 df(t)=0
- 防止 df(t)=N
- 保证数值稳定
- 保证对数有限
这叫 Robertson/Sparck Jones 平滑
六、直觉再强化一次(非常重要)
你可以把 IDF 理解为一句话:
“这个词在‘不该出现的地方’出现得有多少?”
-
若几乎所有文档都有这个词
→ ( N - df(t) ) 很小
→ IDF 很小
→ 几乎不提供信息 -
若只有极少文档有
→ ( N - df(t) ) 很大
→ IDF 很大
→ 强区分词
七、为什么 BM25 不直接用 (log(N/df))?
因为:
| TF-IDF | BM25 |
|---|---|
| 频率视角 | 区分视角 |
| 看“出现多少” | 看“没出现在哪里” |
| 信息论弱 | 概率模型强 |
| 经验算法 | 理论推导 |
八、工程上的直接后果(你会真实感受到)
- 停用词天然权重≈0
- 高频词不需要手动过滤
- 排序更稳定
- 不容易被刷词攻击
九、一句话总结(请记住)
BM25 的 IDF 不是在问:
“这个词有多稀有?”
而是在问:
“这个词能不能把相关文档和不相关文档区分开?”
所以,
分子必须是 (N - df(t)),而不是 ( N )。








浙公网安备 33010602011771号