Почему один результат выше другого
Инвертированный индекс позволил находить документы, содержащие все слова запроса. Но два подходящих материала пока могут иметь одинаковую оценку. Их порядок определяется ID, хотя один урок посвящён теме целиком, а другой лишь упоминает её в примере. Теперь разделим обнаружение совпадения и оценку его полезности.
В учебной библиотеке XML-урок имеет XML в заголовке. Материал о Fetch упоминает XML как один из возможных форматов ответа. Оба документа могут подходить запросу XML, но ожидаемый первый результат — профильный урок. В этом уроке получим простое объяснимое ранжирование по полям, сохранив формат карточек и адреса.
Откуда берётся оценка
Ранжирование — упорядочивание уже найденных кандидатов по выбранным признакам. Один признак может описывать совпадение заголовка, другой — наличие слова в разделе, третий — употребление в основном тексте. Наличие нескольких признаков ещё не означает, что формула точно отражает намерение каждого посетителя.
Начнём с трёх учебных весов: заголовок получает 10, подзаголовки — 4, основной текст — 1. Эти числа выбраны для понятного примера. Они не являются универсальными коэффициентами поисковых систем или результатами измерений ProfessorWeb.
Слово может встречаться в нескольких полях, и тогда оценки складываются. Но повторения внутри одного поля не дают дополнительные единицы. Такой порядок ограничивает преимущество статьи, которая много раз повторяет одно слово, и позволяет увидеть влияние места совпадения.
Сначала оценим условные записи вручную. Если xml находится в заголовке и тексте профильного урока, оценка будет не меньше 11. Если оно встречается только в тексте материала о Fetch, оценка будет 1. Поэтому первый документ окажется выше при прочих одинаковых условиях.
Это не утверждение, что любая статья с ключевым словом в заголовке лучше любой другой. Мы используем признак, который часто соответствует задаче поиска конкретного урока. Контрольные запросы покажут, когда он помогает, а когда мешает.
Изменение построения индекса
В public/core.js замените только функцию buildIndex. Функции normalize, tokenize и searchIndex из предыдущего урока остаются на месте.
export function buildIndex(documents) {
const byId = new Map();
const postings = new Map();
for (const doc of documents) {
if (byId.has(doc.id)) throw new Error("Повторяющийся ID");
byId.set(doc.id, doc);
const fields = [
[doc.title, 10],
[doc.headings.join(" "), 4],
[doc.text, 1]
];
for (const [value, weight] of fields) {
for (const term of tokenize(value)) {
if (!postings.has(term)) postings.set(term, new Map());
const posting = postings.get(term);
posting.set(doc.id, (posting.get(doc.id) || 0) + weight);
}
}
}
return { byId, postings };
}
tokenize уже возвращает уникальные термины. Поэтому десять повторений слова в основном тексте будут обработаны как одно присутствие в этом поле. Отдельное появление в заголовке добавит самостоятельный вес.
Структура словаря не изменилась: термин указывает на Map с ID и числовой оценкой. Функция запроса складывает значения для всех обязательных терминов. Значит, интерфейс по-прежнему получает hits и total, а внутри карточки сохраняется поле score.
После замены требуется построить индекс заново. Старый объект в памяти не получает новые веса из-за изменения файла на диске. При обычной перезагрузке учебного примера модуль и структура создаются повторно; управление версиями разберём позднее.
Разбор нескольких слов
Для запроса LINQ XML документ должен содержать оба термина, как и раньше. Ранжирование не разрешает потерять один из них. Сначала выполняется пересечение, затем оцениваются оставшиеся кандидаты.
Если совпадение одного слова в заголовке даёт большой вес, оно всё равно не спасёт документ, в котором отсутствует второе обязательное слово. Это полезное разделение: правило отбора и формула порядка решают разные задачи.
В качестве наблюдения выведите ID и score из результата. Для запроса XML профильный документ ожидается выше материала, который упоминает формат ответа. Точные числа зависят от подготовленных полей: если слово присутствует ещё и в подзаголовке, добавится соответствующий вес.
Не следует показывать score посетителю как процент уверенности. Число не ограничено интервалом от нуля до единицы, меняется с длиной запроса и определяется нашей формулой. Оно полезно для объяснения порядка разработчику, но не означает вероятность правильного ответа.
Если два документа получили одинаковую оценку, действует вторичная сортировка по ID. Она сохраняет устойчивый порядок между повторными запросами. Добавлять случайную перестановку ради разнообразия не нужно: читателю и оценке качества будет сложнее сравнивать версии.
Частота и длина документа
Наша формула сознательно проста. Она не учитывает, насколько распространён термин в корпусе, какова длина статьи и сколько раз слово встретилось в разных местах текста. Эти признаки используются в других подходах, например в семействах TF-IDF и BM25.
Для понимания рассмотрим собственный пример. Слово «данные» есть почти во всех шести материалах; его наличие плохо отличает один урок от другого. Редкий технический термин обычно сильнее сужает набор кандидатов. Но полезность такого признака зависит от запроса и содержимого.
Длинная статья естественным образом содержит больше разных слов. Если просто складывать все количества совпадений, она может получать преимущество из-за объёма. Поэтому более сложные формулы вводят нормализацию и ограничивают рост влияния повторений.
Мы не называем нашу сумму весов реализацией BM25. Для неё потребовались бы статистика корпуса и другая формула. В серверном блоке воспользуемся готовым движком и отдельно рассмотрим его правила; численные оценки разных реализаций нельзя автоматически считать сопоставимыми.
Настройка по наблюдениям
Изменение веса следует связывать с проблемой конкретного запроса. Например, нужный профильный урок оказывается ниже случайного упоминания. Увеличение значимости заголовка — проверяемая гипотеза для такого случая.
После изменения нужно посмотреть и другие запросы. Если высокий вес заголовка поднимает короткую заметку выше подробного решения задачи, полезность могла ухудшиться. Одна удачная карточка не доказывает улучшение всей библиотеки.
Не меняйте одновременно десятки признаков. Сохраните исходную формулу, набор запросов и список результатов, затем добавьте один приём. Так будет понятно, какое изменение вызвало наблюдаемый эффект.
Для самостоятельного варианта представьте материал, в заголовке которого есть XML, но основной текст описывает другую тему. Формула способна поднять его. Исправление может требовать редакционной работы, а не нового коэффициента: поисковый алгоритм не обязан компенсировать неверное описание статьи.
Теперь результат имеет объяснимый порядок: обязательные слова выбирают кандидатов, поля дают оценку, ID разрешает равенство. Следующий урок разберёт, какие именно термины попадают в индекс и как сохранить технические названия при обработке русского текста.