信息檢索導論學習筆記(4)


索引構建

Image(9)

回顧倒排索引的構建,首先,我們掃描一遍文檔集合得到所有的詞項—文檔 ID 對。然后,我們以詞項為主鍵、文檔 ID 為次鍵進行排序。最后,將每個詞項的文檔 ID組織成倒排記錄表, 並計算諸如詞項頻率或者文檔頻率的統計量。對於小規模文檔集來說,上述過程均可在內存中完成。對於大規模文檔集,由於內存不足,我們必須使用基於磁盤的外部排序算法(external sorting algorithm) 。

 

BSBI(blocked sort-based indexing algorithm,基於塊的排序索引算法)

第 1 步,將文檔集分割成幾個大小相等的部分,如倒排記錄累積到10,000,000條;

第 2 步,將每個部分的詞項 ID—文檔 ID 對在內存中排序;

第 3 步,將中間產生的臨時排序結果存放到磁盤中;

第 4 步,將所有的中間文件合並成最終的索引。

 

注:該算法中有一個關鍵決策就是確定塊的大小

 

算法實現步驟說明:為使索引構建過程效率更高,我們將詞項用其 ID 來代替,每個詞項的 ID 是唯一的序列編號。我們可以在處理文檔集之余將詞項映射成其ID。即該算法將文檔解析成詞項 ID—文檔 ID對,並在內存中一直進行解析處理,直到累積至放滿一個固定大小的塊空間(如倒排記錄累積到10,000,000條)為止。我們選擇合適的塊大小,使之其能方便加載到內存並允許在內存中快速排序。排序后的塊轉換成倒排索引格式后寫入磁盤。算法實現的最后一步是:將若干個塊索引同時合並成一個索引文件。合並時,同時打開所有塊對應的文件,內存中維護了為若干個塊准備的讀緩沖區和一個為最終合並索引准備的寫緩沖區。每次迭代中,利用優先級隊列(即堆結構)或者類似的數據結構選擇最小的未處理詞項 ID 進行處理。讀入該詞項的倒排記錄表並合並,合並結果寫回磁盤中。 

 

BSBI的問題

前提假設是詞典可以在內存放下,並需要一種將詞項映射成其 ID 的數據結構。對於大規模的文檔集來說,該數據結構會很大以致在內存中難以存放。實際上,倒排記錄表可以直接采用term,docID 方式而不是termID,docID方式,但是此時中間文件將會變得很大。

 

SPIMI(single-pass in-memory indexing,內存式單遍掃描索引算法)

1.算法逐一處理每個詞項—文檔 ID 對

2.如果詞項是第一次出現,那么將之加入詞典(最好通過哈希表來實現) ,同時建立一個新的倒排記錄表

3.如果該詞項不是第一次出現則直接在倒排記錄表中增加一項

4.由於倒排記錄表是動態增長的,算法事先並不知道每個詞項的倒排記錄表大小,故一開始會分配一個較小的倒排記錄表空間,每次當該空間放滿的時候,就會申請加倍的空間

5.基於上述步驟可以對每個塊生成一個完整的倒排索引,為加快最后的合並過程,要對詞項進行排序操作,然后寫入磁盤形成單塊索引

6.這些獨立的索引最后合並一個大索引

 

與BSBI算法的比較

  • SPIMI是直接在倒排記錄表中增加一項。和那種一開始就整理出所有的詞項 ID—文檔 ID 並對它們進行排序的做法 (這正好是 BSBI中的做法)不同,由於不需要排序操作,因此處理的速度更快;
  • SPIMI 使用詞項而不是其ID,它將每個塊的詞典寫入磁盤,對於下一個塊則重新采用新的詞典。只要硬盤空間足夠大,SPIMI就能夠索引任何大小的文檔集。 由於詞項的 ID 不需要保存。這樣,每次單獨的處理的塊大小可以非常大,整個倒排索引的構建過程也因此會非常高效。由於事先並不知道每個詞項的倒排記錄表大小,算法一開始會分配一個較小的倒排記錄表空間,每次當該空間放滿的時候,就會申請加倍的空間 。這意味着一些空間被浪費,也正好抵消了不用保存詞項 ID所省下的空間。然而,總體來說,在 SPIMI 中對塊建立索引所需的內存空間仍然比 BSBI 少。

新的問題:實際當中,文檔集通常都很大,在單台計算機上很難高效地構建索引。

 

分布式索引

MapReduce

Wiki地址:http://zh.wikipedia.org/wiki/MapReduce

java開源框架實現hadoop:http://hadoop.apache.org/

 

