使用Lucene的MoreLikeThisQuery实现相关内容推荐

在分析MoreLikeThisQuery之前,首先介绍一下MoreLikeThis。

在实现搜索应用的时候,时常会遇到"更多相似文章","更多相关问题"之类的需求,也即根据当前文档的文本内容,在索引库中查询相类似的文章。

我们可以使用MoreLikeThis实现此功能:

IndexReader reader = IndexReader.open(……);

IndexSearcher searcher = new IndexSearcher(reader);

MoreLikeThis mlt = new MoreLikeThis(reader);

Reader target = ... //此是一个io reader,指向当前文档的文本内容。

Query query = mlt.like( target); //根据当前的文本内容,生成查询对象。

Hits hits = searcher.search(query); //查询得到相似文档的结果。

MoreLikeThis的Query like(Reader r)函数如下:

public Query like(Reader r) throws IOException {

    return createQuery(retrieveTerms(r)); //其首先从当前文档的文本内容中抽取term,然后利用这些term构建一个查询对象。

}

public PriorityQueue <Object[]> retrieveTerms(Reader r) throws IOException {

        Map<String,Int> words = new HashMap<String,Int>();

        //根据不同的域中抽取term,到底根据哪些域抽取,可用函数void setFieldNames(String[] fieldNames)设定。

        for (int i = 0; i < fieldNames.length; i++) {

            String fieldName = fieldNames[i];

            addTermFrequencies(r, words, fieldName);

        }

        //将抽取的term放入优先级队列中

        return createQueue(words);

}

private void addTermFrequencies(Reader r, Map<String,Int> termFreqMap, String fieldName) throws IOException

{

       //首先对当前的文本进行分词,分词器可以由void setAnalyzer(Analyzer analyzer)设定。

       TokenStream ts = analyzer.tokenStream(fieldName, r);

        int tokenCount=0;

        TermAttribute termAtt = ts.addAttribute(TermAttribute.class);

       //遍历分好的每一个词

        while (ts.incrementToken()) {

            String word = termAtt.term();

            tokenCount++;

            //如果分词后的term的数量超过某个设定的值,则停止,可由void setMaxNumTokensParsed(int i)设定。

            if(tokenCount>maxNumTokensParsed)

            {

                break;

            }

            //如果此词小于最小长度,或者大于最大长度,或者属于停词,则属于干扰词。

            //最小长度由void setMinWordLen(int minWordLen)设定。

            //最大长度由void setMaxWordLen(int maxWordLen)设定。

            //停词表由void setStopWords(Set<?> stopWords)设定。

            if(isNoiseWord(word)){

                continue;

            }

            // 统计词频tf

            Int cnt = termFreqMap.get(word);

            if (cnt == null) {

                termFreqMap.put(word, new Int());

            }

            else {

                cnt.x++;

            }

        }

}

 

private PriorityQueue createQueue(Map<String,Int> words) throws IOException {

    //根据统计的term及词频构造优先级队列。

    int numDocs = ir.numDocs();

    FreqQ res = new FreqQ(words.size()); // 优先级队列,将按tf*idf排序

    Iterator<String> it = words.keySet().iterator();

    //遍历每一个词

    while (it.hasNext()) {

        String word = it.next();

        int tf = words.get(word).x;

        //如果词频小于最小词频,则忽略此词,最小词频可由void setMinTermFreq(int minTermFreq)设定。

        if (minTermFreq > 0 && tf < minTermFreq) {

            continue;

        }

        //遍历所有域,得到包含当前词,并且拥有最大的doc 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;

        }

        //如果文档频率小于最小文档频率,则忽略此词。最小文档频率可由void setMinDocFreq(int minDocFreq)设定。

        if (minDocFreq > 0 && docFreq < minDocFreq) {

            continue;

        }

       //如果文档频率大于最大文档频率,则忽略此词。最大文档频率可由void setMaxDocFreq(int maxFreq)设定。

        if (docFreq > maxDocFreq) {

            continue;                

        }

        if (docFreq == 0) {

            continue;

        }

        //计算打分tf*idf

        float idf = similarity.idf(docFreq, numDocs);

        float score = tf * idf;

        //将object的数组放入优先级队列,只有前三项有用,按照第三项score排序。

        res.insertWithOverflow(new Object[]{word,                  // 词

                                topField,               // 域

                                Float.valueOf(score),      // 打分

                                Float.valueOf(idf),         // idf

                                Integer.valueOf(docFreq),   // 文档频率

                                Integer.valueOf(tf) //词频

        });

    }

    return res;

}

 

