4

从TF-IDF 到BM25, BM25+,一文彻底理解文本相关度 - JadePeng

 7 months ago
source link: https://www.cnblogs.com/xiaoqi/p/18003267/bm25
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

相关性描述的是⼀个⽂档和查询语句匹配的程度。我们从搜索引擎召回时,肯定希望召回相关性高的数据,那么如何来量化相关度呢。

首先,我们定义,一个文档doc,由多个词语 term 组成。

最早,通过最简单的TF-IDF来衡量。

TF-IDF#

朴素的思想,相关度应该是词语权重、文档权重的融合。

  • 词频 TF(Term Frequency)朴素的理解,一个词语term在一个doc中出现的频率越高,那么doc的相关性也越高。

$$ \text{TF}(t, d) = \frac{\text{词t在文档d中的出现次数}}{\text{文档d的总词数}} $$

  • 逆向文档频率 IDF(Inverse Document Frequency),通俗的解释每个检索词在索引中出现的频率,频率越高,相关性越低。 比如"的“ 这样的词语,可能每个doc都有,相反一些专业术语出现的文档数很少,这些文档的权重就很高。

$$ \text{IDF}(t, D) = \log\left(\frac{\text{文档集合D的总文档数}}{\text{包含词t的文档数} + 1}\right) $$

  • TF和IDF结合起来计算一个词的TF-IDF值,公式为:

$$ \text{TF-IDF}(t, d, D) = \text{TF}(t, d) \times \text{IDF}(t, D) $$

TF-IDF的优点在于简单有效,但它也有一些局限性,例如不考虑词序和上下文信息。

BM25(Best Matching 25)#

BM25是一种用于文本信息检索的算法,是BM(Best Matching)系列中的一员。它在信息检索领域中广泛应用,特别是在搜索引擎中用于排序搜索结果。整体而言BM25 就是对 TF-IDF 算法的改进**,以下是BM25算法的公式:

$$ \text{BM25}(D, Q) = \sum_{i=1}^{n} \frac{{(k+1) \cdot f_i}}{{f_i + k \cdot (1 - b + b \cdot \frac{{\lvert D \rvert}}{{\text{avgdl}}})}} \cdot \log\left(\frac{{N - n_i + 0.5}}{{n_i + 0.5}}\right) $$

  • n:查询中的term数
  • N:文档集中的文档数
  • fi​:文档中term i的频率
  • ni​:包含术语 term i 的文档数
  • DL:文档长度(term数)
  • AVG_DL:文档集的平均长度(term数量)

对于 TF-IDF 算法,TF(t) 部分的值越大,整个公式返回的值就会越大,如果一个doc文章很长,词语很多,tf频率就会很大。BM25 针对这个问题做了优化,通过b参数,对文档长度进行打压,随着TF(t) 的逐步加大,该算法的返回值会趋于一个数值。

38465-20240202152832033-269066170.png

BM25的优势在于它对于长文本和短文本的处理更为灵活,并且能够适应不同查询的特征。这些可调整的参数使得BM25能够通过调整来适应不同的信息检索场景。

  • b 参数 ,b 默认 0.75(经验值),主要是对长文档做惩罚,如果不希望文档长度更大的相似度搜索更好,可以把 b 设置得更大,如果设置为 0,文档的长度将与分数无关。从下图可以看到,b=0时,L与分数无关,b=1时,L越大,分数打压越厉害。

    38465-20240202152841273-784095378.png
  • k 参数, 默认值 1.2,会影响词语在文档中出现的次数对于得分的重要性,如果希望词语出现次数越大,文档越相关,这个参数可以设置更大。

BM25+#

BM只考虑了term和doc的维度,对query 里term的频率没有考虑,BM25+(Best Matching 25 Plus)正是基于这一点,来改进BM25算法,以下是BM25+的公式,以及每个参数的含义:

$$ \text{BM25+}(D, Q) = \sum_{i=1}^{n} \frac{{(k_1+1) \cdot f_i \cdot (k_3+1) \cdot qf}}{{(f_i + k_1 \cdot (1 - b + b \cdot \frac{{\lvert D \rvert}}{{\text{avgdl}}})) \cdot (k_3 + qf)}} \cdot \log\left(\frac{{N - n_i + 0.5}}{{n_i + 0.5}}\right) $$

