使用 Hits 对象可以得到某个文档的得分。本文将对评分和 Lucene 的评分机制进行介绍。
1 理解评分的概念
评分其实是搜索引擎中很重要的一个概念。通常情况下,当用户输入一个关键字,搜索引擎接收到信息后即可开始进行检索。对于检索到的结果,需要按一定的顺序返回给用户。因此,需要引入一种机制来对检索结果进行排序,以便更加合理地将结果返回给用户。
评分机制就是对检索结果按某种标准进行评估,然后按分值的高低来对结果进行排序。同时,对于一个商用的搜索引擎来说,评分机制是其收入来源的重要部分。例如某公司向搜索引擎缴纳一定数量的费用,则该搜索引擎就将其搜索结果中关于该公司的部分公值加大,以便能在检索结果返回给用户时让该公司获得更加靠前的位置。这种做法增加了用户浏览该公司网页和产品的机会,无形之中也给该公司带来了更大的社会影响和潜在商机。因此,评分机制从各方面来说都是相当重要的。
2 Lucene评分算法
那么,Lucene中是如何确定各个Document评分的呢?下面将详细介绍该功能的基本原理。
文档的得分是在用户进行检索时实时计算出来的。如果在建立索引时就已经将每个文档的得分计算好,那么当用户输入任何关键字时,得分最高的文档都会被排在返回结果的最前面,这显然是不合理的。
因此,所有文档的得分应当都与用户输入的关键字有关系,而且是实时运算的结果。其实,所谓得分,可以简单理解成是某个关键字在某文档中出现的频率。
图11-6所示的公式就是Lucene用于计算某个关键字在对应于某文档的得分。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZwpmL2ADMldWYtl2LcFTMvwFOy8CXzVGbpZ2av9mYvwFdl5mLuR2cj5yav9mYvw1LcpDc0RHaiojIsJye.jpg)
图11-6 Lucene的得分公式
在Lucene得分公式中,已经包含了影响文档评分的各种因素。在表11.1中详细介绍了每一种因素对搜索结果评分的影响作用。
表11-1 Lucene得分公式的解释
因 素 | 在公式中的作用描述 |
tf(t in d) | 词条t在文档d中出现的词频 |
idf( t ) | 词条t在文档中的倒排词频 |
boost(t.field in d) | 在索引过程中设置的字段参数 |
lengthNorm(t.field in d) | 字段的标准化值,表明在字段中存储了多少词条,这个数值是在索引过程中计算出来的,并且也存储在索引中 |
coord(q, d) | 协调因子,它的计算是基于文档d中所包含的所有可供查询的词条数量 |
queryNorm(q) | 在给出每个查询条目的方差和后,计算某查询的标准化值 |
3 改变文档的得分
除了内置的得分算法外,Lucene还提供了一种方法来改变每个文档的得分。
在代码11.3中,初始化Document后,使用了Document的setBoost方法来改变一下文档的boost因子。这种做法的实际目的是将文档的得分乘以这个因子,以这个新的数作为文档的得分。
代码11.3 使用Boost的例子
public static void buildIndex() throws Exception {
//生成新的Document对象,下同
Document doc1 = new Document();
doc1.add(Field.Text("contents", "word1 word"));
doc1.add(Field.Keyword("path", "path//document1.txt"));
//改变文档的boost因子,下同
doc1.setBoost(1.0f);
Document doc2 = new Document();
doc2.add(Field.Text("contents", "word2 word"));
doc2.add(Field.Keyword("path", "path//document2.txt"));
doc2.setBoost(0.1f);
Document doc3 = new Document();
doc3.add(Field.Text("contents", "word3 word"));
doc3.add(Field.Keyword("path", "path//document3.txt"));
doc3.setBoost(0.5f);
Document doc4 = new Document();
doc4.add(Field.Text("contents", "word4 word"));
doc4.add(Field.Keyword("path", "path//document4.txt"));
doc4.setBoost(0.2f);
Document doc5 = new Document();
doc5.add(Field.Text("contents", "word5 word"));
doc5.add(Field.Keyword("path", "path//document5.txt"));
doc5.setBoost(0.8f);
Document doc6 = new Document();
doc6.add(Field.Text("contents", "word6 word"));
doc6.add(Field.Keyword("path", "path//document6.txt"));
doc6.setBoost(0.1f);
Document doc7 = new Document();
doc7.add(Field.Text("contents", "word7 word"));
doc7.add(Field.Keyword("path", "path//document7.txt"));
doc7.setBoost(0.5f);
Document doc8 = new Document();
doc8.add(Field.Text("contents", "word8 word"));
doc8.add(Field.Keyword("path", "path//document8.txt"));
doc8.setBoost(0.7f);
Document doc9 = new Document();
doc9.add(Field.Text("contents", "word9 word"));
doc9.add(Field.Keyword("path", "path//document9.txt"));
doc9.setBoost(0.2f);
Document doc10 = new Document();
doc10.add(Field.Text("contents", "word10 word"));
doc10.add(Field.Keyword("path", "path//document10.txt"));
doc10.setBoost(0.4f);
Document doc11 = new Document();
doc11.add(Field.Text("contents", "word11 word"));
doc11.add(Field.Keyword("path", "path//document11.txt"));
Document doc12 = new Document();
doc12.add(Field.Text("contents", "word12 word"));
doc12.add(Field.Keyword("path", "path//document12.txt"));
IndexWriter writer = new IndexWriter("c://index", new StandardAnalyzer(), true);
//添加到索引中,下同
writer.addDocument(doc1);
writer.addDocument(doc2);
writer.addDocument(doc3);
writer.addDocument(doc4);
writer.addDocument(doc5);
writer.addDocument(doc6);
writer.addDocument(doc7);
writer.addDocument(doc8);
writer.addDocument(doc9);
writer.addDocument(doc10);
writer.addDocument(doc11);
writer.addDocument(doc12);
writer.close();
}
代码11.3的运行效果,如图11-7所示。
图11-7 改变Boost后的运行效果
从图11-7可以看出,每个文档的分值已经发生了变化,其中,由于文档1、11、12的boost值和原来一样,因此分值排在最前面,显示的顺序也到了最前面。而其他的文档则已经因为boost值发生了改变,显示的顺序也发生了变化。可以看到,排在最后一个位置的文档是文档6,它的boost值为0.1,所以分值也成了原来的十分之一。
像代码11.3中这样通过Boost值来改变分值的方式相当灵活,可以很有效的达到对文档顺序进行控制的目的。不过,这仍然不是一种理想的方式,因为在建立索引时还需要人为地指定每个文档的boost值。有关排序的更高级话题,将在后面的章节来说明。