文章目錄
Lucene源碼(一):分詞器的底層原理
Lucene源碼(二):文本相似度TF-IDF原理
核心代碼是下面這幾句。Query query
中存儲搜索文本的分詞結果。具體在Lucene源碼(一):分詞器的底層原理仔細瞭解分詞的底層原理。
IndexReader reader = DirectoryReader.open(FSDirectory.open(Paths.get(index)));
IndexSearcher searcher = new IndexSearcher(reader);
Analyzer analyzer = new StandardAnalyzer();
QueryParser parser = new QueryParser(field, analyzer);
Query query = parser.parse(line);
TopDocs results = searcher.search(query, 5 * hitsPerPage);
index是創建的索引目錄,filed是索引的字段,5 * hitsPerPage其實就是搜索返回的最大數量。
搜索結果存儲在result中,如下:篩選出了分數較高的兩個文檔ScoreDoc
,doc爲文檔id,score爲匹配分數。詳細計算方法Lucene源碼(二):文本相似度TF-IDF原理
IndexSearcher
searchAfter
TopDocs results = searcher.search(query, 5 * hitsPerPage)
,在search
方法中,又會進到下面這裏來:
第一部分的代碼很簡單,就是計算出需要遍歷的最大數量
CollectorManager
第二部分的代碼是創建一個CollectorManager
實例manage,這個實例的作用是管理collectors(可以理解爲數據查詢,因爲數據結構是樹,所以就是收集葉子節點),並且更好的進行並行處理。
這個類需要重寫兩個方法:
- 新建collector的方法
- 對帶有結果的所有獨立的collector進行聚合
search
Lower-level search API,其實就是新建collector,然後進入另一個search方法,這裏需要先執行createNormalizedWeight
方法
createNormalizedWeight
請記住,這裏的query是BooleanQuery
,在createWeight
裏面會進入BooleanWeight的構造方法,在裏面又會重新進入IndexSearcher
的createWeight
,但這個時候的query是TermQuery
createWeight
TermQuery
createWeight
這裏的變量termSate是TermContext
對象,裏面存儲着含有單詞的文檔數量docFreq和單詞的詞頻totalTermFreq,那麼前面部分的代碼作用就是做這個統計的。
然後進入TermWeight
的構造方法,但是在TermQuery
類裏面實現的
TermWeight
進來之後,前面都是一些賦值和初始化。
接着,看關鍵代碼。searcher.collectionStatistics
方法統計文檔中的所有單詞的詞頻,即所有文檔總共有多少個單詞,統計結果存儲在變量collectionStats中;
第二句的話,就是對象轉換了:TermContext
–>TermStatistics
最後,就開始計算TF_IDF了。
TFIDFSimilarity
Lucene在這個類裏面實現TF-IDF計算方法,跟以往的TF-IDF計算是不一樣的。所以,如果我們想自定義TF-IDF的計算方式,可以參考這個類重新實現一個TF-IDF的實現類。
進來computeWeight
方法之後就跳轉到idfExplain
方法
idfExplain
方法的功能其實就是計算IDF,返回一個Explanation
計算完idf之後,Lucene還會進行標準化,返回TFIDFSimilarity
對象
最後,一層層return,還記得最終在哪裏返回嗎…
再梳理一遍,調用鏈是這麼回事
IndexSearch.createNormalizedWeight–>IndexSearcher.createWeight(BooleanQuery)–>BooleanWeight構造器–>IndexSearcher.createWeight(TermQuery)–>TermQuery.createWeight–>TermQuery.TermWeight–>TFIDFSimilarity.computeWeight–>TFIDFSimilarity.IDFStats
所以,最終是返回到了IndexSearch.createNormalizedWeight
方法裏,然後又進行一些IDF標準化處理和加入了一些評分因子,具體看上面IndexSearch.createNormalizedWeight
截圖。
下面就開始計算我們輸入的搜索內容跟每個文檔的相似度了。
其實,在前面計算好每個Term即單詞的TF-IDF值之後,基本上就可以直接得到相似度了,只是把文檔中出現的單詞的TF-IDF累加起來,即是文檔的相似度了,當然這是常規的做法。
但是Lucene的計算方式不一樣,它還引入了文檔長度的加權因子,作用就是提高短文檔的分數,降低長文檔的分數。
對應到代碼中,做法當然是:遍歷每個文檔,然後進行計算。前面的代碼就是獲取文檔對應的葉子節點了。
然後,新建一個對象scorer來計算和存儲最終的相似度。這裏的BulkScorer
是一個接口類,對應的實現類是BooleanScorer
,所以,最終是BooleanScorer.score
方法中完成計算的。
BooleanScorer
從代碼不難看出,這裏實際就是在對每個單詞的分數進行累加,核心的算法還是在TFIDFSimilarity
中計算的,即scorer.score()
獲取分數的方法是TFIDFSimilarity
實現方法。如下:
這個其實就是TF * IDF * 文檔長度加權因子(上面提到的)
歡迎關注同名公衆號:“我就算餓死也不做程序員”。
交個朋友,一起交流,一起學習,一起進步。