理解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

發佈了73 篇原創文章 · 獲贊 19 · 訪問量 110萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章