基于词袋的概率检索模型 BM25 详解

BM25(Best Matching 25)是基于词袋的概率检索模型,目前是搜索引擎、ES/OpenSearch 全文检索默认的相关性打分算法,用来计算查询词 Q 和文档 D 的相关性分数。
核心思想:文档 D 与查询词 Q 的相关性分数越高,则表示越相似(相关)。
前置基础 - TF 和 IDF
TF(Term Frequency):词频,即词 t 在文档 D 中出现的次数。
$$ tf(t, D) = count(t, D) $$- $count(t, D)$:词 t 在文档 D 中出现的次数。
IDF(Inverse Document Frequency):逆文档频率,衡量词稀有度。词越罕见,IDF 越大。
$$ idf(t) = ln(\frac{N - n_t + 0.5}{n_t + 0.5} + 1) $$- $N$:全部文档总数
- $n_t$:包含 t 的文档数
- $0.5$:平滑系数,防止分母为 0
BM25 公式
$$ bm25(Q, D) = \sum_{t \in Q} (idf(t) \cdot \frac{tf(t, D) \cdot (k_1 + 1)}{tf(t, D) + k_1 \cdot (1 - b + b \cdot \frac{\left | D \right | }{avgdl})}) $$- $Q$:查询词集,包含多个查询词 t
- $\left | D \right | $:文档 D 的词数
- $avgdl$:全部文档的平均词数
- $k_1$:词频饱和系数,用于调整词频对相关性的影响,$k_1$ 越大,词频提升对分数影响越强
- $b$:文档长度因子,用于调整文档长度对相关性的影响,$b$ 越大,文档长度对分数影响越强
$k_1$ 在 Elasticsearch 中默认值为 1.2,$b$ 默认值为 0.75。
代码示例
实现 tf 和 idf 函数
| |
实现简单分词并处理 5 个模拟文档
| |
simple_cut:简单分词,使用 jieba 分词器punctuation:标点符号集合,用于过滤掉标点符号TokenizedDocument:分词后的文档,包含原始文本和分词后的词列表_compute_token_freq:计算每个词的文档频率,用于计算 IDFtoken_count:返回文档中词的数量tf:计算词频 (term frequency)contains:检查文档是否包含指定词
以下是分词结果:
| |
bm25 计算、文档检索
| |
bm25:计算 BM25 分数search_documents:检索文档,返回分数最高的 topk 个文档
示例调用
结果输出:
肉眼评价的话,对比其它三个文档,结果中的两个文档与查询词(人工智能、学习)更契合。
小结
BM25 能成为 ES/OpenSearch 全文检索的默认相关性算法,在于它用三个简单而有效的机制,把词频、罕见度和文档长度组合成了一个可控的打分公式:
- IDF 稀有度加权:罕见词比常见词更能代表文档主题,因此获得更高权重
- 词频饱和($k_1$):词频对分数的贡献边际递减,避免了靠堆砌关键词刷分
- 文档长度归一化($b$):对长文档做惩罚,避免长文因词多而天然占优
BM25 是词袋模型,只统计词是否出现、出现多少次,不理解词序与语义——“苹果手机"与"手机苹果"对它等价,也无法召回同义词。这决定了它更适合作为召回层(快速从海量文档中筛出候选集),再交由向量检索或 rerank 模型做语义层面的精排,这也是当前 RAG 系统中BM25 + 向量检索混合召回成为主流的原因。