BM25 是什么?手把手拆解搜索引擎的核心算法

上篇我们理解了 TF-IDF 的基本逻辑。BM25 是它的改进版,修掉了两个明显缺陷。本文从公式结构到实际计算,逐步拆解每一个细节,无需数学基础也能读懂。


一、TF-IDF 的两个缺陷

回顾上篇的结论:TF-IDF 用"词频 × 逆文档频率"来衡量一个词的重要性。但它有两个问题:

缺陷一:词频线性增长 一个词出现 10 次,得分是出现 5 次的整整两倍。但现实中,同一个词反复出现,边际价值是递减的------从 0 次到 1 次是质变,从 9 次到 10 次几乎没什么意义。

缺陷二:没有校正文档长度 同一个词在一篇 500 字的短文里出现 3 次,和在一篇 5 万字的长文里出现 3 次,重要性截然不同。短文里它更"密集",更能代表主题。TF-IDF 对此视而不见。

BM25 就是针对这两点的经典修正。


二、BM25 公式全貌

Score(Q,D)=∑iIDFBM25(qi)⋅ TF(qi,D)(k1+1) TF(qi,D)+k1(1−b+b⋅ ∣D∣avgdl ) \text{Score}(Q, D) = \sum_i \text{IDF}_{\text{BM25}}(q_i) \cdot \frac{\text{TF}(q_i, D)(k_1 + 1)}{\text{TF}(q_i, D) + k_1\left(1 - b + b \cdot \frac{|D|}{\text{avgdl}}\right)} Score(Q,D)=∑iIDFBM25(qi)⋅TF(qi,D)+k1(1−b+b⋅avgdl∣D∣)TF(qi,D)(k1+1)

看起来很长,但拆开来只有三个独立模块,加上一个求和符号。


三、模块一:∑ 求和符号

∑i \sum_i ∑i

查询 Q 通常包含多个词,比如"模型蒸馏"会被拆成"模型"和"蒸馏"两个词。

对每个词 qi q_i qi 分别算一个得分,最后加总,就是这篇文档对这条查询的总得分。多个词同时命中,得分会叠加------这也是为什么"同时包含两个关键词的文档"往往排名更高。


四、模块二:IDF 部分(稀有词加权)

BM25 对 IDF 的写法和 TF-IDF 略有不同:

IDFBM25(t)=ln⁡ N−DF(t)+0.5DF(t)+0.5 \text{IDF}_{\text{BM25}}(t) = \ln \frac{N - \text{DF}(t) + 0.5}{\text{DF}(t) + 0.5} IDFBM25(t)=lnDF(t)+0.5N−DF(t)+0.5

  • N = 语料库文档总数
  • DF(t) = 包含词 t 的文档数

和 TF-IDF 相比,分子从"N(总数)"换成了"N − DF(t)(不含该词的文档数)":

TF-IDF BM25
分子含义 文档总数 不含该词的文档数
传达的意思 总共有多少文档 不含它的文档是含它的多少倍

分子分母各加 0.5,是为了防止极端情况(比如某个词出现在所有文档里,DF = N,分子变成 0.5,公式不会崩掉)。

代入例子感受一下,假设语料库共 10 篇文档:

"模型"出现在 3 篇(df = 3):

IDF=ln⁡ 10−3+0.53+0.5 =ln⁡7.53.5=ln⁡(2.14)≈0.76 \text{IDF} = \ln\frac{10 - 3 + 0.5}{3 + 0.5} = \ln\frac{7.5}{3.5} = \ln(2.14) \approx 0.76 IDF=ln3+0.510−3+0.5=ln3.57.5=ln(2.14)≈0.76

"蒸馏"出现在 2 篇(df = 2):

IDF=ln⁡ 10−2+0.52+0.5 =ln⁡8.52.5=ln⁡(3.4)≈1.22 \text{IDF} = \ln\frac{10 - 2 + 0.5}{2 + 0.5} = \ln\frac{8.5}{2.5} = \ln(3.4) \approx 1.22 IDF=ln2+0.510−2+0.5=ln2.58.5=ln(3.4)≈1.22

"蒸馏"更稀有,IDF 更高,符合直觉。


