python根據(jù)BM25實現(xiàn)文本檢索

目的

給定一個或多個搜索詞,如“高血壓 患者”比搭,從已有的若干篇文本中找出最相關的(n篇)文本艾疟。

理論知識

文本檢索(text retrieve)的常用策略是:用一個ranking function根據(jù)搜索詞對所有文本進行排序,選取前n個敢辩,就像百度搜索一樣蔽莱。
顯然,ranking function是決定檢索效果最重要的因素戚长,本文選用了在實際應用中效果很好的BM25盗冷。BM25其實只用到了一些基礎的統(tǒng)計和文本處理的方法,沒有很高深的算法同廉。

BM25

上圖是BM25的公式仪糖,對于一個搜索q和所有文本,計算每一篇文本d的權重迫肖。整個公式其實是TF-IDF的改進:

  • 第一項c(w,q)就是搜索q中詞w的詞頻
  • 第三項是詞w的逆文檔頻率锅劝,M是所有文本的個數(shù),df(w)是出現(xiàn)詞w的文本個數(shù)
  • 中間的第二項是關鍵蟆湖,實質(zhì)是詞w的TF值的變換故爵,c(w,d)是詞w在文本d中的詞頻。首先是一個TF Transformation隅津,目的是防止某個詞的詞頻過大诬垂,經(jīng)過下圖中公式的約束,詞頻的上限為k+1伦仍,不會無限制的增長结窘。例如,一個詞在文本中的詞頻無論是50還是100充蓝,都說明文本與這個詞有關隧枫,但相關度不可能是兩倍關系。
    TF Transformation
  • 上圖的公式分母中的k還乘了一個系數(shù)谓苟,目的是歸一化文本長度官脓。歸一化公式中,b是[0,1]之間的常數(shù)娜谊,avdl是平均文本長度戴已,d是文本d的長度搀别。顯然桑包,d比平均值大,則normalizer大于1芭商,代入BM25最終的權重變小,反之亦然搀缠。
length normalization

python實現(xiàn)

下面通過一個例子來實現(xiàn)根據(jù)BM25來進行文本檢索☆蹰梗現(xiàn)在從網(wǎng)上爬下來了幾十篇健康相關的文章,部分如下圖所示艺普。模擬輸入搜索詞簸州,如“高血壓 患者 藥物”,搜素最相關的文章歧譬。

文本列表

python的實現(xiàn)用到了gensim庫岸浑,其中的BM25實現(xiàn)的源碼如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Licensed under the GNU LGPL v2.1 - http://www.gnu.org/licenses/lgpl.html

import math
from six import iteritems
from six.moves import xrange


# BM25 parameters.
PARAM_K1 = 1.5
PARAM_B = 0.75
EPSILON = 0.25


class BM25(object):

    def __init__(self, corpus):
        self.corpus_size = len(corpus)
        self.avgdl = sum(map(lambda x: float(len(x)), corpus)) / self.corpus_size
        self.corpus = corpus
        self.f = []
        self.df = {}
        self.idf = {}
        self.initialize()

    def initialize(self):
        for document in self.corpus:
            frequencies = {}
            for word in document:
                if word not in frequencies:
                    frequencies[word] = 0
                frequencies[word] += 1
            self.f.append(frequencies)

            for word, freq in iteritems(frequencies):
                if word not in self.df:
                    self.df[word] = 0
                self.df[word] += 1

        for word, freq in iteritems(self.df):
            self.idf[word] = math.log(self.corpus_size - freq + 0.5) - math.log(freq + 0.5)

    def get_score(self, document, index, average_idf):
        score = 0
        for word in document:
            if word not in self.f[index]:
                continue
            idf = self.idf[word] if self.idf[word] >= 0 else EPSILON * average_idf
            score += (idf * self.f[index][word] * (PARAM_K1 + 1)
                      / (self.f[index][word] + PARAM_K1 * (1 - PARAM_B + PARAM_B * self.corpus_size / self.avgdl)))
        return score

    def get_scores(self, document, average_idf):
        scores = []
        for index in xrange(self.corpus_size):
            score = self.get_score(document, index, average_idf)
            scores.append(score)
        return scores


def get_bm25_weights(corpus):
    bm25 = BM25(corpus)
    average_idf = sum(map(lambda k: float(bm25.idf[k]), bm25.idf.keys())) / len(bm25.idf.keys())

    weights = []
    for doc in corpus:
        scores = bm25.get_scores(doc, average_idf)
        weights.append(scores)

    return weights

