理解Lucene得分计算公式

 

Lucene通过计算文档的得分来确定查询结果文档的相似度。如果你希望通过干预Lucene查询来改变查询结果的排序,你就需要对Lucene的得分计算有所理解。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得分计算的理解,才能在实际应用中根据需要调整各个参数,从而制定满足应用的排序策略。

 

coord(q,d)因子

 

coord(q,d)能够影响到检索结果文档的得分,它的计算公式如下:

coord(q,d) = overlap / maxOverlap

例如,查询关键词中经过解析(QueryParser)处理,得到两个Term分别为title:search和content:lucene,那么对应于公式中有,maxOverlap=2。

例如,对于下面这个索引的Document,代码如下:

			Document doc = new Document();
			doc.add(new Field("title", "search engine", Field.Store.YES, Field.Index.ANALYZED));
			doc.add(new Field("content", "good lucene luke lucene search server", Field.Store.YES, Field.Index.ANALYZED));
			indexWriter.addDocument(doc);
我们看到,检索title:search和content:lucene这两个Term,在title这个Field中( "search engine")匹配上title:search,

在content这个Field中("good lucene luke lucene search server")匹配上了content:lucene,所以对应于公式中有,overlap=2,所以coord(q,d)=2/2=1。

queryNorm(q)因子

 

queryNorm(q)是查询权重对得分的影响,它的计算公式如下:

queryNorm(q) = queryNorm(sumOfSquaredWeights)=1/(sumOfSquaredWeights^(1/2))


sumOfSquaredWeights

继续看一下,在BooleanQuery中sumOfSquaredWeights的计算:

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

因为这是在计算查询的权重,所以上式求和部分中出现的t都是在q里面出现的Term(t in q)。

q.getBoost()

上式中q.getBoost()是一个查询子句被赋予的boost值,因为Lucene中任何一个Query对象是可以通过setBoost(boost)方法设置一个boost值的,下面我们通过一个相对比较复杂的例子来说明一下:

		BooleanQuery bq1 = new BooleanQuery(); // 第一个BooleanQuery查询子句
		TermQuery tq1 = new TermQuery(new Term("title", "search"));
		tq1.setBoost(2.0f);
		bq1.add(tq1, Occur.MUST); 
		
		TermQuery tq2 = new TermQuery(new Term("content", "lucene"));
		tq2.setBoost(5.0f);
		bq1.add(tq2, Occur.MUST);
		bq1.setBoost(0.1f); // 给第一个查询子句乘上0.1,实际是减弱了其贡献得分的重要性
		
		BooleanQuery bq2 = new BooleanQuery(); // 第二个BooleanQuery查询子句
		TermQuery tq3 = new TermQuery(new Term("title", "book"));
		tq3.setBoost(8.0f);
		bq2.add(tq3, Occur.MUST);
		
		TermQuery tq4 = new TermQuery(new Term("content", "lucene"));
		tq4.setBoost(5.0f);
		bq2.add(tq4, Occur.MUST);
		bq2.setBoost(10.0f); // 给第二个查询子句乘上10.0,该子句更重要
		
		BooleanQuery bq = new BooleanQuery(); // 对上述两个BooleanQuery查询子句再进行OR运算
		bq.add(bq1, Occur.SHOULD);
		bq.add(bq2, Occur.SHOULD);


上述代码可以这样理解:“我想要查询包含Lucene的文章,但标题最好是含有book的”,也就是说“我想查找介绍Lucene的书籍,如果没有没有关于Lucene的书籍,包含介绍Lucene查询search的文章也可以”。

所以上述两个布尔查询子句设置的boost值(0.1<<10.0),就对应于我们上述公式中的q.getBoost()。

idf(t)

idf(t)就是反转文档频率,含义是如果文档中出现Term的频率越高显得文档越不重要,Lucene中计算该值的公式如下:

idf(t) = 1.0 + log(numDocs/(docFreq+1))


其中,numDocs表示索引中文档的总数,docFreq表示查询中Term在多个文档中出现。

t.getBoost()

t.getBoost()表示查询中的Term给予的boost值,例如上面代码中:

		TermQuery tq3 = new TermQuery(new Term("title", "book"));
		tq3.setBoost(8.0f);

title中包含book的Term,对匹配上的文档,通过上面公式计算,乘上t.getBoost()的值。

 

∑( tf(t in d)·idf(t)^2·t.getBoost()·norm(t,d) )因子

 

上面t还是在q中出现的Term即t in q。

norm(t,d)

这里,解释一下norm(t,d)的含义,计算公式如下所示:

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

norm(t,d)是在索引时(index-time)进行计算并存储的,在查询时(search-time)是无法再改变的,除非再重建索引。另外,Lucene在索引时存储norm值,而且是被压缩存储的,在查询时取出该值进行文档相关度计算,即文档得分计算。

需要注意的是,norm在进行codec的过程中,是有精度损失的,即不能保证decode(encode(x)) = x永远成立,例如 decode(encode(0.89)) = 0.75。

如果你在相关度调优过程中,发现norm的值破坏了文档相关性,严重的话,可以通过Field.setOmitNorms(true)方法来禁用norm,同时减少了该norm的存储开销,在一定程度上加快了查询过程中文档得分的计算。是否使用norm,需要根据你的应用来决定,例如,如果一个Field只存储一个Term,或者Field很短(包含的Term很少),一般是不需要存储norm的。

doc.getBoost()

这个就是Document的boost值,在索引的时候可以通过setBoost(boost)方法设置,例如我们一般认为title会比content更重要,所以在索引时可以对title进行boost(大于1.0)。

lengthNorm

lengthNorm是一个与Field长度(包含Term数量)有关的因子,Lucene中计算公式如下:

lengthNorm = 1.0 / Math.sqrt(numTerms)

其中,numTerms表示一个Field中Term的数量。

一般来说,一个Term在越短的Field中出现,表示该Term更重要,有点类似idf的含义。

∏ f.getBoost()

Lucene索引时,一个Document实例中,可以多次添加具有同一个Field名称的Field对象,但是值不相同,如下代码:

		Document doc = new Document();
		doc.add(new Field("title", "search engine", Field.Store.YES, Field.Index.ANALYZED));
		Field fcontent1 = new Field("content", "nutch solr lucene lucene search server", Field.Store.YES, Field.Index.ANALYZED);
		fcontent1.setBoost(2.0f);
		doc.add(fcontent1);
		Field fcontent2 = new Field("content", "good lucene luke lucene index server", Field.Store.YES, Field.Index.ANALYZED);
		fcontent2.setBoost(5.0f);
		doc.add(fcontent2);
		indexWriter.addDocument(doc);


我们在doc里面添加了同名content的两个字符串,对与这种情况,在计算得分的时候,是通过 ∏ f.getBoost()连乘积来计算得到的。

例如,我们查询content:lucene,上面Document doc中两个content的Field都匹配上了,在计算的时候有: ∏ f.getBoost() = 2.0 * 5.0 = 10.0。如果查询content:solr,则只有一个Field匹配上了,则 ∏ f.getBoost()=2.0。

参考内容

http://lucene.apache.org/java/3_1_0/api/core/org/apache/lucene/search/Similarity.html

你可能感兴趣的:(server,Lucene,Solr,search,存储,文档)