如何一天做出搜索引擎(2)——搜索與匹配
目錄
- 寫在前面
- 開啟我們的旅程
- 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)——搜索與匹配