gensim中代碼寫得很清楚,我們可以直接利用瑰步。

import jieba.posseg as pseg
import codecs
from gensim import corpora
from gensim.summarization import bm25
import os
import re

構(gòu)建停用詞表

stop_words = '/Users/yiiyuanliu/Desktop/nlp/demo/stop_words.txt'
stopwords = codecs.open(stop_words,'r',encoding='utf8').readlines()
stopwords = [ w.strip() for w in stopwords ]

結(jié)巴分詞后的停用詞性 [標點符號矢洲、連詞、助詞缩焦、副詞读虏、介詞、時語素袁滥、‘的’盖桥、數(shù)詞、方位詞题翻、代詞]

stop_flag = ['x', 'c', 'u','d', 'p', 't', 'uj', 'm', 'f', 'r']

對一篇文章分詞揩徊、去停用詞

def tokenization(filename):
    result = []
    with open(filename, 'r') as f:
        text = f.read()
        words = pseg.cut(text)
    for word, flag in words:
        if flag not in stop_flag and word not in stopwords:
            result.append(word)
    return result

對目錄下的所有文本進行預處理,構(gòu)建字典

corpus = [];
dirname = '/Users/yiiyuanliu/Desktop/nlp/demo/articles'
filenames = []
for root,dirs,files in os.walk(dirname):
    for f in files:
        if re.match(ur'[\u4e00-\u9fa5]*.txt', f.decode('utf-8')):
            corpus.append(tokenization(f))
            filenames.append(f)
    
dictionary = corpora.Dictionary(corpus)
print len(dictionary)
Building prefix dict from the default dictionary ...
Loading model from cache /var/folders/1q/5404x10d3k76q2wqys68pzkh0000gn/T/jieba.cache
Loading model cost 0.328 seconds.
Prefix dict has been built succesfully.


2552

建立詞袋模型

打印了第一篇文本按詞頻排序的前5個詞

doc_vectors = [dictionary.doc2bow(text) for text in corpus]
vec1 = doc_vectors[0]
vec1_sorted = sorted(vec1, key=lambda (x,y) : y, reverse=True)
print len(vec1_sorted)
for term, freq in vec1_sorted[:5]:
    print dictionary[term]
76
高血壓
患者
藥物
血壓
治療

用gensim建立BM25模型

bm25Model = bm25.BM25(corpus)

根據(jù)gensim源碼藐握,計算平均逆文檔頻率

average_idf = sum(map(lambda k: float(bm25Model.idf[k]), bm25Model.idf.keys())) / len(bm25Model.idf.keys())

假設用戶輸入了搜索詞“高血壓 患者 藥物”靴拱,利用BM25模型計算所有文本與搜索詞的相關性

query_str = '高血壓 患者 藥物'
query = []
for word in query_str.strip().split():
    query.append(word.decode('utf-8'))
scores = bm25Model.get_scores(query,average_idf)
# scores.sort(reverse=True)
print scores
[4.722034069722618, 4.5579610648148625, 2.859958016641194, 3.388613898734133, 4.6281563584251995, 4.730042214103296, 1.447106736835707, 2.595169814422283, 2.894213473671414, 2.952010252059601, 3.987044912721877, 2.426869660460219, 1.1583806884161147, 0, 3.242214688067997, 3.6729065940310752, 3.025338037306947, 1.57823130047124, 2.6054874252518214, 3.4606547596124635, 1.1583806884161147, 2.412854586446401, 1.7354863870557247, 1.447106736835707, 3.571235274862516, 2.6054874252518214, 2.695780408029825, 2.3167613768322295, 4.0309963518837595, 0, 2.894213473671414, 3.306255023356817, 3.587349029341776, 3.4401107112269824, 3.983307351035947, 0, 4.508767501123564, 3.6289862140766926, 3.6253442838304633, 4.248297326100691, 3.025338037306947, 3.602635199166345, 3.4960329155028464, 3.3547048399034876, 1.57823130047124, 4.148340973502125, 1.1583806884161147]
idx = scores.index(max(scores))
print idx
5

找到最相關的文本

fname = filenames[idx]
print fname
關于降壓藥的五個問題.txt
with open(fname,'r') as f:
    print f.read()
      高血壓的重要治療方式之一,就是口服降壓藥猾普。對于不少剛剛被確診的“高血壓新手”來說,下面這些關于用藥的事情本谜,是必須知道的初家。