private Query createQuery(PriorityQueue 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

                bestScore = ((Float) ar[2]).floatValue();

            }

            float myScore = ((Float) ar[2]).floatValue();

            //其他的词的打分除以最高打分,乘以boostFactor,得到相应的词所生成的查询的boost,从而在当前文本文档中打分越高的词在查询语句中也有更高的boost,起重要的作用。

            tq.setBoost(boostFactor * myScore / bestScore);

        }

        try {

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

        }

        catch (BooleanQuery.TooManyClauses ignore) {

            break;

        }

        qterms++;

        //如果超过了设定的最大的查询词的数目,则停止,最大查询词的数目可由void setMaxQueryTerms(int maxQueryTerms)设定。

        if (maxQueryTerms > 0 && qterms >= maxQueryTerms) {

            break;

        }

    }

    return query;

}

MoreLikeThisQuery只是MoreLikeThis的封装,其包含了MoreLikeThis所需要的参数,并在rewrite的时候,由MoreLikeThis.like生成查询对象。

  • String likeText;当前文档的文本
  • String[] moreLikeFields;根据哪个域来抽取查询词
  • Analyzer analyzer;分词器
  • float percentTermsToMatch=0.3f;最后生成的BooleanQuery之间都是SHOULD的关系,其中至少有多少比例必须得到满足
  • int minTermFrequency=1;最少的词频
  • int maxQueryTerms=5;最多的查询词数目
  • Set<?> stopWords=null;停词表
  • int minDocFreq=-1;最小的文档频率

public Query rewrite(IndexReader reader) throws IOException

{

    MoreLikeThis mlt=new MoreLikeThis(reader);

    mlt.setFieldNames(moreLikeFields);

    mlt.setAnalyzer(analyzer);

    mlt.setMinTermFreq(minTermFrequency);

    if(minDocFreq>=0)

    {

        mlt.setMinDocFreq(minDocFreq);

    }       

    mlt.setMaxQueryTerms(maxQueryTerms);

    mlt.setStopWords(stopWords);

    BooleanQuery bq= (BooleanQuery) mlt.like(new ByteArrayInputStream(likeText.getBytes()));       

    BooleanClause[] clauses = bq.getClauses();

    bq.setMinimumNumberShouldMatch((int)(clauses.length*percentTermsToMatch));

    return bq;

}

举例,对于http://topic.csdn.net/u/20100501/09/64e41f24-e69a-40e3-9058-17487e4f311b.html?1469中的帖子

 

使用Lucene的MoreLikeThisQuery实现相关内容推荐_第1张图片

 

我们姑且将相关问题中的帖子以及其他共20篇文档索引。

    File indexDir = new File("TestMoreLikeThisQuery/index");

    IndexReader reader = IndexReader.open(indexDir);

    IndexSearcher searcher = new IndexSearcher(reader);

    //将《IT外企那点儿事》作为likeText,从文件读入。

    StringBuffer contentBuffer = new StringBuffer();

    BufferedReader input = new BufferedReader(new InputStreamReader(new FileInputStream("TestMoreLikeThisQuery/IT外企那点儿事.txt"), "utf-8"));

    String line = null;

    while((line = input.readLine()) != null){

      contentBuffer.append(line);

    }

    String content = contentBuffer.toString();

    //分词用中科院分词

    MoreLikeThisQuery query = new MoreLikeThisQuery(content, new String[]{"contents"}, new MyAnalyzer(new ChineseAnalyzer()));

   //将80%都包括的词作为停词,在实际应用中,可以有其他的停词策略。

    query.setStopWords(getStopWords(reader));

   //至少包含5个的词才认为是重要的

    query.setMinTermFrequency(5);

    //只取其中之一

    query.setMaxQueryTerms(1);

    TopDocs docs = searcher.search(query, 50);

    for (ScoreDoc doc : docs.scoreDocs) {

      Document ldoc = reader.document(doc.doc);

      String title = ldoc.get("title");

      System.out.println(title);

    }

static Set<String> getStopWords(IndexReader reader) throws IOException{

  HashSet<String> stop = new HashSet<String>();

  int numOfDocs = reader.numDocs();

  int stopThreshhold = (int) (numOfDocs*0.7f);

  TermEnum te = reader.terms();

  while(te.next()){

    String text = te.term().text();

    if(te.docFreq() >= stopThreshhold){

      stop.add(text);

    }

  }

  return stop;

}

结果为:

揭开外企的底儿(连载六)——外企招聘也有潜规则.txt

去央企还是外企,帮忙分析下.txt

哪种英语教材比较适合英语基础差的人.txt

有在达内外企软件工程师就业班培训过的吗.txt

两个月的“骑驴找马”,面试无数家公司的深圳体验.txt

一个看了可能改变你一生的小说《做单》,外企销售经理做单技巧大揭密.txt

HR的至高机密:20个公司绝对不会告诉你的潜规则.txt

 

阅读全文……

你可能感兴趣的:(java,Lucene,database)