TF-IDF与BM25算法原理

1. TF-IDF原理

TF-IDF是一种统计方法,用以评估一字词对于一个文件集或一个语料库中的一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比,但同时会随着它在语料库中出现的频率成反比

  • 词频TF(item frequency):某一给定词语在该文本中出现次数。该数字通常会被归一化,以防止它偏向长文本,因为不管该词语重要与否,它在长文件中出现的次数很可能比在短文本中出现的次数更大。
    T F ( t ) = 单 词 t 在 文 章 中 出 现 次 数 该 文 章 的 总 词 数 TF(t)=\frac{单词t在文章中出现次数}{该文章的总词数} TF(t)=t
  • 逆向文件频率IDF(inverse document frequency):一个词语普遍重要性的度量。主要思想是:如果包含词条 t t t的文档越少, 则说明词条具有很好的类别区分能力,IDF越大。某一特定词语的IDF,可以由总文件数目除以包含该词语之文件的数目,再将得到的商取对数得到。
    I D F ( t ) = l o g ( 语 料 库 文 章 总 数 包 含 单 词 t 的 文 章 数 + 1 ) IDF(t)=log\left(\frac{语料库文章总数}{包含单词t的文章数+1}\right) IDF(t)=log(t+1)

所以可以得到:
T F − I D F ( t ) = T F ( t ) ∗ I D F ( t ) TF-IDF(t)= TF(t)*IDF(t) TFIDF(t)=TF(t)IDF(t)
可以看出TF-IDF与一个词在文档中出现的次数成正比,与该词在整个语料中该出现的次数成反比,TF-IDF倾向于过滤掉常见的词语,保留重要的词语

2. BM25原理介绍

BM25算法,通常用来作搜索相关性评分。一句话概况其主要思想:对Query进行语素解析,生成语素 q i q_i qi ;然后对于每个搜索结果D,计算每个语素 q i q_i qi与D的相关性得分,最后将 q i q_i qi相对于D的相关性得分进行加权求和,从而得到Query与D的相关性得分。

BM25算法的一般性公式如下:
Score ⁡ ( Q , d ) = ∑ i n W i ⋅ R ( q i , d ) \operatorname{Score}(Q, d)=\sum_{i}^{n} W_{i} \cdot R\left(q_{i}, d\right) Score(Q,d)=inWiR(qi,d)其中,Q表示Query, q i q_i qi表示Q解析之后的一个语素(对中文而言,我们可以把对Query的分词作为语素分析,每个词看成语素 q i q_i qi);d表示一个搜索结果文档; W i W_i Wi表示语素 q i q_i qi的权重; R ( q i , d ) R(q_i, d) R(qi,d)表示语素 q i q_i qi与文档d的相关性得分。

下面我们来看如何定义 W i W_i Wi。判断一个词与一个文档的相关性的权重,方法有多种,较常用的是IDF。这里以IDF为例,公式如下:
I D F ( q i ) = log ⁡ N − n ( q i ) + 0.5 n ( q i ) + 0.5 I D F\left(q_{i}\right)=\log \frac{N-n\left(q_{i}\right)+0.5}{n\left(q_{i}\right)+0.5} IDF(qi)=logn(qi)+0.5Nn(qi)+0.5
其中,N为全部文档数, n ( q i ) n(q_i) n(qi)为包含了单词 q i q_i qi 的文档数。

根据IDF的定义可以看出,对于给定的文档集合,包含单词 q i q_i qi的文档数越多, q i q_i qi的权重则越低。也就是说,当很多文档都包含了 q i q_i qi时, q i q_i qi的区分度就不高,因此使用 q i q_i qi来判断相关性时的重要度就较低

再来看语素 q i q_i qi 与文档d的相关性得分 R ( q i , d ) R(q_i, d) R(qi,d)。首先来看BM25中相关性得分的一般形式:
R ( q i , d ) = f i ⋅ ( k 1 + 1 ) f i + K ⋅ q f i ⋅ ( k 2 + 1 ) q f i + k 2 K = k 1 ⋅ ( 1 − b + b ⋅ d l avg ⁡ ( d l ) ) \begin{gathered} R\left(q_{i}, d\right)=\frac{f_{i} \cdot\left(k_{1}+1\right)}{f_{i}+K} \cdot \frac{q f_{i} \cdot\left(k_{2}+1\right)}{q f_{i}+k_{2}} \\\\ K=k_{1} \cdot\left(1-b+b \cdot \frac{d l}{\operatorname{avg}(d l)}\right) \end{gathered} R(qi,d)=fi+Kfi(k1+1)qfi+k2qfi(k2+1)K=k1(1b+bavg(dl)dl)
其中, k 1 , k 2 , b k_1, k_2, b k1,k2,b为调节因子,通常根据经验设置,一般 k 1 = 2 , b = 0.75 k1=2,b=0.75 k1=2b=0.75 f i f_i fi q i q_i qi在d中的出现的频次, q f i qf_i qfi q i q_i qi在Query中的出现频次。 d l dl dl为文档d的长度, a v g d l avgdl avgdl为所有文档的平均长度。由于绝大部分情况下, q i q_i qi在Query中只会出现一次,即 q f i = 1 qf_i=1 qfi=1,因此公式可以简化为:
R ( q i , d ) = f i ⋅ ( k 1 + 1 ) f i + K R\left(q_{i}, d\right)=\frac{f_{i} \cdot\left(k_{1}+1\right)}{f_{i}+K} R(qi,d)=fi+Kfi(k1+1)

从K的定义中可以看到,参数b的作用是调整文档长度对相关性影响的大小。b越大,文档长度的对相关性得分的影响越大,反之越小。而文档的相对长度越长,K值将越大,则相关性得分会越小。这可以理解为,当文档较长时,包含 q i q_i qi的机会越大,因此,同等 f i f_i fi的情况下,长文档与 q i q_i qi的相关性应该比短文档与 q i q_i qi的相关性弱。

