文章目录
- IndexSearcher
- searchAfter
- CollectorManager
- search
- createNormalizedWeight
- createWeight
- TermQuery
- createWeight
- TermWeight
- TFIDFSimilarity
- BooleanScorer
Lucene源码(一):分词器的底层原理
Lucene源码(二):文本相似度TF-IDF原理
核心代码是下面这几句。Query query
中存储搜索文本的分词结果。具体在Lucene源码(一):分词器的底层原理仔细了解分词的底层原理。
java">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 * 文档长度加权因子(上面提到的)
欢迎关注同名公众号:“我就算饿死也不做程序员”。
交个朋友,一起交流,一起学习,一起进步。