zoukankan      html  css  js  c++  java
  • 转载:solr MoreLikeThis的原理分析

    转载地址:http://blog.sina.com.cn/s/blog_5ddc071f0101muos.html

    在solr中有两种方式实现MoreLikeThis:MoreLikeThisHandler和在SearchHandler中的MoreLikeThisComponent。

    两种方式大同小异:

    一是:将MoreLikeThis作为一个单独的Handler来处理,体现主体地位。

    二是:将MoreLikeThis作为一个组件放到SearchHandler中,为Search加入了MLT的功能,是一种辅助功能。


    这里我们借助方法一,来简单阐述MLT的实现步骤。

    步骤1:

    MLT是根据一篇文档(document)的相关字段进行“相似匹配”,例如:

    http://localhost:8983/solr3.5/core0/mlt?q=id:82790&mlt.fl=ti,ab,mcn&mlt.mindf=1&mlt.mintf=1&fl=id,ti,score

    这里我们提供的检索式为:q=id:82790,因此其只有唯一一个检索结果。

    MLT第一步工作就是根据我们提供的检索式获取文档(document)。

    步骤2:

    MLT可以看成是一种特殊的检索,只是他的检索式是根据我们提供的一篇文档(document)生成的。

    因此关键是怎么生成这个检索式!!!

    MoreLikeThis.java

    public Query like(int docNum) throws IOException {
        if (fieldNames == null) {
          // gather list of valid fields from lucene
          Collection fields = ir
              .getFieldNames(IndexReader.FieldOption.INDEXED);
          fieldNames = fields.toArray(new String[fields.size()]);
        }
        
        return createQuery(retrieveTerms(docNum));
      }

    在创建这个“神奇”的query之前,我们先要获得相关的原始term(retrieveTerms)。

    public PriorityQueue<Object[]> retrieveTerms(int docNum) throws IOException {
        Map<String,Int> termFreqMap = new HashMap<String,Int>();
        for (int i = 0; i < fieldNames.length; i++) {
          String fieldName = fieldNames[i];
          TermFreqVector vector = ir.getTermFreqVector(docNum, fieldName);
          
          // field does not store term vector info
          if (vector == null) {
            Document d = ir.document(docNum);
            String text[] = d.getValues(fieldName);
            if (text != null) {
              for (int j = 0; j < text.length; j++) {
                addTermFrequencies(new StringReader(text[j]), termFreqMap,
                    fieldName);
              }
            }
          } else {
            addTermFrequencies(termFreqMap, vector);
          }
        }<br>  return createQueue(termFreqMap);<br>}

    首先获取每一个字段的TermFreqVector,然后将其添加到TermFrequencies中,该过程是计算TF的过程,结果存放在map<String,Int>中,key为term,value为该term出现的次数(termFrequencies)。

    在该过程中需要降噪,及去掉一些无关紧要的term,其判断方式如下:

    private boolean isNoiseWord(String term) {
        int len = term.length();
        if (minWordLen > 0 && len < minWordLen) {
          return true;
        }
        if (maxWordLen > 0 && len > maxWordLen) {
          return true;
        }
        if (stopWords != null && stopWords.contains(term)) {
          return true;
        }
        return false;
      }

    主要两个依据:

    1.term长度必须在minWordLen和maxWordLen范围内;

    2.term不应出现在stopWords内。

    我们再回到retrieveTerms方法中,他返回的是一个PriorityQueue<Object[]>,因此我们还要将之前创建的map<String,Int>(tf)进行一定的处理(重要)。

    “Find words for a more-like-this query former.”

    “Create a PriorityQueue from a word->tf map.”

    private PriorityQueue<Object[]> createQueue(Map<String,Int> words)
         throws IOException {
       // have collected all words in doc and their freqs
       int numDocs = ir.numDocs();
       FreqQ res = new FreqQ(words.size()); // will order words by score
       
       Iterator<String> it = words.keySet().iterator();
       while (it.hasNext()) { // for every word
         String word = it.next();
         
         int tf = words.get(word).x; // term freq in the source doc
         if (minTermFreq > 0 && tf < minTermFreq) {
           continue; // filter out words that don't occur enough times in the
                     // source
         }
         
         // go through all the fields and find the largest document frequency
         String topField = fieldNames[0];
         int docFreq = 0;
         for (int i = 0; i < fieldNames.length; i++) {
           int freq = ir.docFreq(new Term(fieldNames[i], word));
           topField = (freq > docFreq) ? fieldNames[i] : topField;
           docFreq = (freq > docFreq) ? freq : docFreq;
         }
         
         if (minDocFreq > 0 && docFreq < minDocFreq) {
           continue; // filter out words that don't occur in enough docs
         }
         
         if (docFreq > maxDocFreq) {
           continue; // filter out words that occur in too many docs
         }
         
         if (docFreq == 0) {
           continue; // index update problem?
         }
         
         float idf = similarity.idf(docFreq, numDocs);
         float score = tf * idf;
         
         // only really need 1st 3 entries, other ones are for troubleshooting
         res.insertWithOverflow(new Object[] {word, // the word
             topField, // the top field
             Float.valueOf(score), // overall score
             Float.valueOf(idf), // idf
             Integer.valueOf(docFreq), // freq in all docs
             Integer.valueOf(tf)});
       }
       return res;
     }

    该方法我们遍历所有的term,并取出其tf以及在所有指定字段(例如:mlt.fl=ti,ab,mcn)中最大的df。根据df和当前索引文档数计算idf,然后计算该term的score=tf*idf。

    创建好PriorityQueue后,我们就可以将他转变成之前提到的那个“神奇”的query了。

    “Create the More like query from a PriorityQueue”

    private Query createQuery(PriorityQueue<Object[]> q) {
        BooleanQuery query = new BooleanQuery();
        Object cur;
        int qterms = 0;
        float bestScore = 0;
        
        while (((cur = q.pop()) != null)) {
          Object[] ar = (Object[]) cur;
          TermQuery tq = new TermQuery(new Term((String) ar[1], (String) ar[0]));
          
          if (boost) {
            if (qterms == 0) {
              bestScore = ((Float) ar[2]).floatValue();
            }
            float myScore = ((Float) ar[2]).floatValue();
            
            tq.setBoost(boostFactor * myScore / bestScore);
          }
          
          try {
            query.add(tq, BooleanClause.Occur.SHOULD);
          } catch (BooleanQuery.TooManyClauses ignore) {
            break;
          }
          
          qterms++;
          if (maxQueryTerms > 0 && qterms >= maxQueryTerms) {
            break;
          }
        }
        
        return query;
      }

    构建一个BooleanQuery,按照score从大到小取出一定数量的term(maxQueryTerm)进行组建:

    query.add(tq, BooleanClause.Occur.SHOULD);

    这里简单理解就是——取出文档中(相关字段)最重要(tf*idf)的前N个term,组建一个BooleanQuery(Should关联)。

     

    步骤3:

    用第二步创建的query进行一次检索,取出得分最高的N篇文档即可。

     


     

    原理分析:

    (1)在MLT中主要是tf、idf,根据score(tf*idf)获取对分类最重要的term,并构建目标Query。

    MLT可以理解为:找出给定文档同一类的其他文档。

    在一份给定的文件里,词频(term frequency,TF)指的是某一个给定的词语在该文件中出现的频率。这个数字是对词数(term count)的归一化,以防止它偏向长的文件。(同一个词语在长文件里可能会比短文件有更高的词数,而不管该词语重要与否。)对于在某一特定文件里的词语 ti 来说,它的重要性可表示为:

     mathrm{tf_{i,j}} = frac{n_{i,j}}{sum_k n_{k,j}}

    以上式子中 ni,j 是该词在文件dj中的出现次数,而分母则是在文件dj中所有字词的出现次数之和。

    逆向文件频率(inverse document frequency,IDF)是一个词语普遍重要性的度量。某一特定词语的IDF,可以由总文件数目除以包含该词语之文件的数目,再将得到的商取对数得到:

     mathrm{idf_{i}} = log frac{|D|}{|{j: t_{i} in d_{j}}|}

    其中

    • |D|:语料库中的文件总数
    •  |{ j: t_{i} in d_{j}}| :包含词语ti的文件数目(即 n_{i,j} 
eq 0的文件数目)如果该词语不在语料库中,就会导致被除数为零,因此一般情况下使用1 + |{j <wbr>: t_{i} in d_{j}}|

    然后

     mathrm{tf{}idf_{i,j}} = mathrm{tf_{i,j}} 	imes mathrm{idf_{i}}

    某一特定文件内的高词语频率,以及该词语在整个文件集合中的低文件频率,可以产生出高权重的TF-IDF。因此,TF-IDF倾向于过滤掉常见的词语,保留重要的词语。

     

    (2)根据提供的Query,利用lucene的打分算法,找到相似文档。

     Lucene 将信息检索中的Boolean model (BM)和Vector Space Model (VSM)联合起来,实现了自己的评分机制。

    具体内容参见:

    http://lucene.apache.org/core/old_versioned_docs/versions/2_9_1/api/core/org/apache/lucene/search/Similarity.html

     


     

    那么有哪些环节可以提高相似检索精度呢?

    1.降噪环节需要强化,目前solr中是基于term长度和停用此表联合过滤。

    例如将term的最小长度限定成2,即单个字不能作为计算的term,例如:

    ab:扩印 ab:胶卷 ab:印机 ab:彩色 ab:传动轴 ab:两根 ab:垫板 ab:手轮 ab:齿轮 ab:从动 ab:传动 ab:设置 ab:自动 ab:电动机 mcn:g03b27/46 ab:电动 ab:上片 ab:上手 ab:支撑 ab:精确度 ab:动机 ab:压片 ab:以及 ab:机构 ab:下压

    2.提高分词器的精度,并且对于行业性的业务最好提供行业性的词库,并且进行人工维护。

     

    3.调整、改进相似度算法。

    简单的我们试试将term的数量(构建目标query的term数量)进行控制,设置成10。例如:

    ab:扩印 ab:胶卷 ab:印机 ab:彩色 ab:传动轴 ab:两根 ab:垫板 ab:手轮 ab:齿轮 ab:从动

     

    以上实例只是一个简单说明,更多调整(挑战)还需要在实践中具体分析。

  • 相关阅读:
    [java] 深入理解内部类: inner-classes
    [java] 更好的书写equals方法-汇率换算器的实现(4)
    [java] 注释以及javadoc使用简介-汇率换算器的实现-插曲3
    [java] jsoup使用简介-汇率换算器实现-插曲2
    [java] 汇率换算器实现(3)
    [java] 汇率换算器实现-插曲1-正则表达式(1)
    [java] 汇率换算器实现(2)
    [java] 汇率换算器实现(1)
    [Basic] The most basic things about java
    电路相关知识–读<<继电器是如何成为CPU的>>
  • 原文地址:https://www.cnblogs.com/a198720/p/4016178.html
Copyright © 2011-2022 走看看