转载:solr MoreLikeThis的原理分析

2023-12-14 21:30

本文主要是介绍转载: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} \neq 0的文件数目)如果该词语不在语料库中,就会导致被除数为零,因此一般情况下使用1 + |\{j <wbr>: t_{i} \in d_{j}\}|

然后

\mathrm{tf{}idf_{i,j}} = \mathrm{tf_{i,j}} \times \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:从动

 

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

转载于:https://www.cnblogs.com/a198720/p/4016178.html

这篇关于转载:solr MoreLikeThis的原理分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/493927

相关文章

Springboot中分析SQL性能的两种方式详解

《Springboot中分析SQL性能的两种方式详解》文章介绍了SQL性能分析的两种方式:MyBatis-Plus性能分析插件和p6spy框架,MyBatis-Plus插件配置简单,适用于开发和测试环... 目录SQL性能分析的两种方式:功能介绍实现方式:实现步骤:SQL性能分析的两种方式:功能介绍记录

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动

MySQL中的MVCC底层原理解读

《MySQL中的MVCC底层原理解读》本文详细介绍了MySQL中的多版本并发控制(MVCC)机制,包括版本链、ReadView以及在不同事务隔离级别下MVCC的工作原理,通过一个具体的示例演示了在可重... 目录简介ReadView版本链演示过程总结简介MVCC(Multi-Version Concurr

C#使用DeepSeek API实现自然语言处理,文本分类和情感分析

《C#使用DeepSeekAPI实现自然语言处理,文本分类和情感分析》在C#中使用DeepSeekAPI可以实现多种功能,例如自然语言处理、文本分类、情感分析等,本文主要为大家介绍了具体实现步骤,... 目录准备工作文本生成文本分类问答系统代码生成翻译功能文本摘要文本校对图像描述生成总结在C#中使用Deep

Redis主从/哨兵机制原理分析

《Redis主从/哨兵机制原理分析》本文介绍了Redis的主从复制和哨兵机制,主从复制实现了数据的热备份和负载均衡,而哨兵机制可以监控Redis集群,实现自动故障转移,哨兵机制通过监控、下线、选举和故... 目录一、主从复制1.1 什么是主从复制1.2 主从复制的作用1.3 主从复制原理1.3.1 全量复制

Redis主从复制的原理分析

《Redis主从复制的原理分析》Redis主从复制通过将数据镜像到多个从节点,实现高可用性和扩展性,主从复制包括初次全量同步和增量同步两个阶段,为优化复制性能,可以采用AOF持久化、调整复制超时时间、... 目录Redis主从复制的原理主从复制概述配置主从复制数据同步过程复制一致性与延迟故障转移机制监控与维

SpringCloud配置动态更新原理解析

《SpringCloud配置动态更新原理解析》在微服务架构的浩瀚星海中,服务配置的动态更新如同魔法一般,能够让应用在不重启的情况下,实时响应配置的变更,SpringCloud作为微服务架构中的佼佼者,... 目录一、SpringBoot、Cloud配置的读取二、SpringCloud配置动态刷新三、更新@R

Redis连接失败:客户端IP不在白名单中的问题分析与解决方案

《Redis连接失败:客户端IP不在白名单中的问题分析与解决方案》在现代分布式系统中,Redis作为一种高性能的内存数据库,被广泛应用于缓存、消息队列、会话存储等场景,然而,在实际使用过程中,我们可能... 目录一、问题背景二、错误分析1. 错误信息解读2. 根本原因三、解决方案1. 将客户端IP添加到Re

Redis主从复制实现原理分析

《Redis主从复制实现原理分析》Redis主从复制通过Sync和CommandPropagate阶段实现数据同步,2.8版本后引入Psync指令,根据复制偏移量进行全量或部分同步,优化了数据传输效率... 目录Redis主DodMIK从复制实现原理实现原理Psync: 2.8版本后总结Redis主从复制实

锐捷和腾达哪个好? 两个品牌路由器对比分析

《锐捷和腾达哪个好?两个品牌路由器对比分析》在选择路由器时,Tenda和锐捷都是备受关注的品牌,各自有独特的产品特点和市场定位,选择哪个品牌的路由器更合适,实际上取决于你的具体需求和使用场景,我们从... 在选购路由器时,锐捷和腾达都是市场上备受关注的品牌,但它们的定位和特点却有所不同。锐捷更偏向企业级和专