Lucene рассчитать среднюю частоту терминов

В настоящее время я реализую модификацию стандарта Lucene Сходство BM25, основанное на следующем статье. Реализация фактической формулы проста, но я борюсь с вычислением необходимой статистики.

Мне нужны следующие две статистики:

  • Средняя частота терминов в документе: length of document / # unique terms of the document (т. е. показатель повторяемости документа — для документа без повторений это будет 1, если каждый термин встречается дважды, это будет 2 и т. д. )
  • Средняя частота терминов. Это среднее арифметическое приведенного выше показателя по всем документам коллекции. Это можно рассматривать как среднюю повторяемость всего корпуса.

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

Однако это не помогает найти среднюю среднюю частоту терминов. Очевидно, что это значение для всей коллекции, и поэтому его следует вычислять в length of document / # unique terms of the document0/core/org/apache/lucene/search/similarities/Similarity.html#computeWeight. -float-org.apache.lucene.search.CollectionStatistics-org.apache.lucene.search.TermStatistics...-" rel="nofollow noreferrer">Similarity.computeWeight насколько я понимаю, но не не вижу, как это можно сделать, учитывая аргументы функции.

Какое место было бы идеальным для расчета этой статистики?

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


person scriptator    schedule 09.12.2017    source источник


Ответы (2)


arrow_upward
0
arrow_downward

Similarity.computeWeight имеет параметр CollectionStatistics, который содержит maxDoc (возвращает общее количество документов, независимо от того, все ли они содержат значения для этого поля), и TermStatistics, который содержит term и totalTermFreq (возвращает общее количество вхождений этого термина) и по разделив его, вы можете получить среднюю частоту термина

person Mysterion    schedule 10.12.2017
comment
почему общая частота терминов, деленная на общее количество документов, не дает вам среднего значения? - person Mysterion; 10.12.2017
comment
Я не получил вашу формулу, не могли бы вы объяснить ее в исходном посте? - person Mysterion; 10.12.2017
comment
Я не думаю, что это соответствует тому, что мне нужно. Мне нужно 1/|D| * sum for d ∈ D (L_d / |T_d| ), т.е. сколько в среднем повторяющихся слов в документе. Вы предложили что-то другое - это можно также назвать средней частотой терминов, но не в смысле статьи, о которой я говорю. (Извините за этот беспорядок - к сожалению, мне пока не разрешено публиковать формулы в виде изображений, иначе формулы выглядели бы лучше) - person scriptator; 10.12.2017
comment
общая частота терминов, деленная на общее количество документов, дает мне среднее значение, но не то, которое я ищу. Я попытался объяснить это лучше сейчас в комментарии выше и в исходном вопросе. - person scriptator; 10.12.2017

arrow_upward
0
arrow_downward

Вам нужно будет рассчитать свою собственную «норму», чтобы придерживаться индекса Lucene. По сути, вы можете сохранить дополнительные функции для использования в вашей оценке с помощью NumericDocValuesField.

Это означает, что во время индексации вы сами захотите токенизировать свой текст. У меня есть пример кода (на Kotlin, но с удовольствием отвечу на дополнительные вопросы, если вы предпочитаете Java)

Токенизация на основе любого анализатора Lucene: (выраженная как функция расширения Kotlin, просто представьте, что this является первым аргументом этого статического метода в качестве анализатора, если вам удобнее работать с Java.

fun Analyzer.tokenize(field: String, input: String): List<String> {
    val tokens = arrayListOf<String>()
    this.tokenStream(field, input).use { body ->
        val charTermAttr = body.addAttribute(CharTermAttribute::class.java)

        // iterate over tokenized field:
        body.reset()
        while(body.incrementToken()) {
            tokens.add(charTermAttr.toString())
        }
    }
    return tokens
}

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

    fun setTextField(field: String, text: String, terms: List<String>): List<IndexableField> {
        val length = terms.size
        val uniqLength = terms.toSet().size

        val keep = ArrayList<IndexableField>()
        keep.add(TextField(field, text, Field.Store.YES))
        keep.add(NumericDocValuesField("lengths:$field", length.toLong()))
        keep.add(NumericDocValuesField("unique:$field", uniqLength.toLong()))
        return keep
    }

Это статистика для каждого документа, поэтому вы можете отслеживать среднее значение при индексировании и хранить его отдельно от Lucene, т. е. я обычно создаю «meta.json» рядом с индексом для таких вещей.

Я не знаком с SOLR как таковой, но когда вы приступите к реализации подкласса Weight в Lucene, у вас будет доступ к этим числовым значениям документа следующим образом:

class SpecialBM25(...) : Weight(...) {
    ...
    override fun scorer(context: LeafReaderContext): Scorer {
        val uniq = context.reader().getNumericDocValues("unique:$field")
        val lengths = context.reader().getNumericDocValues("lengths:$field")
        ... generate Scorer and give it your additional features ...
    }
}
person John Foley    schedule 10.12.2017
comment
Это выглядит очень красиво. Я уже видел, что могу получить numericDocValues, но понятия не имел, как их вычислить. Единственное, чего мне все еще не хватает, — это средней средней частоты терминов (т. е. значения для всей коллекции). Это среднее значение всех length / uniqLength для каждого документа. У вас есть предложение, где вычислить это значение в вашем подходе? - person scriptator; 10.12.2017
comment
Я бы возился с методом setTextField, чтобы взять документ, добавить в него поля и вернуть length/uniqLength как double. Затем в своем цикле индексации документов отслеживайте sumLengthRatio и totalNumDocuments, а затем разделяйте их, когда вам нужно среднее значение. В Lucene нет подходящего места для ее хранения, но вы можете добавить волшебный документ для этой статистики, то есть документ со специальным идентификатором и StoredField("lengthRatioMean", sumLengthRatio/totalNumDocuments). - person John Foley; 11.12.2017