五、模块三:TF 饱和(BM25 最核心的改进)

TF×(k1+1) TF+k1 \frac{\text{TF} \times (k_1 + 1)}{\text{TF} + k_1} TF+k1TF×(k1+1)

(先忽略长度部分,只看 k1 k_1 k1 的作用)

当 TF 非常大时,这个分数会趋近于 (k1+1) (k_1 + 1) (k1+1),不会无限增长------这就是饱和

用数字感受一下( k1=1.5 k_1 = 1.5 k1=1.5):

出现次数(TF) 得分 增量
1 1.00 ---
2 1.40 +0.40
5 2.00 +0.60
10 2.20 +0.20
100 2.49 几乎不动

出现 10 次不会是出现 5 次的两倍,边际贡献越来越小------这才符合现实。

k1 k_1 k1 的值控制饱和的速度, k1 k_1 k1 越小,越早到达上限; k1 k_1 k1 越大,增长空间越大。


六、模块四:长度归一化(分母里的 b)

分母里这一段负责惩罚长文档:

k1(1−b+b⋅ ∣D∣avgdl ) k_1\left(1 - b + b \cdot \frac{|D|}{\text{avgdl}}\right) k1(1−b+b⋅avgdl∣D∣)

  • ∣D∣|D| ∣D∣ = 当前文档长度(词数)
  • avgdl\text{avgdl} avgdl = 语料库平均文档长度
  • b∈0,1b \in 0, 1 b∈0,1,控制归一化强度

直觉:文档比平均值 ∣D∣/avgdl>1|D|/\text{avgdl} > 1 ∣D∣/avgdl>1,分母变大,得分被压低;文档比平均值,分母变小,得分略微提升。

b 的值 效果
b = 0 完全不考虑文档长度
b = 1 完全按长度归一化
b = 0.75 实践中最常用的默认值

以 doc_1(文档长度 200 词,avgdl = 250)为例

1.5×(1−0.75+0.75×200250)=1.5×(0.25+0.6)=1.5×0.85=1.275 1.5 \times \left(1 - 0.75 + 0.75 \times \frac{200}{250}\right) = 1.5 \times (0.25 + 0.6) = 1.5 \times 0.85 = 1.275 1.5×(1−0.75+0.75×250200)=1.5×(0.25+0.6)=1.5×0.85=1.275

doc_1 比平均文档短,惩罚项(1.275)小于 k1 k_1 k1(1.5),因此短文档得到了轻微奖励


七、手算一遍:验证日志里的数字

以下是一段真实的 BM25 运行日志,我们来手算验证它:

ini 复制代码
查询分词: ["模型", "蒸馏"]

词 "模型" → df=3, IDF=0.76
  doc_1: TF=5, 文档长度=200词, BM25贡献=1.52

词 "蒸馏" → df=2, IDF=1.22
  doc_1: TF=3, 文档长度=200词, BM25贡献=2.15

最终排序: doc_1 (3.67) > doc_7 (1.68) > doc_5 (1.22) > doc_3 (0.82)

参数: k1=1.5 k_1 = 1.5 k1=1.5, b=0.75b = 0.75 b=0.75, avgdl=250\text{avgdl} = 250 avgdl=250


第一步:算 doc_1 的长度惩罚项

k1(1−b+b⋅ ∣D∣avgdl )=1.5×(0.25+0.75×200250)=1.5×0.85=1.275 k_1\left(1 - b + b \cdot \frac{|D|}{\text{avgdl}}\right) = 1.5 \times \left(0.25 + 0.75 \times \frac{200}{250}\right) = 1.5 \times 0.85 = 1.275 k1(1−b+b⋅avgdl∣D∣)=1.5×(0.25+0.75×250200)=1.5×0.85=1.275


第二步:算"模型"在 doc_1 的贡献(IDF = 0.76,TF = 5)

0.76× 5×(1.5+1)5+1.275 =0.76×12.56.275=0.76×1.99≈1.51 0.76 \times \frac{5 \times (1.5 + 1)}{5 + 1.275} = 0.76 \times \frac{12.5}{6.275} = 0.76 \times 1.99 \approx 1.51 0.76×5+1.2755×(1.5+1)=0.76×6.27512.5=0.76×1.99≈1.51

