Lucene學習總結之一:全文檢索的基本原理

【轉載】: https://blog.csdn.net/forfuture1978/article/details/4711308


一、總論

根據http://lucene.apache.org/java/docs/index.html 定義:

Lucene 是一個高效的,基於Java 的全文檢索庫。

所以在瞭解Lucene之前要費一番工夫瞭解一下全文檢索。

那麼什麼叫做全文檢索呢?這要從我們生活中的數據說起。

我們生活中的數據總體分爲兩種:結構化數據 和非結構化數據 。

  • 結構化數據: 指具有固定格式或有限長度的數據,如數據庫,元數據等。
  • 非結構化數據: 指不定長或無固定格式的數據,如郵件,word文檔等。

當然有的地方還會提到第三種,半結構化數據,如XML,HTML等,當根據需要可按結構化數據來處理,也可抽取出純文本按非結構化數據來處理。

非結構化數據又一種叫法叫全文數據。

按照數據的分類,搜索也分爲兩種:

  • 對結構化數據的搜索 :如對數據庫的搜索,用SQL語句。再如對元數據的搜索,如利用windows搜索對文件名,類型,修改時間進行搜索等。
  • 對非結構化數據的搜索 :如利用windows的搜索也可以搜索文件內容,Linux下的grep命令,再如用Google和百度可以搜索大量內容數據。

對非結構化數據也即對全文數據的搜索主要有兩種方法:

一種是順序掃描法 (Serial Scanning): 所謂順序掃描,比如要找內容包含某一個字符串的文件,就是一個文檔一個文檔的看,對於每一個文檔,從頭看到尾,如果此文檔包含此字符串,則此文檔爲我們要找的文件,接着看下一個文件,直到掃描完所有的文件。如利用windows的搜索也可以搜索文件內容,只是相當的慢。如果你有一個80G硬盤,如果想在上面找到一個內容包含某字符串的文件,不花他幾個小時,怕是做不到。Linux下的grep命令也是這一種方式。大家可能覺得這種方法比較原始,但對於小數據量的文件,這種方法還是最直接,最方便的。但是對於大量的文件,這種方法就很慢了。

有人可能會說,對非結構化數據順序掃描很慢,對結構化數據的搜索卻相對較快(由於結構化數據有一定的結構可以採取一定的搜索算法加快速度),那麼把我們的非結構化數據想辦法弄得有一定結構不就行了嗎?

這種想法很天然,卻構成了全文檢索的基本思路,也即將非結構化數據中的一部分信息提取出來,重新組織,使其變得有一定結構,然後對此有一定結構的數據進行搜索,從而達到搜索相對較快的目的。

這部分從非結構化數據中提取出的然後重新組織的信息,我們稱之索引 。

這種說法比較抽象,舉幾個例子就很容易明白,比如字典,字典的拼音表和部首檢字表就相當於字典的索引,對每一個字的解釋是非結構化的,如果字典沒有音節表和部首檢字表,在茫茫辭海中找一個字只能順序掃描。然而字的某些信息可以提取出來進行結構化處理,比如讀音,就比較結構化,分聲母和韻母,分別只有幾種可以一一列舉,於是將讀音拿出來按一定的順序排列,每一項讀音都指向此字的詳細解釋的頁數。我們搜索時按結構化的拼音搜到讀音,然後按其指向的頁數,便可找到我們的非結構化數據——也即對字的解釋。

這種先建立索引,再對索引進行搜索的過程就叫全文檢索(Full-text Search) 

下面這幅圖來自《Lucene in action》,但卻不僅僅描述了Lucene的檢索過程,而是描述了全文檢索的一般過程。

[圖]全文檢索的一般過程

 

全文檢索大體分兩個過程,索引創建 (Indexing) 和搜索索引 (Search) 。

  • 索引創建:將現實世界中所有的結構化和非結構化數據提取信息,創建索引的過程。
  • 搜索索引:就是得到用戶的查詢請求,搜索創建的索引,然後返回結果的過程。

於是全文檢索就存在三個重要問題:

