BM25算法详解

绫波波 发布于 2024-03-15 1225 次阅读


本文最后更新于2024年3月19日,已超过 1 年没更新!内容可能已失效,请评论区反馈,谢谢啦~

简介

BM25是一种用于信息检索的算法,可以用于衡量文档的相关性。该算法最早由Robertson等人于1995年提出,是基于Okapi TF-IDF算法的改进版本,旨在解决Okapi TF-IDF算法的一些不足。其被广泛应用于信息检索领域的排名函数,用于估计文档D和用户查询Q之间的相关性。他是一种基于概率检索框架的改进。特别是在处理长文档和短查询时表现出色。BM25的核心思想是基于词频(TF)和逆文档频率(IDF),同时还引入了文档的长度信息来计算文档D和查询Q之间的相关性。目前被广泛用于的搜索引擎ES就内置了BM25算法进行全文检索。

基本公式

BM25算法的基本公式如下:

其中:

Score(D,Q)是文档D和查询Q的相关性得分。
q_i是查询中的第i个词。
f(q_i,D)是词q_i在文档D中的频率。
IDF(q_i)是词q_i的逆文档频率。
|D|是文档|D|的长度。
avgdl是所有文档的平均长度
k_1b是可调的参数,通常k_1在1.2到2之间,b通常设置为0.75。

IDF的计算方法为:

N是文档集合中的文档总数
n(q_i)是包含词q_i的文档数

公式解释

  • 词频(f(q_i,D)):这是查询中的词q_i在文档D中出现的频率。词频是很亮一个词在文档中重要性的基本指标。词频越高,这个词在文档的重要性通常越大。
  • 逆文档频率(IDF(q_i)):逆文档频率是衡量一个词对于整个文档集合的独特性或信息量的指标。它是由整个文档集合中包含该词的文档数量决定的。一个词在很多文档中出现,其IDF值就会低,反之则高。这意味着罕见的词通常有更高的IDF值,从而在相关性评分中拥有更大的权重。
  • 文档长度(|D|):这是文档D中的词汇数量。文档长度用于调整词频的影响,因为较长的文档可能仅因为他们的长度就有更高的词频
  • 平均文档长度(avgdl):这是整个文档集合中所有文档长度的平均值。它用于标准化不同文档的长度,可以公平比较不同长度的文档。
  • 可调参数(k1和b)
    • k_1是一个正系数,用于控制词频的饱和度。较高的k_1值意味着词频对评分的影响更大。
    • b用于控制文档长度对评分影响的参数,取值在0到1之间。当b=1时,文档长度的影响最大;当b=0时,文档长度不影响评分。

BM25算法优缺点

优点

  • 与传统的TF-IDF算法相比,BM25算法可以考虑文档长度,有效避免文档长度的影响。
  • BM25算法的常数k_1b可以根据不同领域的数据进行调整,以得到最佳性能。这也使得BM25算法能够适用于不同领域的数据。

缺点

  • 对数据质量要求较高:BM25算法需要干净、高质量的数据,因为它需要准确的检测查询项,并计算文档和查询条件之间的匹配程度。
  • BM25算法效果受到k_1b的影响。这些参数必须经过调整才能获得最佳性能,但是这也意味着BM25算法对参数敏感。

示例代码

import math
from collections import Counter

class BM25:
    def __init__(self,docs,k1=1.5,b=0.75):
        """
        BM算法构造器
        :param docs:分词后的文档列表,每一个文档是一个包含词汇的列表
        :param k1:BM25算法中的调节参数k1
        :param b:BM25算法中的调节参数b
        """

        self.docs = docs
        self.k1 = k1
        self.b = b
        self.doc_len = [len(doc) for doc in docs] #计算每个文档的长度
        self.avgdl=sum(self.doc_len)/len(docs) #计算所有文档的平均长度
        self.doc_freqs=[] #存储每个文档的词频
        self.idf={} #存储每个词的逆文档频率
        self.initialize()

    def initialize(self):
        """
        初始化方法,计算所有词的逆文档频率
        """
        df = {} #用于存储每个词在不同文档中出现的频率

        for doc in self.docs:
            #为每个文档创建一个词频统计
            self.doc_freqs.append(Counter(doc))
            #更新df值
            for word in set(doc):
                df[word] = df.get(word,0)+1
        # 计算每个词的IDF值
        for word,freq in df.items():
            self.idf[word] = math.log((len(self.docs) - freq + 0.5) / (freq + 0.5) + 1)

    def score(self,doc,query):
    """
    计算文档与查询的BM25得分
    :param doc: 文档的索引
    :param query: 查询词列表
    :return: 该文档与查询的相关性得分
    """
    score=0,0

    for word in query:
        if word in self.doc_freqs[doc]:
            freq=self.doc_freqs[doc][word] #词在文档中的频率
            # 应用BM25计算公式
            score += (self.idf[word] * freq * (self.k1 + 1)) / (freq + self.k1 * (1 - self.b + self.b * self.doc_len[doc] / self.avgdl))
    return score

# 示例文档集和查询
docs = [["the", "quick", "brown", "fox"],
        ["the", "lazy", "dog"],
        ["the", "quick", "dog"],
        ["the", "quick", "brown", "brown", "fox"]]
query = ["quick", "brown"]

# 初始化BM25模型并计算得分
bm25 = BM25(docs)
scores = [bm25.score(i, query) for i in range(len(docs))]

## query和文档的相关性得分:
## sores = [1.0192447810666774, 0.0, 0.3919504878447609, 1.2045355839511414]
  • alipay_img
  • wechat_img
Talk is cheap, show me the code.
最后更新于 2024-03-19