7

在 Solr (3.3) 中,是否可以通过 a 逐字母搜索字段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.

4

4 回答 4

6

由于查询 slop 参数默认为 0,因此精确短语搜索不起作用。搜索短语“Hello World”,它会搜索具有顺序位置的术语。我希望 EdgeNGramFilter 有一个参数来控制输出定位,这看起来像是一个老问题

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

在此处输入图像描述

为了支持精确的短语查询,我最终为 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复制字段:

您可以为每个字段定义单独的 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>
于 2012-02-08T20:09:59.280 回答
2

可惜我无法使用PositionFilterJayendra Patil 建议的权限(PositionFilter 使任何查询成为 OR 布尔查询),我使用了不同的方法。

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

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

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

于 2011-10-07T08:29:50.567 回答
1

这就是我的想法 -
对于要与短语匹配的 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>
于 2011-09-30T18:33:16.940 回答
1

我已经对 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;
    }
}
于 2013-02-11T16:42:29.370 回答