Lucene评分算法解释



Lucene评分算法解释
首先,需要学习Lucene的评分计算公式——
分值计算方式为查询语句q中每个项t与文档d的匹配分值之和,当然还有权重的因素。其中每一项的意思如下表所示:
3.5
评分公式中的因子
评分因子
 
tf(t in d)
项频率因子——文档d)中出现项t)的频率
idf(t)
项在倒排文档中出现的频率:它被用来衡量项的唯一性.出现频率较高的term具有较低的idf,出现较少的term具有较高的idf
boost(t.field in d)
域和文档的加权,在索引期间设置.你可以用该方法 对某个域或文档进行静态单独加权
lengthNorm(t.field in d)
域的归一化Normalization)值,表示域中包含的项数量.该值在索引期间计算,并保存在索引norm.对于该因子,更短的域或更少的语汇单元)能获得更大的加权
coord(q,d)
协调因子(Coordination factor),基于文档中包含查询的项个数.该因子会对包含更多搜索项的文档进行类似AND的加权
queryNorm(q)
每个査询的归一化值指毎个查询项权重的平方和

总匹配分值的计算

具体到上面的测试来讲,每个文档有两个域:title和content,最终匹配分值=查询语句在两个域中的得分之和。即最终结果5.6394258 = 5.3901243 + 0.24930152。

查询语句在某个域匹配分值计算

这个5.3901243是如何来的呢?查询语句有两个项t:"食品"和"安全"。所以计算结果等于这两部分的和:“食品”在title中的匹配分值 + “安全”在title中的匹配分值。即 5.3901243 = 3.2243047 + 2.16582 。

某个项在某个域的匹配分值的计算

接下来我们看看“食品”在title中的匹配分值 3.2243047 是怎么算出来的。t在field中的分值score = 查询权重queryWeight * 域权重fieldWeight,即 3.2243047 = 0.66116947 * 4.876669 。
queryWeight的计算
queryWeight的计算可以在TermQuery$TermWeight.normalize(float)方法中看到计算的实现:
1
2
3
4
5
6
public void normalize(float queryNorm) {
this.queryNorm = queryNorm;
//原来queryWeight 为idf*t.getBoost(),现在为queryNorm*idf*t.getBoost()。
queryWeight *= queryNorm;
value = queryWeight * idf;
}
其实默认情况下,queryWeight = idf * queryNorm,因为Lucene中默认的boost = 1.0。
查询权重queryWeight 0.66116947 的计算方法:查询权重queryWeight = idf * queryNorm,即 0.66116947 = 5.5733356 * 0.11863084。
idf的计算
idf是项在倒排文档中出现的频率,计算方式为
1
2
3
4
5
/** Implemented as <code>log(numDocs/(docFreq+1)) + 1</code>. */
  @Override
  public float idf(long docFreq, long numDocs) {
    return (float)(Math.log(numDocs/(double)(docFreq+1)) + 1.0);
  }
docFreq是根据指定关键字进行检索,检索到的Document的数量,我们测试的docFreq=14;numDocs是指索引文件中总共的Document的数量,我们测试的numDocs=1453。用计算器验证一下,没有错误,这里就不啰嗦了。
queryNorm的计算
queryNorm的计算在DefaultSimilarity类中实现,如下所示:
1
2
3
4
/** Implemented as <code>1/sqrt(sumOfSquaredWeights)</code>. */
public float queryNorm(float sumOfSquaredWeights) {
    return (float)(1.0 / Math.sqrt(sumOfSquaredWeights));
}
这里,sumOfSquaredWeights的计算是在org.apache.lucene.search.TermQuery.TermWeight类中的sumOfSquaredWeights方法实现:
    
1
2
3
4
public float sumOfSquaredWeights() {
      queryWeight = idf * getBoost();             // compute query weight
      return queryWeight * queryWeight;          // square it
    }
