Lucene 是一個高效的,基於Java 的全文檢索庫。html
因此在瞭解Lucene以前要費一番工夫瞭解一下全文檢索。java
那麼什麼叫作全文檢索呢?這要從咱們生活中的數聽說起。程序員
咱們生活中的數據整體分爲兩種:結構化數據 和非結構化數據 。算法
固然有的地方還會提到第三種,半結構化數據,如XML,HTML等,當根據須要可按結構化數據來處理,也可抽取出純文本按非結構化數據來處理。數據庫
非結構化數據又一種叫法叫全文數據。apache
按照數據的分類,搜索也分爲兩種:windows
對非結構化數據也即對全文數據的搜索主要有兩種方法:app
一種是順序掃描法 (Serial Scanning): 所謂順序掃描,好比要找內容包含某一個字符串的文件,就是一個文檔一個文檔的看,對於每個文檔,從頭看到尾,若是此文檔包含此字符串,則此文檔爲咱們要找的文件,接着看下一個文件,直到掃描完全部的文件。如利用windows的搜索也能夠搜索文件內容,只是至關的慢。若是你有一個80G硬盤,若是想在上面找到一個內容包含某字符串的文件,不花他幾個小時,怕是作不到。Linux下的grep命令也是這一種方式。你們可能以爲這種方法比較原始,但對於小數據量的文件,這種方法仍是最直接,最方便的。可是對於大量的文件,這種方法就很慢了。ide
有人可能會說,對非結構化數據順序掃描很慢,對結構化數據的搜索卻相對較快(因爲結構化數據有必定的結構能夠採起必定的搜索算法加快速度),那麼把咱們的非結構化數據想辦法弄得有必定結構不就好了嗎?oop
這種想法很自然,卻構成了全文檢索的基本思路,也即將非結構化數據中的一部分信息提取出來,從新組織,使其變得有必定結構,而後對此有必定結構的數據進行搜索,從而達到搜索相對較快的目的。
這部分從非結構化數據中提取出的而後從新組織的信息,咱們稱之索引 。
這種說法比較抽象,舉幾個例子就很容易明白,好比字典,字典的拼音表和部首檢字表就至關於字典的索引,對每個字的解釋是非結構化的,若是字典沒有音節表和部首檢字表,在茫茫辭海中找一個字只能順序掃描。然而字的某些信息能夠提取出來進行結構化處理,好比讀音,就比較結構化,分聲母和韻母,分別只有幾種能夠一一列舉,因而將讀音拿出來按必定的順序排列,每一項讀音都指向此字的詳細解釋的頁數。咱們搜索時按結構化的拼音搜到讀音,而後按其指向的頁數,即可找到咱們的非結構化數據——也即對字的解釋。
這種先創建索引,再對索引進行搜索的過程就叫全文檢索(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」的文件。
看到這個地方,有人可能會說,全文檢索的確加快了搜索的速度,可是多了索引的過程,二者加起來不必定比順序掃描快多少。的確,加上索引的過程,全文檢索不必定比順序掃描快,尤爲是在數據量小的時候更是如此。而對一個很大量的數據建立索引也是一個很慢的過程。
然而二者仍是有區別的,順序掃描是每次都要掃描,而建立索引的過程僅僅須要一次,之後即是一勞永逸的了,每次搜索,建立索引的過程沒必要通過,僅僅搜索建立好的索引就能夠了。
這也是全文搜索相對於順序掃描的優點之一:一次索引,屢次使用。
全文檢索的索引建立過程通常有如下幾步:
爲了方便說明索引建立過程,這裏特地用兩個文件爲例:
文件一: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)會作如下幾件事情( 此過程稱爲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」。
語言處理組件(linguistic processor)主要是對獲得的詞元(Token)作一些同語言相關的處理。
對於英語,語言處理組件(Linguistic Processor) 通常作如下幾點:
1. 變爲小寫(Lowercase) 。
2. 將單詞縮減爲詞根形式,如「cars 」到「car 」等。這種操做稱爲:stemming 。
3. 將單詞轉變爲詞根形式,如「drove 」到「drive 」等。這種操做稱爲:lemmatization 。
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也能被搜索出來。
索引組件(Indexer)主要作如下幾件事情:
1. 利用獲得的詞(Term)建立一個字典。
在咱們的例子中字典以下:
Term | Document ID |
student | 1 |
allow | 1 |
go | 1 |
their | 1 |
friend | 1 |
allow | 1 |
drink | 1 |
beer | 1 |
my | 2 |
friend | 2 |
jerry | 2 |
go | 2 |
school | 2 |
see | 2 |
his | 2 |
student | 2 |
find | 2 |
them | 2 |
drink | 2 |
allow | 2 |
2. 對字典按字母順序進行排序。
Term | Document ID |
allow | 1 |
allow | 1 |
allow | 2 |
beer | 1 |
drink | 1 |
drink | 2 |
find | 2 |
friend | 1 |
friend | 2 |
go | 1 |
go | 2 |
his | 2 |
jerry | 2 |
my | 2 |
school | 2 |
see | 2 |
student | 1 |
student | 2 |
their | 1 |
them | 2 |
3. 合併相同的詞(Term) 成爲文檔倒排(Posting List) 鏈表。
在此表中,有幾個定義:
因此對詞(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作的很不錯,您一下就找到了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等。
通過第二步,咱們獲得一棵通過語言處理的語法樹。
此步驟有分幾小步:
雖然在上一步,咱們獲得了想要的文檔,然而對於查詢結果應該按照與查詢語句的相關性進行排序,越相關者越靠前。
如何計算文檔和查詢語句的相關性呢?
不如咱們把查詢語句看做一片短小的文檔,對文檔與文檔之間的相關性(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) 。
下面仔細分析一下這兩個過程:
影響一個詞(Term)在一篇文檔中的重要性主要有兩個因素:
容易理解嗎?詞(Term)在文檔中出現的次數越多,說明此詞(Term)對該文檔越重要,如「搜索」這個詞,在本文檔中出現的次數不少,說明本文檔主要就是講這方面的事的。然而在一篇英語文檔中,this出現的次數更多,就說明越重要嗎?不是的,這是由第二個因素進行調整,第二個因素說明,有越多的文檔包含此詞(Term), 說明此詞(Term)太普通,不足以區分這些文檔,於是重要性越低。
這也如咱們程序員所學的技術,對於程序員自己來講,這項技術掌握越深越好(掌握越深說明花時間看的越多,tf越大),找工做時越有競爭力。然而對於全部程序員來講,這項技術懂得的人越少越好(懂得的人少df小),找工做越有競爭力。人的價值在於不可替代性就是這個道理。
道理明白了,咱們來看看公式:
這僅僅只term weight計算公式的簡單典型實現。實現全文檢索系統的人會有本身的實現,Lucene就與此稍有不一樣。
咱們把文檔看做一系列詞(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索引庫的建立的時候,咱們有兩種不一樣的索引庫建立方式
根據兩種索引庫的特色咱們能夠將兩種索引庫結合起來,設計的思路是在程序啓動時,將文件索引庫中的索引拷貝到內存索引庫中,而後讓程序與內存索引庫交互,當交互完畢後再將內存索引庫的索引持久化到文件索引庫。