史上最小白之BM25详解与实现
原理
BM25算法是一种计算句子与文档相关性的算法,它的原理十分简单:将输入的句子sentence进行分词,然后分别计算句子中每个词word与文档doc的相关度,然后进行加权求和。得到句子与文档的相关度评分。评分公式如下:
S
c
o
r
e
(
Q
,
d
)
=
∑
i
n
W
i
R
(
q
i
,
d
)
Score(Q,d) = \sum_i^nW_iR(q_i,d)
Score(Q,d)=i∑nWiR(qi,d)
上面公式中Wi表示权重,也就是idf值。R(qi,d)是word q与文档d的相关性得分。
IDF
idf 也就是逆文档频率,计算公式如下:
I
D
F
(
q
i
)
=
l
o
g
(
N
−
n
(
q
i
)
+
0.5
n
(
q
i
)
+
0.5
)
IDF(q_i)=log(\frac{N-n(q_i)+0.5}{n(q_i)+0.5})
IDF(qi)=log(n(qi)+0.5N−n(qi)+0.5)
N表示所有文档D中的文档d的数目,也就是总共有多少篇文档来与sentence计算相关性得分,n(qi)为文档d中包含了词qi的数目。从idf的公式我们可以看出,n(qi)越大则分母越大,分子越小,也就是相应地IDF值越小。这是因为加入一个词在多篇文档中出现,那么一定程度上能说明这个词应该是一个使用比较普遍的词,在任何sentence中他都存在,不能体现sentence这一句话的特殊性,因此赋予它更小的idf值。
代入到BM25算法中idf值作为权重,也就是说明一个词word在越多的文档d中出现,那么他与文档d计算的相关性得分就应该赋予更小的权重。
R相关性得分
先来看看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\cdot(k_1+1)}{f_i+K}\cdot\frac{qf_i\cdot(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=k1\cdot(1-b+b\cdot\frac{dl}{avgdl}) K=k1⋅(1−b+b⋅avgdldl)
上述公式中,k1,k2,b是调节因子,一般根据经验来自己设置,通常k1=2,b=0.75;fi表示qi在文档d中出现的频率,qfi为qi在输入句子sentence中的频率。dl为文档d的长度,avgdl为文档D中所有文档的平均长度。
又因为在绝大部分情况下,qi在sentence中只出现一次,即所有的qi的qfi基本上都是一样地,因此可以将k2取0,然后将上述公式进行简化。
KaTeX parse error: Undefined control sequence: \ at position 40: …(k_1+1)}{f_i+K}\̲ ̲
接下来就是需要计算K,便能计算出相关性得分R了。
从K的公式可以看出,参数b是调整文档长度对于相关性的影响。可以看出b越大,文档长度对相关性得分的影响越大,反之越小。而文档d的相对长度越长,K越大,也就是R的分母越大,R相关性得分也就越小。这里可以理解成,当文档较长时,包含的词语也就越多,那么相应地包含有qi这个词的可能性也就越大,但是虽然可能性要大一点,要是当qi的频率fi同等的情况下,长文档与qi的相关性就应该比短文档与qi的相关性弱。
最后可以将BM25算法的相关性得分的公式进行汇总:
S
c
o
r
e
(
Q
,
d
)
=
∑
i
n
I
D
F
(
q
i
)
⋅
f
i
⋅
(
k
1
+
1
)
f
i
+
k
1
⋅
(
1
−
b
+
b
⋅
d
l
a
v
g
d
l
)
Score(Q,d) = \sum_i^nIDF(q_i)\cdot\frac{f_i\cdot(k_1+1)}{f_i+k_1\cdot(1-b+b\cdot\frac{dl}{avgdl})}
Score(Q,d)=i∑nIDF(qi)⋅fi+k1⋅(1−b+b⋅avgdldl)fi⋅(k1+1)
代码实现:
import math
import jieba
import numpy as np
import logging
import pandas as pd
from collections import Counter
jieba.setLogLevel(logging.INFO)
# 测试文本
text = '''
自然语言处理是计算机科学领域与人工智能领域中的一个重要方向。它研究能实现人与计算机之间用自然语言进行有效通信的各种理论和方法。
自然语言处理是一门融语言学、计算机科学、数学于一体的科学。因此,这一领域的研究将涉及自然语言,即人们日常使用的语言,所以它与语言学的研究有着密切的联系,但又有重要的区别。
自然语言处理并不是一般地研究自然语言,而在于研制能有效地实现自然语言通信的计算机系统,特别是其中的软件系统。因而它是计算机科学的一部分。
'''
class BM25(object):
def __init__(self,docs):
self.docs = docs # 传入的docs要求是已经分好词的list
self.doc_num = len(docs) # 文档数
self.vocab = set([word for doc in self.docs for word in doc]) # 文档中所包含的所有词语
self.avgdl = sum([len(doc) + 0.0 for doc in docs]) / self.doc_num # 所有文档的平均长度
self.k1 = 1.5
self.b = 0.75
def idf(self,word):
if word not in self.vocab:
word_idf = 0
else:
qn = {}
for doc in self.docs:
if word in doc:
if word in qn:
qn[word] += 1
else:
qn[word] = 1
else:
continue
word_idf = np.log((self.doc_num - qn[word] + 0.5) / (qn[word] + 0.5))
return word_idf
def score(self,word):
score_list = []
for index,doc in enumerate(self.docs):
word_count = Counter(doc)
if word in word_count.keys():
f = (word_count[word]+0.0) / len(doc)
else:
f = 0.0
r_score = (f*(self.k1+1)) / (f+self.k1*(1-self.b+self.b*len(doc)/self.avgdl))
score_list.append(self.idf(word) * r_score)
return score_list
def score_all(self,sequence):
sum_score = []
for word in sequence:
sum_score.append(self.score(word))
sim = np.sum(sum_score,axis=0)
return sim
if __name__ == "__main__":
# 获取停用词
stopwords = open('./drive/My Drive/Colab/stopwords/哈工大停用词表.txt').read().split('\n')
doc_list = [doc for doc in text.split('\n') if doc != '']
docs = []
for sentence in doc_list:
sentence_words = jieba.lcut(sentence)
tokens = []
for word in sentence_words:
if word in stopwords:
continue
else:
tokens.append(word)
docs.append(tokens)
bm = BM25(docs)
score = bm.score_all(['自然语言', '计算机科学', '领域', '人工智能', '领域'])
print(score)
结果:
可以看到我们的输入与第二句的相关性得分最高。
结语:
好了,这期的BM25算法的介绍就到这里啦,不知道说得算不算清晰,要是还有不懂的或者发现有遗漏和错误的地方欢迎指正。
生命不息,学习不止,一起加油吧!!!奥利给!!!
参考:
https://www.jianshu.com/p/1e498888f505