相比BM25,增加k3 和qf,
其中:

  • qf:查询中词项的频率(Query Term Frequency)
  • k3:控制查询中词项频率对得分的影响程度的参数,下面是不同的k3,在不同的query weight情况下,对分数的影响

![[Pasted image 20240202151809.png]]

  • 注意k1就是BM25里的k

这里的qf,我们可以当做term weight来使用,训练term weight模型,来对重点的term做激励。

谷歌的End-to-End Query Term Weighting#

谷歌在2023年发布了一篇论文,介绍如何端到端学习term weighting。

背景是term based recall的方法相对于embedding recall来说很繁琐,好处就是时延低且对基建要求低,向量召回会遇到分不清楚query中哪个词是核心词的问题,导致召回出现了非核心词的结果。

38465-20240202152852893-1548577569.png

在左侧,展示了传统的信息检索(IR),所有的term都是默认的权重,在右侧,我们插入了一个BERT模型来评估term的权重,BM25+ 打分时将这个weighting作为query freq,就是上面说的qf。

可以看谷歌论文的打分:

38465-20240202152904043-1457046708.png

上面的$f(T_i, T,w)$ 就是模型学习的权重。

附录#

可视化不同参数变化,BM25分数的变化

import numpy as np
import matplotlib.pyplot as plt


def calculate_bm25(tf, term_weight, corpus_freq, total_docs,
                   k1=1.5, k3=8, doc_len=100, avg_doc_len=120, b=0.75):
    idf = np.log((total_docs - corpus_freq + 0.5) / (corpus_freq + 0.5) + 1)  # IDF计算
    K = k1 * ((1.0 - b) + b * doc_len / avg_doc_len + tf)
    tf_term = tf * (k3 + 1) * term_weight / (K * (k3 + term_weight))  # TF计算

    return idf * tf_term  # BM25计算


def visualize_bm25_scores_k3(term_weight_values, tf_values, corpus_freq, total_docs, k1=1.5):
    # Plotting
    plt.figure(figsize=(12, 8))

    for k3 in np.linspace(2, 10, 5):
        scores = [calculate_bm25(5, query_weight, corpus_freq, total_docs, k1=k1, k3=k3)
                  for query_weight in term_weight_values]
        plt.plot(term_weight_values, scores, label=f'k3={k3}')

    plt.title('BM25 Scores with Different Query_freq Values (b=0)')
    plt.xlabel('Query weight')
    plt.ylabel('BM25 Score')
    plt.legend()
    plt.show()

def visualize_bm25_scores_b(term_weight_values, tf_values, corpus_freq, total_docs, k1=1.5):
    # b 用于惩罚文档长度
    plt.figure(figsize=(12, 8))

    doc_lens = np.linspace(100, 1000, 10)
    avg_doc_len = 100
    L = [doc_len /avg_doc_len for doc_len in doc_lens]
    for b in [0, 0.5, 0.75, 1.0]:
        scores = [calculate_bm25(5, 1, corpus_freq, total_docs, k1=k1, b=b, doc_len=doc_len)
                  for doc_len in doc_lens]
        plt.plot(L, scores, label=f'b={b}')

    plt.title('BM25 Scores with Different b')
    plt.xlabel('L(doc_length/avg_doc_length')
    plt.ylabel('BM25 Score')
    plt.legend()
    plt.show()



term_weight_values = np.linspace(0.1, 10, 10)  # query weight
corpus_freq = 500  # 包含term的文档数量,文档频率
total_docs = 5000000  # 总文档数量
tf_values = list(range(1, 101))
visualize_bm25_scores_k3(term_weight_values, tf_values, corpus_freq, total_docs)

visualize_bm25_scores_b(term_weight_values, tf_values, corpus_freq, total_docs)

欢迎关注作者微信公众号, 一起交流软件开发:

欢迎关注作者微信公众号

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK