跳转至

BM25算法

一、BM25 是什么,它在 RAG 系统里做什么?

我们先从宏观上理解一下。

  • 它的名字叫 BM25,全称是 "Best Match 25",你可以把它理解成一个非常经典的、经过了20多次迭代优化后诞生的“最佳匹配”算法。
  • 它的本质是一个排名函数评分函数

想象一下,在 RAG 流程中,我们根据用户的提问(Query),需要在知识库(一大堆文档)里找到最相关的几篇文档,然后交给语言模型去生成答案。

问题来了:怎么判断哪些文档“最相关”呢?

BM25 就是解决这个问题的工具。它会为每一篇文档都打一个“相关性分数”。分数越高的文档,就代表它和用户的提问越相关,我们也就越应该把它排在前面。

二、BM25 的核心思想

核心思想是根据词项的出现频率来评估文档对于查询的相关性。

BM25 主要考虑了三个核心要素来计算这个分数:

  1. 查询词在文档中出现的次数(TF - Term Frequency)。
  2. 查询词在所有文档中的普遍程度(IDF - Inverse Document Frequency)。
  3. 文档的长度。

简单来说,一个词在某篇文档里出现次数越多,这篇文档的相关性可能就越高。但是,我们还需要考虑这个词本身的重要性以及文档的长度,不能一概而论。BM25 就是一个把这三者巧妙结合起来的数学公式。

三、庖丁解牛:一步步拆解 BM25 公式

我们再来看一下这个公式:

\[ Score(D, Q) = \sum_{i=1}^{n} IDF(q_i) \cdot \frac{f(q_i, D) \cdot (k_1 + 1)}{f(q_i, D) + k_1 \cdot (1 - b + b \cdot \frac{|D|}{avgdl})} \]

别怕,我们把它拆成三块来看:

第一部分:\(IDF(q_i)\) - 查询词的重要性权重

  • \(q_i\) 指的是你查询语句中的第 \(i\) 个词。比如查询是“人工智能的应用”,那 \(q_1\) 就是“人工智能”,\(q_2\) 就是“的”,\(q_3\) 就是“应用”。(当然,像“的”这种停用词通常会预先被去掉)。
  • \(IDF(q_i)\) 是“逆文档频率”(Inverse Document Frequency)。它的作用是给每个查询词一个权重。

这个思想很直观:一个词如果在所有文档里都很少出现(比如“量子纠缠”),那它一旦在某篇文档里出现,这篇文档就很可能是我们想要的,所以这个词的权重就应该很高。相反,一个词如果到处都是(比如“技术”、“一个”),那它就没什么区分度,权重自然就应该很低。

补充

IDF 常见的计算方式:

\[ IDF(q_i) = \log\left(\frac{N - n(q_i) + 0.5}{n(q_i) + 0.5} + 1\right) \]
  • \(N\) 是文档总数。
  • \(n(q_i)\) 是包含词 \(q_i\) 的文档数量。

包含这个词的文档 \(n(q_i)\) 越多,分母就越大,算出来的 IDF 值就越小。这就通过数学公式实现了我们刚才说的那个直观想法。

第二部分:那个复杂的大分数 - 词频与文档长度的平衡艺术

这部分是 BM25 的精髓,我们把它也拆开看。

\[ \frac{f(q_i, D) \cdot (k_1 + 1)}{f(q_i, D) + k_1 \cdot (1 - b + b \cdot \frac{|D|}{avgdl})} \]
  • \(f(q_i, D)\):这个很简单,就是查询词 \(q_i\) 在文档 \(D\) 中出现的次数(词频)。
  • \(|D|\):文档 \(D\) 的长度(比如包含多少个词)。
  • \(avgdl\):所有文档的平均长度。

这部分引入了两个讲义中提到的重要参数 \(k_1\)\(b\),它们是 BM25 的“调节旋钮”。

旋钮1:\(k_1\) (通常在 1.2 到 2.0 之间)

\(k_1\) 用来控制“词频饱和度”。什么意思呢?

想象一下,一个词在一篇文档里从出现 1 次变成出现 3 次,相关性可能会大大增加。但如果它从 30 次变成 32 次,相关性的提升可能就没那么明显了。也就是说,词频带来的收益不是无限线性增长的,它会“饱和”。\(k_1\) 就是用来调节这个饱和曲线的。\(k_1\) 值越小,饱和的速度越快,词频的微小差异带来的影响就越小。

旋钮2:\(b\) (通常为 0.75)

\(b\) 用来控制“文档长度”的影响力。

这个也很好理解:一个 100 字的短评里出现了“好吃”,和一个 5000 字的美食报告里出现了“好吃”,前者的重要性显然更高。BM25 认为,长文档有天然的优势(更容易包含查询词),所以需要对它们进行一些“惩罚”。

  • 当文档 \(D\) 的长度 \(|D|\) 大于平均长度 \(avgdl\) 时,\(\frac{|D|}{avgdl}\) 大于 1,分母会变大,最终得分会变小(惩罚)。
  • 当文档 \(D\) 的长度 \(|D|\) 小于平均长度 \(avgdl\) 时,\(\frac{|D|}{avgdl}\) 小于 1,分母会变小,最终得分会变高(鼓励)。

参数 \(b\) 就是控制这个惩罚/鼓励力度的。如果 \(b=0\),那文档长度就完全不起作用了。如果 \(b=1\),则完全用文档长度来做调节。

第三部分:\(\sum_{i=1}^{n}\) - 汇总所有词的得分

这个 \(\sum\) 符号是求和的意思。因为一个查询通常包含多个词(比如 "RAG 系统 培训"),BM25 会为 "RAG"、"系统"、"培训" 这三个词分别计算上面两大块(IDF 和那个大分数)的乘积,最后再把这三个词的得分加起来,就得到了这篇文档相对于整个查询的总分 \(Score(D, Q)\)

四、总结与梳理

好了,现在我们把所有零件都组装起来,重新梳理一下 BM25 的工作流程:

  1. 接收任务:拿到一个查询 \(Q\)(比如 "人工智能的应用")和一篇待评分的文档 \(D\)
  2. 分词:把查询 \(Q\) 拆成一个个的词 \(q_i\) ("人工智能", "应用")。
  3. 逐词计算:对每一个词 \(q_i\)
    a. 计算重要性:计算这个词的 \(IDF(q_i)\) 值。这个词越稀有,值越高。
    b. 计算词频相关性:计算那个大分数。它会考虑词 \(q_i\) 在文档 \(D\) 中出现的次数,并用 \(k_1\) 来防止词频影响过大,同时用 \(b\) 和文档长度来对得分进行校正。
    c. 相乘:将 a 和 b 的结果乘起来,得到词 \(q_i\) 对总分的贡献。
  4. 累加总分:把所有查询词("人工智能"、"应用")的得分加起来,就是文档 \(D\) 的最终 BM25 分数。
  5. 排序:对知识库里所有文档都执行一遍这个流程,得到一个分数列表,然后按分数从高到低排序,排名最靠前的就是与查询最相关的文档。