【自然语言处理】文本相似度算法:TF-IDF与BM25

文本相似度算法:TF-IDF与BM25

1.TF-IDF

TF(Term Frequency)是指归一化后的词频,IDF(Inverse Document Frequency)是指逆文档频率。给定一个文档集合 D D D,有 d 1 , d 2 , d 3 , . . . . . . , d n ∈ D d_1, d_2, d_3, ......, dn∈D d1,d2,d3,......,dnD。文档集合总共包含 m m m 个词(注:一般在计算 TF−IDF 时会去除如 “的” 这一类的停用词),有 w 1 , w 2 , w 3 , . . . . . . , w m ∈ W w_1, w_2, w_3, ......, w_m∈W w1,w2,w3,......,wmW。我们现在以计算词 w i w_i wi 在文档 d j d_j dj 中的 TF−IDF 值为例。
T F = f r e q ( i , j ) l e n ( d j ) TF=\frac{freq(i,j)}{len(d_j)} TF=len(dj)freq(i,j)其中 f r e q ( i , j ) freq(i,j) freq(i,j) w i w_i wi d j d_j dj 中出现的频率, l e n ( d j ) len(d_j) len(dj) d j d_j dj 长度,即文档的总词数。

TF 只能描述词在文档中的频率,但假设现在有个词为 “我们”,这个词可能在文档集 D D D 中每篇文档中都会出现,并且有较高的频率。那么这一类词就不具有很好的区分文档的能力,为了降低这种通用词的作用,引入了 IDF。
I D F = l o g ( l e n ( D ) n ( i ) + 1 ) IDF=log(\frac{len(D)}{n(i)+1}) IDF=log(n(i)+1len(D))其中 l e n ( D ) len(D) len(D) 表示文档集合 D D D 中文档的总数, n ( i ) n(i) n(i) 表示含有 w i w_i wi 这个词的文档的数量。如果一个词越常见,那么分母就越大,逆文档频率就越小越接近 0。分母之所以要加 1,是为了避免分母为 0(即所有文档都不包含该词)。
T F − I D F = 词频 ( T F ) × 逆文档频率 ( I D F ) TF-IDF=词频(TF)×逆文档频率(IDF) TFIDF=词频(TF)×逆文档频率(IDF)TF 可以计算在一篇文档中词出现的频率,而 IDF 可以降低一些通用词的作用。因此对于一篇文档我们可以用文档中每个词的 TF−IDF 组成的向量来表示该文档,再根据余弦相似度这类的方法来计算文档之间的相关性。

2.BM25

BM25 算法通常用来做搜索相关性评分的,也是 ES 中的搜索算法,通常用来计算 q u e r y query query 和文本集合 D D D 中每篇文本之间的相关性。我们用 Q Q Q 表示 q u e r y query query,在这里 Q Q Q 一般是一个句子。在这里我们要对 Q Q Q 进行语素解析(一般是分词),在这里以分词为例,我们对 Q Q Q 进行分词,得到 q 1 , q 2 , . . . . . . , q t q_1,q_2,......,q_t q1,q2,......,qt 这样一个词序列。给定文本 d ∈ D d∈D dD,现在以计算 Q Q Q d d d 之间的分数(相关性),其表达式如下: S c o r e ( Q , d ) = ∑ i = 1 t w i ∗ R ( q i , d ) Score(Q,d)=\sum^{t}_{i=1}w_i*R(q_i,d) Score(Q,d)=i=1twiR(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 d d 的相关性, S c o r e ( Q , d ) Score(Q,d) Score(Q,d) 就是每个语素 q i q_i qi d d d 的相关性的加权和。

w i w_i wi 的计算方法有很多,一般是用 IDF 来表示的,但这里的 IDF 计算和上面的有所不同,具体的表达式如下: w i = I D F ( q i ) = l o g N − n ( q i ) + 0.5 n ( q i ) + 0.5 w_i=IDF(q_i)=log\frac{N-n(q_i)+0.5}{n(q_i)+0.5} wi=IDF(qi)=logn(qi)+0.5Nn(qi)+0.5其中 N N N 表示文本集合中文本的总数量, n ( q i ) n(q_i) n(qi) 表示包含 q i q_i qi 这个词的文本的数量,0.5 主要是做平滑处理。

R ( q i , d ) R(q_i,d) R(qi,d) 的计算公式如下: R ( q i , d ) = f i ∗ ( k 1 + 1 ) f i + K ∗ q f i ∗ ( k 2 + 1 ) q f i + k 2 R(q_i,d)=\frac{f_i*(k_1+1)}{f_i+K}*\frac{qf_i*(k_2+1)}{qf_i+k_2} R(qi,d)=fi+Kfi(k1+1)qfi+k2qfi(k2+1)其中: K = k 1 ∗ ( 1 − b + b ∗ d l a v g d l ) K=k_1*(1-b+b*\frac{dl}{avgdl}) K=k1(1b+bavgdldl)
f i f_i fi q i q_i qi 在文本 d d d 中出现的频率, q f i qf_i qfi q i q_i qi Q Q Q 中出现的频率, k 1 k_1 k1 k 2 k_2 k2 b b b 都是可调节的参数, d l dl dl a v g d l avgdl avgdl 分别为文本 d d d 的长度和文本集 D D D 中所有文本的平均长度。

一般 q f i = 1 qf_i=1 qfi=1,取 k 2 = 0 k_2=0 k2=0,则可以去除后一项,将上面式子改写成: R ( q i , d ) = f i ∗ ( k 1 + 1 ) f i + K R(q_i,d)=\frac{f_i*(k_1+1)}{f_i+K} R(qi,d)=fi+Kfi(k1+1)通常设置 k 1 = 2 k_1=2 k1=2 b = 0.75 b=0.75 b=0.75。参数 b b b 的作用主要是调节文本长度对相关性的影响。

3.BM25实现

SnowNLP 是一个 python 写的类库,可以方便的处理中文文本内容,是受到了 TextBlob 的启发而写的,由于现在大部分的自然语言处理库基本都是针对英文的,于是写了一个方便处理中文的类库,并且和 TextBlob 不同的是,这里没有用 NLTK,所有的算法都是自己实现的,并且自带了一些训练好的字典。

SnowNLP 中的相似算法即是 BM25 实现的,源码如下所示。

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import math

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 = []
        self.df = {}
        self.idf = {}
        self.k1 = 1.5
        self.b = 0.75
        self.init()

    def init(self):
        for doc in self.docs:
            tmp = {}
            for word in doc:
                if not word in tmp:
                    tmp[word] = 0
                tmp[word] += 1
            self.f.append(tmp)
            for k, v in tmp.items():
                if k not in self.df:
                    self.df[k] = 0
                self.df[k] += 1
        for k, v in self.df.items():
            self.idf[k] = math.log(self.D-v+0.5)-math.log(v+0.5) # 对应上文提到的 wi

    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))) # 对应上文提到的 wi * R(qi,d)
        return score

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

你可能感兴趣的:(自然语言处理,自然语言处理,算法,tf-idf,BM25,文本相似)