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,......,dn∈D。文档集合总共包含 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,......,wm∈W。我们现在以计算词 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) TF−IDF=词频(TF)×逆文档频率(IDF)TF 可以计算在一篇文档中词出现的频率,而 IDF 可以降低一些通用词的作用。因此对于一篇文档我们可以用文档中每个词的 TF−IDF 组成的向量来表示该文档,再根据余弦相似度这类的方法来计算文档之间的相关性。
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 d∈D,现在以计算 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=1∑twi∗R(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.5N−n(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∗(1−b+b∗avgdldl)
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 的作用主要是调节文本长度对相关性的影响。
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