一個信息可視化Demo的設計(二):Index & Search

 

一個信息可視化Demo的設計(二):Index & Search

作者:憤怒的小狐狸    撰寫日期:2011-10-29 ~ 2011-10-29

博客鏈接: http://blog.csdn.net/MONKEY_D_MENG

 

此爲系列博文,前續請看第一部分:《一個信息可視化Demo的設計(一):架構設計》

 

一、信息檢索

    愈演愈烈的雲計算來勢洶洶,鋪天蓋地般席捲全球,逢人言必稱雲。彷彿只是在一夜間,海量信息、大數據即充斥着我們的世界。信息量極度膨脹的現在,雲存儲顯得極爲重要,然而大量的、海量的信息我們存下來究竟寓意爲何?答曰:將此數據轉換成有用的信息和知識,並將之廣泛用於各種應用,包括商務管理、生產控制、市場分析、工程設計和科學探索等。

    數據豐富,即會需要有強有力的數據分析工具的支撐去尋求和發現知識。快速增長的海量數據存在於大型和大量的數據庫中,沒有強有力的工具,理解它們已經遠遠超出了人的能力,決策者難以直觀地從海量數據中提取有價值的知識。結果,數據雖豐富,但信息卻貧乏,收集在大型數據庫中的數據信息變成了“數據墳墓”。

    要對海量數據進行分析和處理,先決條件即需要將信息按照一定的方式組織起來,使得數據分析員能夠迅速地找出相關的信息,這一過程即爲信息檢索。至於數據找到後如何分析,一般是基於數據挖掘和機器學習理論,本文暫不討論。

    如果直接對信息資源內容做檢索,順序匹配檢索請求,對於小數據量的環境,這種方法是非常直接、簡單和易於實現的,而且效果也不會太差,但在海量數據環境下,這種掃描過程將是非常耗時的,也是絕對不可取的。

    對於非結構化數據順序掃描很慢,但對於結構化數據的搜索相對較快。原因在於:結構化數據都是有一定結構的,我們可以採取一些技巧:使用搜索優化算法來加快搜索的速度。所以,到此我們認識到問題的關鍵在於:我們應該想辦法把非結構化的數據處理成結構化數據。這種想法是如此的自然而然,卻構成了信息檢索的基本思路:即將非結構化數據中的一部分信息提取出來,使其變得有一定結構,然後設計出相應的高效的數據搜索算法和機制,從而簽到搜索相對較快的目的。而這部分從非結構化數據中提取出來的,然後重新組織,變得有結構的信息,在信息檢索領域,我們便稱之爲索引。舉例說明一下:我們都用過字典,如果沒有拼音和部首檢索表,要你在那麼大一坨字典裏找一個詞組,那真不是一件Happy的事情,但如果按照拼音或部首進行查詢,我們可以很快地定位並找到這個詞組,拼音和部首的檢索表對於字典而言就是所謂的索引。

 

二、檢索需求

    在軟件開發過程中,需求這種東西是最懸乎的,飄忽不定,很容易說變就變。筆者在MSRA/STC做過兩個月的Dev,期間需求並未形成統一的文檔,只是口口相傳。誤傳和誤解是常有的事情,不是說MSRA/STC開發不夠規範,而是筆者作爲暑期實習生,做的只是一個Demo,所受的重視程度不夠高導致的。這種情況下,交流和溝通顯得無比之重要。再到後來,有些需求是我們兩個Dev結合應用場景想出來的,最終也得到了大家的認可,其中之一即是提供表達式級別的信息檢索。

    爲便於說明,精簡的需求如下:有數千棵Decision Tree,每棵Tree包含數百個節點,每個節點的內容是一個特徵Feature序列,現需要檢索以下內容:

(1)    某一Feature被哪些Tree包含

(2)    某一Feature被哪些Node包含

(3)    某一Tree包含了哪些Feature

(4)    Feature的模糊檢索,如檢索“PerStream%”所對應的Tree或Node

(5)    表達式級別檢索,如([FeatureName] = PerStreamBM25F_Body || [FeatureID] = 18) && ([TreeID] = 1 || [TreeID] = 2)

 

三、Index設計

    如果你瞭解過搜索引擎,學習或使用過開源全文檢索框架Lucene的話,不難會理解爲什麼要創建索引,以及倒排索引的基本結構。基於筆者信息檢索理論與實踐的積累,給出了下列索引方案:

(1)    倒排索引:Feature-->Tree

(2)    倒排索引:Feature-->Node

(3)    正排索引:Tree-->Feature

