Релевантность: scoring, TF-IDF и BM25

Разбираем, как Elasticsearch решает, какой документ показать выше — то есть как считается _score.

Релевантность (score) — числовая оценка того, насколько документ соответствует запросу; в ES по умолчанию считается формулой BM25.

Зачем нужна оценка

Полнотекстовый поиск возвращает не «да/нет», а упорядоченный список: самое подходящее — сверху. Чтобы упорядочить, каждому документу присваивается число _score. Вопрос — по какому принципу его считать, чтобы порядок совпадал с интуицией пользователя.

Три интуиции, на которых стоит scoring

ФакторИдея
Частота терма (TF)чем чаще искомое слово в документе, тем он релевантнее
Обратная частота (IDF)редкое слово важнее частого: совпадение по «квантовый» ценнее, чем по «и»
Длина документав длинном тексте слово встречается «случайнее»; короткое попадание весомее

От TF-IDF к BM25

Классическая формула TF-IDF просто перемножает частоту терма и его обратную частоту. Но у неё есть проблема: если слово встретилось в документе 100 раз, оценка растёт линейно и неограниченно — это перекос. BM25 (Best Match 25) — улучшенная формула, которую ES использует по умолчанию. Её отличия:

  • Насыщение по TF. Вклад частоты растёт всё медленнее: с 1 до 2 вхождений score прибавляется заметно, со 100 до 101 — почти нет. Логично: документ со словом 100 раз не в 100 раз релевантнее.
  • Нормировка по длине. Учитывается, длиннее или короче документ среднего по индексу.
  • IDF сохраняется: редкие термы дают больший вклад.

Как увидеть, почему такой score

ES умеет объяснить оценку — добавьте к запросу explain:

{
  "explain": true,
  "query": { "match": { "title": "кофемашина" } }
}

В ответе появится разбор: вклад IDF, TF и нормировки длины для каждого терма. Это незаменимо при отладке «почему этот документ выше того».

Как работает под капотом

Для каждого документа, совпавшего по терму, ES берёт из инвертированного индекса частоту терма в этом документе (TF) и число документов с этим термом во всём индексе (для IDF), а также длину документа. Подставляет в формулу BM25 с коэффициентами k1 (насыщение) и b (влияние длины) и получает вклад терма. Если термов несколько (или есть should), вклады складываются. Итоговая сумма и есть _score.

Частые ошибки

  • Сравнивать score между разными запросами. Оценка не абсолютна: _score=8 в одном запросе и _score=2 в другом несопоставимы. Score имеет смысл только для упорядочивания внутри одной выдачи.
  • Ждать score там, где его нет. В секции filter и при сортировке по полю релевантность не считается — _score будет нулевым или отсутствует.

Итоги

  • Релевантность опирается на TF (частота терма), IDF (редкость слова) и длину документа.
  • ES по умолчанию использует BM25 — улучшенный TF-IDF с насыщением частоты и нормировкой длины.
  • explain: true показывает, из чего сложился _score; сравнивать score между запросами нельзя.
Проверьте себя
1. Чем BM25 лучше классического TF-IDF?
ABM25 не учитывает частоту слов
BBM25 даёт насыщение по частоте терма и нормирует на длину документа, а не растёт линейно
CBM25 работает только с числами
DМежду ними нет различий
2. Что отражает фактор IDF (обратная частота документа)?
AЧто частые слова важнее редких
BЧто редкие слова важнее частых: совпадение по редкому терму ценнее
CДлину документа
DВремя выполнения запроса