1. 程式人生 > >如何一天做出搜索引擎(2)——搜索與匹配

如何一天做出搜索引擎(2)——搜索與匹配

length 標點符號 clas read 簡單的 做出 com 基於 proc

目錄

  • 寫在前面
  • 開啟我們的旅程
    • 1. 處理搜索語句
    • 2. 從數據庫中取出新聞詞頻統計
    • 3. 對新聞的相關性和時效性進行綜合評估
  • 寫在後面

@

寫在前面

大家好!這一章主要介紹搜索引擎的搜索與匹配部分的思路與實現。在上一章中,我們實現了新浪新聞的搜集和數據庫的建立。這為我們這一章的搜索打下了基礎。我們在這一章要實現搜索引擎的最為重要的部分——將用戶輸入的文字與數據庫中的新聞進行匹配,從而為用戶推薦與他的搜索最為相關的、且時效性較好的幾條新聞。

這篇文章只是講解思路,代碼的展示也是為了配合講解。如果大家要查看源碼,請移步我的github,這篇文章所講內容在Search.py中。

開啟我們的旅程

我們的目標是對於用戶輸入的語句,搜索引擎能夠從數據庫中找到最相關的新聞。當然,相關性不是我們唯一的目標,因為如果一篇新聞的相關性很高,但它是幾年前發布的,那它就失去了時效性,這種新聞一般也是沒有價值的。所以我們程序在給新聞打分的時候,要兼顧相關性和時效性。

讓我們開啟旅程吧??

1. 處理搜索語句

首先我們對用戶輸入的語句進行處理。和處理新聞的思路一樣,我們先用jieba庫對用戶輸入的語句分詞。再將這些詞中的停用詞和標點符號去除(停用詞的概念請參考我的上一篇博客)。剩下的詞就可以作為我們搜索的關鍵詞啦。

舉個栗子,如果我輸入“中國和美國之間的貿易戰”,在處理完以後,剩下的關鍵詞就是“中國”、“美國”、“之間”、“貿易戰”,而停用詞“和”、“的”就被去除了。

提取出關鍵詞後,我們還要對關鍵詞的詞頻進行統計。試想,如果在搜索語句中“中國”出現了3次,而“美國”只出現了1次,那麽說明用戶更關心“中國”,所以與“中國”有關的新聞就應該排在與“美國”有關新聞之前。於是我們還應該統計關鍵詞的詞頻,並用一個字典記錄下來,留著之後推薦度打分用。

輸入“中國與美國之間的貿易戰,中國將如何應對”,程序的關鍵詞提取和詞頻統計結果:
技術分享圖片
代碼如下:

def search(sentence, N, avg_l):
    build_StopWords()   #建立停用詞文檔
    #對輸入的詞進行相關度評價
    searchTerms1 = jieba.lcut(sentence, cut_all = False)
    #清除停用詞
    searchTerms = {}
    for p in searchTerms1:
        p=p.strip()
        if len(p)>0 and p not in stop_words and not p.isdigit():
            if p not in searchTerms:
                searchTerms[p] = 1
            else:
                searchTerms[p] += 1

2. 從數據庫中取出新聞詞頻統計

分析完輸入語句後,我們接下來將我們上一篇博客中保存在數據庫裏的數據(包括新聞的日期,關鍵詞頻,標題等)提取出來。
這一步比較簡單,就是一個對sqlite3數據庫的一個簡單的提取操作。(如果對數據庫操作不熟悉,請自行百度sqlite3的基本用法)代碼如下:
(這段代碼接上一段,也在search函數中)

    db = sqlite3.connect('news.sqlite')
    #從數據庫sqlite中讀出表
    df2 = pandas.read_sql_query('SELECT * FROM TermDict', con = db)
    df3=df2.T
    Dict1 = df3.to_dict()
    Dict = {}
    for i in Dict1:
        Dict[Dict1[i]['index']] = [Dict1[i]['0'], Dict1[i]['1']]

3. 對新聞的相關性和時效性進行綜合評估

經過前兩步的準備,可謂是萬事俱備,只欠打分這一股東風了。我們接下來就是要依據我們第一步中提取出的搜索關鍵詞詞頻,與第二步中的數據中的關鍵詞詞頻比對,再結合新聞發布的時間,給出推薦度打分。

因為我們的搜索引擎要追求極致的體驗(??),所以搜索的速度必須快,且數據庫的容量必須相對較大。所以我們只對新聞的標題和關鍵字進行匹配。因為如果匹配正文的話,就會多消耗幾倍甚至幾十倍的時間。

在這裏,我們利用BM25算法進行打分。(有關BM25算法的介紹請參考這篇博客,本文和此篇博客做了相同的化簡)
代碼如下:
(這段代碼接上一段,也在search函數中)

    #采用基於概率的BM25模型計算相關度
    RelaScore = {}  #相關度分數,和allPages中的index相對應

    #參數
    b = 0.75
    k1 = 1.2

    RelaWeight = 0.7
    TimeWeight = 0.3

    #文檔平均長度avg_l, 文檔總數N 在前面已經求出
    for word in searchTerms:
        #將qtf近似為1
        #qtf = searchTerms[word]  #查詢中的詞頻(query's term frequency)
        if word in Dict:
            df = Dict[word][0]   #文檔頻率(document frequency),即該詞在所有新聞中出現的總次數
            docs = Dict[word][1].split('\n')
            IDF = math.log2( (N + 0.5 + df) / (df+0.5))  #將分子上的df去掉了
            for x in docs:  #對每一個新聞處理,加上與這個詞的相關度
                doc = x.split('\t')
                doc_id = int(doc[0])
                doc_time = doc[1]   #新聞時間
                tf = int(doc[2])    #文檔中的詞頻(term frequency)
                ld = int(doc[3])    #文檔長度(length of document)


                K = k1 * ( 1 - b + b*ld/avg_l )
                #計算w(word, doc)
                RelaW = IDF * (tf*(k1+1)) / (tf+K) 

                #w = qtf*tf/ld

                #計算時間因子
                newsTime = datetime.strptime(doc_time,'%Y-%m-%d %H:%M')
                nowTime = datetime.now()
                timeDis = (nowTime.day-newsTime.day)*24 + nowTime.hour-newsTime.hour +(nowTime.minute-newsTime.minute)/60   #以小時為單位

                w = RelaW * RelaWeight + TimeWeight/timeDis

                #print("%f %f"%(RelaW * RelaWeight,TimeWeight/timeDis ))
                if doc_id in RelaScore:
                    RelaScore[doc_id] += w
                else:
                    RelaScore[doc_id] = w 

打分打完了,最後在排一波序就ok啦:
(這段代碼接上一段,也在search函數中)

minheap = MinHeap()
    for i in RelaScore:
        minheap.add((RelaScore[i],i))
                 
    return minheap

其中MinHeap是我寫的一個最小堆,代碼就不貼出來了,好奇的朋友(??)請參考我的github中的MinHeap.py

寫在後面

這一章總體來說並不是很復雜,主要的難點在於理解BM25算法。其實說實話,即使不理解BM25算法,只要會用也可以做出來。當然啦,身為積極向上的有誌青年,我們當然是不能滿足只會用的!(是吧...)

源碼請移步我的github,這篇文章所講內容在Search.py中。

希望大家能有所收獲!

如何一天做出搜索引擎(2)——搜索與匹配