Lucene - придать больше веса, тем ближе термин к началу названия

Я понимаю, как форсировать поля либо по времени индекса, либо по времени запроса. Однако, как я мог бы увеличить баллы соответствия термина ближе к началу названия?

Пример:

Query = "lucene"

Doc1 title = "Lucene: Homepage"
Doc2 title = "I have a question about lucene?"

Я бы хотел, чтобы первый документ забил больше, так как "lucene" ближе к началу (игнорируя термин freq на данный момент).

Я вижу, как использовать SpanQuery для определения близости между терминами, но я не уверен, как использовать информацию о позиции в поле.

Я использую Lucene 4.1 в Java.

Ответ 1

Я бы использовал SpanFirstQuery, который соответствует терминам в начале поля. Как и все запросы диапазона, он полагается на позиции, которые по умолчанию включаются при индексировании в lucene.

Позвольте проверить его самостоятельно: вам просто нужно предоставить SpanTermQuery и максимальную позицию, в которой этот термин можно найти (один в моем пример).

SpanTermQuery spanTermQuery = new SpanTermQuery(new Term("title", "lucene"));
SpanFirstQuery spanFirstQuery = new SpanFirstQuery(spanTermQuery, 1);

Учитывая ваши два документа, этот запрос найдет только первый с заголовком "Lucene: Homepage", если вы проанализировали его с помощью StandardAnalyzer.

Теперь мы можем каким-то образом объединить вышеуказанный SpanFirstQuery с обычным текстовым запросом и иметь первое, что влияет только на оценку. Вы можете легко сделать это, используя BooleanQuery и поместив запрос span как предложение should, как это:

Term term = new Term("title", "lucene");
TermQuery termQuery = new TermQuery(term);
SpanFirstQuery spanFirstQuery = new SpanFirstQuery(new SpanTermQuery(term), 1);
BooleanQuery booleanQuery = new BooleanQuery();
booleanQuery.add(new BooleanClause(termQuery, BooleanClause.Occur.MUST));
booleanQuery.add(new BooleanClause(spanFirstQuery, BooleanClause.Occur.SHOULD));

Возможно, существуют разные способы достижения того же, возможно, с помощью CustomScoreQuery или настраиваемого кода для реализации оценки, но это кажется мне самым легким.

Код, который я использовал для его проверки, выводит следующий результат (включая оценку), выполняющий только TermQuery, затем только SpanFirstQuery и, наконец, комбинированный BooleanQuery:

------ TermQuery --------
Total hits: 2
title: I have a question about lucene - score: 0.26010898
title: Lucene: I have a really hard question about it - score: 0.22295055
------ SpanFirstQuery --------
Total hits: 1
title: Lucene: I have a really hard question about it - score: 0.15764984
------ BooleanQuery: TermQuery (MUST) + SpanFirstQuery (SHOULD) --------
Total hits: 2
title: Lucene: I have a really hard question about it - score: 0.26912516
title: I have a question about lucene - score: 0.09196242

Вот полный код:

public static void main(String[] args) throws Exception {

        Directory directory = FSDirectory.open(new File("data"));

        index(directory);

        IndexReader indexReader = DirectoryReader.open(directory);
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);

        Term term = new Term("title", "lucene");

        System.out.println("------ TermQuery --------");
        TermQuery termQuery = new TermQuery(term);
        search(indexSearcher, termQuery);

        System.out.println("------ SpanFirstQuery --------");
        SpanFirstQuery spanFirstQuery = new SpanFirstQuery(new SpanTermQuery(term), 1);
        search(indexSearcher, spanFirstQuery);

        System.out.println("------ BooleanQuery: TermQuery (MUST) + SpanFirstQuery (SHOULD) --------");
        BooleanQuery booleanQuery = new BooleanQuery();
        booleanQuery.add(new BooleanClause(termQuery, BooleanClause.Occur.MUST));
        booleanQuery.add(new BooleanClause(spanFirstQuery, BooleanClause.Occur.SHOULD));
        search(indexSearcher, booleanQuery);
    }

    private static void index(Directory directory) throws Exception {
        IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_41, new StandardAnalyzer(Version.LUCENE_41));

        IndexWriter writer = new IndexWriter(directory, config);

        FieldType titleFieldType = new FieldType();
        titleFieldType.setIndexOptions(FieldInfo.IndexOptions.DOCS_AND_FREQS_AND_POSITIONS);
        titleFieldType.setIndexed(true);
        titleFieldType.setStored(true);

        Document document = new Document();
        document.add(new Field("title","I have a question about lucene", titleFieldType));
        writer.addDocument(document);

        document = new Document();
        document.add(new Field("title","Lucene: I have a really hard question about it", titleFieldType));
        writer.addDocument(document);

        writer.close();
    }

    private static void search(IndexSearcher indexSearcher, Query query) throws Exception {
        TopDocs topDocs = indexSearcher.search(query, 10);

        System.out.println("Total hits: " + topDocs.totalHits);

        for (ScoreDoc hit : topDocs.scoreDocs) {
            Document result = indexSearcher.doc(hit.doc);
            for (IndexableField field : result) {
                System.out.println(field.name() + ": " + field.stringValue() +  " - score: " + hit.score);
            }
        }
    }

Ответ 2

Из книги "Lucene In Action 2"

"Lucene предоставляет встроенный запрос PayloadTermQuery в пакете org.apache.lucene.search.payloads. Этот запрос просто например SpanTermQuery, так как он соответствует всем документам, содержащим указанный термин и отслеживает фактические события (пробелы) совпадений.

Но затем он идет дальше, позволяя вам вносить скоринговый коэффициент на основе полезных нагрузок при каждом появлении. Для этого вам нужно создать свой собственный класс сходства который определяет метод scorePayload, как этот "

public class BoostingSimilarity extends DefaultSimilarity {
public float scorePayload(int docID, String fieldName,
int start, int end, byte[] payload,
int offset, int length) {
....
}

"start" в приведенном выше коде является ничем иным, как начальной позицией полезной нагрузки. Полезная нагрузка связана с термином. Таким образом, начальная позиция также относится к термину (по крайней мере, того, что я считаю.)

Используя приведенный выше код, но, не обращая внимания на полезную нагрузку, вы получите доступ к позиции "start" в месте скоринга, а затем можете увеличить оценку на основе этого начального значения.

Например: новая оценка = исходная оценка * (1.0f/начальная позиция)

Я надеюсь, что вышеупомянутое работает, пожалуйста, напишите здесь, если вы найдете любое другое эффективное решение.