1. 貴的藥,真的比便宜的藥好用嗎乌助?

      事實上溜在,降壓藥物的化學機構(gòu)和作用機制不一樣。每一種降壓藥他托,其降壓機理和適應人群都不一樣掖肋。只要適合自己的病情和身體狀況,就是好藥赏参。因此志笼,不存在“貴重降壓藥一定比便宜的降壓藥好使”這一說法沿盅。

2. 能不吃藥就不吃藥,靠身體調(diào)節(jié)血壓纫溃,這種想法對嗎腰涧?

     這種想法很幼稚。其實紊浩,高血壓是典型的窖铡,應該盡早服藥的疾病。如果服藥太遲坊谁,高血壓對重要臟器持續(xù)形成傷害费彼,會讓我們的身體受很大打擊。所以口芍,高血壓患者盡早服藥敌买,是對身體的最好的保護。

3. 降壓藥是不是得吃一輩子阶界?

      對于這個問題虹钮,中醫(yī)和西醫(yī)有著不同的認識。西醫(yī)認為膘融,降壓藥服用之后不應該停用芙粱,以免血壓形成波動,造成對身體的進一步傷害氧映。中醫(yī)則認為春畔,通過適當?shù)倪\動和飲食調(diào)節(jié),早期的部分高血壓患者岛都,可以在服藥之后的某段時間里停藥律姨。總之臼疫,處理這一問題的時候择份,我們還是要根據(jù)自己的情況而定。對于高血壓前期烫堤,或者輕度高血壓的人來說荣赶,在生活方式調(diào)節(jié)能夠讓血壓穩(wěn)定的情況下,可以考慮停藥鸽斟,采取非藥物療法拔创。對于中度或者重度的高血壓患者來說,就不能這么做了富蓄。

4. 降壓藥是不是要早晨服用剩燥?

      一般來說,長效的降壓藥物立倍,都是在早晨服用的灭红。但是侣滩,我們也可以根據(jù)高血壓患者的波動情況,適當改變服藥時間比伏。

5. 降壓藥是不是一旦服用就不能輕易更換胜卤?

      高血壓病人一旦服用了某種降壓藥物,就不要輕易更換赁项。只要能維持血壓在正常范圍而且穩(wěn)定葛躏,就不用換藥。只有在原有藥物不能起到控制作用的時候悠菜,再考慮更換服藥方案舰攒。對于新發(fā)高血壓病人來說,長效降壓藥若要達到穩(wěn)定的降壓效果悔醋,往往需要4到6周或者更長時間摩窃。如果經(jīng)過4到6周也實現(xiàn)不了好的控制效果,可以考慮加第二種藥物來聯(lián)合降壓芬骄,不必盲目換藥猾愿。

參考資料:

Coursera: Text Mining and Analytics

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市账阻,隨后出現(xiàn)的幾起案子蒂秘,更是在濱河造成了極大的恐慌,老刑警劉巖淘太,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件姻僧,死亡現(xiàn)場離奇詭異,居然都是意外死亡蒲牧,警方通過查閱死者的電腦和手機撇贺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來冰抢,“玉大人松嘶,你說我怎么就攤上這事∩故海” “怎么了喘蟆?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鼓鲁。 經(jīng)常有香客問我,道長港谊,這世上最難降的妖魔是什么骇吭? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮歧寺,結(jié)果婚禮上燥狰,老公的妹妹穿的比我還像新娘棘脐。我一直安慰自己,他們只是感情好龙致,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布蛀缝。 她就那樣靜靜地躺著,像睡著了一般目代。 火紅的嫁衣襯著肌膚如雪屈梁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天榛了,我揣著相機與錄音在讶,去河邊找鬼。 笑死霜大,一個胖子當著我的面吹牛构哺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播战坤,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼曙强,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了途茫?” 一聲冷哼從身側(cè)響起碟嘴,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎慈省,沒想到半個月后臀防,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡边败,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年袱衷,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片笑窜。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡致燥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出排截,到底是詐尸還是另有隱情嫌蚤,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布断傲,位于F島的核電站脱吱,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏认罩。R本人自食惡果不足惜箱蝠,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宦搬,春花似錦牙瓢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽憔足。三九已至胁附,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間四瘫,已是汗流浹背汉嗽。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留找蜜,地道東北人饼暑。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像洗做,于是被迫代替她去往敵國和親弓叛。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

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