文本相似度-bm25算法原理及實(shí)現(xiàn)

原理

BM25算法,通常用來作搜索相關(guān)性平分。一句話概況其主要思想:對Query進(jìn)行語素解析,生成語素qi;然后,對于每個搜索結(jié)果D,計(jì)算每個語素qi與D的相關(guān)性得分,最后,將qi相對于D的相關(guān)性得分進(jìn)行加權(quán)求和,從而得到Query與D的相關(guān)性得分。
BM25算法的一般性公式如下:


其中,Q表示Query,qi表示Q解析之后的一個語素(對中文而言,我們可以把對Query的分詞作為語素分析,每個詞看成語素qi。);d表示一個搜索結(jié)果文檔;Wi表示語素qi的權(quán)重;R(qi,d)表示語素qi與文檔d的相關(guān)性得分。
下面我們來看如何定義Wi。判斷一個詞與一個文檔的相關(guān)性的權(quán)重,方法有多種,較常用的是IDF。這里以IDF為例,公式如下:



其中,N為索引中的全部文檔數(shù),n(qi)為包含了qi的文檔數(shù)。
根據(jù)IDF的定義可以看出,對于給定的文檔集合,包含了qi的文檔數(shù)越多,qi的權(quán)重則越低。也就是說,當(dāng)很多文檔都包含了qi時,qi的區(qū)分度就不高,因此使用qi來判斷相關(guān)性時的重要度就較低。
我們再來看語素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中只會出現(xiàn)一次,即qfi=1,因此公式可以簡化為:


從K的定義中可以看到,參數(shù)b的作用是調(diào)整文檔長度對相關(guān)性影響的大小。b越大,文檔長度的對相關(guān)性得分的影響越大,反之越小。而文檔的相對長度越長,K值將越大,則相關(guān)性得分會越小。這可以理解為,當(dāng)文檔較長時,包含qi的機(jī)會越大,因此,同等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)域中的一個重要方向。
它研究能實(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 = []  # 列表的每一個元素是一個dict,dict存儲著一個文檔中每個詞的出現(xiàn)次數(shù)
        self.df = {} # 存儲每個詞及出現(xiàn)了該詞的文檔數(shù)量
        self.idf = {} # 存儲每個詞的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  # 存儲每個文檔中每個詞的出現(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)域', '中', '一個', '方向'], 
['研究', '人', '計(jì)算機(jī)', '之間', '自然語言', '通信', '理論', '方法'], 
['自然語言', '一門', '融', '語言學(xué)', '計(jì)算機(jī)科學(xué)', '數(shù)學(xué)', '一體', '科學(xué)'], 
[],
['這一', '領(lǐng)域', '研究', '涉及', '自然語言'], 
['日常', '語言'], 
['語言學(xué)', '研究'], 
['區(qū)別'], 
['自然語言', '研究', '自然語言'], 
['在于', '研制', '自然語言', '通信', '計(jì)算機(jī)系統(tǒng)'], 
['特別', '軟件系統(tǒng)'], 
['計(jì)算機(jī)科學(xué)', '一部分']]

s.f
列表的每一個元素是一個dict,dict存儲著一個文檔中每個詞的出現(xiàn)次數(shù)

[{'中': 1, '計(jì)算機(jī)科學(xué)': 1, '領(lǐng)域': 2, '一個': 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
存儲每個詞及出現(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, '一個': 1, '方法': 1}

s.idf
存儲每個詞的idf值

{'在于': 2.0368819272610397, '一部分': 2.0368819272610397, '一個': 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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內(nèi)容