從TF-IDF 到BM25, BM25+,一文徹底理解文本相關度

相關性描述的是⼀個⽂檔和查詢語句匹配的程度。我們從搜索引擎召回時,肯定希望召回相關性高的數據,那麼如何來量化相關度呢。

首先,我們定義,一個文檔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) $$

  • D:文檔
  • Q:查詢
  • n:查詢中的term數
  • N:文檔集中的文檔數
  • fi​:文檔中term i的頻率
  • ni​:包含術語 term i 的文檔數
  • DL:文檔長度(term數)
  • AVG_DL:文檔集的平均長度(term數量)

對於 TF-IDF 算法,TF(t) 部分的值越大,整個公式返回的值就會越大,如果一個doc文章很長,詞語很多,tf頻率就會很大。BM25 針對這個問題做了優化,通過b參數,對文檔長度進行打壓,隨着TF(t) 的逐步加大,該算法的返回值會趨於一個數值。

BM25的優勢在於它對於長文本和短文本的處理更爲靈活,並且能夠適應不同查詢的特徵。這些可調整的參數使得BM25能夠通過調整來適應不同的信息檢索場景。

  • b 參數 ,b 默認 0.75(經驗值),主要是對長文檔做懲罰,如果不希望文檔長度更大的相似度搜索更好,可以把 b 設置得更大,如果設置爲 0,文檔的長度將與分數無關。從下圖可以看到,b=0時,L與分數無關,b=1時,L越大,分數打壓越厲害。

  • 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中哪個詞是核心詞的問題,導致召回出現了非核心詞的結果。

谷歌論文

在左側,展示了傳統的信息檢索(IR),所有的term都是默認的權重,在右側,我們插入了一個BERT模型來評估term的權重,BM25+ 打分時將這個weighting作爲query freq,就是上面說的qf。

可以看谷歌論文的打分:

上面的$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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章