来源:互联网
Lucene 评分体系/机制(lucene scoring)是 Lucene 出名的一核心部分。它对用户来说隐藏了很多复杂的细节,致使用户可以简单地使用 lucene。但个人觉得:如果要根据自己的应用调节评分(或结构排序),十分有必须深入了解 lucene 的评分机制。
Lucene scoring 组合使用了 信息检索的向量空间模型 和 布尔模型 。
首先来看下 lucene 的评分公式(在 Similarity 类里的说明)
score(q,d) = coord(q,d) · queryNorm(q) ·
∑
( tf(t in d) · idf(t)2 · t.getBoost() · norm(t,d) )
t in q
其中:
- tf(t in d) 关联到项频率,项频率是指 项 t 在 文档 d 中出现的次数 frequency。默认的实现是:tf(t in d) =
frequency½
- idf(t) 关联到反转文档频率,文档频率指出现 项 t 的文档数 docFreq。docFreq 越少 idf 就越高(物以稀为贵),但在同一个查询下些值是相同的。默认实现:idf(t) =
1 + log (numDocs
–––––––––
docFreq+1
)
- coord(q,d) 评分因子,是基于文档中出现查询项的个数。越多的查询项在一个文档中,说明些文档的匹配程序越高。默认是出现查询项的百分比。
- queryNorm(q)查询的标准查询,使不同查询之间可以比较。此因子不影响文档的排序,因为所有有文档都会使用此因子。默认值:queryNorm(q) = queryNorm(sumOfSquaredWeights) =1
––––––––––––––
sumOfSquaredWeights½
每个查询项权重的平分方和(sumOfSquaredWeights)由 Weight 类完成。例如 BooleanQuery 地计算:
sumOfSquaredWeights = q.getBoost() 2 ·
∑
( idf(t) · t.getBoost() ) 2t in q
- t.getBoost()查询时期的 项 t 加权(如:java^1.2),或者由程序使用 setBoost()。
- norm(t,d)压缩几个索引期间的加权和长度因子:
- Document boost – 文档加权,在索引之前使用 doc.setBoost()
- Field boost – 字段加权,也在索引之前调用 field.setBoost()
- lengthNorm(field) – 由字段内的 Token 的个数来计算此值,字段越短,评分越高,在做索引的时候由 Similarity.lengthNorm 计算。
以上所有因子相乘得出 norm 值,如果文档中有相同的字段,它们的加权也会相乘:
norm(t,d) = doc.getBoost() · lengthNorm(field) ·
∏
f.getBoost()field f in d named as t
索引的时候,把 norm 值压缩(encode)成一个 byte 保存在索引中。搜索的时候再把索引中 norm 值解压(decode)成一个 float 值,这个 encode/decode 由 Similarity 提供。官方说:这个过程由于精度问题,以至不是可逆的,如:decode(encode(0.89)) = 0.75。
计算这个评分涉及到几个核心的类/接口:Similarity、Query、Weight、Scorer、Searcher,由它们或其子类来完成评分的计算。先来看下它们的类图:
lucene search score uml, 点击放大
搜索中,评分的过程:
- 创建一个查询对象 Query,传给 Searcher,具体来讲可能是 IndexSearcher。
- Searcher 根据 Query 创建一个对应的 Weight(是 Query 的内部特征表示),接着 Weight 会创建对应的 Scorer。
- Searcher 会创建 Hitcollector 并传到 Scorer,scorer 找到匹配的文档并计算评分,最后写到 Hitcollector 中。
Query、Weight、Scorer 三都关系十分密切,尤其是 Query 和 Weight。Weight 是计算查询权重和创建 Scorer 的。Query 为了可以重用把内部的特征抽象为 Weight,由子类去完成一些相关评分的计算。
任何 Searcher 依赖的状态都存储在 Weight 实现中,而不是在Query 中,这样可以重用 Query。
Weight 的生命周期(被使用):
- Weight 由顶层的 Query 创建。Query.createWeight(Searcher),创建的 Weight 给 Searcher 去使用。
- 当用 Similarity.queryNorm(float) 来计算查询标准化因子(query normalization)的时候,Weight.sumOfSquaredWeights() 会被调用。
- 查询标准化因子(query normalization)会传给 Weight.normalize(float)计算,这个时候权重(weighting)计算完成。
- 创建一个 Scorer。
自定义评分的计算
可以实现一个 Similarity 换掉默认的。它仅限于 Scorer、Weight 计算好的因子值再加工。要想对评分有更强的控制力,可以实现一套 Query、Weight、Scorer。
- Query 是用户信息需要的抽象
- Weight 是 Query 的内部特性表示的抽象
- Scorer 抽象公用的计算评分功能,提供计算评分和解说(explanation)评分的能力。
Query 子类实现的方法:
- createWeight(Searcher searcher) — Weight 是 Query 内部代表,所以每个 Query 都必实现一个 Weight,此方法就是生成一个Query对应的Weight对象。
- rewrite(IndexReader reader) — 重写查询为原始的查询,原始的查询有:TermQuery,BooleanQuery……
Weight 接口方法:
- Weight#getQuery() — 指出代表 Weight 的 Query。
- Weight#getValue() — Query 的权重,例如:TermQuery.TermWeight 的 value = idf^2 * boost * queryNorm
- Weight#sumOfSquaredWeights() — 各查询项的平方和,如,TermWeight 的 = (idf * boost)^2
- Weight#normalize(float) — 决定查询标准化的因子,查询标准化值可以在不同 Query 比较 score
- Weight#scorer(IndexReader) — 创建 Query 对应的评分器 Scorer,它的责任是给 Query 匹配到的文档评分。
- Weight#explain(IndexReader, int) — 给指定的文档详细解说评分值是怎么得来了。
Scorer 子类实现的方法:
- Scorer#next() — 预取匹配到的下一文档,有才返回 true。
- Scorer#doc() — 返回当前匹配到的文档id,它必须 next() 调用后才有效。
- Scorer#score() — 返回当前文档的评分,此值可以由应用程序以任何适当的方式给出,如 TermScorer 返回 tf * Weight.getValue() * fieldNorm
- Scorer#skipTo(int) — 跳到大于或等于 int 的匹配文档上。很多情况下,在结果集中 skipTo 比较循环更加快速高效。
- Scorer#explain(int) — 给出评分产生的细节。
要实现一套 Query、Weight、Scorer,最好还是看下 TermQuery、TermWeight、TermScorer。
当 Lucene 中没有想要的查询时(包括不同的评分细节),自定义Query 可能帮得上忙。
11.3.2 Lucene评分算法
那么,Lucene中是如何确定各个Document评分的呢?下面将详细介绍该功能的基本原理。
文档的得分是在用户进行检索时实时计算出来的。如果在建立索引时就已经将每个文档的得分计算好,那么当用户输入任何关键字时,得分最高的文档都会被排在返回结果的最前面,这显然是不合理的。
因此,所有文档的得分应当都与用户输入的关键字有关系,而且是实时运算的结果。其实,所谓得分,可以简单理解成是某个关键字在某文档中出现的频率。
图11-6所示的公式就是Lucene用于计算某个关键字在对应于某文档的得分。
图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)
在给出每个查询条目的方差和后,计算某查询的标准化值
11.3.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值。
Lucene 的 Scoring 评分机制
Lucene 评分体系/机制(lucene scoring)是 Lucene出名的一核心部分。它对用户来说隐藏了很多复杂的细节,致使用户可以简单地使用lucene。但个人觉得:如果要根据自己的应用调节评分(或结构排序),十分有必须深入了解 lucene 的评分机制。
Lucene scoring 组合使用了??和?布尔模型?。
首先来看下 lucene 的评分公式(在 Similarity 类里的说明)
score(q,d) ? =??coord(q,d)·??queryNorm(q) ·∑(?tf(t in d)·??idf(t)2?·??t.getBoost·??norm(t,d)?)?t in q?
其中:
tf(t in d) =frequency?
idf(t) =1 +log?(numDocs–––––––––docFreq+1)
queryNorm(q) ? =??queryNorm(sumOfSquaredWeights)=1––––––––––––––sumOfSquaredWeights?每个查询项权重的平分方和(sumOfSquaredWeights)由 Weight 类完成。例如 BooleanQuery地计算:
sumOfSquaredWeights =??q.getBoost?2?·∑(?idf(t)·??t.getBoost?)?2?t in q
- Document boost?- 文档加权,在索引之前使用doc.setBoost
- Field boost?- 字段加权,也在索引之前调用field.setBoost
- lengthNorm(field)?- 由字段内的Token 的个数来计算此值,字段越短,评分越高,在做索引的时候由 Similarity.lengthNorm 计算。
以上所有因子相乘得出 norm 值,如果文档中有相同的字段,它们的加权也会相乘:norm(t,d) ? =??doc.getBoost·??lengthNorm(field) ·∏f.getBoost?field?f?in?d?namedas?t?
索引的时候,把 norm 值压缩(encode)成一个 byte 保存在索引中。搜索的时候再把索引中 norm值解压(decode)成一个 float 值,这个 encode/decode 由 Similarity提供。官方说:这个过程由于精度问题,以至不是可逆的,如:decode(encode(0.89)) = 0.75。
计算这个评分涉及到几个核心的类/接口:Similarity、Query、Weight、Scorer、Searcher,由它们或其子类来完成评分的计算。先来看下它们的类图:
lucene search score uml, 点击放大
搜索中,评分的过程:
Weight 的生命周期(被使用):
- Query 是用户信息需要的抽象
- Weight 是 Query 的内部特性表示的抽象
- Scorer 抽象公用的计算评分功能,提供计算评分和解说(explanation)评分的能力。
Query 子类实现的方法: