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

原理

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)性得分懂盐。

下面我們來看如何定義W_i。判斷一個(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)性得分R(q_i,d)。首先來看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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末呻率,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子呻引,更是在濱河造成了極大的恐慌礼仗,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件苞七,死亡現(xiàn)場離奇詭異藐守,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蹂风,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門卢厂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人惠啄,你說我怎么就攤上這事慎恒。” “怎么了撵渡?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵融柬,是天一觀的道長。 經(jīng)常有香客問我趋距,道長粒氧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任节腐,我火速辦了婚禮外盯,結(jié)果婚禮上摘盆,老公的妹妹穿的比我還像新娘。我一直安慰自己饱苟,他們只是感情好孩擂,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著箱熬,像睡著了一般类垦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上城须,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天蚤认,我揣著相機(jī)與錄音,去河邊找鬼酿傍。 笑死烙懦,一個(gè)胖子當(dāng)著我的面吹牛驱入,可吹牛的內(nèi)容都是我干的赤炒。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼亏较,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼莺褒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起雪情,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤遵岩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后巡通,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體尘执,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年宴凉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了誊锭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡弥锄,死狀恐怖丧靡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情籽暇,我是刑警寧澤温治,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站戒悠,受9級(jí)特大地震影響熬荆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜绸狐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一卤恳、第九天 我趴在偏房一處隱蔽的房頂上張望捏顺。 院中可真熱鬧,春花似錦纬黎、人聲如沸幅骄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拆座。三九已至,卻和暖如春冠息,著一層夾襖步出監(jiān)牢的瞬間挪凑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國打工逛艰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留躏碳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓散怖,卻偏偏與公主長得像菇绵,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子镇眷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

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