BM25 是一个文本相关性排序算法。
在 RAG 系统的检索阶段,它能快速地从海量文档中找到与查询最匹配的候选段落,为后续的生成模型提供更加聚焦的上下文。
BM25 可以独立使用,也可以和 dense 检索联合构建更强的 hybrid 检索器
-
1. BM25是做什么的? -
2. BM25是怎么判断相关性的? -
2.1 词频 (Term Frequency, TF) -
2.2 逆文档频率 (Inverse Document Frequency, IDF) -
2.3 文档长度 -
3. BM25的实现原理和公式 -
4. 例子:计算BM25分数 -
4.1 文档信息 -
4.2 假设查询 为:“模型 算法 性能” -
4.3 计算 IDF (使用 BM25 的 IDF 公式): -
4.4 计算每个文档的 TF-IDF 分数 -
4.5 计算每个文档的 BM25 分数 -
4.6 和TF-IDF的对比分析: -
5. BM25的优势 -
6. 使用LangChain实现BM25检索 -
总结
— 领取学习资料大礼包,见文末
当用户提出问题时,RAG(检索增强生成,Retrieval-Augmented Generation)首先会从海量数据中“检索”出相关的文档片段,然后将这些检索结果提供给LLM模型,以生成更准确、更可靠的答案。
信息检索是 RAG 的基石,而实现高效准确的检索,需要依赖各种排序和匹配算法。
在信息检索领域,既有基于向量相似度的现代方法,也有基于关键词的传统方法。
TF-IDF(词频-逆文档频率,Term Frequency-Inverse Document Frequency)是经典的关键词匹配算法之一,而 BM25(Best Matching 25)则是在 TF-IDF 基础上发展起来的一种更先进的算法。
BM25在信息检索领域久经验证且广泛应用,在 RAG 的检索阶段扮演着重要角色。
相关阅读:TF-IDF:理解“关键词”的价值
1. BM25是做什么的?
Okapi BM25(BM 是最佳匹配的缩写)是一种用来估计文档与给定搜索查询相关性的排序函数。
排序函数的名称是 BM25。完整的名称是Okapi BM25。
Okapi 是第一个使用它的系统的名称,即 1980 年代和 1990 年代在伦敦城市大学实施的 Okapi 信息检索系统。
简单来说,BM25是一个用来计算“用户查询”和“文档”之间相关性分数的算法。分数越高,说明文档与用户查询越相关。
在RAG的检索阶段,当用户输入一个查询时,BM25会计算这个查询与知识库中每一个文档的相关性分数,然后把得分最高的几个文档挑出来,交给生成模型。
2. BM25是怎么判断相关性的?
BM25的核心思想和你平时找信息时的感觉很像:
2.1 词频 (Term Frequency, TF)
如果一个词在文档中出现的次数越多,那么这个文档可能就越和这个词相关。
比如,你搜索“苹果手机”,如果一个文档里“苹果”和“手机”这两个词出现了很多次,那它很可能就是讲苹果手机的。
但是, BM25不会无限制地强调高词频,它有一个“饱和”的概念。 想象一下你吃苹果,吃第一个很开心,吃第二个也不错,但吃到第十个可能就没那么开心了。
BM25认为词频的贡献会逐渐趋于平缓,达到一定次数后,再增加词频对相关性的提升效果有限。
2.2 逆文档频率 (Inverse Document Frequency, IDF)
一个词在整个文档集合中出现的文档数量越少,它就越能区分文档的主题,因此它的重要性越高。
比如,“的”、“是”、“在”这些词几乎在所有文档里都会出现,它们对判断文档主题没什么帮助,IDF值就很低。
但像“BM25”、“RAG”、“Transformer”这些词,只会在特定领域的文档中出现,它们的IDF值就很高,更能说明文档是关于这些主题的。
2.3 文档长度
BM25会考虑文档的长度。一篇只有 100 个字的新闻报道里提到了两次“人工智能”,和一本 10 万字的百科全书里提到了两次“人工智能”。很明显,前者的主题更可能集中在“人工智能”上。
BM25会倾向于认为,一个词在短文档中出现,比在长文档中出现更能体现相关性。它会通过一个机制来“惩罚”过长的文档,避免它们仅仅因为词多而获得高分。
BM25就是结合了这三个主要因素,通过一个公式来计算最终的相关性分数。公式里还有一些参数(比如和),可以调整词频饱和度和文档长度的影响程度。
3. BM25的实现原理和公式
现在我们来看看BM25是如何通过公式来实现这些想法的。
BM25计算查询 和文档 之间相关性分数 的基本思想是,将查询 中的每个词 的相关性贡献加起来。
公式可以左右滑动
对于查询中的 每一个词 ,它对文档 的贡献分数由以下几部分决定:
-
词 在文档 中的词频 (Term Frequency):
-
词 的逆文档频率 (Inverse Document Frequency ):
这是衡量词 在整个文档集合中的稀有程度。计算公式通常是:
-
是文档集合中的总文档数,
-
是包含词 的文档数量。
-
这里的 函数和 是为了平滑处理,避免某些词出现文档数为0或时出现问题。
IDF值越高,说明词越稀有,越重要。
-
词频饱和部分:
这一部分用来处理词频的饱和问题,公式是:
-
词频: 是词 在文档 中的词频;
-
饱和参数: 是一个正参数,控制词频(TF)对文档得分的影响,通常取值在 到 之间通常能获得较好的结果。
-
文档长度归一化(惩罚长文档): :
这一部分用来处理文档长度的影响。
公式中的 表示文档 的长度 与整个文档集合的平均文档长度 的比值。
它使用一个参数 来控制文档长度归一化的程度。参数 通常取值在 到 之间。当 时,不进行文档长度归一化; 当 时,进行完全的文档长度归一化;
拆完会发现,BM25 的结构就是三块:
-
IDF:筛掉没用的高频词 -
TF 饱和:限制关键词堆砌带来的加分 -
文档长度归一化:短文优先、长文降权
整个公式就是在模仿我们人类“找关键词”、“判断重点”、“嫌弃水词多”的一系列直觉,只不过变成了数学语言。
4. 例子:计算BM25分数
为了更好地说明 BM25 相较于 TF-IDF 在处理文档长度和词频饱和度方面的优势,我们使用一个包含 3 篇文档的文档集合进行演示。
为了简化,我们假设这些文本已经经过了分词处理。
-
文档 A: 机器学习 模型 训练 算法 性能 (长度 5)
-
文档 B: 深度学习 模型 神经网络 训练 大数据 算力 优化 性能 (长度 8)
-
文档 C: 算法 效率 优化 性能 (长度 4)
4.1 文档信息
-
总文档数
-
文档 A 长度
-
文档 B 长度
-
文档 C 长度
-
平均文档长度
4.2 假设查询 为:“模型 算法 性能”
查询中的词是 “模型”, “算法”, “性能”。
4.3 计算 IDF (使用 BM25 的 IDF 公式):
-
词“模型”出现在文档 A 和文档 B 中,
-
词“算法”出现在文档 A 和文档 C 中,
-
词“性能”出现在文档 A, 文档 B 和文档 C 中,
4.4 计算每个文档的 TF-IDF 分数
我们使用标准的 TF-IDF 公式:
词频 (TF):
TF-IDF 分数计算:
-
文档 A:
-
文档 B:
-
文档 C:
TF-IDF 排名结果:
根据 TF-IDF 分数,文档排名为:
文档 A (0.998) > 文档 B (0.528) = 文档 C (0.528)
在这个简单的 TF-IDF 计算中,文档 B 和文档 C 的得分相同,因为它们都包含查询中的两个词,且这些词的 IDF 值相同。TF-IDF 在这种形式下没有考虑文档长度对相关性的影响。
4.5 计算每个文档的 BM25 分数
假设 ,,
首先计算每个文档的长度归一化项:
-
文档 A (长度 5):
-
文档 B (长度 8):
-
文档 C (长度 4):
然后使用 BM25 公式计算每个词在文档中的贡献并求和:
文档 A (D_A, 软长度归一化项 ≈ 0.91):
-
词“模型” (TF=1, IDF≈0.47):
-
词“算法” (TF=1, IDF≈0.47):
-
词“性能” (TF=1, IDF≈0.058):
-
文档 B (D_B, 软长度归一化项 ≈ 1.31):
-
词“模型” (TF=1, IDF≈0.47):
-
词“算法” (TF=0): 0
-
词“性能” (TF=1, IDF≈0.058):
-
文档 C (D_C, 软长度归一化项 ≈ 0.78):
-
词“模型” (TF=0): 0
-
词“算法” (TF=1, IDF≈0.47):
-
词“性能” (TF=1, IDF≈0.058):
-
BM25 排名结果:
根据 BM25 分数,文档排名为:
文档 A (1.055) > 文档 C (0.609) > 文档 B (0.445)
4.6 和TF-IDF的对比分析:
通过这个新的例子,我们可以更清楚地看到 BM25 和 TF-IDF 在排名上的区别:
-
TF-IDF 排名: 文档 A (0.998) > 文档 B (0.528) = 文档 C (0.528)
-
BM25 排名: 文档 A (1.055) > 文档 C (0.609) > 文档 B (0.445)
尽管文档 B 和文档 C 都包含查询中的两个词,且这些词的 IDF 值相同,但 BM25 给予了文档 C 更高的分数,并将其排在了文档 B 的前面。这是因为:
文档长度归一化: 文档 C (长度 4) 比平均文档长度 (≈ 5.67) 短,BM25 的软长度归一化项 (≈ 0.78) 小于 1,这会提升文档 C 中词的贡献。文档 B (长度 8) 比平均文档长度长,软长度归一化项 (≈ 1.31) 大于 1,这会降低文档 B 中词的贡献。
这个例子有效地展示了 BM25 如何通过文档长度归一化来调整相关性分数,使其排名结果更符合“短文档如果包含查询词,可能更聚焦于该主题”的直觉,从而在信息检索中产生更精确的排名,这对于 RAG 系统召回高质量的相关文档至关重要。
5. BM25的优势
TF-IDF和BM25都是基于词频(TF)和逆文档频率(IDF)思想的文本检索算法,但在RAG场景中,BM25更加常用,因为它解决了TF-IDF在实际信息检索中的一些主要局限性,提供了更优越的排名效果。
BM25可以看作是TF-IDF的一个优化版本,它在TF的饱和度、文档长度归一化以及IDF的平滑处理上做得更好。
除了传统的基于关键词的检索方式( TF-IDF 和 BM25),现在很多 RAG 系统会使用基于向量的 dense 检索,但其实在一些实际场景中,BM25 依然非常有竞争力,特别是在以下几类任务中表现尤为出色:
-
文本长度比较短、术语明确的任务(比如产品问答):关键词信号本身已经很强,不需要复杂的语义建模; -
数据集规模不算大、或者对 embedding 的训练不够充分的情况下,BM25 往往能提供更稳定的基础检索效果; -
作为 hybrid 检索的一部分:很多高性能的 RAG 系统其实是“稀疏 + 稠密”结合的—用 BM25 提供关键词召回的精度,再用向量召回补充语义覆盖,最后融合排序(re-ranking)提升整体检索效果。
所以 BM25 在某些任务中非常实用,它也可以作为构建复杂多阶段检索系统的第一步。
6. 使用LangChain实现BM25检索
LangChain 提供了一个内置的 BM25 实现。以下是使用 LangChain 实现 BM25 检索的基本步骤:
# 安装必要的库
# pip install jieba rank_bm25
from langchain.schema import Document
from langchain_community.retrievers import BM25Retriever
import jieba
def chinese_tokenizer(text):
return list(jieba.cut(text))
# 示例文档内容
doc_contents = [
"人工智能 技术 发展 迅速 机器学习 人工智能 重要 分支",
"自然语言处理 人工智能 另一个 重要 分支 文本分析 自然语言处理 常见 应用",
"技术 发展 带来 了 很多 新的 应用 领域",
"这是一个关于自然语言处理的额外文档,包含更多文本分析的内容。"
]
# 将文档内容转换为 LangChain 的 Document 对象列表
documents = [Document(page_content=content) for content in doc_contents]
# 创建 BM25 检索器
# 默认情况下,k=4,即返回最相关的4个文档
# BM25Retriever默认分词器是针对英文的,需要传入中文分词器,避免分词错误导致检索排序不准
retriever = BM25Retriever.from_documents(documents, k=2, preprocess_func=chinese_tokenizer)
# 定义查询
query = "人工智能 技术 应用"
# 执行检索
relevant_docs = retriever.invoke(query)
# 打印检索结果
print(f"对于查询 '{query}', 检索到的相关文档:")
for i, doc in enumerate(relevant_docs):
print(f"n--- 文档 {i + 1} ---")
print(doc.page_content)
总结
总的来说,BM25 是一个兼具简单性、可解释性和高效性的文本相关性排序算法。
在 RAG 系统的检索阶段,它能在不依赖训练的前提下,快速地从海量文档中找到与查询最匹配的候选段落,为后续的生成模型提供更加聚焦的上下文。
BM25 可以独立使用,也可以和 dense 检索联合构建更强的 hybrid 检索器。