MapReduce是Google提出的一個軟件架構(一個魯棒的分布式計算框架),用於大規模數據集(大於1TB)的並行運算。概念“Map(映射)”和“Reduce(化簡)”,及他們的主要思想,都是從函數式編程語言借來的,還有從矢量編程語言借來的特性。
當前的軟件實現是指定一個Map(映射)函數,用來把一組鍵值對映射成一組新的鍵值對,指定並發的Reduce(化簡)函數,用來保證所有映射的鍵值對中的每一個共享相同的鍵組。

 

分布式索引構建核心思想:

  • 維持一台主機(Master)來指揮索引構建任務‐這台主機被認為是安全的
  • 把整個任務分成易分配的子任務塊,集群中的主控節點(master node)負責處理任務在工作節點上的分配和重分配。

Image(10)

 

實現步驟說明:

Map階段:

主控節點將一個數據片分配給一台空閑的分析器(Parser),分析器一次讀一篇文檔然后輸出(term,docID)對,然后分析器將這些(term,docID)對又分成j個詞項分區,每個分區按照詞項首字母進行划分。E.g., a‐f, g‐p, q‐z (這里j = 3)

Reduce階段:

主控節點將一個詞項分區分配給一台空閑的倒排器(Inverter),倒排器收集對應某一詞項分區(e.g., a-f分區)所有的(term,docID) 對(即倒排記錄表),排序並寫進倒排記錄表

基於MapReduce的索引構建示圖(Casesar簡寫成 C,conquered 簡寫成 c’ed)

Image(11)

注:分布式索引是個極其復雜的問題,主控節點任務分配的控制,分布式集群網絡通訊的數據傳遞問題等等

 

新問題:以上我們都假設文檔集是靜態的,這對於很少甚至永遠不會改變的文檔集(如《聖經》或莎士比亞的著作)來說沒有任何問題。然而,大部分文檔集會隨文檔的增加、刪除或更新而不斷改變。這也意味着需要將新的詞項加入詞典,並對已有詞項的倒排記錄表進行更新。

 

動態索引構建

簡單辦法:

最簡單的索引更新辦法就是周期性地對文檔集從頭開始進行索引重構。如果隨時間的推移文檔更新的次數不是很多,並且能夠接受對新文檔檢索的一定延遲,再加上如果有足夠的資源能夠支持在建立新索引的同時讓舊索引繼續工作, 那么周期性索引重構不失為一種較好的選擇。

 

引入輔助索引的解決辦法:

同時保持兩個索引:一個是大的主索引,另一個是小的用於存儲新文檔信息的輔助索引(auxiliary index) ,后者保存在內存中。檢索時可以同時遍歷兩個索引並將結果合並。每當輔助索引變得很大時,就將它合並到主索引中。文檔的刪除操作記錄在一個無效位向量(invalidation bit vector)中,在返回結果之前可以利用它過濾掉已刪除文檔。某篇文檔的更新通過先刪除后重新插入來實現。

 

主輔索引合並分析:

  • 主輔索引的構建索引辦法會導致合並過於頻繁(內存索引變大時就得進行合並操作),合並時如果正好在搜索,那么搜索的性能將很低
  • 理想情況下,如果將每個詞項的倒排記錄表都單獨存成一個文件,那么要合並主索引和輔助索引,只需要將輔助索引的倒排記錄表擴展到主索引對應的倒排記錄表即可完成。遺憾的是,由於絕大多數文件系統不能對大數目的文件進行高效處理,因此將每個倒排記錄表存成一個文件這種方式實際是不可行的。另一種簡單的方法是將索引存到一個大文件中,也就說將所有倒排記錄表存到一起。
  • 現實當中常常介於上述兩者之間(例如:將大的倒排記錄表分割成多個獨立的文件,將多個小倒排記錄表存放在一個文件當中)

對數索引合並(lucene最早應用)

Image(12)

 

同以往一樣,內存中的輔助索引(我們稱之為Z0)最多能容納n個倒排記錄。當達到上限n時, Z0中的n個倒排記錄會移到一個建立在磁盤上的新索引I0中。 當Z0下一次放滿時,它會和I0合並,並建立一個大小為 2×n的索引Z1。Z1可以以I1的方式存儲(如果I1不存在)或者和I1合並成Z2(如果I1已存在)。上述過程可以持續下去。對搜索請求進行處理時,我們會利用內存的索引Z0和現有的磁盤上的有效索引Ii進行處理,並將結果合並。看到這里,對二項式堆結構比較熟悉的讀者會發現它與剛才提到的對數合並下的倒排索引結構之間具有很高的相似性。

 

LinkedIn的java開源實時索引框架Zoie:http://javasoze.github.com/zoie/


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com