Оглавление цикла:

  1. Часть первая

  2. Часть вторая

  3. Часть третья (вы тут)


Это третья и заключительная статья из цикла, в которой рассмотрим стандартную модель ранжирования документов в Elasticsearch.

Релевантность документов

После того как определено множество документов, которые удовлетворяют параметрам полнотекстового запроса, Elasticsearch рассчитывает метрику релевантности для каждого найденного документа. По значению метрики набор документов сортируется и отдается потребителю.

В Elasticsearch существует несколько моделей ранжирования документов, о которых можно почитать в документации. По умолчанию используется Okapi BM25. Три кита этой модели:

Концепция Okapi BM25
Концепция Okapi BM25
  • Term frequency (TF) — частотность терма. Чем чаще терм появляется в поле документа, тем он релевантнее;

  • Inverse document frequency (IDF) — обратная частота употребления терма в документах. Чем больше документов содержат искомый терм в поле, тем меньше значимость этого терма. Данный параметр вводится для снижения веса часто употребляемых слов в выборке;

  • Normalization by field length — нормализация по длине поля. Если два документа имеют одинаковое количество вхождения искомого терма и документы отличаются по размеру, то наиболее релевантным будет документ, размер которого меньше.

Описание функции ранжирования:

????????????????????(????,????)=∑_????^????????????????(????_????)\frac{????(????_????,????)\cdot(????_1+1)}{????(????_????,????)+????_1\cdot(1−????+????\cdot\frac{????????????????????????????????????????ℎ}{????????????????????????????????????????????????????ℎ})}IDF(q_i)=\ln{\left(1+\frac{(docCount-f\left(q_i\right)+0.5)}{f\left(q_i\right)+0.5}\right)}
  • Q (query) — текущий запрос;

  • ???? (document) — текущий документ;

  • ???????? — ????-ый терм;

  • ????(????????, ????) — частота появления терма ???????? в документе ????;

  • ????????????????????????????????????????ℎ — длина поля в термах;

  • ????????????????????????????????????????????????????ℎ — средняя длина поля в термах;

  • ????????????(????????) — обратная частота употребления терма ???????? в документах;

  • ????(???????? ) — кол-во документов, в которых встречается искомый терм;

  • ???????????????????????????????? — количество документов, которые имеют искомое поле;

  • ????1 — коэффициент, который задает порог насыщения по частоте терма (в Elasticsearch равен 1.2);

  • ???? — коэффициент, который усиливает отношение длины документа к средней длине (в Elasticsearch равен 0.75).

Рассмотрим несколько графиков.

Зависимость метрики релевантности от частоты термов при разных значениях k1

Зависимость метрики релевантности от частоты термов при разных значениях k1
Зависимость метрики релевантности от частоты термов при разных значениях k1

Чем больше коэффициент k1, тем выше порог и меньше скорость насыщения. При k1 равным нулю все параметры игнорируются кроме ????????????.

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

Зависимость метрики релевантности от частоты термов при разных значениях отношения длины документы к средней длине

Зависимость метрики релевантности от частоты термов при разных значениях отношения длины документы к средней длине
Зависимость метрики релевантности от частоты термов при разных значениях отношения длины документы к средней длине

Короткие документы гораздо быстрее достигают порога насыщения, т.к. рассматриваемое отношение находится в знаменателе. Чем больше это отношение, тем меньше значение метрики релевантности.

После достижения "потолка" добавление новых термов, соответствующих поисковому запросу, не вносит дополнительный вклад в значение метрики релевантности.

Задав коэффициент b в ноль, можно полностью убрать влияние данного параметра.

Зависимость IDF от частоты термов при фиксированном количестве документов в выборке

Зависимость IDF от частоты термов при фиксированном количестве документов в выборке
Зависимость IDF от частоты термов при фиксированном количестве документов в выборке

Чем чаще терм встречается в документах, тем меньший вес он будет иметь среди других термов в поисковом запросе.

Пример

Для примера используется индекс с названием kotlin_articles, который имеет стандартные настройки и ровно одну шарду. Если повторить шаги примера для индекса, который имеет больше чем одну шарду, то результат будет отличаться от приведенного. Это связано с тем, что Elasticsearch рассчитывает метрику релевантности в рамках одного шарда.

В рассматриваемый индекс сохраним 5 документов.

Сохраняемые документы
Сохраняемые документы

Наш поисковый запрос состоит ровно из одного слова — Kotlin. На этом шаге несложно догадаться, в каком порядке будут выданы документы:

Результат ранжирования
Результат ранжирования

Результат ранжирования Elasticsearch:

Результат ранжирования Elasticsearch
Результат ранжирования Elasticsearch

Значение релевантности находится в после "_score".

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

Самый релевантный документ в выборке
Самый релевантный документ в выборке
????????????=\ln{\left(1+\frac{(docCount-f\left(q\right)+0.5)}{f\left(q\right)+0.5}\right)} = \ln{\left(1+\frac{(5-5+0.5)}{5+0.5}\right)} = 0.087????????????????????=????????????\cdot\frac{????(????,????)\cdot(????_1+1)}{????(????,????)+????_1\cdot(1−????+????\cdot\frac{????????????????????????????????????????ℎ}{????????????????????????????????????????????????????ℎ})} =\\ 0.087\cdot\frac{2\cdot(1.2+1)}{2+1.2\cdot(1−0.75+0.75\cdot\frac{5}{5.6})} = 0.1233

И мы видим, что значения совпадают!

Для закрепления проверим наши расчеты с помощью Explain API:

Использование explain API для документа с id = 2
Использование explain API для документа с id = 2
{
    "_index": "kotlin_articles",
    "_id": "2",
    "matched": true,
    "explanation": {
        "value": 0.12335789,
        "description": "weight(name:kotlin in 1) [PerFieldSimilarity], result of:",
        "details": [
            {
                "value": 0.12335789,
                "description": "score(freq=2.0), computed as boost * idf * tf from:",
                "details": [
                    {
                        "value": 2.2,
                        "description": "boost",
                        "details": []
                    },
                    {
                        "value": 0.087011375,
                        "description": "idf, computed as log(1 + (N - n + 0.5) / (n + 0.5)) from:",
                        "details": [
                            {
                                "value": 5,
                                "description": "n, number of documents containing term",
                                "details": []
                            },
                            {
                                "value": 5,
                                "description": "N, total number of documents with field",
                                "details": []
                            }
                        ]
                    },
                    {
                        "value": 0.64441884,
                        "description": "tf, computed as freq / (freq + k1 * (1 - b + b * dl / avgdl)) from:",
                        "details": [
                            {
                                "value": 2,
                                "description": "freq, occurrences of term within document",
                                "details": []
                            },
                            {
                                "value": 1.2,
                                "description": "k1, term saturation parameter",
                                "details": []
                            },
                            {
                                "value": 0.75,
                                "description": "b, length normalization parameter",
                                "details": []
                            },
                            {
                                "value": 5,
                                "description": "dl, length of field",
                                "details": []
                            },
                            {
                                "value": 5.6,
                                "description": "avgdl, average length of field",
                                "details": []
                            }
                        ]
                    }
                ]
            }
        ]
    }
}

В ответе можно узнать функцию ранжирования Okapi BM25, а также какие значения были использованы для расчета.


Это была последняя статья из цикла. Спасибо, что дочитали, если у вас будут какие-то вопросы — добро пожаловать в комментарии.

Комментарии (1)


  1. murkin-kot
    29.09.2023 11:34

    Почему тема называется Elastic Search? На самом деле всё сказанное относится к поисковому движку Lucene.

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

    Вот очень простое пояснение.