原文地址:TFIDFSimilarity (Lucene 4.9.0 API)
TFIDFSimilarity类中定义了Lucene评分的要素。重写这些要素计算实现可以方便的修改Lucene的打分机制。
扩展阅读:Introduction To Information Retrieval, Chapter 6
下面将从信息检索模型到具体(有效地)实现来描述Lucene如何实现评分。我们首先简要介绍下向量空间模型评分(VSM Score),之后导出Lucene理论评分公式,并据此最终推导出Lucene实际评分方法(与Lucene实现中使用的类、方法相关联)
Lucene混合了信息检索的布尔模型(BM)和信息检索的向量空间模型——通过布尔模型确定文档并通过VSM进行匹配程度评分。
在向量空间模型中,文档和查询被表示为多维空间中的加权向量集。其每个不同的索引词(Term)对应一个维度,在此维度上的权重是词频/逆向文件频率(tf-idf)值。
向量空间模型并不要求权重使用tf-idf值,不过tf-idf值被认为可以高效地评价搜索结果,所以Lucene使用tf-idf。之后将更加详细的表述词频tf和逆向文件频率idf,但现在(作为结论)我们先设定:对于给定的词(Term)t和文档(或查询)x,用tf(t,x)表示t在x中出现的次数,类似的用idf(t)表示包含词t的所有文档数的反比。
文档d对于查询q的向量空间模型评分VSM score等于加权查询向量V(q)和V(d)之间的余弦相似性(Cosine Similarity):
cosine-similarity(q,d) = |
V(q) · V(d) |
––––––––– |
|V(q)| |V(d)| |
|
|
|
VSM Score |
其中
V(q)·
V(d)是加权向量的 点乘积(dot product),
|V(q)|和
|V(d)|是各自的 欧几里得度量(Euclidean norms)(或者被叫作欧几里得距离、模)。
Note:上述等式可被视作标准化加权向量(V(q)除以它自己的欧几里得度量,将其标准化为一个单位向量)的点乘积。
Lucene从搜索质量与可用性方面重新定义了VSM score:
- 将文档向量V(d)标准化为单位向量是有问题的,因为它移除了全部的文档长度信息。对部分文档而言,这样做大概没有问题,例如一个由重复10遍的相同片段组成的文档,尤其是这片片段由不同词组成时。但对另一个不包含重复片段的文档而言这样做就可能错了。为了避免这个问题,Lucene使用了一个不同的文档长度标准化因子,其使标准化后的向量等于或大于其单位向量:doc-len-norm(d)。(译注:这个因子用来取代“1/|V(d)|”)
- 在创建索引时,用户可以指定一个确定的文档要比其他文档更加重要,通过指定一个文档加权因子。在这种情况下,各文档的评分结果也要与这个被称这为doc-boost(d)的加权因子值相乘。
- Lucene是基于域的,因此每个查询term也要应用到单独的域上,文档长度的标准化会基于查询对应域的长度,而且作为相对文档的加权因子的补充文档域也会存在加权因子。
- 同一个域在文档索引时可以多次被重复添加,所以这个域的加权因子是文档中此域各次添入部分时的加权因子的乘积。
- 在搜索时用户可以指定各查询、子查询和各查询term的加权因子,所以查询term对于文档评分的贡献应该乘以查询term的加权因子:query-boost(q).
- 一个文档可能被一个复合的查询term匹配,即使该文档不包含查询中所有的term(对于一些查询这是正确的),而用户可以进一步的通过一个协调因子为匹配更多查询term的文档加分,匹配了越多加分越高。协调因子记作coord-factor(q,d)。
在上述单个索引域的简化假定下,我们可以得到Lucene理论评分公式:
score(q,d) = coord-factor(q,d) · query-boost(q) · |
V(q) · V(d) |
––––––––– |
|V(q)| |
|
· doc-len-norm(d) · doc-boost(d) |
|
|
Lucene理论评分公式 |
这个理论公式是基于以下前提下的简化:(1)查询term和文档是基于域的(2)加权因子通常是加在查询term上的而不是查询上的(译注:一个查询可以有众多的查询term)
现在我们描述下Lucene如何实现上述理论公式,并导出Lucene实际评分方法。
为了高效率的评分运算,一些评分组成被预先运算集成:
- 查询(实际是查询term)的query-boost在搜索开始时就被告知。
- 查询的欧几里得度量|V(q)|在搜索开始时被算好,因为它对各文档打分来说是独立的(译注:即对各文档来说是一致的)。从搜索优化的角度来看,这确实是个很明显的问题:既然所有的评分文档将会乘以相同的|V(q)|,这样文档的排序(按评分排序)不会被这样的标准化操作影响,为什么要麻烦的做查询标准化呢?其实有两个足够好的理由保留这项标准化操作:
- 重新调用余弦相似性(Cosine Similarity)评估可以用来找出两个文档间的相似度。用户可以使用Lucene来进行如聚类这样的操作,也可以使用一个文档作为查询来计算与其他文档间的相似程度。出于这种用途,文档d3对查询(文档)d1的评分与其对查询(文档)d2的评分之前的比较是很重要的。换言之,同一文档针对不同查询之前的评分应该是可比较的。可能其他的程序会需要使用这一特性。而这正是查询向量V(q)标准化所提供的:在两个或更多查询之间的(某种程度上的)可比较性
- 在评分时应用查询(向量)标准化可以帮助保持评分结果在单位向量附近,因此可以阻止因浮点数精度限制导致的打分数据损失。
- 文档长度标准化因子doc-len-norm(d)和文档权重因子doc-boost(d)在索引创建时确定。它们已经被事先(译注:在索引创建时)运算并且它们的乘积已经在索引数据中单独存储:norm(d)。(在之后导出的等式中,norm(t in d)意为norm(field(t) in doc d)其中field(t)是查询term t对应的域。)
Lucene实际评分方法从之前叙述中得出:
score(q,d) = coord(q,d) · queryNorm(q) · |
∑ |
( tf(t in d) · idf(t)2 · t.getBoost() · norm(t,d) ) |
|
t in q |
|
|
|
Lucene实际评分方法 |
其中:
- tf(t in d)与term的频率有关,定义为当前评分的文档d中term t出现的次数。文档中出现给定term的次数越多得到的分值越高。注意tf(t in q)被假定为1,因此它不会出现在等式中,不过,如果一个查询包含两次相同的查询term,那么将会有两个具有相同term的term子查询,因此计算结果仍然会保持正确(虽然不怎么高效)。
DefaultSimilarity
中定义的默认的tf(t in d)计算方式为:
- idf(t)意为反向文件频率。这个值是文档频率(docFreq)(出现term t的文档数)的反比。这意味着罕见的term会为查询的总评分贡献更多的分数。t的idf(t)值在查询和文档中都会出现因此它在等式中被执行乘方。
DefaultSimilarity
中定义的默认的idf(t)计算方式为:
idf(t) = |
1 + log ( |
numDocs |
––––––––– |
docFreq+1 |
|
) |
- coord(q,d)是一个基于“在指定文档中查询term匹配数”的评分因子。(译注:term集:field1:query_value1,field2:query_value2,...中匹配数)。典型地,一个匹配更多查询term的文档要比一个匹配少的文档得到更高的分数。这是一个在进行搜索时生效的被Similarity类中的方法
coord(q,d)
进行运算得出的搜索态因子。
- queryNorm(q)是一个用于使评分结果在查询间可以进行比较的标准化因子。这个因子不会影响文档的排序(因为所有的排序文档(的分值)都会乘以相同的因子),而只是尝试使评分结果在不同查询间可比较。这是一个在Similarity类中在搜索时生效进行运算得出的搜索态因子。
DefaultSimilarity
中默认的运算会给出一个欧几里得度量(Euclidean norms):
queryNorm(q) = queryNorm(sumOfSquaredWeights) = |
1 |
–––––––––––––– |
sumOfSquaredWeights½ |
|
(查询term的)平方加权和通过查询Weight
对象计算。例如,一个BooleanQuery
通过如下方式运算出这个值:
sumOfSquaredWeights = q.getBoost() 2 · |
∑ |
( idf(t) · t.getBoost())2 |
|
t in q |
|
- t.getBoost()在查询文本中指定(参见query syntax)或者通过程序调用
setBoost()
进行设定的查询q中term t的搜索态加权因子。注意,确实没有一个直接的API可以访问一组混合term查询中的一个term的加权因子,不过混合的term被表示成一个混合了多个TermQuery
对象的查询,于是查询中的一个term的加权因子可以通过调用子查询的getBoost()
方法来访问。
- norm(t,d)封装了一小部分加权和长度因子(在索引创建阶段):
- Field boost - 在将域加入文档前通过调用
field.setBoost()
设定。
- lengthNorm - 在文档加入索引时按照文档中此域包含的token数计算,所以域(包含的token数)越短得分越高。LengthNorm在索引创建时被Similarity类计算。
computeNorm(org.apache.lucene.index.FieldInvertState)
方法负责把结合这些因子运算成一个单独的浮点数。 当一个文档被加入索引时,以上所有因子会被相乘。如果文档中存在同名的域(译注:多值域),那么它们的加权会乘在一起(译注:即进行乘方运算):
norm(t,d) = lengthNorm · |
∏ |
f.boost () |
|
field f in d named as t |
|
注意到了执行搜索操作的时候再修改评分的norm(译注:规则,而不是值)就太晚了, 例如使用一个不同的Similarity
类来执行搜索时。