从BM25的公式可以看到,通过使用不同的语素分析方法、语素权重判定方法,以及语素与文档的相关性判定方法,我们可以衍生出不同的搜索相关性得分计算方法,这就为我们设计算法提供了较大的灵活性。更多bm25算法的变体见论文 Trotmam et al, Improvements to BM25 and Language Models Examined

3. BM25的简单实现

下面的实现是为了能更好的理解上文中的公式,更多bm25及其实现可参考 ddupfun/rank_bm25

import math
import jieba
from nltk import corpus

class BM25(object):

    def __init__(self, docs):
        self.D = len(docs)
        self.avgdl = sum([len(doc)+0.0 for doc in docs]) / self.D
        self.docs = docs
        self.f = []  # 列表的每一个元素是一个dict,dict存储着一个文档中每个词的出现次数
        self.df = {} # 存储每个词及出现了该词的文档数量
        self.idf = {} # 存储每个词的idf值
        self.k1 = 1.5
        self.b = 0.75
        self.init()

    def init(self):
        for doc in self.docs:
            tmp = {}
            for word in doc:
                tmp[word] = tmp.get(word, 0) + 1  # 存储每个文档中每个词的出现次数
            self.f.append(tmp)
            for k in tmp.keys():
                self.df[k] = self.df.get(k, 0) + 1
        for k, v in self.df.items():
            self.idf[k] = math.log(self.D-v+0.5)-math.log(v+0.5)

    def sim(self, doc, index):
        score = 0
        for word in doc:
            if word not in self.f[index]:
                continue
            d = len(self.docs[index])
            score += (self.idf[word]*self.f[index][word]*(self.k1+1)
                      / (self.f[index][word]+self.k1*(1-self.b+self.b*d
                                                      / self.avgdl)))
        return score

    def simall(self, doc):
        scores = []
        for index in range(self.D):
            score = self.sim(doc, index)
            scores.append(score)
        return scores

if __name__ == '__main__':
    corpus = ["自然语言处理是计算机科学领域与人工智能领域中的一个重要方向。",
        "它研究能实现人与计算机之间用自然语言进行有效通信的各种理论和方法。",
        "自然语言处理是一门融语言学、计算机科学、数学于一体的科学。",
        "因此,这一领域的研究将涉及自然语言,即人们日常使用的语言",
        "所以它与语言学的研究有着密切的联系,但又有重要的区别。",
        "自然语言处理并不是一般地研究自然语言",
        "而在于研制能有效地实现自然语言通信的计算机系统",
        "特别是其中的软件系统。因而它是计算机科学的一部分。"
        ]
    doc = []
    for sent in corpus:
        words = list(jieba.cut(sent))
        doc.append(words)
    s = BM25(doc)
    print("相似度:", s.simall(['自然语言', '计算机科学', '领域', '人工智能', '领域']))

输出:

  • s.f : 每个字的频次 列表的每一个元素是一个dict,dict存储着一个文档中每个词的出现次数
[{'中': 1, '计算机科学': 1, '领域': 2, '一个': 1, '人工智能': 1, '方向': 1, '自然语言': 1}, 
{'之间': 1, '方法': 1, '理论': 1, '通信': 1, '计算机': 1, '人': 1, '研究': 1, '自然语言': 1}, 
{'融': 1, '一门': 1, '一体': 1, '数学': 1, '科学': 1, '计算机科学': 1, '语言学': 1, '自然语言': 1}, 
{}, 
{'领域': 1, '这一': 1, '涉及': 1, '研究': 1, '自然语言': 1}, 
{'日常': 1, '语言': 1}, 
{'语言学': 1, '研究': 1}, 
{'区别': 1}, 
{'研究': 1, '自然语言': 2}, 
{'通信': 1, '计算机系统': 1, '研制': 1, '在于': 1, '自然语言': 1}, 
{'软件系统': 1, '特别': 1}, 
{'一部分': 1, '计算机科学': 1}]
  • s.df 存储每个词及出现了该词的文档数量
{
    '在于': 1,  
    '人工智能': 1,  
    '语言': 1,  
    '领域': 2,  
    '融': 1, 
    '日常': 1, 
    '人': 1, 
    '这一': 1, 
    '软件系统': 1, 
    '特别': 1, 
    '数学': 1, 
    '通信': 2, 
    '区别': 1, 
    '之间': 1, 
    '计算机科学': 3, 
    '科学': 1, 
    '一体': 1, 
    '方向': 1, 
	...
}

s.idf 存储每个词的idf值

{
    '在于': 2.0368819272610397, 
    '一部分': 2.0368819272610397, 
    '一个': 2.0368819272610397, 
    '语言': 2.0368819272610397, 
    '领域': 1.4350845252893225, 
    '融': 2.0368819272610397, 
    '日常': 2.0368819272610397, 
    '人': 2.0368819272610397, 
    '这一': 2.0368819272610397, 
    '软件系统': 2.0368819272610397, 
    '特别': 2.0368819272610397, 
    '数学': 2.0368819272610397, 
    '通信': 1.4350845252893225, 
    '区别': 2.0368819272610397, 
    '之间': 2.0368819272610397, 
    '一门': 2.0368819272610397, 
    '科学': 2.0368819272610397, 
	...
}
  • 最后print输出相似度如下
相似度: [3.8602268757499467, -0.830879517415162, -0.49187661205157857, 0.9054721922953262, 0, -1.589034350592673, -1.064792812640545, 0.4705598548557856]

参考

TF-IDF 原理与实现
TF-IDF原理理解
文本相似度-bm25算法原理及实现

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值