Solr:使用EdgeNGramFilterFactory进行精确短语查询

时间:2011-09-30 15:47:27

标签: solr tokenize phrase

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

例如,我正在寻找一个字段,如果包含“contrat informatique”,将在用户输入时找到:

  • contrat
  • INFORMATIQUE
  • 对照
  • Informa的
  • “contrat informatique”
  • “contrat info”

目前,我做了类似的事情:

<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中的模式分析器时,我发现“contrat informatique”生成了以下标记:

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

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

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

4 个答案:

答案 0 :(得分:5)

由于查询slop参数= 0,默认情况下,精确短语搜索不起作用。 在搜索短语“Hello World”时,它会搜索具有连续位置的词语。 我希望EdgeNGramFilter有一个参数来控制输出定位,这看起来像一个旧的question

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

enter image description here

要支持完全词组查询,我最终会使用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

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

<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"/>

或者您可以将所有ngrams放入一个字段:

<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>

答案 1 :(得分:2)

唉,我无法像Jayendra Patil建议的那样使用PositionFilter权限(PositionFilter使任何查询成为一个OR布尔查询),我使用了不同的方法。

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

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

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

答案 2 :(得分:1)

这就是我的想法 -
对于ngram的短语匹配,为每个单词生成的标记的位置应该相同 我检查了边克过滤器,它增加了令牌,并没有找到任何参数来防止它 有一个位置过滤器可用,这使得令牌位置保持与开始时相同的标记 因此,如果使用以下配置,则所有令牌都处于相同位置并且它与短语查询匹配(相同的令牌位置匹配为短语)
我通过anaylsis工具检查了它并匹配了查询。

所以你可能想尝试一下提示: -

<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>

答案 3 :(得分: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;
    }
}