Python實現內容檢索子系統(BM25算法)


一、檢索模型

搜索引擎一般流程如下:

 

從檢索后面都屬於檢索模型的范疇。

搜索結果排序是搜索引擎最核心的部分,很大程度度上決定了搜索引擎的質量好壞及用戶滿意度。實際搜索結果排序的因子有很多,但最主要的兩個因素是用戶查詢和網頁內容的相關度,以及網頁鏈接情況。這里主要介紹網頁內容和用戶查詢相關的內容。判斷網頁內容是否與用戶査詢相關,這依賴於搜索引擎所來用的檢索模型。檢索模型是搜索引擎的理論基礎,為量化相關性提供了一種數學模型,是對查詢詞和文檔之間進行相似度計算的框架和方法。其本質就是相關度建模。

 

二、信息檢索特點

檢索有文件檢索、數據庫檢索、信息檢索等,常用的是數據庫檢索和信息檢索。

 

數據庫檢索

信息檢索

匹配程度

精確

模糊

查詢語言

SQL

自然語言

查詢描述

完善

不完善

數據規模

TB

PB

評價標准

客觀(二元)

主觀(多元)

檢索模型

決定性

可能性

信息檢索任務是對索引結果進行相關性排序。

影響結果排序的因素有相似度、網頁質量、用戶偏好等等。

 

三、檢索模型分類

檢索模型一般有布爾模型、向量空間模型、概率模型、知識模型。

 

 

信息檢索模型四元組[D, Q, F, R(qi, dj)]

  • D: 文檔集的機內表示
  • Q: 用戶需求的機內表示
  • F: 文檔表示、查詢表示和它們之間的關系的模型框架(Frame)
  • R(qi, dj): 給query qi 和document dj評分

 

四、BM25算法

BM25算法,通常用來作搜索相關性平分。對Query進行語素解析,生成語素qi;然后,對於每個搜索結果D,計算每個語素qi與D的相關性得分,最后,將qi相對於D的相關性得分進行加權求和,從而得到Query與D的相關性得分。

BM25算法的一般性公式如下:

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

 

 其中,N為索引中的全部文檔數,n(qi)為包含了qi的文檔數。

根據IDF的定義可以看出,對於給定的文檔集合,包含了qi的文檔數越多,qi的權重則越低。也就是說,當很多文檔都包含了qi時,qi的區分度就不高,因此使用qi來判斷相關性時的重要度就較低。

我們再來看語素qi與文檔d的相關性得分R(qi,d)。首先來看BM25中相關性得分的一般形式:

 

其中,k1,k2,b為調節因子,通常根據經驗設置,一般k1=2,b=0.75;fi為qi在d中的出現頻率,qfi為qi在Query中的出現頻率。dl為文檔d的長度,avgdl為所有文檔的平均長度。由於絕大部分情況下,qi在Query中只會出現一次,即qfi=1,因此公式可以簡化為:

從K的定義中可以看到,參數b的作用是調整文檔長度對相關性影響的大小。b越大,文檔長度的對相關性得分的影響越大,反之越小。而文檔的相對長度越長,K值將越大,則相關性得分會越小。這可以理解為,當文檔較長時,包含qi的機會越大,因此,同等fi的情況下,長文檔與qi的相關性應該比短文檔與qi的相關性弱。

綜上,BM25算法的相關性得分公式可總結為:

從BM25的公式可以看到,通過使用不同的語素分析方法、語素權重判定方法,以及語素與文檔的相關性判定方法,我們可以衍生出不同的搜索相關性得分計算方法,這就為我們設計算法提供了較大的靈活性。

 

五、BM25算法實現

bm25.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-

'''
作者:chl
時間:2017/11/23
用圖:bm25算法
'''

import math

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存儲着一個文檔中每個詞的出現次數
        self.df = {} # 存儲每個詞及出現了該詞的文檔數量
        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  # 存儲每個文檔中每個詞的出現次數
            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

    # 總共有N篇文檔,傳來的doc為查詢文檔,計算doc與所有文檔匹配
    # 后的得分score,總共有多少篇文檔,scores列表就有多少項,
    # 每一項為doc與這篇文檔的得分,所以分清楚里面裝的是文檔得分,
    # 不是詞語得分。
    def simall(self, doc):
        scores = []
        for index in range(self.D):
            score = self.sim(doc, index)
            scores.append(score)
        return scores
View Code

 

六、應用示例

(1)python腳本

sortnovel.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-

'''
作者:chl
時間:2017/11/24
用圖:小說排序
'''

#調用到Django的model塊
import os, django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "novel0.settings")# novel0 項目名稱
django.setup()

import jieba
from search import utils
from search.bm25 import BM25
from novel.models import Novel

