Lucnen做爲搜索引擎中,應用最爲普遍和成功的開源框架,它對搜索結果的排序,有一套十分完整的機制來控制;但咱們控制搜索結果排序的目的永遠只有一個,那就是信息過濾,讓用戶快速,準確的找到其想要的結果,豐富用戶體驗。java
之前看過一個牛人的博客,總結了4個地方,可對Lucene檢索結果的排序進行控制,如今已經記不住。我本身簡單整理了下面幾個,如有疏漏,歡迎補充:apache
1. 經過Lucene自有的查詢表達式:Lucene提供至關豐富的表達式解析,要細講就多了去了;這裏只強調下,我在項目中用的比較多的是經過對指定域的加權,來影響檢索結果(例如:field1:(XXX)^10 or field2:(XXX)^5;其中XXX是用戶提交的檢索)框架
2. 權重的控制:這是在建索引的時候就寫入索引的,查詢時只是讀取出來,用乘的方式來對一些檢索結果加分。據我本身看Lucene代碼,Similarity中也能在建索引時,對權重的寫入進行控制;後面會細講。eclipse
3. Controller 模塊:Lucene的排序流程控制模塊,裏面提供的一些接口能讓你對打分後的搜索結果進行一些篩選和調整。ide
4. Similarity 模塊:Lucene的搜索結果打分控制模塊,也是這裏要詳細分析的模塊。他能讓你對一個檢索結果的打分進行優化,或面目全非,哈哈。函數
要理解Similarity模塊對打分結果控制,首先要了解Lucene本身評分原理:類似度評分公式;次公式是目前公認的用戶體驗較好的一個,原理是根據餘弦定理,我在之前的博文中有介紹過。下面是在摘自 《Lucene實戰》(第二版)的公式表達式:優化
其中q 爲查詢語句,t 是q 分詞後的每一項, d爲去匹配的文檔。this
接下來對公式中的每個函數,進行詳解,他們都能在 Similarity 模塊中進行控制。搜索引擎
首先,我簡單說明下,Lucene對一次查詢是如何封裝;這涉及到對打分公式是如何調用的,如此一來更能全面的瞭解打分流程:spa
第一步的處理確定是分詞,查詢語句 => term_1, term_2 … term_n(n∈[1,∞]),緊接着是將這些term 封裝成Query對象,總的Query對象是BooleanQuery,它裏面包含和分詞數相同TermQuery,和分詞後的詞項一一對應;這是對一個域的查詢,若你多個域聯合查詢,那就會有多個這樣的BooleanQuery,被一個更大的BooleanQuery包裹着。
而打分公式,貫穿全部Query,其中一部分在TermQuery中計算,一部分在BooleanQuery計算,最後按此計算出的得分進行排序,就獲得了咱們的搜索結果。
下面是我經過explain(Query query, int doc) 導出的打分解釋:
對照Lucene的打分解釋,咱們一層一層往裏撥(上述每一個縮進爲一層),每個函數都能在Similarity中進行控制。
1. 首先第一層:3.3936599 = (MATCH) product of:,是此條記錄的總得分。
2. 第二層:8.48414992 = (MATCH) sum of: 它對應一個BooleanQuery,它把它包含的TermQuery的得分累加起來,因爲TermQuery總共有5個,此條結果中只匹配了2個,因此最後一行將得分乘以了0.4獲得最後的打分,coord()在Similarity中的默認實現以下:
/** Implemented as <code>overlap / maxOverlap</code>. */ @Override public float coord(int overlap, int maxOverlap) { return overlap / (float)maxOverlap; }
你也能夠繼承Similarity對此方法重寫。
3. 第三層:(MATCH) weight(field:XXX in m), product of: 有2個,它們分別是「三國」、「無雙」對應的詞項查詢TermQuery的得分。
再往裏,就是TermQuery的打分規則了,裏面的函數已經和公式有所對應了,下面就詳細介紹TermQuery中每一項計算的做用。
Similarity 函數詳解
TermQuery中有4個函數,都是Similarity裏能夠控制的函數,他們分別是queryNorm、tf、idf、fieldNorm;其中queryNorm對於某一次搜索中結果的排序沒有影響,在一次搜索中queryNorm的值是固定的。這裏就不介紹了
此函數表示詞項T 在該文檔中的該字段裏 出現的頻率;對應到上圖的例子中:既是分詞後的詞項(三國 或 無雙)在此條記錄中Name字段裏出現的頻率。固然出現的次數越多,它返回的值越大,也正好反映了此文檔的重要性。下面是DefaultSimilarity 中的默認實現的默認實現。
/** Implemented as <code>sqrt(freq)</code>. */ @Override public float tf(float freq) { return (float)Math.sqrt(freq); }
默認實現是開平方。一樣你能夠重寫此函數。
它實際對結果排序的影響,表現和它的意義同樣,一個搜索結果包含的搜索關鍵詞越多,得分越高。
此函數出現了兩次,也恰好對應公式中的 idf(t)^2;
這個函數被稱做倒頻率,表示詞項T 在全部文檔中出現的頻率。若它在全部文檔中出現的次數越多,代表這個詞項T 越不重要;如下是DefaultSimilarity的默認實現。
/** Implemented as <code>log(numDocs/(docFreq+1)) + 1</code>. */ @Override public float idf(int docFreq, int numDocs) { return (float)(Math.log(numDocs/(double)(docFreq+1)) + 1.0); }
此函數實際對結果排序的影響,在於當一次搜索包含多個詞項時,文檔A和B分別包含了其中一個詞項;好比A包含「三國」,B包含「無雙」;那麼「三國」和「無雙」的倒頻率就會影響,讓A、B的得分產生差別了。若詞項只有一個,那本次搜索idf(t) 實際對結果排序不會產生影響。
它的值對應着公式中 boost(t.field in d)×lengthNorm(t.field in d) 的值。其中boost(t.field in d)的值,在建立索引時就被記錄下來,而lengthNorm(t.field in d)得值,會在查詢過程當中計算;它表示此條搜索結果中,給定字段中包含詞項的總數;若值越大,得分越低;你能夠這麼理解;若A文檔有包含了1000個詞項,關鍵詞出現的頻率爲10;而B文檔包20個詞項,相同關鍵詞出現的頻率爲8;很明顯B文檔的打分應該要高一些;由此函數能夠起到這樣的效果。如下是Similarity 的默認實現,函數名在3.0之後變了,原來就叫lengthNorm
/** Decodes a normalization factor stored in an index. * <p> * <b>WARNING: If you override this method, you should change the default * Similarity to your implementation with {@link Similarity#setDefault(Similarity)}. * Otherwise, your method may not always be called, especially if you omit norms * for some fields.</b> * @see #encodeNormValue(float) */ public float decodeNormValue(byte b) { return NORM_TABLE[b & 0xFF]; // & 0xFF maps negative bytes to positive above 127 }
至此Lucene打分流程和Similarity模塊的函數已經將的差很少了。能夠經過這些函數讓你的搜索展現出徹底不同的效果,這也須要根據不一樣的業務慢慢調試,才能得出最優化的搜索結果
原創博客,轉載請註明http://my.oschina.net/BreathL/blog/51498