【问题标题】:Solr: exact phrase query with a EdgeNGramFilterFactorySolr:使用 EdgeNGramFilterFactory 进行精确短语查询
【发布时间】:2011-09-30 15:47:27
【问题描述】:

在 Solr (3.3) 中,是否可以通过 EdgeNGramFilterFactory 逐字母搜索字段并且对短语查询敏感?

例如,我正在寻找一个字段,如果包含“对比度信息”,如果用户键入:

  • 对比
  • 信息化
  • 控制
  • 信息
  • “对比信息”
  • “合同信息”

目前,我做了这样的事情:

<fieldtype name="terms" class="solr.TextField">
    <analyzer type="index">
        <charFilter class="solr.MappingCharFilterFactory" mapping="mapping-ISOLatin1Accent.txt"/>
        <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0" splitOnCaseChange="1"/>
        <tokenizer class="solr.LowerCaseTokenizerFactory"/>
        <filter class="solr.EdgeNGramFilterFactory" minGramSize="2" maxGramSize="15" side="front"/>
    </analyzer>
    <analyzer type="query">
        <charFilter class="solr.MappingCharFilterFactory" mapping="mapping-ISOLatin1Accent.txt"/>
        <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0" splitOnCaseChange="1"/>
        <tokenizer class="solr.LowerCaseTokenizerFactory"/>
    </analyzer>
</fieldtype>

...但它在短语查询上失败了。

当我在 solr admin 中查看模式分析器时,我发现“对比度信息”生成了以下标记:

[...] contr contra contrat in inf info infor inform [...]

所以查询可以使用“contrat in”(连续标记),但不能使用“contrat inf”(因为这两个标记是分开的)。

我很确定任何类型的词干提取都可以处理短语查询,但我找不到在 EdgeNGramFilterFactory 之前使用的正确过滤器标记器。

【问题讨论】:

    标签: solr tokenize phrase


    【解决方案1】:

    由于查询 slop 参数默认为 0,因此无法进行精确短语搜索。 搜索短语“Hello World”,它会搜索具有顺序位置的术语。 我希望 EdgeNGramFilter 有一个参数来控制输出定位,这看起来像一个旧的question

    通过将 qs 参数设置为某个非常高的值(超过 ngram 之间的最大距离),您可以获得短语。这部分解决了允许短语但不精确的排列的问题。 因此,搜索“contrat informatique”将匹配类似“...contract被放弃。Informatique...”的文本

    为了支持精确短语查询,我最终使用separate fields for ngrams

    所需步骤:

    定义单独的字段类型来索引常规值和克:

    <fieldType name="text" class="solr.TextField" omitNorms="false">
      <analyzer>
        <tokenizer class="solr.StandardTokenizerFactory"/>
        <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
    </fieldType>
    
    <fieldType name="ngrams" class="solr.TextField" omitNorms="false">
      <analyzer type="index">
        <tokenizer class="solr.StandardTokenizerFactory"/>
        <filter class="solr.LowerCaseFilterFactory"/>
        <filter class="solr.EdgeNGramFilterFactory" minGramSize="2" maxGramSize="15" side="front"/>
      </analyzer>
      <analyzer type="query">
        <tokenizer class="solr.StandardTokenizerFactory"/>
        <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
    </fieldType>
    

    索引时告诉 solr 给copy fields

    您可以为每个字段定义单独的 ngram 反射:

    <field name="contact_ngrams" type="ngrams" indexed="true" stored="false"/>
    <field name="product_ngrams" type="ngrams" indexed="true" stored="false"/>
    <copyField source="contact_text" dest="contact_ngrams"/>
    <copyField source="product_text" dest="product_ngrams"/>
    

    或者你可以把所有的 ngram 放到一个字段中:

    <field name="heap_ngrams" type="ngrams" indexed="true" stored="false"/>
    <copyField source="*_text" dest="heap_ngrams"/>
    

    请注意,在这种情况下,您将无法分离助推器。

    最后一件事是在查询中指定 ngrams 字段和助推器。 一种方法是配置您的应用程序。 另一种方法是在 solrconfig.xml 中指定“附加”参数

       <lst name="appends">
         <str name="qf">heap_ngrams</str>
       </lst>
    

    【讨论】:

      【解决方案2】:

      可惜我无法像 Jayendra Patil 建议的那样正确使用 PositionFilter(PositionFilter 使任何查询成为 OR 布尔查询),我使用了不同的方法。

      仍然使用EdgeNGramFilter,我添加了用户输入的每个关键字都是强制性的,并且禁用了所有短语。

      所以如果用户请求"cont info",它会转换为+cont +info。一个真正的短语会更宽松一点,但它设法做我想要的(并且不会返回只有一个术语的结果)。

      反对这种解决方法的唯一缺点是可以在结果中排​​列术语(因此也会找到带有“informatique contrat”的文档),但这并不是什么大问题。

      【讨论】:

      • 嗨,泽维尔。您能否解释一下您是如何将“cont info”转换为 +cont+info 的?是否有任何开箱即用的 util 类?或者这只是识别双引号并手动转换?我正在尝试解决这个问题:stackoverflow.com/questions/37033381/…
      • 是手动操作,找双引号加加号。我没有找到任何可以为我自动化的东西:-/
      • 感谢 xavier 的回复,对我来说,在爬了这么多内容后也找不到开箱即用的解决方案。我以为我正在通过手动执行此操作来重新发明轮子。但我想手动操作是唯一可用的选项:|
      【解决方案3】:

      这就是我的想法-
      对于要进行短语匹配的 ngram,为每个单词生成的标记的位置应该相同。
      我检查了边缘克过滤器,它增加了标记,但没有找到任何参数来阻止它。
      有一个可用的位置过滤器,这可以将标记的位置保持在与开头相同的标记。
      因此,如果使用以下配置,所有标记都在同一位置,并且匹配短语查询(相同标记位置匹配为短语)
      我通过分析工具检查了它并且查询匹配。

      所以你可能想试试这个提示:-

      <analyzer type="index">
          <tokenizer class="solr.WhitespaceTokenizerFactory" />
          <charFilter class="solr.MappingCharFilterFactory" 
                  mapping="mapping-ISOLatin1Accent.txt" />
          <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" 
                  generateNumberParts="1" catenateWords="1" catenateNumbers="1" 
                  catenateAll="0" splitOnCaseChange="1"/>
          <filter class="solr.LowerCaseFilterFactory" />
          <filter class="solr.EdgeNGramFilterFactory" minGramSize="2" 
                  maxGramSize="15" side="front"/>
          <filter class="solr.PositionFilterFactory" />
      </analyzer>
      

      【讨论】:

      • 这个想法很简洁,但似乎无论如何都行不通:-/即使我通过管理分析工具获得了匹配项,真正的查询也没有返回任何内容(可能是因为在分析工具中,方式它突出显示标记不会打扰短语)。此外,PositionFilter 使查询 boolean 就像 wiki 上所说的那样,因此“contrat informatique”甚至“+contrat +informatique”返回带有“contrat”但也没有“informatique”作为默认值的文档运算符是 OR。我认为,一种可能的替代方法是将查询转换为 +contrat +informatique。
      【解决方案4】:

      我已对 EdgeNGramFilter 进行了修复,因此令牌内的位置不再增加:

          public class CustomEdgeNGramTokenFilterFactory extends TokenFilterFactory {
          private int maxGramSize = 0;
      
          private int minGramSize = 0;
      
          @Override
          public void init(Map<String, String> args) {
              super.init(args);
              String maxArg = args.get("maxGramSize");
              maxGramSize = (maxArg != null ? Integer.parseInt(maxArg)
                      : EdgeNGramTokenFilter.DEFAULT_MAX_GRAM_SIZE);
      
              String minArg = args.get("minGramSize");
              minGramSize = (minArg != null ? Integer.parseInt(minArg)
                      : EdgeNGramTokenFilter.DEFAULT_MIN_GRAM_SIZE);
      
          }
      
          @Override
          public CustomEdgeNGramTokenFilter create(TokenStream input) {
              return new CustomEdgeNGramTokenFilter(input, minGramSize, maxGramSize);
          }
      }
      
      public class CustomEdgeNGramTokenFilter extends TokenFilter {
          private final int minGram;
          private final int maxGram;
          private char[] curTermBuffer;
          private int curTermLength;
          private int curGramSize;
      
          private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
          private final OffsetAttribute offsetAtt = addAttribute(OffsetAttribute.class);
          private final PositionIncrementAttribute positionIncrementAttribute = addAttribute(PositionIncrementAttribute.class);
      
          /**
           * Creates EdgeNGramTokenFilter that can generate n-grams in the sizes of the given range
           *
           * @param input   {@link org.apache.lucene.analysis.TokenStream} holding the input to be tokenized
           * @param minGram the smallest n-gram to generate
           * @param maxGram the largest n-gram to generate
           */
          public CustomEdgeNGramTokenFilter(TokenStream input, int minGram, int maxGram) {
              super(input);
      
              if (minGram < 1) {
                  throw new IllegalArgumentException("minGram must be greater than zero");
              }
      
              if (minGram > maxGram) {
                  throw new IllegalArgumentException("minGram must not be greater than maxGram");
              }
      
              this.minGram = minGram;
              this.maxGram = maxGram;
          }
      
      @Override
      public final boolean incrementToken() throws IOException {
          while (true) {
              int positionIncrement = 0;
              if (curTermBuffer == null) {
                  if (!input.incrementToken()) {
                      return false;
                  } else {
                      positionIncrement = positionIncrementAttribute.getPositionIncrement();
                      curTermBuffer = termAtt.buffer().clone();
                      curTermLength = termAtt.length();
                      curGramSize = minGram;
                  }
              }
              if (curGramSize <= maxGram) {
                  if (!(curGramSize > curTermLength         // if the remaining input is too short, we can't generate any n-grams
                          || curGramSize > maxGram)) {       // if we have hit the end of our n-gram size range, quit
                      // grab gramSize chars from front
                      int start = 0;
                      int end = start + curGramSize;
                      offsetAtt.setOffset(start, end);
                      positionIncrementAttribute.setPositionIncrement(positionIncrement);
                      termAtt.copyBuffer(curTermBuffer, start, curGramSize);
                      curGramSize++;
      
                      return true;
                  }
              }
              curTermBuffer = null;
          }
      }
      
          @Override
          public void reset() throws IOException {
              super.reset();
              curTermBuffer = null;
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多