其实默认情况下,sumOfSquaredWeights = idf * idf,因为Lucune中默认的boost = 1.0。
上面测试例子中sumOfSquaredWeights的计算如下所示:
sumOfSquaredWeights = 5.5733356 * 5.5733356 + 4.5678134 * 4.5678134 + 3.6274254 * 3.6274254 + 2.4436553 * 2.4436553 = 71.05665522523017;
上面的四个weight分别来自 {食品, 安全} * {title, content} 这四个组合。
然后,就可以计算queryNorm的值了,计算如下所示:
queryNorm = (float)(1.0 / Math.sqrt(71.05665522523017) = 0.11863084386918748683822481722352;
fieldWeight的计算
在org/apache/lucene/search/similarities/TFIDFSimilarity.java的explainScore方法中有:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// explain field weight
    Explanation fieldExpl = new Explanation();
    fieldExpl.setDescription("fieldWeight in "+doc+
                             ", product of:");
 
    Explanation tfExplanation = new Explanation();
    tfExplanation.setValue(tf(freq.getValue()));
    tfExplanation.setDescription("tf(freq="+freq.getValue()+"), with freq of:");
    tfExplanation.addDetail(freq);
    fieldExpl.addDetail(tfExplanation);
    fieldExpl.addDetail(stats.idf);
 
    Explanation fieldNormExpl = new Explanation();
    float fieldNorm = norms != null ? decodeNormValue(norms.get(doc)) : 1.0f;
    fieldNormExpl.setValue(fieldNorm);
    fieldNormExpl.setDescription("fieldNorm(doc="+doc+")");
    fieldExpl.addDetail(fieldNormExpl);
     
    fieldExpl.setValue(tfExplanation.getValue() *
                       stats.idf.getValue() *
                       fieldNormExpl.getValue());
 
    result.addDetail(fieldExpl);
重点是这一句:
1
2
3
fieldExpl.setValue(tfExplanation.getValue() *
                       stats.idf.getValue() *
                       fieldNormExpl.getValue());
使用计算式表示就是
fieldWeight = tf * idf * fieldNorm
tf和idf的计算参考前面的,fieldNorm的计算在索引的时候确定了,此时直接从索引文件中读取,这个方法并没有给出直接的计算。如果使用DefaultSimilarity的话,它实际上就是lengthNorm,域越长的话Norm越小,在org/apache/lucene/search/similarities/DefaultSimilarity.java里面有关于它的计算:
1
2
3
4
5
6
7
8
  public float lengthNorm(FieldInvertState state) {
    final int numTerms;
    if (discountOverlaps)
      numTerms = state.getLength() - state.getNumOverlap();
    else
      numTerms = state.getLength();
   return state.getBoost() * ((float) (1.0 / Math.sqrt(numTerms)));
  }
这个我就不再验算了,每个域的Terms数量开方求倒数乘以该域的boost得出最终的结果。
Please read full article from Lucene评分算法解释

No comments:

Post a Comment

Labels

Algorithm (219) Lucene (130) LeetCode (97) Database (36) Data Structure (33) text mining (28) Solr (27) java (27) Mathematical Algorithm (26) Difficult Algorithm (25) Logic Thinking (23) Puzzles (23) Bit Algorithms (22) Math (21) List (20) Dynamic Programming (19) Linux (19) Tree (18) Machine Learning (15) EPI (11) Queue (11) Smart Algorithm (11) Operating System (9) Java Basic (8) Recursive Algorithm (8) Stack (8) Eclipse (7) Scala (7) Tika (7) J2EE (6) Monitoring (6) Trie (6) Concurrency (5) Geometry Algorithm (5) Greedy Algorithm (5) Mahout (5) MySQL (5) xpost (5) C (4) Interview (4) Vi (4) regular expression (4) to-do (4) C++ (3) Chrome (3) Divide and Conquer (3) Graph Algorithm (3) Permutation (3) Powershell (3) Random (3) Segment Tree (3) UIMA (3) Union-Find (3) Video (3) Virtualization (3) Windows (3) XML (3) Advanced Data Structure (2) Android (2) Bash (2) Classic Algorithm (2) Debugging (2) Design Pattern (2) Google (2) Hadoop (2) Java Collections (2) Markov Chains (2) Probabilities (2) Shell (2) Site (2) Web Development (2) Workplace (2) angularjs (2) .Net (1) Amazon Interview (1) Android Studio (1) Array (1) Boilerpipe (1) Book Notes (1) ChromeOS (1) Chromebook (1) Codility (1) Desgin (1) Design (1) Divide and Conqure (1) GAE (1) Google Interview (1) Great Stuff (1) Hash (1) High Tech Companies (1) Improving (1) LifeTips (1) Maven (1) Network (1) Performance (1) Programming (1) Resources (1) Sampling (1) Sed (1) Smart Thinking (1) Sort (1) Spark (1) Stanford NLP (1) System Design (1) Trove (1) VIP (1) tools (1)

Popular Posts