MoreLikeThis 相似检索

来源:转载


出处:http://www.cnblogs.com/huangfox/archive/2012/07/05/2578179.html

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

 

1)MoreLikeThis的使用

                FSDirectory directory = SimpleFSDirectory.open(newFile("d:/nrtTest2"));        IndexReader reader = IndexReader.open(directory);        IndexSearcher searcher =newIndexSearcher(reader);        //        MoreLikeThis mlt =newMoreLikeThis(reader);        mlt.setFieldNames(newString[] { "ab"}); //用于计算的字段        //        intdocNum = 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

publicQuery like(intdocNum) throwsIOException {    if(fieldNames == null) {      // gather list of valid fields from lucene      Collection<String> fields = ReaderUtil.getIndexedFields(ir);      fieldNames = fields.toArray(newString[fields.size()]);    }         returncreateQuery(retrieveTerms(docNum));  }

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

 

2.2)retrieveTerms

publicPriorityQueue<Object[]> retrieveTerms(intdocNum) throwsIOException {    Map<String,Int> termFreqMap =newHashMap<String,Int>();    for(inti = 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(intj = 0; j < text.length; j++) {            addTermFrequencies(newStringReader(text[j]), termFreqMap,                fieldName);          }        }      }else{//如果之前保存了termVector那么就方便多了。        addTermFrequencies(termFreqMap, vector);      }           }

 

2.3)addTermFrequencies

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

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

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

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

那么这里怎么判断一个Term是不是噪音呢?

privatebooleanisNoiseWord(String term) {    intlen = term.length();    if(minWordLen > 0&& len < minWordLen) {      returntrue;    }    if(maxWordLen > 0&& len > maxWordLen) {      returntrue;    }    if(stopWords != null&& stopWords.contains(term)) {      returntrue;    }    returnfalse;  }

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

 

2.4)createQueue

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

privatePriorityQueue<Object[]> createQueue(Map<String,Int> words)      throwsIOException {    // 获取当前index的文档总数。    intnumDocs = ir.numDocs();    FreqQ res =newFreqQ(words.size());// 按照term的得分进行存放         Iterator<String> it = words.keySet().iterator();    while(it.hasNext()) { // 对所有term进行遍历      String word = it.next();             inttf = words.get(word).x; // 对应term的tf      if(minTermFreq > 0&& tf < minTermFreq) {        continue;// 和去噪类似,tf太小的term直接过掉。      }             // 对于同一个term,找到df最大的那个字段,存放到topField。      String topField = fieldNames[0];      intdocFreq = 0;      for(inti = 0; i < fieldNames.length; i++) {        intfreq = ir.docFreq(newTerm(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又来了      floatidf = similarity.idf(docFreq, numDocs);      floatscore = tf * idf;             //将结果存放到优先队列中。      res.insertWithOverflow(newObject[] {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)});    }    returnres;  }

在这里,我们对每个term进行了打分排序,主要还是通过tf、idf进行计算。

这里他的意思就是:

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

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

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

 

2.5)createQuery

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

privateQuery createQuery(PriorityQueue<Object[]> q) {    BooleanQuery query =newBooleanQuery();    Object cur;    intqterms = 0;    floatbestScore = 0;         while(((cur = q.pop()) != null)) {      Object[] ar = (Object[]) cur;      TermQuery tq =newTermQuery(newTerm((String) ar[1], (String) ar[0]));      //这里还可以对termquery进行boost的设置。默认为false      if(boost) {        if(qterms == 0) {          bestScore = ((Float) ar[2]).floatValue();        }        floatmyScore = ((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;      }    }         returnquery;  }

这样就根据一篇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的各项参数进行设置,这里就不在实验了!




分享给朋友:
您可能感兴趣的文章:
随机阅读: