搜索引擎核心技术与算法 —— 倒排索引初体验
今天開啟一個新篇章——智能搜索與NLP。本篇章將由羸弱菜雞小Q和大家一同學習與智能搜索相關的知識和技術,希望能和大家一同學習與進步,沖鴨!!
這里首先區分兩個概念:搜索和檢索
檢索:數據庫時代的概念,及將數據存入數據庫,有需要的時候進行查取。對結果的要求絕對精確;比如我要在圖書館里找到所有出現“白馬”字樣的圖書,這里用到的就是檢索。
搜索:互聯網時代的概念,人們將信息資源放在網上,第三方將互聯網的信息搜羅起來,建立索引,所以搜索更多是指基于問題相關性的信息收集方式。當我想知道“如何騎白馬最帥?”的時候,這里就不能只羅列出現過“白馬”字樣的圖書了,畢竟騎白馬的不一定是王子,還有可能是唐僧 (╯-_-)╯~╩╩。所以這時候就需要真正的搜索了。
聞道有先后,術業有專攻。那我們就先來講一講關于檢索的那些事兒。
一、倒排索引の初體驗
讓我來先進入一個例子:我想要在《莎士比亞全集》中找到所有出現過“Brutus”和“Caesar”的章節,一個辦法就是從頭到尾閱讀這本書,留意每一個出現“Brutus”和“Caesar”的地方。這種線性掃描就是一種最簡單的計算機文檔檢索方式。那我現在想找出所有出現過“Calpurnia”章節,難道還是需要對整本書重新閱讀一遍嘛?這樣時間開銷也太大了吧!!!為了解決這個問題,我們引入第一個核心概念——倒排索引。
一個典型的倒排索引分為兩部分:詞項詞典和倒排記錄表(如下圖所示)
每一個詞項對應一個倒排記錄,一條倒排記錄表示該詞項所出現過的所有文檔列表(如“Brutus”在第1、2、4、11...篇文檔中出現過),文檔列表的可以是無序的,但是升序更有利于我們之后的高效檢索,下文會提到。
易得,建立一個倒排索引的時間復雜度是O(N),其中N是所有文檔中單詞的數量。
圖一 ?倒排索引的兩個部分
那么有了倒排索引后,我們如何完成上訴檢索任務呢?
先以一個簡單的查詢為例:找到同時出現“Brutus”和“Calpurnia”兩個單詞的文檔。
具體步驟如下:
(1)在詞典中定位Brutus;
(2)返回其倒排記錄表;
(3)在詞典中定位Calpurnia;
(4)返回其倒排記錄表;
(5)對兩個倒排記錄表求交集,如下圖
圖二 ?求交集示意圖
在這里,交集(interp)操作非常關鍵,這是因為我們必須快速將倒排記錄表求交集以盡快找到哪些文檔同時包括兩個詞項。該操作有時也稱為合并(merge)。經常刷題的同學一眼就看出來,這不就是有序數組求交集嘛?leetcode原題實錘了嗷!(leetcode 1213 原題,easy難度)⊙﹏⊙|||
偽代碼如下:
圖三 ?merge偽代碼
思路就是用兩個指針p1、p2分別指向兩個倒排記錄(倒排記錄是按升序排列的)的頭部,如果兩個指針指向的文檔ID相同,則將該文檔放入結果集中,并同時后移兩個指針;如果不相同,則將指向文檔ID較小的指針向后移動。
a?=?[1,?2,?3,?6,?9,?11,?45,?67]b = [4, 6, 13, 45, 69, 98] i?=?j?=?0result?=?[]while?i?<?len(a)?and?j?<?len(b):????if?a[i]?==?b[j]:????????result.append(a[i])????????i?=?i?+?1????????j?=?j?+?1????elif?a[i]?<?b[j]:????????i?=?i?+?1????else:????????j?=?j?+?1print(result) #[6, 45]假設兩個倒排記錄表的大小分別是x和y,那么上述求交集的過程需要 O(x+y)次操作。更正式的說法是,查詢的時間復雜度為O(N)。和之前“再讀一遍書”的掃描方法相比,同樣是O(N)的復雜度,但是該方法的中的N是文檔數,而前者的N是單詞數,假設平均一篇文檔有1000個單詞,那么倒排索引的效率至少也提高了1000倍。
二、構建倒排索引
為獲得檢索速度的提升,就必須要事先建立索引。建立索引的主要步驟如下。?
(1)收集需要建立索引的文檔,如:
Doc1: Friends Romans countrymen.
Doc2: So let it be with Caesar …
(2)將每篇文檔轉換成一個個詞條的列表,這個過程通常稱為詞條化(tokenization)或者分詞,如:
Friends, Romans, countrymen, So, …
(3) 進行語言學預處理,產生歸一化的詞條來作為詞項,如:
Friends, roman, countrymen ,So …
(4) 對所有文檔按照其中出現的詞項來建立倒排索引,索引中包括一部詞典和一個全體倒排記錄表。
建立索引最核心的步驟是將這個列表按照詞項的字母順序進行排序,其中一個詞項在同一文檔中的多次出現會合并在一起。詞典中同樣可以記錄一些統計信息,比如出現某詞項的文檔的數目,即文檔頻率,這里就是指每個倒排記錄表的長度。
在最終得到的倒排索引中,詞項詞典和倒排記錄表都有存儲開銷。前者往往放在內存中,而后者由于規模大得多,通常放在磁盤上。因此,兩部分的大小都非常重要。
那么倒排記錄表一般利用哪一種數據結構進行存儲呢?
由于有些詞在很多文檔中出現,而另外一些詞出現的文檔數目卻很少,所以,如果采用定長數組的方式將會浪費很多空間。對于內存中的一個倒排記錄表,可以采用兩種好的存儲方法:
(1)單鏈表(singly linked list)便于文檔的插入和更新(比如,對更新的網頁進行重新采集),因此通過增加指針的方式可以很自然地擴展到更高級的索引策略。
(2)變長數組(variable length array)的存儲方式一方面可以節省指針消耗的空間,另一方面由于采用連續的內存存儲,可以充分利用現代計算機的緩存(cache)技術來提高訪問速度。額外的指針在實際中可以編碼成偏移地址融入到表中。如果索引更新不是很頻繁的話,變長數組的存儲方式在空間上更緊湊,遍歷也更快。
三、布爾查詢及性能優化
在第一章中——找到同時出現“Brutus”和“Calpurnia”兩個單詞的文檔,這個任務其實就是一個布爾查詢。
布爾檢索模型:接受布爾表達式查詢,即通過AND、OR及NOT等邏輯操作符將詞項連接起來的查詢。在該模型下,每篇文檔只被看成是一系列詞的集合。例如一個標準的布爾查詢可以表示為:
(Brutus OR Calpurnia)AND NOT Caesar
在很多情況下,不論是由于查詢語言本身的性質所決定,還是僅僅由于這是用戶所提交的最普遍的查詢類型,查詢往往是由純“與”操作構成的。正是因為這種特性的存在,我們可以進一步優化查詢速度。
查詢優化指的是如何通過組織查詢的處理過程來使處理工作量最小。對布爾查詢進行優化要考慮的一個主要因素是倒排記錄表的訪問順序。那么,哪種訪問順序具有最優性呢?
一些工程優化策略:
(1)對每個詞項,我們必須取出其對應的倒排記錄表,然后將它們合并。一個啟發式的想法是,按照詞項的文檔頻率(也就是倒排記錄表的長度)從小到大依次進行處理,如果我們先合并兩個最短的倒排記錄表,那么所有中間結果的大小都不會超過最短的倒排記錄表,這樣處理所需要的工作量很可能最少。
(2)在多個布爾檢索合并中,不是將倒排記錄表合并看成兩個輸入加一個不同輸出的函數,而是將每個返回的倒排記錄表和當前內存中的中間結果進行合并,這樣做的效率更高而最初的中間結果中可以調入最小文檔頻率的詞項(即該詞對應的文檔數目最小)所對應的倒排記錄表。算法如下圖所示:
圖四 ?利用中間結果進行合并
(3)在很多情況下,中間結果表可能會短一個甚至多個數量級。這時可以通過在長倒排記錄表中對中間結果表中的每個元素進行二分查找也可以實現合并,使得時間復雜度下降為O(alogb),其中a、b分別是2個倒排記錄的長度。
這里就有同學要問了,之前的復雜度不是O(a+b)??這難道是下降嘛?—— 因為中間結果表很短,意味著a << b的情況是可能發生的,所以這里的線性復雜度并不一定要優于多項式對數復雜度。
或者將長倒排記錄表用哈希方式存儲,這樣對中間結果表的每個元素,就可以通過常數時間而不是線性或者對數時間來實現查找。
(4)對于中間結果表,合并算法可以就地對失效元素進行破壞性修改或只添加標記。破壞性修改更適合單鏈表結構的倒排記錄表,而添加標記更適合數組形式。
好了,通過這篇文章,小Q和大家一起對倒排索引在檢索中是如何使用的有了一個大體的認識。但搜索引擎之路道阻且長,下一篇文章我們將會繼續學習詞項集合的確定和另外高階版本的倒排記錄表。
?? ? ? ? ? ? ? ? ? ? ??
參考文獻:
《信息檢索導論 修訂版》
倒排索引優化 - 跳表 ?—— ?博客園 海鳥
總結
以上是生活随笔為你收集整理的搜索引擎核心技术与算法 —— 倒排索引初体验的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 推荐系统顶会RecSys’20亮点赏析
- 下一篇: 如何匹配两段文本的语义?