原理
BM25算法,通常用來作搜索相關(guān)性平分桌肴。一句話概況其主要思想:對Query進(jìn)行語素解析皇筛,生成語素qi;然后坠七,對于每個(gè)搜索結(jié)果D水醋,計(jì)算每個(gè)語素qi與D的相關(guān)性得分,最后彪置,將qi相對于D的相關(guān)性得分進(jìn)行加權(quán)求和拄踪,從而得到Query與D的相關(guān)性得分。
BM25算法的一般性公式如下:
其中拳魁,Q表示Query惶桐,qi表示Q解析之后的一個(gè)語素(對中文而言,我們可以把對Query的分詞作為語素分析潘懊,每個(gè)詞看成語素qi姚糊。);d表示一個(gè)搜索結(jié)果文檔授舟;Wi表示語素qi的權(quán)重救恨;R(qi,d)表示語素qi與文檔d的相關(guān)性得分释树。
下面我們來看如何定義Wi肠槽。判斷一個(gè)詞與一個(gè)文檔的相關(guān)性的權(quán)重,方法有多種奢啥,較常用的是IDF秸仙。這里以IDF為例,公式如下:
其中扫尺,N為索引中的全部文檔數(shù)筋栋,n(qi)為包含了qi的文檔數(shù)。
根據(jù)IDF的定義可以看出正驻,對于給定的文檔集合弊攘,包含了qi的文檔數(shù)越多抢腐,qi的權(quán)重則越低。也就是說襟交,當(dāng)很多文檔都包含了qi時(shí)迈倍,qi的區(qū)分度就不高,因此使用qi來判斷相關(guān)性時(shí)的重要度就較低捣域。
我們再來看語素qi與文檔d的相關(guān)性得分R(qi啼染,d)。首先來看BM25中相關(guān)性得分的一般形式:
其中焕梅,k1迹鹅,k2,b為調(diào)節(jié)因子贞言,通常根據(jù)經(jīng)驗(yàn)設(shè)置斜棚,一般k1=2,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)整文檔長度對相關(guān)性影響的大小奏纪。b越大鉴嗤,文檔長度的對相關(guān)性得分的影響越大,反之越小序调。而文檔的相對長度越長醉锅,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ì)算方法,這就為我們設(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.df
存儲(chǔ)每個(gè)詞及出現(xiàn)了該詞的文檔數(shù)量
{'在于': 1, '人工智能': 1, '語言': 1, '領(lǐng)域': 2, '融': 1, '日常': 1, '人': 1, '這一': 1, '軟件系統(tǒng)': 1, '特別': 1, '數(shù)學(xué)': 1, '通信': 2, '區(qū)別': 1, '之間': 1, '計(jì)算機(jī)科學(xué)': 3, '科學(xué)': 1, '一體': 1, '方向': 1, '中': 1, '理論': 1, '計(jì)算機(jī)': 1, '涉及': 1, '研制': 1, '一門': 1, '研究': 4, '語言學(xué)': 2, '計(jì)算機(jī)系統(tǒng)': 1, '自然語言': 6, '一部分': 1, '一個(gè)': 1, '方法': 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
參考
http://www.cnblogs.com/hdflzh/p/4034602.html
http://www.aiuxian.com/article/p-2690039.html