class Sortnovel(object):
    def __init__(self, docs):
        self.novels = Novel.objects.filter().order_by()
        self.sents = []     #所有原文檔
        self.docs = []  #所有文檔列表,詞表示
        for novel in self.novels:
            sent = novel.novelname + ":" + novel.description
            self.sents.append(sent)
            docs.append(self.dealwords(sent))   #分詞處理
        self.bm25 = BM25(docs)

    def top(self, query):
        print(self.dealwords(query))
        self.top = list(enumerate(self.bm25.simall(self.dealwords(query))))
        self.sorttop = sorted(self.top, key=lambda x: x[1], reverse=True) #排序,匿名函數
        i = 0
        self.list = list(map(lambda x: x[0], self.sorttop))
        print(self.list)    #輸出序號
        searchtotal = object
        for index in self.list:     #輸出id,書名,得分
            print(self.novels[index].novelid, self.novels[index].novelname, self.sorttop[i][1])
            i += 1
        return self.list

    def top1(self, query, limit = 10):
        print(self.dealwords(query))
        self.top = list(enumerate(self.bm25.simall(self.dealwords(query))))
        self.sorttop = sorted(self.top, key=lambda x: x[1], reverse=True) #排序,匿名函數
        i = 0
        self.list = list(map(lambda x: x[0], self.sorttop))[:limit]
        print(self.list)
        for index in self.list[:limit]:
            print(self.novels[index].novelid, self.novels[index].novelname, self.sorttop[i][1])
            i += 1
        return self.list

    def dealwords(self, sent):
        words = list(jieba.cut(sent))   #分詞
        words = utils.filter_stop(words)    #過濾沒意義的詞
        return words


if __name__ == "__main__":
    docs = []
    s = Sortnovel(docs)
    print(s.sents)
    print(docs)
    print("f詞頻: ", s.bm25.f)      #謀篇文章中的詞頻
    print("df詞頻:    ", s.bm25.df)     #所有文章的詞頻
    print("idf權重:   ", s.bm25.idf)    #詞的權重
    query = "一個理科男穿越到唐末,在這個英雄輩出的時代逍遙地生存下去。"
    # qwords = s.dealwords(query)     #查詢語句
    print()
    s.top1(query, 5)
    print(s.top)
    print(s.sorttop)
View Code
結果:






 

 

 

 

 

 

注意:本腳本主要服務於我的Django程序,里面調用了Django的model塊,本地數據庫配置等等,不能直接copy,僅供參考學習。

這里提供一個過濾不重要詞語的腳本utils.py。

utils.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-

'''
作者:chl
時間:2017/11/23
用圖:過濾垃圾詞
'''

import os
import re
import codecs

stop_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'stopwords.txt')

stop = set()
fr = codecs.open(stop_path, 'r', 'utf-8')
for word in fr:
    stop.add(word.strip())
fr.close()
re_zh = re.compile('([\u4E00-\u9FA5]+)')


def filter_stop(words):
    return list(filter(lambda x: x not in stop, words))


def get_sentences(doc):
    line_break = re.compile('[\r\n]')
    delimiter = re.compile('[,。?!;]')
    sentences = []
    for line in line_break.split(doc):
        line = line.strip()
        if not line:
            continue
        for sent in delimiter.split(line):
            sent = sent.strip()
            if not sent:
                continue
            sentences.append(sent)
    return sentences
View Code

濾詞文件:stopwords.txt

分詞工具:https://pypi.python.org/pypi/jieba/

 

(2)Django運用

  views.py部分代碼

#響應搜索頁面
def search(request):
    global user
    request.encoding='utf-8'
    message = ""
    try:
        message = request.GET['kw']
    except Exception as e:
        print(e)
    finally:
        page = 1
        if request.GET.get("page"):
            page = int(request.GET.get("page"))
        if request.GET.get("kw1"):
            message = request.GET.get("kw1")

        if message[:4] == "BM25" or message[:4] == "bm25":
            # searchtotal =
            # print(docs)
            print("這是BM25算法----------------------------------------")
            print("查詢語句 ", message[4:])
            stoplist = sortnovel.top(message[4:])
            searchtotal = []
            searchtotal1 = Novel.objects.filter().order_by()
            for inovel in stoplist:
                searchtotal.append(searchtotal1[inovel])
            del sortnovel.top   #這里必須要銷毀這個list對象,否則第二次查詢會出現'list' object is not callable錯誤,即list()情況。
        else:
            searchtotal = Novel.objects.filter(novelname__contains=message).order_by("novelid")
        novels = searchtotal[:10]
…   …
View Code
結果:

 

  搜索結果:

 

 

 

  后台顯示:

 

七、體會與總結

搜索引擎非常重要,BM25算法只是其中的一個分支,機器語言和算法才是核心,加油吧!

 

八、參考

【1】[轉]搜索引擎的文檔相關性計算和檢索模型(BM25/TF-IDF) - CSDN博客

http://blog.csdn.net/heiyeshuwu/article/details/43429447

【2】文本相似度-bm25算法原理及實現 - 簡書

http://www.jianshu.com/p/1e498888f505

【3】BM25相關度打分公式 - 白帆mvp - 博客園

https://www.cnblogs.com/hdflzh/p/4034602.htm

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM