lucene 介紹
lucene是一個高效的,基於Java的全文檢索庫,利用lucene可以很方便加入到現有應用中來提供全文檢索的功能。比如分散式搜尋引擎Elastic Search底層的搜尋就是使用lucene。(全文檢索是從大量的現有的文件中,搜尋出與查詢語句最相關的文件集的過程)
為了更高效的完成全文檢索的過程,lucene使用倒排索引來完成查詢詞到文件查詢。lucene全文檢索的過程主要由建立索引和搜尋索引,本文主要介紹文件相關性排序部分。
lucene中相關概念:
- document:lucene儲存的基本單位,類似於mysql資料庫中一條記錄
- field:document中一個欄位,類似於mysql資料庫表中一個欄位
note:本文分析的lucene版本為5.1.0
倒排索引模型
左邊一列是按照一定順序排列的經過分詞處理後的Term,稱為字典;每個詞都指向的包含該詞的文件組成連結串列稱為倒排表。
lucene處理流程
建立索引的過程:
- 準備待索引的原文件,資料來源可能是檔案、資料庫或網路
- 對文件的內容進行分片語件處理,形成一系列的Term
- 索引元件對文件和Term處理,形成字典和倒排表
搜尋索引的過程:
- 對查詢語句進行分詞處理,形成一系列Term
- 根據倒排索引表查詢出包含Term的文件,並進行合併形成符合結果的文件集
- 比對查詢語句與各個文件相關性得分,並按照得分高低返回
評分公式推導
空間向量模型
lucene的評分計算模型是基於VSM,即空間向量模型,但實現上稍有不同。
空間向量模型的邏輯就是將每個文件看做是由N個Term組成的向量,文件間相關性就是兩個向量的在N維空間內的夾角大小,由於兩個向量的之間的夾角越小,相關性就越大,所以就可以將夾角的餘弦值作為相關性的得分,夾角越小,餘弦值越大,相關性越大。
VSM(空間向量模型) 計算公式:
比如索引中的文件
Document Vector = {Weight1, Weight2, ..., Weight N},查詢語句也可以看做是一個文件,查詢文件:Query Vector = {Weight1, Weight2, ..., Weight N},Weight是某個Term的權重,N是所有文件包括查詢文件在內的所有Term總數,如果文件不包含某個Term,則文件向量中Term的Weight為0。
Term的權重計算
影響一個Term在文件中權重大小的因素主要有兩個:
- Term Frequency(tf):即該Term在該文件中出現的次數,tf越大說明越重要;
- Document Frequency(df):即有多少個文件包含了該Term,df越小說明越重要;
計算公式:
以上是Term weight計算的典型實現,Lucene實現於此稍有不同,使用的是tf(t in d ) * idf(t)。
lucene公式推導
我們首先計算餘弦公式的分子部分,也即兩個向量的點積:
在這裡有三點需要指出:
- 由於是點積,則此處的 t1, t2, ..., tn 只有查詢語句和文件的並集有非零值,只在查詢語句出現的或只在文件中出現的 Term 的項的值為零。
- 在查詢的時候,很少有人會在查詢語句中輸入同樣的詞,因而可以假設 tf(t, q)都=1
- idf 是指 Term 在多少篇文件中出現過,其中也包括查詢語句這篇小文件,因而 idf(t, q)和 idf(t, d)其實是一樣的,是索引中的文件總數加一,當索引中的文件總數足夠大的時候,查詢語句這篇小文件可以忽略,因而可以假設 idf(t, q) = idf(t, d) = idf(t)
下面推導查詢語句的長度
由上面的討論,查詢語句中 tf 都為 1,idf 都忽略查詢語句這篇小文件,得到如下公式:
接下來推導文件的長度
為什麼在打分過程中,需要除以文件的長度呢? 因為在索引中,不同的文件長度不一樣,很顯然,對於任意一個 term,在長的文件中的 tf 要大的多,因而分數也越高,這樣對小的文件不公平,舉一個極端的例子,在一篇 1000 萬 個詞的鴻篇鉅著中,“lucene” 這個詞出現了 11 次,而在一篇 12 個詞的短小文件中,“lucene” 這個詞出現了 10 次,如果不考慮長度在內,當然鴻篇鉅著應該分數更高,然而顯然這篇小文件才是真正關注“lucene” 的。然而如果按照標準的餘弦計算公式,完全消除文件長度的影響,則又對長文件不公平(畢竟它是包含更多的資訊),偏向於首先返回短小的文件的,這樣在實際應用中使得搜尋結果 很難看。
在預設狀況下,Lucene 採用 DefaultSimilarity,認為在計算文件的向量長度的時候,每個 Term 的權重就不再考慮在內了,而是全部為一。
再加上各種 boost 和 coord,則可得出 Lucene 的計算公式
lucene計算公式
coord(q,d):得分因子,score factor,一個文件中包含越多的查詢Term詞,則該文件的得分越高,對應lucene類TFIDFSimilarity.coord(int overlap, int maxOverlap)
;
queryNorm(q):歸一化因子,normalizing factor,使得不同查詢間的得分具有可比性,但並不會影響文件的排序,對應lucene類TFIDFSimilarity.queryNorm(float sumOfSquaredWeights)
;
tf(t in d):Term在文件d中出現的次數,對應lucene類TFIDFSimilarity.tf(float freq)
;
idf(t):Term的逆文件頻率,即一個Term在所有文件中出現的次數越多,重要性越小,對應lucene類TFIDFSimilarity.idf(long docFreq, long numDocs)
;
norm(t,d):封裝了文件欄位field的權重和field內容長度因子,在index時計算,對應lucene類 TFIDFSimilarity.computeNorm(FieldInvertState state)
;
lengthNorm:field內容長度因子,field欄位內容長度越短,則值越大,對應lucene類TFIDFSimilarity.lengthNorm(FieldInvertState state);
q.getBoost():查詢的權重(預設值為1.0)
t.getBoost():子查詢的權重(預設值為1.0)
f.getBoost():filed欄位的權重(認值為1.0)
設定Query權重
BooleanQuery parentQuery=new BooleanQuery();
parentQuery.setBoost(1.0f);
TermQuery termQuery=new TermQuery(new Term("search"));
termQuery.setBoost(2.0f);
parentQuery.add(termQuery,BooleanClause.Occur.SHOULD);
termQuery=new TermQuery(new Term("掘金"));
termQuery.setBoost(2.0f);
parentQuery.add(termQuery,BooleanClause.Occur.SHOULD);
reader=DirectoryReader.open(index_directory);
IndexSearcher searcher=new IndexSearcher(reader);
TopScoreDocCollector collector=TopScoreDocCollector.create(5);
searcher.search(parentQuery,collector);
ScoreDoc[]hits=collector.topDocs().scoreDocs;複製程式碼
設定Field權重
Document document = new Document();
TextField questionField = new TextField(FieldConstants.QUESTION, question, Field.Store.YES);
questionField.setBoost(2.0f);
TextField answerField = new TextField(FieldConstants.ANSWER, question, Field.Store.YES);
answerField.setBoost(2.0f);
document.add(questionField);
document.add(answerField);
documents.add(document);複製程式碼
note:從lucene4.0之後就不在支援設定document的boost,應該使用filed