1. 索引裏面究竟存些什麼?(Index)

2. 如何創建索引?(Indexing)

3. 如何對索引進行搜索?(Search)

下面我們順序對每個個問題進行研究。

 

二、索引裏面究竟存些什麼

索引裏面究竟需要存些什麼呢?

首先我們來看爲什麼順序掃描的速度慢:

其實是由於我們想要搜索的信息和非結構化數據中所存儲的信息不一致造成的。

非結構化數據中所存儲的信息是每個文件包含哪些字符串,也即已知文件,欲求字符串相對容易,也即是從文件到字符串的映射。而我們想搜索的信息是哪些文件包含此字符串,也即已知字符串,欲求文件,也即從字符串到文件的映射。兩者恰恰相反。於是如果索引總能夠保存從字符串到文件的映射,則會大大提高搜索速度。

由於從字符串到文件的映射是文件到字符串映射的反向過程,於是保存這種信息的索引稱爲反向索引 。

反向索引的所保存的信息一般如下:

假設我的文檔集合裏面有100篇文檔,爲了方便表示,我們爲文檔編號從1到100,得到下面的結構

[圖]字典及倒排表結構

左邊保存的是一系列字符串,稱爲詞典 。

每個字符串都指向包含此字符串的文檔(Document)鏈表,此文檔鏈表稱爲倒排表 (Posting List)。

有了索引,便使保存的信息和要搜索的信息一致,可以大大加快搜索的速度。

比如說,我們要尋找既包含字符串“lucene”又包含字符串“solr”的文檔,我們只需要以下幾步:

1. 取出包含字符串“lucene”的文檔鏈表。

2. 取出包含字符串“solr”的文檔鏈表。

3. 通過合併鏈表,找出既包含“lucene”又包含“solr”的文件。

[圖]倒排表合併過程

看到這個地方,有人可能會說,全文檢索的確加快了搜索的速度,但是多了索引的過程,兩者加起來不一定比順序掃描快多少。的確,加上索引的過程,全文檢索不一定比順序掃描快,尤其是在數據量小的時候更是如此。而對一個很大量的數據創建索引也是一個很慢的過程。

然而兩者還是有區別的,順序掃描是每次都要掃描,而創建索引的過程僅僅需要一次,以後便是一勞永逸的了,每次搜索,創建索引的過程不必經過,僅僅搜索創建好的索引就可以了。

這也是全文搜索相對於順序掃描的優勢之一:一次索引,多次使用。

 

三、如何創建索引

全文檢索的索引創建過程一般有以下幾步:

第一步:一些要索引的原文檔(Document)。

爲了方便說明索引創建過程,這裏特意用兩個文件爲例:

文件一:Students should be allowed to go out with their friends, but not allowed to drink beer.

文件二:My friend Jerry went to school to see his students but found them drunk which is not allowed.

 

第二步:將原文檔傳給分次組件(Tokenizer)。

分詞組件(Tokenizer)會做以下幾件事情( 此過程稱爲Tokenize) 

1. 將文檔分成一個一個單獨的單詞。

2. 去除標點符號。

3. 去除停詞(Stop word) 

所謂停詞(Stop word)就是一種語言中最普通的一些單詞,由於沒有特別的意義,因而大多數情況下不能成爲搜索的關鍵詞,因而創建索引時,這種詞會被去掉而減少索引的大小。

英語中挺詞(Stop word)如:“the”,“a”,“this”等。

對於每一種語言的分詞組件(Tokenizer),都有一個停詞(stop word)集合。

經過分詞(Tokenizer) 後得到的結果稱爲詞元(Token) 

在我們的例子中,便得到以下詞元(Token):

“Students”,“allowed”,“go”,“their”,“friends”,“allowed”,“drink”,“beer”,“My”,“friend”,“Jerry”,“went”,“school”,“see”,“his”,“students”,“found”,“them”,“drunk”,“allowed”。

 

第三步:將得到的詞元(Token)傳給語言處理組件(Linguistic Processor)。

語言處理組件(linguistic processor)主要是對得到的詞元(Token)做一些同語言相關的處理。

對於英語,語言處理組件(Linguistic Processor) 一般做以下幾點:

1. 變爲小寫(Lowercase) 

2. 將單詞縮減爲詞根形式,如“cars ”到“car ”等。這種操作稱爲:stemming 

3. 將單詞轉變爲詞根形式,如“drove ”到“drive ”等。這種操作稱爲:lemmatization 

 

Stemming 和 lemmatization的異同:

  • 相同之處:Stemming和lemmatization都要使詞彙成爲詞根形式。
  • 兩者的方式不同:
    • Stemming採用的是“縮減”的方式:“cars”到“car”,“driving”到“drive”。
    • Lemmatization採用的是“轉變”的方式:“drove”到“drove”,“driving”到“drive”。
  • 兩者的算法不同:
    • Stemming主要是採取某種固定的算法來做這種縮減,如去除“s”,去除“ing”加“e”,將“ational”變爲“ate”,將“tional”變爲“tion”。
    • Lemmatization主要是採用保存某種字典的方式做這種轉變。比如字典中有“driving”到“drive”,“drove”到“drive”,“am, is, are”到“be”的映射,做轉變時,只要查字典就可以了。
  • Stemming和lemmatization不是互斥關係,是有交集的,有的詞利用這兩種方式都能達到相同的轉換。

語言處理組件(linguistic processor)的結果稱爲詞(Term) 

在我們的例子中,經過語言處理,得到的詞(Term)如下:

“student”,“allow”,“go”,“their”,“friend”,“allow”,“drink”,“beer”,“my”,“friend”,“jerry”,“go”,“school”,“see”,“his”,“student”,“find”,“them”,“drink”,“allow”。

 

也正是因爲有語言處理的步驟,才能使搜索drove,而drive也能被搜索出來。

 

第四步:將得到的詞(Term)傳給索引組件(Indexer)。

索引 組件(Indexer)主要做以下幾件事情:

1. 利用得到的詞(Term)創建一個字典。

在我們的例子中字典如下:

TermDocument ID
student1
allow1
go1
their1
friend1
allow1
drink1
beer1
my2
friend2
jerry2
go2
school2
see2
his2
student2
find2
them2
drink2
allow2

 

2. 對字典按字母順序進行排序。

 

TermDocument ID
allow1
allow1
allow2
beer1
drink1
drink2
find2
friend1
friend2
go1
go2
his2
jerry2
my2
school2
see2
student1
student2
their1
them2

3. 合併相同的詞(Term) 成爲文檔倒排(Posting List) 鏈表。

[圖]倒排表結構

 

在此表中,有幾個定義:

  • Document Frequency 即文檔頻次,表示總共有多少文件包含此詞(Term)。
  • Frequency 即詞頻率,表示此文件中包含了幾個此詞(Term)。

所以對詞(Term) “allow”來講,總共有兩篇文檔包含此詞(Term),從而詞(Term)後面的文檔鏈表總共有兩項,第一項表示包含“allow”的第一篇文檔,即1號文檔,此文檔中,“allow”出現了2次,第二項表示包含“allow”的第二個文檔,是2號文檔,此文檔中,“allow”出現了1次。

到此爲止,索引已經創建好了,我們可以通過它很快的找到我們想要的文檔。

而且在此過程中,我們驚喜地發現,搜索“drive”,“driving”,“drove”,“driven”也能夠被搜到。因爲在我們的索引中,“driving”,“drove”,“driven”都會經過語言處理而變成“drive”,在搜索時,如果您輸入“driving”,輸入的查詢語句同樣經過我們這裏的一到三步,從而變爲查詢“drive”,從而可以搜索到想要的文檔。

 

 

三、如何對索引進行搜索?

到這裏似乎我們可以宣佈“我們找到想要的文檔了”。

然而事情並沒有結束,找到了僅僅是全文檢索的一個方面。不是嗎?如果僅僅只有一個或十個文檔包含我們查詢的字符串,我們的確找到了。然而如果結果有一千個,甚至成千上萬個呢?那個又是您最想要的文件呢?

打開Google吧,比如說您想在微軟找份工作,於是您輸入“Microsoft job”,您卻發現總共有22600000個結果返回。好大的數字呀,突然發現找不到是一個問題,找到的太多也是一個問題。在如此多的結果中,如何將最相關的放在最前面呢?

[圖]Google的搜索實例

當然Google做的很不錯,您一下就找到了jobs at Microsoft。想象一下,如果前幾個全部是“Microsoft does a good job at software industry…”將是多麼可怕的事情呀。

如何像Google一樣,在成千上萬的搜索結果中,找到和查詢語句最相關的呢?

如何判斷搜索出的文檔和查詢語句的相關性呢?

這要回到我們第三個問題:如何對索引進行搜索?

搜索主要分爲以下幾步:

第一步:用戶輸入查詢語句。

查詢語句同我們普通的語言一樣,也是有一定語法的。

不同的查詢語句有不同的語法,如SQL語句就有一定的語法。

查詢語句的語法根據全文檢索系統的實現而不同。最基本的有比如:AND, OR, NOT等。

舉個例子,用戶輸入語句:lucene AND learned NOT hadoop。

說明用戶想找一個包含lucene和learned然而不包括hadoop的文檔。

第二步:對查詢語句進行詞法分析,語法分析,及語言處理。

由於查詢語句有語法,因而也要進行語法分析,語法分析及語言處理。

1. 詞法分析主要用來識別單詞和關鍵字。

如上述例子中,經過詞法分析,得到單詞有lucene,learned,hadoop, 關鍵字有AND, NOT。

如果在詞法分析中發現不合法的關鍵字,則會出現錯誤。如lucene AMD learned,其中由於AND拼錯,導致AMD作爲一個普通的單詞參與查詢。

2. 語法分析主要是根據查詢語句的語法規則來形成一棵語法樹。

如果發現查詢語句不滿足語法規則,則會報錯。如lucene NOT AND learned,則會出錯。

如上述例子,lucene AND learned NOT hadoop形成的語法樹如下:

[圖]語法樹

 

3. 語言處理同索引過程中的語言處理幾乎相同。

如learned變成learn等。

經過第二步,我們得到一棵經過語言處理的語法樹。

[圖]經過語言處理的語法樹

 

第三步:搜索索引,得到符合語法樹的文檔。

此步驟有分幾小步:

  1. 首先,在反向索引表中,分別找出包含lucene,learn,hadoop的文檔鏈表。
  2. 其次,對包含lucene,learn的鏈表進行合併操作,得到既包含lucene又包含learn的文檔鏈表。
  3. 然後,將此鏈表與hadoop的文檔鏈表進行差操作,去除包含hadoop的文檔,從而得到既包含lucene又包含learn而且不包含hadoop的文檔鏈表。
  4. 此文檔鏈表就是我們要找的文檔。

第四步:根據得到的文檔和查詢語句的相關性,對結果進行排序。

雖然在上一步,我們得到了想要的文檔,然而對於查詢結果應該按照與查詢語句的相關性進行排序,越相關者越靠前。

如何計算文檔和查詢語句的相關性呢?

不如我們把查詢語句看作一片短小的文檔,對文檔與文檔之間的相關性(relevance)進行打分(scoring),分數高的相關性好,就應該排在前面。

那麼又怎麼對文檔之間的關係進行打分呢?

這可不是一件容易的事情,首先我們看一看判斷人之間的關係吧。

首先 看一個人,往往有很多要素 ,如性格,信仰,愛好,衣着,高矮,胖瘦等等。

其次 對於人與人之間的關係,不同的要素重要性不同 ,性格,信仰,愛好可能重要些,衣着,高矮,胖瘦可能就不那麼重要了,所以具有相同或相似性格,信仰,愛好的人比較容易成爲好的朋友,然而衣着,高矮,胖瘦不同的人,也可以成爲好的朋友。

因而判斷人與人之間的關係,首先要找出哪些要素對人與人之間的關係最重要 ,比如性格,信仰,愛好。其次要判斷兩個人的這些要素之間的關係 ,比如一個人性格開朗,另一個人性格外向,一個人信仰佛教,另一個信仰上帝,一個人愛好打籃球,另一個愛好踢足球。我們發現,兩個人在性格方面都很積極,信仰方面都很善良,愛好方面都愛運動,因而兩個人關係應該會很好。

 

我們再來看看公司之間的關係吧。

首先 看一個公司,有很多人組成,如總經理,經理,首席技術官,普通員工,保安,門衛等。

其次對於公司與公司之間的關係,不同的人重要性不同 ,總經理,經理,首席技術官可能更重要一些,普通員工,保安,門衛可能較不重要一點。所以如果兩個公司總經理,經理,首席技術官之間關係比較好,兩個公司容易有比較好的關係。然而一位普通員工就算與另一家公司的一位普通員工有血海深仇,怕也難影響兩個公司之間的關係。

因而判斷公司與公司之間的關係,首先要找出哪些人對公司與公司之間的關係最重要 ,比如總經理,經理,首席技術官。其次要判斷這些人之間的關係 ,不如兩家公司的總經理曾經是同學,經理是老鄉,首席技術官曾是創業夥伴。我們發現,兩家公司無論總經理,經理,首席技術官,關係都很好,因而兩家公司關係應該會很好。

 

分析了兩種關係,下面看一下如何判斷文檔之間的關係 了。

首先,一個文檔有很多詞(Term)組成 ,如search, lucene, full-text, this, a, what等。

其次對於文檔之間的關係,不同的Term重要性不同 ,比如對於本篇文檔,search, Lucene, full-text就相對重要一些,this, a , what可能相對不重要一些。所以如果兩篇文檔都包含search, Lucene,fulltext,這兩篇文檔的相關性好一些,然而就算一篇文檔包含this, a, what,另一篇文檔不包含this, a, what,也不能影響兩篇文檔的相關性。

因而判斷文檔之間的關係,首先找出哪些詞(Term)對文檔之間的關係最重要,如search, Lucene, fulltext。然後判斷這些詞(Term)之間的關係。

 

找出詞(Term) 對文檔的重要性的過程稱爲計算詞的權重(Term weight) 的過程。

計算詞的權重(term weight)有兩個參數,第一個是詞(Term),第二個是文檔(Document)。

詞的權重(Term weight)表示此詞(Term)在此文檔中的重要程度,越重要的詞(Term)有越大的權重(Term weight),因而在計算文檔之間的相關性中將發揮更大的作用。

判斷詞(Term) 之間的關係從而得到文檔相關性的過程應用一種叫做向量空間模型的算法(Vector Space Model) 

下面仔細分析一下這兩個過程:

1. 計算權重(Term weight)的過程。

影響一個詞(Term)在一篇文檔中的重要性主要有兩個因素:

  • Term Frequency (tf):即此Term在此文檔中出現了多少次。tf 越大說明越重要。
  • Document Frequency (df):即有多少文檔包含次Term。df 越大說明越不重要。

容易理解嗎?詞(Term)在文檔中出現的次數越多,說明此詞(Term)對該文檔越重要,如“搜索”這個詞,在本文檔中出現的次數很多,說明本文檔主要就是講這方面的事的。然而在一篇英語文檔中,this出現的次數更多,就說明越重要嗎?不是的,這是由第二個因素進行調整,第二個因素說明,有越多的文檔包含此詞(Term), 說明此詞(Term)太普通,不足以區分這些文檔,因而重要性越低。

這也如我們程序員所學的技術,對於程序員本身來說,這項技術掌握越深越好(掌握越深說明花時間看的越多,tf越大),找工作時越有競爭力。然而對於所有程序員來說,這項技術懂得的人越少越好(懂得的人少df小),找工作越有競爭力。人的價值在於不可替代性就是這個道理。

道理明白了,我們來看看公式:

[圖]權重計算公式

[圖]權重計算公式變量

 

