Дикий _score в match_phrase_prefix

Доброго времени суток!

Изначально задача выдавать результат в зависимости от приоритета подзапроса.
Соответвенно, был dis_max c match_phrase, match_phrase_prefix и так далее. С boost соответвующим приоритету. По нашей логике match_phrase, должен всегда быть выше остальных, затем match_phrase_prefix и так далее по самый мусор.

Результаты были не всегда правильными и я компенсировал это значительной разницей в boost (обычно на 2-3 порядка, 10000 -> 100 -> 1). На днях ко мне обратился коллега за помощью в решении подобной задачи... В общем, мы пришли к выводу, что это не самое правильное решение, т.к. зависит от данных.

В результате опытов мы выяснили, что на таких же запросах _score match_phrase_prefix мог быть в 10-20 больше match_phrase у того же документа. Выяснилось, что _score зависит от количества префиксов попадающих в запрос. Т.е. делаем запрос максимально приближенных к простому match_phrase:

{
  "explain": true,
  "from": 0,
  "size": 1,
  "_source": {
    "includes": [
      "title",
      "content"
    ]
  },
  "query": {
    "match_phrase_prefix": {
      "content.original": {
        "max_expansions": 1,
        "query": "налоговый анализ",
        "boost": 100
      }
    }
  }
}

Получаем ответ на 100% совпадающий с match_phrase. Включаем explain и шаг за шагом увеличиваем max_expansions. Видим, что количество секций в подсчете _scale увеличивается в соответвенно. Видим, что на _scale влияют слова которых даже нет в документе и они его не только не уменьшают (это бы м.б. я понял ), а увеличивают.

mapping простейший: "tokenizer": "standard", "filter": "lowercase"

Нашел подобную issue, но там тишина:

Вопрос: М.б. мы делаем, что-то не так и эту задачу можно решить другим путём?
P.S. естественно документы их количество и сам набор не менялись,

Вы не могли бы более подробно описать задачу?

Изначально задача выдавать результат в зависимости от приоритета подзапроса.

Тут может быть много трактовок. Хотелось-бы конкретных деталей.

Есть коллекция документов. В простейшем варианте два поля "title","body".
По запросу я должен выдать список в котором документы упорядочены так:

  1. Сначала идут документы в которых есть точное совпадение фразы в поле "title".
  2. Потом точное совпадение фразы с префиксом в "title"
  3. Всё остальное с морфологией в "title". (по возможности без мусора AND или 75%)
  4. Тоже самое по полю "body"
  5. -\-
  6. -\-

Так то и dis_max устраивает, если бе не _score в match_phrase_prefix.

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

Я бы предпочел небольшой перехлест

Полностью с вами согласен. Примерно так и получается, но в некоторых конкретных случаях _score в match_phrase_prefix очень перевешивает. Особенно на тестовых данных.

Я бы прибавлял score

Через function_score ?

Это обычно бывает, когда тестовых данных мало либо и не используется флаг dfs_query_then_fetch, либо в тестовых данных много повторений, которые отсутствуют в реальных данных. Вообще, отладка релевантности на тестовых данных - дело бестолковое.

Да, идея такая, чтобы разнести score. То есть самый нерелевантный запрос будет давать score между 0-10, следующий - 1000-1010, следующий 2000-2010 и т.д. То есть можно подобрать шаг так, чтобы они никак не пересекались.

Только сел за реализацию. Получилось примерно следующее:

{
  "query": {
    "dis_max": {
      "queries": [
        {
          "function_score": {
            "query": {
              "match_phrase": {
                "name.original": {
                  "query": "query"
                }
              }
            },
            "boost_mode": "replace",
            "script_score": {
              "script": {
                "source": "200000 + _score"
              }
            }
          }
        },
        {
          "function_score": {
            "query": {
              "match_phrase_prefix": {
                "name.original": {
                  "query": "query",
                  "max_expansions": 20
                }
              }
            },
            "boost_mode": "replace",
            "script_score": {
              "script": {
                "source": "100000 + _score"
              }
            }
          }
        }
      ]
    }
  }
}

В таком варианте score берется только из скрипта.

Если boost_mode оставить по-дефотлу, то score в сочетании boost в подзапроса match_phrase_prefix спокойно перебивает score в match_phrase (В моём случа там получаются миллионы).

И то, остается вопрос какую дельту закладывать в этом случае, т.к. все равно результат зависит от данных. Насколько я понимаю, минимум логарифм от общего количества документов в степени количества префиксов. (Но это не точно :wink: )

В идеале, получить бы max_score в подзапросе, чтобы можно было маштабировать итоговый score значение точно от "A" до "B" и/или иметь возможность настраивать вычисление score для match_phrase_prefix

По умолчанию происходит умножение (multiply). Вам нужно сложение (sum) если использовать "weight" : 100000 или replace если использовать скрипт, как у вас.

Можно поделить эти миллионы на 10,000, или взять от них логарифм, или еще как-нибудь привести из в чувства чтобы они точно поместились в 100,000. Конкретное значение ведь не так важно. Важно чтобы порядок значений не менялся.

1 Like

Как раз важно, т.к. надо попасть в диапазон, чтобы один подзапрос не пересекался с другим. Теоретическиscore лежит в диазоне от 0 до +∞, поэтому мы не можем гарантированно ограничить вернюю границу.

Теоретически, оно ограничено float в java. Так что score не может превышать 3.40282347e38. log от него - 38.5.

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

Это технически, а не теоретически... :smile: в общем, да что-то я не подумал, что скору всё ранво сколько у меня индексов в алисе.
В целом, так и сделал - разделил с запасом.
Спасибо!