lucene3.0.3中的CustomerScoreQuery

      我原本以爲我已經把lucene3.0.3看的很詳細了,結果發現漏了一個很重要的query——CustomerScoreQuery,從名字上看錶示用戶自定義得分的query,我表示很好奇,因爲我花了好大力氣才明白了lucene的得分公式,貌似這裏竟然可以自己寫得分公式了,於是我抱着極大的好奇心看了他的源碼,記錄在此,方便大家。

     CustomerScoreQuery的原理是這樣的,它包含一個主query以及一個或者多個valueScourceQuery,主query和我們平時的query是一樣的,可以是任意的query的子類,valueSourceQuery是用來完成我們自定義得分的,他不會進行召回,召回是主query的功能,對於主query召回的的每一個doc,valueSourceQuery可以對其得分進行修改,修改的規則由CustomerScoreQuery配置的CustomScoreProvider設置,他的主要思想就是這樣。ValueSourceQuery對doc的得分的修改是根據其內部的ValueSource屬性來設置的,我們從這個ValueSouce下手。

      ValueSource:這個類是個抽象類,他最關鍵的方法是DocValues getValues(IndexReader reader),他表示從指定的reader中獲得的對於每一個doc的值,用DocValues進行封裝,DocValues也是一個抽象類,通過調用它的floatVal(int doc)、intVal(int doc)來獲得每一個doc的值,這個值就可以影響最終的doc的打分。我們看一個ValueSource的實現類FieldCacheSource,它的邏輯是從詞典表中獲得每一個doc對應的term,然後根據term進行打分,所以他必須執行一個域(即term所在的域),並且這個域必須建立索引且不能分詞(這個和之前的FieldCache的邏輯是一樣的)。他對getValues的實現是這樣的:

 

/**
 * 獲得當前的段下當前的域的值,通過DocValues封裝,和Sort是一個邏輯。
 */
@Override
public final DocValues getValues(IndexReader reader) throws IOException {
	return getCachedFieldValues(FieldCache.DEFAULT, field, reader);//這裏的field就是我們說的term所在的域的名字。
}

 FieldCacheSource類也是一個抽象類,他的getCachedFieldValues沒有實現,我們看一個他的實現類:IntFieldSource.getCachedFieldValues(FieldCache, String, IndexReader),即將每個doc所對應的某個域中的term轉換爲一個int類型的整數的類,他的邏輯和我們在FieldCache中介紹的是一樣,代碼如下:

 

@Override
public DocValues getCachedFieldValues(FieldCache cache, String field, IndexReader reader) throws IOException {
	final int[] arr = cache.getInts(reader, field, parser);//將FieldCache中當前的reader下的field域的term用parser轉換爲int,
	return new DocValues() {
		@Override
		public float floatVal(int doc) {
			return (float) arr[doc];
		}
		@Override
		public int intVal(int doc) {
			return arr[doc];
		}
		@Override
		public String toString(int doc) {
			return description() + '=' + intVal(doc);
		}
		@Override
		Object getInnerArray() {
			return arr;
		}
	};
}

 這樣就能獲得最終的DocValues了,ValueSource的最終目的就是獲得DocValues,然後我們再看一下ValueSourceQuery的過程。

      一個Query最重要的就兩個,一個是召回doc(doc的召回是scorer的功能),一個是對召回的doc的打分(打分是weight和scorer的功能),也就是生成weight、scorer的方法,ValueSourceQuery的最終生成的Scorer是ValueSourceScorer,其裏面的termDocs是一個AllTermDocs,也就是從所有的域中獲得term的termDocs,從這裏可以看出valueSourceQuery是如何召回的doc了,只不過他是召回了所有的doc(也就是說他召回的doc實際上是沒有用的)。我們再看一下ValueSourceScorer的得分的計算也就是其score方法

 

/* (non-Javadoc) @see org.apache.lucene.search.Scorer#score() */
@Override
public float score() throws IOException {
	return qWeight * vals.floatVal(termDocs.doc());//這裏的vals就是從ValueSourceQuery中獲得的ValueSource屬性
}

 他的得分的計算是通過DocValues來計算的,也就是將緩存的term轉換爲數字來作爲得分,還有個qWeight,他是weight計算出來的值,在默認的情況下他是1,因爲他是由ValueSourceQuery的getBoost來計算的(計算出來是1)。這樣我們搞懂了ValueSourceQuery最終的功能是召回所有的doc,並將每個doc的term解析爲數字作爲得分的。接下來我們回到CustomerScoreQuery來看一下他是如何召回doc和計算得分的。

      召回doc和得分都是在CustomerScoreQuery最終生成的Scorer——CustomScorer中計算的,我們看一下召回的方法nextDoc,代碼:

 

/** 查找下一個doc時是優先調用subQueryScorer的id,然後讓各個valueSource也指向這個doc*/
@Override
public int nextDoc() throws IOException {
   int doc = subQueryScorer.nextDoc();//先根據主query來召回doc,
   if (doc != NO_MORE_DOCS) {//如果召回了
        for (int i = 0; i < valSrcScorers.length; i++) {
   	    valSrcScorers[i].advance(doc);//將每一個ValueSourceQuery的scorer都前進到主query召回的doc,
	}
   }
   return doc;
}

 可以看出召回是根據主query召回的,我們看一下打分:

 

@Override
public float score() throws IOException {
	for (int i = 0; i < valSrcScorers.length; i++) {
		vScores[i] = valSrcScorers[i].score();//計算當前的doc下所有的valueSourceScorer的得分
	}
	return qWeight * provider.customScore(subQueryScorer.docID(), subQueryScorer.score(), vScores);//最終的得分要看provider的實現。
}

 provider通過CustomerScoreQuery的getCustomScoreProvider方法獲得,這個類決定了如何計算最終的得分,他的customScore方法:

 

@Deprecated
public float customScore(int doc, float subQueryScore, float valSrcScores[]) {
if (valSrcScores.length == 1) {
	return customScore(doc, subQueryScore, valSrcScores[0]);//如果只要一個valueSourceQuery,是將兩個的得分相乘,
}
if (valSrcScores.length == 0) {
	return customScore(doc, subQueryScore, 1);//如果沒有valueSourceQuery,則直接返回subQuerScore,乘以1和沒乘一樣。
}
float score = subQueryScore;
for (int i = 0; i < valSrcScores.length; i++) {//如果有多個,是將所有的得分乘起來。
	score *= valSrcScores[i];
}
return score;
}

 通過上面的分析我們知道了customerScorerQuery的來龍去脈,他就是通過valueSource對通過主Query召回的doc的打分進行修改。那麼他的用處是什麼呢,我們可以做什麼拓展呢?我的理解是我們可以通過對得分進行修改,來影響排序,在CustomerScoreQuery的javadoc中也對此進行了說明:可以通過幾成CustomerScoreQuery複寫其getCustomScoreProvider方法來實現自己的CustomScoreProvider來實現最終的得分的計算,比如我們在搜電商網站中的商品的時候,對於剛上架的商品要比上架很久的商品排在前面,或者新上架的排在後面,就可以通過自己實現一個CustomScoreProvider,用當前的時間減去doc的上架時間作爲一個得分的計算方式,然後再和主query的得分通過一定的方式結合起來做出最後的得分。

 

 

 

      

      

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章