閱讀本篇文章首先要對“詞匯文檔矩陣”和“倒排索引”有個基本的認識,要了解相關的知識可以閱讀上一篇文章:搜索引擎概述之倒排索引索引。
布爾檢索是最基礎,也是使用最廣泛的信息檢索模型了。所謂布爾查詢就是通過AND、OR、NOT等邏輯操作符將檢索詞連接起來的查詢。比如:
李白 AND (杜甫 OR 白居易) NOT 蘇軾
那么,布爾檢索時如何利用倒排索引進行查詢的呢?我們還是先從詞匯文檔矩陣說起吧~
從詞匯文檔矩陣說起
我們先假設我們有一個詞匯文檔矩陣,如下所示:
當我進行布爾查詢的時候,其實本質就是在為文檔矩陣中的每行1和0組成的二進制數做布爾邏輯運算。
李白 AND 杜甫
=110001 AND 110100
=110000
AND操作就是,相同的位同時為1,則結果為1,否則為0。李白 AND 杜甫最終得出的結果就是文檔1和文檔2
杜甫 OR 白居易
=110100 OR 110111
=111111
OR操作就是,相同的位有一個位1,則結果為1,都為0結果才是0。杜甫 OR 白居易最終得出的結果就是所有的文檔。
李白 NOT 蘇軾
=110001 NOT 010000
=110001 AND 101111
=100001
NOT操作就是先將NOT之后的內容取反,再進行AND操作。李白 NOT 蘇軾最終得出的結果就是文檔1和文檔6。
我們可以發現使用詞匯文檔矩陣的話,進行布爾檢索十分簡單。但是我們在“搜索引擎概述之倒排索引”(回復“倒排索引”查看)中說過,詞匯文檔矩陣是稀疏的,極其浪費空間資源,使用這種結構存儲大量的數據是不現實的。因此,我們要使用的是倒排索引。
倒排索引的布爾查詢
那么在倒排索引中我們如何進行布爾查詢呢?首先我們先將上邊的詞匯文檔矩陣轉換為倒排索引:
那么,如果我們進行:“李白 AND 白居易”的查詢則會進行如下操作:
1、在詞典中定位“李白”
2、返回其倒排記錄:“1,2,6”
3、在詞典中定位“白居易”
4、返回其倒排記錄:“1,2,4,5,6”
5、對另個倒排記錄表求交集
最終的得到的結果就是“1,2”,也就是文檔1和文檔2。
同理,OR查詢就是取並集,NOT查詢就是從從第一個倒排記錄中排除第二個倒排記錄的內容。為了高效的完成交集,並集和排除操作,一般我們會要求倒排記錄中的文檔id是有序的。
下面我們以三種操作中比較復雜的求交集為例,來說一下布爾查詢的算法實現。一種比較常見的倒排記錄求交集的算法如下面偽代碼所示:
我們的倒排記錄是有序的。我們依次比對兩個倒排記錄中文檔id的值。如果,如果兩者id一樣則輸出該id,然后同時比對下一個文檔id。如果兩者id不一樣,則較大的那個不變,之后去和較小的id的下一個id去做比對。這個算法的時間復雜度是O(x+y),也就是O(N),要遠優於無序列表依次作比對的O(N²)。
優化
查詢優化——從小到大
李白 AND 白居易 AND 蘇軾
當我們去查詢一種多個AND組成的查詢時,其實本質上就是依次取交集。而且我們很容易知道,對於上邊所述的算法來說,如果其中一個集合越短,那么計算可能就越快。因此一個啟發式的優化方法就是,在取多個交集的時候,不是去依次的計算,而是先將倒排記錄表按照長度從小到大排列,我們先合並最短的兩個倒排記錄表。這樣所有的中間結果的大小都不會超過最短的倒排記錄表。因為多個集合的交集元素個數,一定不會大於其中任何一個集合的元素個數。
查詢優化——二分查找
如果兩個倒排記錄表的元素個數差距極大的時候(比如比較極端的:1和10000),我們就沒必要依次去比較它們的元素了。采用對短列表中的全部元素分別在長列表中做二分查找的方式,可能會更快。
查詢優化——跳表
另外,使用跳表的方式去實現倒排記錄表,也可以加快倒排記錄表求交集的速度。但是,由於跳表是一種用空間換時間的數據結構,因此會占用更大的空間。同時,雖然現代計算機的cpu運算速度很快,但是磁盤的訪問速度依舊很慢。在這樣的前提下,如果是一個將所有索引數據都存在內存中的搜索引擎,使用跳表會加快速度;但如果是將索引數據存儲在硬盤上的搜索引擎,反而可能會大大的降低速度。由於篇幅有限,我無法在這里詳細的介紹跳表這種數據結構的實現。在未來的文章中我會單獨介紹這種數據結構,到時候會再次深入的討論這個問題。
布爾查詢的缺點
無法排序
布爾查詢的本質只是查詢了某些詞匯在文檔中的有無,但是卻無法告訴用戶哪些是更相關的,哪些是不那么相關的。也就是說,布爾查詢本身無法按照相關度進行排序。
正確率和召回率難以均衡
評價搜索引擎的最重要的兩個指標就是正確率和召回率。
- 正確率:返回的結果真正的和用戶信息需求相關的文檔所占的比率。
- 召回率:所有和用戶 信息需求真正相關的文檔中檢索系統返回的百分比。
比如說如果每次查詢,我都將所有的文檔返回,召回率必然是100%(所有的文檔中必然包含所有的相關文檔),但是正確率就會很低很低(100萬文檔中只有1萬文檔真的和需求相關)。而如果我每次只返回一條數據,而且保證這條數據百分之百和用戶需求相關,那么正確率就是百分之百(共返回1篇文檔,有1篇和用戶信息相關,因此是百分之百) ,但召回率很低(1萬篇相關文檔之返回1篇)。
使用布爾查詢在實際應用中會遇到這樣的問題:
如果我要查一篇名字為“Semantic information retrieval research based on co-occurrence analysis”的文章,如果我將所有的空格都識別為AND,這時就只會返回標題為這篇文章的文檔,用戶無法獲得任何其他相關信息。此時正確率很高,但召回率很低。同樣,如果我將所有的空格都識別為OR,這時我雖然會獲得相關信息了,但是很可能很多相關信息只和information有關,但這並不是我想要的。因此此時雖然召回率很高,但是正確率很低。布爾檢索在召回率的問題上很容易走兩個極端,很難達到理想的均衡狀態。
然而在檢索引擎幾十年的發展中,已經有很多方案在完善這些問題,或者說在增強布爾查詢的能力。而另一方面,也有了一些新的檢索模型或技術(如,自由文本查詢)來解決這些問題。這些我都將在未來的文章中,慢慢的介紹給大家。