(4)    字典樹索引(鍵樹):Feature Fuzzy Search

    在這個Demo中,我們輸入Feature,然後檢索其對應的所有Tree或Node。而原始的信息是,某棵Tree中包含了若干節點,每個節點又包含了Feature的序列。如果沒有索引,我們就需要遍歷所有的Tree,然後遍歷每棵Tree的所有節點,並匹配每個節點的Feature序列是否包含了該Feature,直到掃描完所有的Tree爲止。這種低效的檢索實現方式是如此讓人無法忍受,以至於你即使沒學習過信息檢索都會想到要設計一個高效的算法來解決這個尷尬。

那麼,我們來分析一下爲什麼順序掃描的速度會慢。其實是我們想要搜索的信息和原始數據中所存儲的信息不一致造成的。原始數據中所存儲的信息是每個Tree包含哪些Node,每個Node又包含哪些Feature,即從Tree到Node的映射,以及從Node到Feature的映射。然而我們想搜索的信息是哪些Tree或Node包含此Feature,即已知Feature,求相應的Tree或Node,即從Feature到Tree或Node的映射。兩者恰恰是相反的,因此順序掃描的速度會很慢。這樣一來,如果索引中存儲的是從Feature到Tree或Node的映射,則會大大提高搜索速度。

    於是乎倒排索引的方式就形成了,具體的實現就不用細談了吧,每個Feature對應於一個Tree List或Node List即可。如果要求同時包含兩個Feature的Tree或Node,只需要把兩個Feature對應的Tree或Node List取交集即可,兩個以上的情況你懂的…

    輸入Tree,檢索其包含哪些Feature就沒什麼好講的了,正排索引搞定,Tree-->Feature List即可。

    比較有意思的是,對Feature的檢索支持模糊匹配,比如你輸入“PerStream%”用於檢索前綴爲PerStream的Feature所對應的Tree或Node。我提供的索引方案是字典樹索引,在數據結構中又叫鍵樹,或共享前綴樹。這處結構可以用來實現輸入法的智能提示功能,當然它僅支持前綴匹配,如果你硬是先把字符串先倒排一遍,然後再用字典樹創建索引,當然也能實現後綴匹配,但這個…

    關於字典樹的設計比較精巧,我在之前的博文《輸入法核心數據結構及算法的設計》已經做過簡單介紹了,讀者可以參閱,這裏不再過多言論。總之,利用字典樹存儲了Fuzzy FeatureàTree List或Node List的映射,從而實現了Feature的模糊檢索。

 

四、Index優化

    雖然說概念上索引結構大致是確定下來了,有倒排索引、正排索引還有字典樹索引,但基於這一思想不同的人會有不同的實現。比如Feature-->Tree List的映射關係如何組織存儲,有人直接就會用一個ArrayList存儲就完了;有人會存完之後按照升序關係排個序。再比如,求同時包含兩個Feature的Tree,則需要將兩個Tree List求交集,如果Tree List之前是排過序的,O(n)時間就能搞定,沒排序的就SB了。當然這只是舉個例,我的意思是想說,同樣一個思路,雖然說看似已經很明朗了,但是不同的實現其效率和效果相關甚遠。

    結合實際的開發場景,我們應該儘可能地設計出更加高效的結構和算法。我的實現方案即不是ArrayList,也不需要排序,而是引入了BitMap來存儲Tree List。如果BitMap[100]=1表示ID=100的Tree存在於Tree List中,BitMap[99]=0表示ID=99的Tree不存在。這樣設計的原因是分析了當時的場景,只有數千棵Tree,數量不大,可放入一個BitMap中,用下標來標識Tree ID,在一定程度上還能節點存儲空間,因爲一個Feature對應的ArrayList可能會很大。同時,也不需要排序過程,如果求兩個List的交集,直接用BitMap做個&運算即可,方便快捷。

    這是結合具體場景做性能優化的其中一個案例。當然,我說的只是一個基本的做法,如何做優化,還需要在開發過程中細細琢磨。

    到此爲止,索引方案有了,索引結構有了,索引也被我們一步一步地構建起來了,但各位有沒有注意到少了點什麼?你有沒有發現所有的索引數據全部都存儲於內存中啊。內存是什麼,是一種不可靠的存儲介質,斷電就全部擦除,數據全部丟失啊。如果哪天服務器掛了,索引數據豈不是全部沒了,服務器重啓是不是又要加載原始數據重建所有索引呢?數據量小還好說,海量的大數據怎麼處理?一次索引創建幾天都搞不定。也即是說,我們還需要一個環節:索引落地!

    索引落地即是將內存中的索引,以一定的格式和組織結構寫入磁盤,這種格式必然會區別於內存,比如剛纔提到的倒排索引表在磁盤上如何存儲,如何能夠更爲迅速地被服務加載,並在內存中重構完整的倒排索引表等。當然索引落地就沒那麼簡單了,道是可以參考一個Lucene的索引格式,筆者在實習的兩個月中也沒有那麼多時間去研究。由於基於BitMap做了優化,除了在檢索效率得到提升之外,索引所佔用的空間並不大,只有1~2M的樣子,對於一個實習Demo而言,不考慮容災容錯的話已經足夠了。

    但這並不妨礙我們繼續擴展思路深入思考:海量數據情況下,巨大索引量不能全部載入內存,這時還需要藉助於Cache去緩存當前最熱的索引,用於提升檢索效率,而磁盤則作爲沉澱層,存儲最不常使用的索引,當然又是一個比較困難卻有趣的問題了。

 

五、Search設計

    當然,我們創建索引的目的也即是爲了Search,用戶不需要知道Index,但用戶一定要知道Search。一開始,我們Search的定位只是單一短語的Search,後來在筆者的建議下,我們確定了表達式級別Search的需求,如([FeatureName] = PerStreamBM25F_Body || [FeatureID] = 18) && ([TreeID] = 1 || [TreeID] = 2),當然單一短語也是一種最爲簡單的表達式。

    我們的表達式是通過&&、||、!以及()等運算符組裝在一起的,然而,表達式是不能直接被用於檢索的,要想理解表達式的含義,則需要表達式的解析組件。關於表達式的解析算法,是藉助於逆波蘭表達式完成的,這些內容留在了本系列博文第三部分:算法篇中詳細論述,請大家持續關注本博客~

    既然是對表達式做檢索,那麼我們的檢索類是如何定義的呢?筆者思考了一個上午,想到了一個算是比較巧妙的解決方案與大家分享。最頂層是抽象類爲Query,抽象抽象方法search(),直接從Query派生出WordQuery、NotQuery和BinaryQuery。WordQuery和NotQuery只有一個操作數,而BinaryQuery卻有兩個操作數。從BinaryQuery派生出AndQuery和OrQuery分別對應於&&和||操作。一個簡單的類繼承關係圖如下所示:

 

Query

檢索抽象基類,提供抽象方法search()

WordQuery

從Query派生的類,查找給定的單詞或短語,基礎檢索類

NotQuery

從Query派生的類,對應於運算符!

BinaryQuery

從Query派生的類,表示帶兩個Query操作數的查詢

AndQuery

從BinaryQuery派生的類,表示兩個Query的與操作,對應於運算符&&

OrQuery

從BinaryQuery派生的類,表示兩個Query的或操作,對應於運算符||

query1 && query2

返回AndQuery(query1, query2)

query1 || query2

返回OrQuery(query1, query2)

!query

返回NotQuery(query)

    按照我們的設計方式,則對於檢索表達式:([FeatureName] = PerStreamBM25F_Body || [FeatureID] = 18) && ([TreeID] = 1 || [TreeID] = 2),分別對應於以下對象:

    query1:WordQuery([FeatureName] = PerStreamBM25F_Body)

    query2:WordQuery([FeatureID] = 18)

    query3:WordQuery([TreeID] = 1)

    query4:WordQuery([TreeID] = 2)

    表達式:AndQuery(OrQuery(query1, query2), OrQuery(query3, query4))

 

六、Search優化

    這塊當時沒有具體去做,只是大概地想了想。我們在用C語言寫程序時,會知道運算符&&、||都有短路效應,基於短路效應,我們可以完成某些判斷語句的優化。而我們的Search也不例外,對於檢索請求:[FeatureName] = PerStreamBM25F_Body && [FeatureID] = 18,如果[FeatureName] = PerStreamBM25F_Body的Tree根本就不存在,則就沒必要去執行[FeatureID] = 18的檢索過程了。再比如類似於:[FeatureID] = 1 && [FeatureID] != 1這樣的檢索請求,則可以直接過濾即可,因爲結果必爲NULL。

    當然優化是門高深的學問,想做到更好只有不斷地實踐和思考了~

 

七、總結

    Index & Search這塊,我個人認爲是Demo中比較巧妙的一塊,也是代碼最優雅的部分,整個Demo做下來,細細品味,亦覺得寫代碼也是件非常快樂和有成就感的事情,特別是自己對於這個需求實現,是從需求分析、到方案設計、到編碼實現、再到功能測試、以及後期性能優化都做過充分的思考,收穫彼豐。當然本文並未粘貼任何代碼,只談思路只談想法,只談設計只談框架,大而不虛,簡而言之。

    在本系列博文第三部分:算法篇中,我還會單獨列出一段描述Search表達式的解析算法,請大家持續關注本博客~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章