• [ lucene扩展 ] MoreLikeThis 相似检索


    MoreLikeThis,相似检索。找出某篇文档的相似文档,常见于“类似新闻”、“相关文章”等,这里完全是基于内容的分析。

    1)MoreLikeThis的使用

                    FSDirectory directory = SimpleFSDirectory.open(new File("d:/nrtTest2")); 
    		IndexReader reader = IndexReader.open(directory);
    		IndexSearcher searcher = new IndexSearcher(reader);
    		//
    		MoreLikeThis mlt = new MoreLikeThis(reader);
    		mlt.setFieldNames(new String[] { "ab" }); //用于计算的字段
    		//
    		int docNum = 1;
    //		TermFreqVector vector = reader.getTermFreqVector(docNum, "ab");
    //		System.out.println(vector.toString());
    		Query query = mlt.like(docNum);//试图找到与docnum=1相似的documents
    		System.out.println(reader.document(docNum));
    		System.out.println(query.toString());//查看构造的query,后面的就是常规的lucene的检索过程。
    		TopDocs topDocs = searcher.search(query, 10);
    		ScoreDoc[] scoreDocs = topDocs.scoreDocs;
    		for (ScoreDoc sdoc : scoreDocs) {
    			Document doc = reader.document(sdoc.doc);
    			System.out.println(doc.get("ti"));
    			// System.out.println(doc.get("ti"));
    		}
    

    2)MoreLikeThis的源码解读

    把MLT运行起来很简单,那么我们进一步看看他是怎么实现的。

    关键就是 Query query = mlt.like(docNum); 我们就从他下手。

    2.1)like

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

    filedNames为参与“more like this”运算的字段,在moreLikeThis对象的setFiledNames方法中进行设置。

    2.2)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);//取出term向量
          
          // 如果当前字段没有存储termVector,那么需要重新计算。其实这里就是分词,并计算term词频的过程,注意他默认使用的是StandardAnalyzer分词器!!!
          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 {//如果之前保存了termVector那么就方便多了。
            addTermFrequencies(termFreqMap, vector);
          }
          
        }
    

    2.3)addTermFrequencies

    由于TermVector中的term和field没有关系,不管是标题还是正文,只要term内容一样就将其频率累加。addTermFrequencies就做这个事情!

    把累加的结果存放到termFreqMap中。

    private void addTermFrequencies(Map<String,Int> termFreqMap,
          TermFreqVector vector) {
        String[] terms = vector.getTerms();
        int freqs[] = vector.getTermFrequencies();
        for (int j = 0; j < terms.length; j++) {
          String term = terms[j];
          
          if (isNoiseWord(term)) {
            continue;
          }
          // increment frequency
          Int cnt = termFreqMap.get(term);
          if (cnt == null) {
            cnt = new Int();
            termFreqMap.put(term, cnt);
            cnt.x = freqs[j];
          } else {
            cnt.x += freqs[j];
          }
        }
      }
    

    截止,我们将指定的文档(被匹配文档)按照指定的运算字段,将其term和对应的frequency存放到了map中。在这个过程中,我们看到了一个听起来比较牛逼的操作——去噪

    那么这里怎么判断一个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;
      }
    

    他判断的标准十分简单,第一:是否是规定的停用词;第二:term长度是否过长或过短,这个范围由minWordLen和maxWordLen控制。

    2.4)createQueue

    这里的queue应该是一个优先级队列,上一步我们获得了所有<term, frequency>,虽然做了去噪,但是term项目还是太多了,还需要找出相对重要的前N个Term。

    private PriorityQueue<Object[]> createQueue(Map<String,Int> words)
          throws IOException {
        // 获取当前index的文档总数。
        int numDocs = ir.numDocs();
        FreqQ res = new FreqQ(words.size()); // 按照term的得分进行存放
        
        Iterator<String> it = words.keySet().iterator();
        while (it.hasNext()) { // 对所有term进行遍历
          String word = it.next();
          
          int tf = words.get(word).x; // 对应term的tf
          if (minTermFreq > 0 && tf < minTermFreq) {
            continue; // 和去噪类似,tf太小的term直接过掉。
          }
          
          // 对于同一个term,找到df最大的那个字段,存放到topField。
          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;
          }
          //df太小的term也要直接过掉
          if (minDocFreq > 0 && docFreq < minDocFreq) {
            continue; // filter out words that don't occur in enough docs
          }
          //df太大的term也要直接过掉
          if (docFreq > maxDocFreq) {
            continue; // filter out words that occur in too many docs
          }
          //df==0的term也要直接过掉,怎么会有df的term???这里说是index文件的问题
          if (docFreq == 0) {
            continue; // index update problem?
          }
          //经典的idf、tf又来了
          float idf = similarity.idf(docFreq, numDocs);
          float score = tf * idf;
          
          //将结果存放到优先队列中。
          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、idf进行计算。

    这里他的意思就是:

    1.将指定参与运算字段的term的frequency进行累加;(这里对ti、ab字段的tf进行累加)

    2.通过df的比较,选取df大的字段作为最终“运算”的字段,但tf为所有字段的累加值。这和我们看普通检索时的打分算法不太一样,普通检索中tf为当前字段的词频。

    至于为什么这么做,还得验证!!!

    2.5)createQuery

    到此我们将term的打分排序拿到了,分值越大的term更能表述整篇document的主要内容!(有没有想到这就类似主题词!!!

    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]));
          //这里还可以对termquery进行boost的设置。默认为false
          if (boost) {
            if (qterms == 0) {
              bestScore = ((Float) ar[2]).floatValue();
            }
            float myScore = ((Float) ar[2]).floatValue();
            
            tq.setBoost(boostFactor * myScore / bestScore);
          }
          //构建boolean query,should关联。
          try {
            query.add(tq, BooleanClause.Occur.SHOULD);
          } catch (BooleanQuery.TooManyClauses ignore) {
            break;
          }
          
          qterms++;
          if (maxQueryTerms > 0 && qterms >= maxQueryTerms) {//限定参与运算的term的数量
            break;
          }
        }
        
        return query;
      }
    

    这样就根据一篇document和指定字段得到了一个query。这个query作为代表着document的灵魂,将寻找和他类似的documents。

    3)实例

    被匹配的document为:

    Document<stored,indexed,tokenized<an:CN00103249.6>

    stored,indexed<ad:20000320> stored,indexed,tokenized,termVector<ab:    本发明涉及一种高级毛料服装洗涤剂。使用该洗涤剂,洗衣服可不用到干洗店,自己在家水洗就行,且洗涤后毛料服装笔挺膨松,抗静电,不缩水,洗涤中不刺激皮肤。此剂主要由去污剂、抗静电剂、防缩剂、表面活性剂与其它助剂配制而成。去污率≥90%,缩水率≤1‰。>

    stored,indexed,tokenized,termVector<ti:高级毛料服装洗涤剂>>

    计算出来的query为:

    ab:毛料 ab:服装 ab:洗涤剂 ab:抗静电 ab:高级 ab:剂 ab:洗涤

    计算结果为:

    高级毛料服装洗涤剂
    抗静电防尘污灭菌广谱洗涤剂及制备方法
    一种抗紫外线的织物涂层材料
    服装绿色干洗及服装翻新技术
    洗碟用柔性含蛋白酶的液体或凝胶洗涤组合物
    复合洗霉制剂
    洗衣机
    一种抗静电合成纤维
    实验室专用洗涤剂
    液晶相结构型液体洗涤剂及制造工艺

    貌似结果不是很相似,那么我们可以试着只用ti做运算,这样从标题看起来比较相似。

    还可以对MLT的各项参数进行设置,这里就不在实验了!

  • 相关阅读:
    Linux # $
    python英文学习字典
    python中read(),readline(),和readlines()
    if _name_ =="_main_"
    win10菜单打不开,任务栏右击没有反应
    python中csv转json文件出现:File was loaded in the wrong encoding: 'UTF-8'
    Python字典
    python 匿名函数实现求素数平方和
    Python快速排序的实现
    P1880 [NOI1995]石子合并
  • 原文地址:https://www.cnblogs.com/huangfox/p/2578179.html
Copyright © 2020-2023  润新知