文本相似度算法: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,......,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 组成的向量来表示该文档,再根据余弦相似度这类的方法来计算文档之间的相关性。
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 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 的作用主要是调节文本长度对相关性的影响。
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