有关Lucene的问题(3): 向量空间模型与Lucene的打分机制

问题:

在你的文章中提到了:

于是我们把所有此文档中词(term)的权重(term weight) 看作一个向量。   

Document = {term1, term2, …… ,term N}   

Document Vector = {weight1, weight2, …… ,weight N}   

同样我们把查询语句看作一个简单的文档,也用向量来表示。   

Query = {term1, term 2, …… , term N}   

Query Vector = {weight1, weight2, …… , weight N} 

于是我们把所有此文档中词(term)的权重(term weight) 看作一个向量。Document = {term1, term2, …… ,term N} Document Vector = {weight1, weight2, …… ,weight N}同样我们把查询语句看作一个简单的文档,也用向量来表示。 Query = {term1, term 2, …… , term N}Query Vector = {weight1, weight2, …… , weight N}

其中查询语句的term的权重如何定义的呢?

在这里,既然要放到相同的向量空间,自然维数是相同的,不同时,取二者的并集,如果不含某个词(Term)时,则权重(Term Weight)为0。 

为什么要取二者的并集,直接用查询语句向量的维度应该就可以吧,毕竟其他的字段也没有实际的用途哈,我是这么想的,不知道行得通不?

回答:

对于第一个问题,文章中既然说,查询语句也看成一个很短很短的文档,则按理论来讲,term的权重也是应用tf * idf进行计算的。

但是由于query语句是一个特殊的文档,首先对于tf来讲,一般来说不会有人在查询中将一个词输入两次,因而tf一般认为是1

而对于idf,是包含此term的文档总数,本应该包括query语句这篇很短的文档,但当文档数量达到一定的数目的时候,这一篇对分数的影响也可忽略不计,因而idf为索引中包含此term的文档总数。

所以对于score计算公式的分子部分来讲:

设query向量为:<q(i), q(j)>,而document的向量<d(1), .. , d(i), .. , d(j), .., d(n)>,两者取点积后为q(i)*d(i) + q(j)*d(j),其他项都是0.

到这里似乎看起来,和你感觉的一样,向量空间是n维同向量空间是2维是一样的。

这里说一些题外话,有利于理解lucene score的公式的由来和推理:

将其分解为df*idf后为,tf(i in q)*idf(i in q) * tf(i in d) * idf(i in d) + tf(j in q) * idf(j in q) * tf(j in d) * idf(j in d)

由上面的分析我们知道,对于query,tf为1,所以tf(i in q) = tf(j in q) = 1,而idf对query和document都是一样的,都是不计算query这个短文档的数目的,因而idf(i in q) = idf(i in d),idf(j in q) = idf(j in d),

于是上面的公式变成,tf(i in d)*idf(i)^2 + tf(j in d)* idf(j)^2,这就是为什么Lucene的score计算公式里有tf乘以idf平方的样子:

score(q,d)   =   coord(q,d)  ·  queryNorm(q)  ·  ( tf(t in d)  ·  idf(t)2  ·  t.getBoost() ·  norm(t,d) )

                                                                        t in q

然而在score的计算部分,除了分子部分,还有分母部分,也即两个向量都要除以自己的长度,从这个角度来讲,向量空间是n维和2维算出来的向量长度差别就很大了,所以必须要取两者的并集。

那么为什么要除以向量的长度呢?在这里叫做归一化处理。因为在索引中,不同的文档长度不一样,很显然,对于任意一个term,在长的文档中的tf要大的多,因而分数也越高,这样对小的文档不公平,举一个极端的例子,在一篇1000万个词的鸿篇巨著中,"lucene"这个词出现了11次,而在一篇12个词的短小文档中,"lucene"这个词出现了10次,如果不考虑长度在内,当然鸿篇巨著应该分数更高,然而显然这篇小文档才是真正关注"lucene"的。

在Lucene的score公式中,query语句长度的归一化是在queryNorm中体现的:

queryNorm(q)   =   queryNorm(sumOfSquaredWeights)   = 1/sumOfSquaredWeights½

sumOfSquaredWeights   =   q.getBoost() 2  ·  ( idf(t)  ·  t.getBoost() ) 2

                                                                    t in q

除了boost之外,queryNorm = 1/sqrt(q(i) ^ 2 + q(j) ^2),而q(x) = df(x) * idf(x),对于query来讲,df(x) = 1,因而queryNorm成了查询词的idf值的平方和的开方后的值分之一。

document的长度归一化是在 norm(t,d)中体现的:

norm(t,d)   =   doc.getBoost()  ·  lengthNorm(field)  ·  f.getBoost()

                                                                         field f in d named as t

其中文档的boost和field的boost在nrm文件中的一节已经讲过,表示某篇文章的某个域可能更重要一些,也可能更不重要一些。

而lengthNorm(field)正是文档长度的归一化的体现。

默认的DefaultSimilarity的实现中,lengthNorm如下计算:

public float lengthNorm(String fieldName, int numTerms) {
  return (float)(1.0 / Math.sqrt(numTerms));
}

我们会发现,lengthNorm的计算并不是按照经典理论,是向量长度分之一,而是文档长度开方分之一,也即忽略了每个term的权重,认为每个term的权重都是1,方才能够得出上述的公式。Lucene之所以这样做可能主要考虑两点:

  • 首先,不想完全抛弃文件长度的影响,否则又对长文档不公平,毕竟它是包含了更多的信息。我们可以简单的做个试验可以得知,即便是现在这个公式,lucene还是偏向于首先返回短小的文档的,这样在实际应用中使得搜索结果很难看。
  • 其次,此接口是开放出来,在Similarity中的,用户可以根据自己应用的需要,改写lengthNorm的计算公式。比如我想做一个经济学论文的搜索系统,经过一定时间的调研,发现大多数的经济学论文的长度在8000到10000词,因而lengthNorm的公式应该是一个倒抛物线型的,8000到10000词的论文分数最高,更短或更长的分数都应该偏低,方能够返回给用户最好的数据。

你可能感兴趣的:(Lucene)