這僅僅只term weight計算公式的簡單典型實現。實現全文檢索系統的人會有自己的實現,Lucene就與此稍有不同。

2. 判斷Term之間的關係從而得到文檔相關性的過程,也即向量空間模型的算法(VSM)。

我們把文檔看作一系列詞(Term),每一個詞(Term)都有一個權重(Term weight),不同的詞(Term)根據自己在文檔中的權重來影響文檔相關性的打分計算。

於是我們把所有此文檔中詞(term)的權重(term weight) 看作一個向量。

Document = {term1, term2, …… ,term N}

Document Vector = {weight1, weight2, …… ,weight N}

同樣我們把查詢語句看作一個簡單的文檔,也用向量來表示。

Query = {term1, term 2, …… , term N}

Query Vector = {weight1, weight2, …… , weight N}

我們把所有搜索出的文檔向量及查詢向量放到一個N維空間中,每個詞(term)是一維。

如圖:

[圖]向量空間模型

 

我們認爲兩個向量之間的夾角越小,相關性越大。

所以我們計算夾角的餘弦值作爲相關性的打分,夾角越小,餘弦值越大,打分越高,相關性越大。

有人可能會問,查詢語句一般是很短的,包含的詞(Term)是很少的,因而查詢向量的維數很小,而文檔很長,包含詞(Term)很多,文檔向量維數很大。你的圖中兩者維數怎麼都是N呢?

在這裏,既然要放到相同的向量空間,自然維數是相同的,不同時,取二者的並集,如果不含某個詞(Term)時,則權重(Term Weight)爲0。

 

相關性打分公式如下:

[圖]相關性打分的公式

 

舉個例子,查詢語句有11個Term,共有三篇文檔搜索出來。其中各自的權重(Term weight),如下表格。

 

t1

t2

t3

t4

t5

t6

t7

t8

t9

t10

t11

D1

0

0

.477

0

.477

.176

0

0

0

.176

0

D2

0

.176

0

.477

0

0

0

0

.954

0

.176

D3

0

.176

0

0

0

.176

0

0

0

.176

.176

Q

0

0

0

0

0

.176

0

0

.477

0

.176

於是計算,三篇文檔同查詢語句的相關性打分分別爲:

[圖]文檔一的打分計算

[圖]文檔二的打分計算

[圖]文檔三的打分計算

 

於是文檔二相關性最高,先返回,其次是文檔一,最後是文檔三。

到此爲止,我們可以找到我們最想要的文檔了。

說了這麼多,其實還沒有進入到Lucene,而僅僅是信息檢索技術(Information retrieval)中的基本理論,然而當我們看過Lucene後我們會發現,Lucene是對這種基本理論的一種基本的的實踐。所以在以後分析Lucene的文章中,會常常看到以上理論在Lucene中的應用。

在進入Lucene之前,對上述索引創建和搜索過程所一個總結,如圖:

此圖參照http://www.lucene.com.cn/about.htm 中文章《開放源代碼的全文檢索引擎Lucene》

[圖]索引創建和搜索過程

 

1. 索引過程:

1) 有一系列被索引文件

2) 被索引文件經過語法分析和語言處理形成一系列詞(Term) 

3) 經過索引創建形成詞典和反向索引表。

4) 通過索引存儲將索引寫入硬盤。

2. 搜索過程:

a) 用戶輸入查詢語句。

b) 對查詢語句經過語法分析和語言分析得到一系列詞(Term) 

c) 通過語法分析得到一個查詢樹。

d) 通過索引存儲將索引讀入到內存。

e) 利用查詢樹搜索索引,從而得到每個詞(Term) 的文檔鏈表,對文檔鏈表進行交,差,並得到結果文檔。

f) 將搜索到的結果文檔對查詢的相關性進行排序。

g) 返回查詢結果給用戶。

下面我們可以進入Lucene的世界了。

 

另:

博客園此文章的鏈接爲:http://www.cnblogs.com/forfuture1978/archive/2009/12/14/1623594.html

Javaeye此文章的鏈接爲:http://forfuture1978.javaeye.com/blog/546771

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