zoukankan      html  css  js  c++  java
  • Solr——评分公式修改

    以下来自solr in action。

    包含:

    • 词项频次。查询词项出现在当前查询文档中的次数。
    • 反向文档频次。查询词项出现在所有文档总的次数。
    • 此项权重。
    • 标准化因子:
      • 字段规范:
        • 文档权重。
        • 字段权重。
        • 长度归一化。消除长文档的优势。因为长文档的词项频次一般会比较大。
      • 协调因子。避免一个文档中出现某一个词项的次数太多导致总分值太大。目的是让结果中包含更多的是出现所有词项的文档。

    具体说明见下文。

    以下转载自网络。原文地址: http://tec.5lulu.com/detail/110d8n2ehpg2j85ec.html

    简述

    内容的相似性计算由搜索引擎的几种常见的检索模型有:

    向量空间模型

        简述中介绍了好多种相似性计算方法,    Solr的索引文件中有.tvx,.tvd,tvf存储了term vector的信息,首先我们学习如何利用term vector来反映相似性程度。

     

        用v(d1)表示了term d1的term向量,termterm    给定一个查询以及一个文档,如何计算他们的相似值呢,请看以下公式,它使用了以下概念:term frequency (tf), inverse document frequency (idf), term boosts (t.getBoost), field normalization (norm), coordination factor (coord), and query normalization (queryNorm). 

     

     

    • tf(t in d ) 表示该idf(t) 表示 t.getBoost() 也叫此项权重。标准化因子,它包括三个参数:
    • :此值越大,说明此文档越重要。也叫文档权重。:此域越大,说明此域越重要。也叫字段权重。:一个域中包含的总数越多(我理解的是所有这个文档的所有,而不局限于查询中的),也即文档越长,此值越小,文档越短,此值越大。也叫长度归一化。目的是消除长字段的优势。

     coord(q,d):一次搜索可能包含多个搜索词,而一篇文档中也可能包含多个搜索词,此项表示,当一篇文档中包含的搜索词越多,则此文档则打分越高 ,numTermsInDocumentFromQuery / numTermsInQuery 

     queryNorm(q):计算每个查询条目的方差和,此值并不影响排序,而仅仅使得不同的query之间的分数可以比较。

    ps:理解到这里就可以了,下面的细节可以以后再研究。

    评分机制

    表示匹配文章的程度,如果在一篇文章中该出现了次数越多,说明该对该文章的重要性越大,因而更加匹配。相反的出现越少说明该越不匹配文章。但是这里需要注意,出现次数与重要性并不是成正比的,比如出现次,出现次,对于该文章的重要性并不是的倍,所以这里的值进行平方根计算。

    tf(t in d) = numTermOccurrencesInDocument 1/2

    • idf, 表示包含该文章的个数,与tf不同,idf 越大表明该term越不重要。比如this很多文章都包含,但是它对于匹配文章帮助不大。这也如我们程序员所学的技术,对于程序员本身来说,这项技术掌握越深越好(掌握越深说明花时间看的越多,tf越大),找工作时越有竞争力。然而对于所有程序员来说,这项技术懂得的人越少越好(懂得的人少df小),找工作越有竞争力。人的价值在于不可替代性就是这个道理。

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

    • t.getBoost,boost是人为给term提升权重的过程,我们可以在Index和Query中分别加入term boost,但是由于Query过程比较灵活,所以这里介绍给Query boost。term boost 不仅可以对Pharse进行,也可以对单个term进行,在查询的时候用^后面加数字表示:
    • title:(solr in action)^2.5  对solr in action 这个pharse设置boost
    • title:(solr in action)  默认的boost时1.0
    • title:(solr^2in^.01action^1.5)^3OR"solrinaction"^2.5 
    • norm(t,d) 即field norm,它包含Document boost,Field boost,lengthNorm。相比于t.getBoost()可以在查询的时候进行动态的设置,norm里面的f.getBoost()和d.getBoost()只能建索引过程中设置,如果需要对这两个boost进行修改,那么只能重建索引。他们的值是存储在.nrm文件中。

       

      norm(t,d) = d.getBoost() • lengthNorm(f) • f.getBoost() 

    • d.getBoost() document的boost,对document设置boost是通过对每一个field设置boost实现的。
    • f .getBoost() field的boost,这里需要提以下,Solr是支持多值域方式建索引的,即同一个field多个value,如以下代码。当一个文档里出现同名的多值域时候,倒排索引和项向量都会在逻辑上将这些域的词汇单元附加进去。当对多值域进行存储的时候,它们在文档中的存储顺序是分离的,因此当你在搜索期间对文档进行检索时,你会发现多个Field实例。如下图例子所示,当查询author:Lucene时候出现两个author域,这就是所谓的多值域现象。 
    • Document doc = new Document();
      for (String author : authors){
      doc.add(new Field("author",author,Field.Store.YES,Field.Index.ANALYZED));
      }
       
      //首先对多值域建立索引
      Directory dir = FSDirectory.open(new File("/Users/rcf/workspace/java/solr/Lucene"));
      IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LUCENE_48,new WhitespaceAnalyzer(Version.LUCENE_48));
      @SuppressWarnings("resource")
      IndexWriter writer = new IndexWriter(dir,indexWriterConfig);
      Document doc = new Document();
      doc.add(new Field("author","lucene",Field.Store.YES,Field.Index.ANALYZED));
      doc.add(new Field("author","solr",Field.Store.YES,Field.Index.ANALYZED));
      doc.add(new Field("text","helloworld",Field.Store.YES,Field.Index.ANALYZED));
      writer.addDocument(doc);
      writer.commit();
      //对多值域进行查询
      IndexReader reader = IndexReader.open(dir);
      IndexSearcher search = new IndexSearcher(reader);
      Query query = new TermQuery(new Term("author","lucene"));
      TopDocs docs = search.search(query, 1);
      Document doc = search.doc(docs.scoreDocs[0].doc);
      for(IndexableField field : doc.getFields()){
      System.out.println(field.name()+":"+field.stringValue());
      }
      System.out.print(docs.totalHits);
      //运行结果
      author:lucene
      author:solr
      text:helloworld
      2
    • 当对多值域设置boost的时候,那么该field的boost最后怎么算呢?即为每一个值域的boost相乘。比如title这个field,第一次boost是3.0,第二次1,第三次0.5,那么结果就是3*1*0.5.
    • Boost: (3) · (1) · (0.5) = 1.5 
    • lengthNorm, Norm的长度是field中term的个数的平方根的倒数,field的term的个数被定义为field的长度。field长度越大,Norm Field越小,说明term越不重要,反之越重要,这很好理解,在10个词的title中出现北京一次和在有200个词的正文中出现北京2次,哪个field更加匹配,当然是title。

       

    • 最后再说明下,document boost,field boost 以及lengthNorm在存储为索引是以byte形式的,编解码过程中会使得数值损失,该损失对相似值计算的影响微乎其微。
    • queryNorm,  计算每个查询条目的方差和,此值并不影响排序,而仅仅使得不同的query之间的分数可以比较。也就说,对于同一词查询,他对所有的document的影响是一样的,所以不影响查询的结果,它主要是为了区分不同query了。

      queryNorm(q) = 1 / (sumOfSquaredWeights )

      sumOfSquaredWeights = q.getBoost()2 • ∑ ( idf(t) • t.getBoost() )

      coord(q,d),表示文档中符合查询的term的个数,如果在文档中查询的term个数越多,那么这个文档的score就会更高。

      numTermsInDocumentFromQuery / numTermsInQuery 

              比如Query:AccountantAND("SanFrancisco"OR"NewYork"OR"Paris") 

              文档A包含了上面的3个term,那么coord就是3/4,如果包含了1个,则coord就是4/4

    4源码

        上面介绍了相似值计算的公式,那么现在就来查看Solr实现的代码,这部分实现是在DefaultSimilarity类中。 

    @Override
    public float coord(int overlap, int maxOverlap) {
    return overlap / (float)maxOverlap;
    }
     
    @Override
    public float queryNorm(float sumOfSquaredWeights) {
    return (float)(1.0 / Math.sqrt(sumOfSquaredWeights));
    }
       
    @Override
    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)));
    }
       
    @Override
    public float tf(float freq) {
    return (float)Math.sqrt(freq);
    }
     
    @Override
    public float idf(long docFreq, long numDocs) {
    return (float)(Math.log(numDocs/(double)(docFreq+1)) + 1.0);
    }
     

    Solr计算score(q,d)的过程如下:

    1:调用IndexSearcher.createNormalizedWeight()计算queryNorm()

    public Weight createNormalizedWeight(Query query) throws IOException {
    query = rewrite(query);
    Weight weight = query.createWeight(this);
    float v = weight.getValueForNormalization();
    float norm = getSimilarity().queryNorm(v);
    if (Float.isInfinite(norm) || Float.isNaN(norm)) {
    norm = 1.0f;
    }
    weight.normalize(norm, 1.0f);
    return weight;
    }
    1. 具体实现步骤如下:

    • Weight weight = query.createWeight(this);
    • 创建BooleanWeight->new TermWeight()->this.stats = similarity.computeWeight)->this.weight = idf * t.getBoost()
    • public IDFStats(String field, Explanation idf, float queryBoost) {
      // TODO: Validate?
      this.field = field;
      this.idf = idf;
      this.queryBoost = queryBoost;
      this.queryWeight = idf.getValue() * queryBoost; // compute query weight
      }
      计算sumOfSquaredWeights
      s = weights.get(i).getValueForNormalization()计算( idf(t) • t.getBoost() )2 如以下代码所示,queryWeight在上一部中计算出 
      public float getValueForNormalization() {
      // TODO: (sorta LUCENE-1907) make non-static class and expose this squaring via a nice method to subclasses?
      return queryWeight * queryWeight; // sum of squared weights
      }
    • BooleanWeight->getValueForNormalization->sum = (q.getBoost)*∑(this.weight)= (q.getBoost)*∑(idf * t.getBoost())2
    •  
    • public float getValueForNormalization() throws IOException {
      float sum = 0.0f;
      for (int i = 0 ; i < weights.size(); i++) {
      // call sumOfSquaredWeights for all clauses in case of side effects
      float s = weights.get(i).getValueForNormalization(); // sum sub weights
      if (!clauses.get(i).isProhibited()) {
      // only add to sum for non-prohibited clauses
      sum += s;
      }
      }
       
      sum *= getBoost() * getBoost(); // boost each sub-weight
       
      return sum ;
      }
    • 计算完整的querynorm() = 1 / Math.sqrt(sumOfSquaredWeights)); 
    • public float queryNorm(float sumOfSquaredWeights) {
      return (float)(1.0 / Math.sqrt(sumOfSquaredWeights));
      }
    • weight.normalize(norm, 1.0f) 计算norm()
    • topLevelBoost *= getBoost();   
    • 计算value = idf()*queryWeight*queryNorm=idf()2*t.getBoost()*queryNorm(queryWeight在前面已计算出) 

    • public void normalize(float queryNorm, float topLevelBoost) {
      this.queryNorm = queryNorm * topLevelBoost;
      queryWeight *= this.queryNorm; // normalize query weight
      value = queryWeight * idf.getValue(); // idf for document
      }
    •  

      2:调用IndexSearch.weight.bulkScorer()计算coord(q,d),并获取每一个term的docFreq,并将docFreq按td从小到大排序。 

      

    if (optional.size() == 0 && prohibited.size() == 0) {
    float coord = disableCoord ? 1.0f : coord(required.size(), maxCoord);
    return new ConjunctionScorer(this, required.toArray(new Scorer[required.size()]), coord);
    }
    1. 3:score.score()进行评分计算,获取相似值,并放入优先级队列中获取评分最高的doc id。

    • weightValue= value =idf()2*t.getBoost()*queryNorm
    • sore = ∑(tf()*weightValue)*cood 计算出最终的相似值
    • 这里貌似没有用到lengthNorm, 
    • public float score(int doc, float freq) {
      final float raw = tf(freq) * weightValue; // compute tf(f)*weight
      return norms == null ? raw : raw * decodeNormValue(norms.get(doc)); // normalize for field
      }
         
      public float score() throws IOException {
      // TODO: sum into a double and cast to float if we ever send required clauses to BS1
      float sum = 0.0f;
      for (DocsAndFreqs docs : docsAndFreqs) {
      sum += docs.scorer.score();
      }
      return sum * coord;
      }
       
      public void collect(int doc) throws IOException {
      float score = scorer.score();
         
      // This collector cannot handle these scores:
      assert score != Float.NEGATIVE_INFINITY;
      assert !Float.isNaN(score);
         
      totalHits++;
      if (score <= pqTop.score) {
      // Since docs are returned in-order (i.e., increasing doc Id), a document
      // with equal score to pqTop.score cannot compete since HitQueue favors
      // documents with lower doc Ids. Therefore reject those docs too.
      return;
      }
      pqTop.doc = doc + docBase;
      pqTop.score = score;
      pqTop = pq.updateTop();
      }
  • 相关阅读:
    不务正业系列-浅谈《过气堡垒》,一个RTS玩家的视角
    [LeetCode] 54. Spiral Matrix
    [LeetCode] 40. Combination Sum II
    138. Copy List with Random Pointer
    310. Minimum Height Trees
    4. Median of Two Sorted Arrays
    153. Find Minimum in Rotated Sorted Array
    33. Search in Rotated Sorted Array
    35. Search Insert Position
    278. First Bad Version
  • 原文地址:https://www.cnblogs.com/LCharles/p/11413550.html
Copyright © 2011-2022 走看看