原理
BM25算法邑贴,通常用來作搜索相關(guān)性平分席里。一句話概況其主要思想:對(duì)Query進(jìn)行語素解析,生成語素qi拢驾;然后奖磁,對(duì)于每個(gè)搜索結(jié)果D,計(jì)算每個(gè)語素qi與D的相關(guān)性得分繁疤,最后署穗,將qi相對(duì)于D的相關(guān)性得分進(jìn)行加權(quán)求和,從而得到Query與D的相關(guān)性得分嵌洼。
BM25算法的一般性公式如下:
其中案疲,Q表示Query,qi表示Q解析之后的一個(gè)語素(對(duì)中文而言麻养,我們可以把對(duì)Query的分詞作為語素分析褐啡,每個(gè)詞看成語素qi。)鳖昌;d表示一個(gè)搜索結(jié)果文檔备畦;Wi表示語素qi的權(quán)重;R(qi许昨,d)表示語素qi與文檔d的相關(guān)性得分懂盐。
下面我們來看如何定義。判斷一個(gè)詞的權(quán)重糕档,方法有多種莉恼,較常用的是IDF。這里以IDF為例速那,公式如下:
其中俐银,N為索引中的全部文檔數(shù),n(qi)為包含了qi的文檔數(shù)端仰。
根據(jù)IDF的定義可以看出捶惜,對(duì)于給定的文檔集合,包含了qi的文檔數(shù)越多荔烧,qi的權(quán)重則越低吱七。也就是說汽久,當(dāng)很多文檔都包含了qi時(shí),qi的區(qū)分度就不高踊餐,因此使用qi來判斷相關(guān)性時(shí)的重要度就較低景醇。
我們?cè)賮砜凑Z素qi與文檔d的相關(guān)性得分。首先來看BM25中相關(guān)性得分的一般形式:
其中市袖,k1啡直,k2烁涌,b為調(diào)節(jié)因子苍碟,通常根據(jù)經(jīng)驗(yàn)設(shè)置,一般k1=2撮执,k2=1微峰,b=0.75;fi為qi在d中的出現(xiàn)頻率抒钱,qfi為qi在Query中的出現(xiàn)頻率蜓肆。dl為文檔d的長度,avgdl為所有文檔的平均長度谋币。由于絕大部分情況下仗扬,qi在Query中只會(huì)出現(xiàn)一次,即qfi=1蕾额,因此公式可以簡化為:
從K的定義中可以看到早芭,參數(shù)b的作用是調(diào)整文檔長度對(duì)相關(guān)性影響的大小。b越大诅蝶,文檔長度的對(duì)相關(guān)性得分的影響越大退个,反之越小。而文檔的相對(duì)長度越長调炬,K值將越大语盈,則相關(guān)性得分會(huì)越小。這可以理解為缰泡,當(dāng)文檔較長時(shí)刀荒,包含qi的機(jī)會(huì)越大,因此棘钞,同等fi的情況下照棋,長文檔與qi的相關(guān)性應(yīng)該比短文檔與qi的相關(guān)性弱。
綜上武翎,BM25算法的相關(guān)性得分公式可總結(jié)為:
從BM25的公式可以看到烈炭,通過使用不同的語素分析方法、語素權(quán)重判定方法宝恶,以及語素與文檔的相關(guān)性判定方法符隙,我們可以衍生出不同的搜索相關(guān)性得分計(jì)算方法趴捅,這就為我們?cè)O(shè)計(jì)算法提供了較大的靈活性。
實(shí)現(xiàn)
import math
import jieba
from utils import utils
# 測試文本
text = '''
自然語言處理是計(jì)算機(jī)科學(xué)領(lǐng)域與人工智能領(lǐng)域中的一個(gè)重要方向霹疫。
它研究能實(shí)現(xiàn)人與計(jì)算機(jī)之間用自然語言進(jìn)行有效通信的各種理論和方法拱绑。
自然語言處理是一門融語言學(xué)、計(jì)算機(jī)科學(xué)丽蝎、數(shù)學(xué)于一體的科學(xué)猎拨。
因此,這一領(lǐng)域的研究將涉及自然語言屠阻,即人們?nèi)粘J褂玫恼Z言红省,
所以它與語言學(xué)的研究有著密切的聯(lián)系,但又有重要的區(qū)別国觉。
自然語言處理并不是一般地研究自然語言吧恃,
而在于研制能有效地實(shí)現(xiàn)自然語言通信的計(jì)算機(jī)系統(tǒng),
特別是其中的軟件系統(tǒng)麻诀。因而它是計(jì)算機(jī)科學(xué)的一部分痕寓。
'''
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 = [] # 列表的每一個(gè)元素是一個(gè)dict,dict存儲(chǔ)著一個(gè)文檔中每個(gè)詞的出現(xiàn)次數(shù)
self.df = {} # 存儲(chǔ)每個(gè)詞及出現(xiàn)了該詞的文檔數(shù)量
self.idf = {} # 存儲(chǔ)每個(gè)詞的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 # 存儲(chǔ)每個(gè)文檔中每個(gè)詞的出現(xiàn)次數(shù)
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__':
sents = utils.get_sentences(text)
doc = []
for sent in sents:
words = list(jieba.cut(sent))
words = utils.filter_stop(words)
doc.append(words)
print(doc)
s = BM25(doc)
print(s.f)
print(s.idf)
print(s.simall(['自然語言', '計(jì)算機(jī)科學(xué)', '領(lǐng)域', '人工智能', '領(lǐng)域']))
分段再分詞結(jié)果
[['自然語言', '計(jì)算機(jī)科學(xué)', '領(lǐng)域', '人工智能', '領(lǐng)域', '中', '一個(gè)', '方向'],
['研究', '人', '計(jì)算機(jī)', '之間', '自然語言', '通信', '理論', '方法'],
['自然語言', '一門', '融', '語言學(xué)', '計(jì)算機(jī)科學(xué)', '數(shù)學(xué)', '一體', '科學(xué)'],
[],
['這一', '領(lǐng)域', '研究', '涉及', '自然語言'],
['日常', '語言'],
['語言學(xué)', '研究'],
['區(qū)別'],
['自然語言', '研究', '自然語言'],
['在于', '研制', '自然語言', '通信', '計(jì)算機(jī)系統(tǒng)'],
['特別', '軟件系統(tǒng)'],
['計(jì)算機(jī)科學(xué)', '一部分']]
s.f
列表的每一個(gè)元素是一個(gè)dict蝇闭,dict存儲(chǔ)著一個(gè)文檔中每個(gè)詞的出現(xiàn)次數(shù)
[{'中': 1, '計(jì)算機(jī)科學(xué)': 1, '領(lǐng)域': 2, '一個(gè)': 1, '人工智能': 1, '方向': 1, '自然語言': 1},
{'之間': 1, '方法': 1, '理論': 1, '通信': 1, '計(jì)算機(jī)': 1, '人': 1, '研究': 1, '自然語言': 1},
{'融': 1, '一門': 1, '一體': 1, '數(shù)學(xué)': 1, '科學(xué)': 1, '計(jì)算機(jī)科學(xué)': 1, '語言學(xué)': 1, '自然語言': 1},
{},
{'領(lǐng)域': 1, '這一': 1, '涉及': 1, '研究': 1, '自然語言': 1},
{'日常': 1, '語言': 1},
{'語言學(xué)': 1, '研究': 1},
{'區(qū)別': 1},
{'研究': 1, '自然語言': 2},
{'通信': 1, '計(jì)算機(jī)系統(tǒng)': 1, '研制': 1, '在于': 1, '自然語言': 1},
{'軟件系統(tǒng)': 1, '特別': 1},
{'一部分': 1, '計(jì)算機(jī)科學(xué)': 1}]
s.idf
存儲(chǔ)每個(gè)詞的idf值
{'在于': 2.0368819272610397, '一部分': 2.0368819272610397, '一個(gè)': 2.0368819272610397, '語言': 2.0368819272610397, '領(lǐng)域': 1.4350845252893225, '融': 2.0368819272610397, '日常': 2.0368819272610397, '人': 2.0368819272610397, '這一': 2.0368819272610397, '軟件系統(tǒng)': 2.0368819272610397, '特別': 2.0368819272610397, '數(shù)學(xué)': 2.0368819272610397, '通信': 1.4350845252893225, '區(qū)別': 2.0368819272610397, '之間': 2.0368819272610397, '一門': 2.0368819272610397, '科學(xué)': 2.0368819272610397, '一體': 2.0368819272610397, '方向': 2.0368819272610397, '中': 2.0368819272610397, '理論': 2.0368819272610397, '計(jì)算機(jī)': 2.0368819272610397, '涉及': 2.0368819272610397, '研制': 2.0368819272610397, '計(jì)算機(jī)科學(xué)': 0.9985288301111273, '研究': 0.6359887667199966, '語言學(xué)': 1.4350845252893225, '計(jì)算機(jī)系統(tǒng)': 2.0368819272610397, '自然語言': 0.0, '人工智能': 2.0368819272610397, '方法': 2.0368819272610397}
s.simall(['自然語言', '計(jì)算機(jī)科學(xué)', '領(lǐng)域', '人工智能', '領(lǐng)域'])
['自然語言', '計(jì)算機(jī)科學(xué)', '領(lǐng)域', '人工智能', '領(lǐng)域']與每一句的相似度
[5.0769919814311475, 0.0, 0.6705449078118518, 0, 2.5244316697250033, 0, 0, 0, 0.0, 0.0, 0, 1.2723636062357853]
詳細(xì)代碼
https://github.com/jllan/jannlp/blob/master/similarity/bm25.py