日志显示 1.52,误差来自中间步骤的四舍五入,✓


第三步:算"蒸馏"在 doc_1 的贡献(IDF = 1.22,TF = 3)

1.22× 3×(1.5+1)3+1.275 =1.22×7.54.275=1.22×1.75≈2.14 1.22 \times \frac{3 \times (1.5 + 1)}{3 + 1.275} = 1.22 \times \frac{7.5}{4.275} = 1.22 \times 1.75 \approx 2.14 1.22×3+1.2753×(1.5+1)=1.22×4.2757.5=1.22×1.75≈2.14

日志显示 2.15,✓


第四步:doc_1 总分

3.67=1.52⏟∗"模型"贡献+2.15⏟∗"蒸馏"贡献 3.67 = \underbrace{1.52} *{\text{"模型"贡献}} + \underbrace{2.15}* {\text{"蒸馏"贡献}} 3.67= 1.52∗"模型"贡献+ 2.15∗"蒸馏"贡献


八、为什么"蒸馏"贡献反而更大?

"蒸馏"在 doc_1 里的词频(TF = 3)明明低于"模型"(TF = 5),为什么贡献更大?

IDF TF 饱和后的TF项 最终贡献
"模型" 0.76(常见) 5 1.99 1.52
"蒸馏" 1.22(稀有) 3 1.75 2.15

"蒸馏"的 IDF 比"模型"高出约 60%,完全弥补了 TF 的差距。

这正是 BM25 的核心权衡:稀有性比出现次数更重要。一个词在文档集合里越罕见,哪怕只出现一两次,也能提供极强的区分信号。


九、稀疏检索的优势与局限

通过上面的计算,BM25(稀疏检索)的特点已经很清晰:

方面 说明
优势 精确关键词匹配,在技术术语、人名、代码等查询上表现极佳
局限 读不懂同义表达------查"汽车",匹配不到只写"车辆"的文档

这种局限正是下一步引入混合检索(稀疏 + 稠密向量)的动机:用语义向量弥补关键词匹配的盲区。


十、三个模块一句话总结

最终得分=∑每个查询词IDF⏟∗稀有词加权× TF⋅(k1+1) TF+k1(⋯ ) ⏟ ∗饱和的词频+长度惩罚 \text{最终得分} = \sum_{\text{每个查询词}} \underbrace{\text{IDF}} *{\text{稀有词加权}} \times \underbrace{\frac{\text{TF} \cdot (k_1+1)}{\text{TF} + k_1(\cdots)}}* {\text{饱和的词频} + \text{长度惩罚}} 最终得分=∑每个查询词 IDF∗稀有词加权× TF+k1(⋯)TF⋅(k1+1)∗饱和的词频+长度惩罚

BM25 没有引入全新的概念,只是把 TF-IDF 的两个缺陷------线性词频忽略文档长度------用数学方式优雅地修掉了。


理解 BM25,是读懂稀疏检索、RAG 管道和现代搜索引擎的关键一步。

相关推荐
古少侠1 小时前
AI 内容粘到 Word 就乱?用 DS随心转 把复制的内容直接变成排版好的一段
人工智能·c#·word
浅安的邂逅1 小时前
260910-DeepSeek 发布 V4.1 Flash:1M 上下文、552B MoE,KV 缓存降到上一代 1/4,同步开源
人工智能·开源·ai大模型·deepseek·ai日报
海宇服务1 小时前
零信任架构实战:基于海宇柠檬查出险-登记证构建自动化二手车直卖合规网关
运维·人工智能·架构·自动化
长谷深风1111 小时前
Agent 跑了 30 分钟宕机,如何从断点继续?
java·大数据·人工智能·ai·大模型·memory·aiagent
Mr.朱鹏1 小时前
科技周报(第2026-09-10期):模型激战量子降温
人工智能·科技·chatgpt·机器人·开源
JAVA老架构AI智能化1 小时前
如何高质量分块
人工智能·python
雷焰财经1 小时前
从辅助到自主:宇信科技“可信智能体”战略带来哪些行业启示?
人工智能·科技