Solr: точный запрос фразы с EdgeNGramFilterFactory - PullRequest
7 голосов
/ 30 сентября 2011

В Solr (3.3) возможно ли сделать поле буквенно-буквенным для поиска через EdgeNGramFilterFactory, а также чувствительным к запросам фраз?

Например, я ищу полекоторый, если он содержит «contrat informatique», будет найден, если пользователь введет:

  • contrat
  • informatique
  • contr
  • 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 ]

5 голосов
/ 09 февраля 2012

Точный поиск по фразе не работает из-за параметра slop запроса = 0 по умолчанию.При поиске фразы «Hello World» он ищет термины с последовательными позициями.Мне бы хотелось, чтобы в EdgeNGramFilter был параметр для управления выходным позиционированием, это похоже на старый вопрос .

Установив для параметра qs очень высокое значение (больше максимального расстояния между нграммами), вы можете получить фразы обратно.Это частично решает проблему, позволяющую найти фразы, но не точные перестановки.Таким образом, поиск "contrat informatique" будет соответствовать тексту типа "... контракт отменен. Informatique ..."

enter image description here

Для поддержки точного запроса фразыВ итоге я использую отдельные поля для нграмм .

Необходимые шаги:

Определить отдельные типы полей для индексации обычных значений и граммов:

<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 копировать поля при индексации:

Вы можете определить отдельное отражение 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"/>

Или вы можете поставитьвсе нграммы в одном поле:

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

Обратите внимание, что в этом случае вы не сможете разделить бустеры.

И последнее - указать поля и бустеры нграмм в запросе.,Одним из способов является настройка вашего приложения.Другой способ - указать параметры «добавления» в файле solrconfig.xml

   <lst name="appends">
     <str name="qf">heap_ngrams</str>
   </lst>
2 голосов
/ 07 октября 2011

Поскольку, увы, мне не удавалось использовать право PositionFilter, как предложил Джаендра Патил (PositionFilter делает любой запрос логическим запросом ИЛИ), я использовал другой подход.

Тем не менее с EdgeNGramFilter я добавил тот факт, что каждое ключевое слово, введенное пользователем, является обязательным, и отключил все фразы.

Таким образом, если пользователь запрашивает "cont info", он преобразуется в +cont +info. Это немного более допустимо, чем истинная фраза, но она сумела сделать то, что я хочу (и не возвращает результатов только с одним термином из двух).

Единственный аргумент против этого обходного пути заключается в том, что термины могут быть переставлены в результатах (поэтому также будет найден документ с "informatique contrat"), но это не так уж важно.

1 голос
/ 11 февраля 2013

Я исправил 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;
    }
}
1 голос
/ 30 сентября 2011

Вот то, о чем я думал -
Для того, чтобы нграммы соответствовали фразе, положение токенов, сгенерированных для каждого слова, должно быть одинаковым.
Я проверил фильтр грамм грамм, и он увеличивает токены, и не нашел ни одного параметра, который бы предотвращал его.
Доступен фильтр позиции, который поддерживает положение токенов на том же токене, что и в начале.
Таким образом, если используется следующая конфигурация, все токены находятся в одной позиции и соответствуют фразе запроса (те же позиции токенов совпадают с фразами)
Я проверил это с помощью инструмента анализа, и запросы совпали.

Так что вы можете попробовать подсказку: -

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