Learning to Rank
Pointwise, pairwise, listwise подходы. LambdaMART, CatBoost ranking.
Learning to Rank — когда важен не ответ, а порядок
Загрузка интерактивного виджета...
Классификация отвечает на вопрос «да или нет?» — кликнет пользователь или нет. Ранжирование отвечает на вопрос сложнее: «в каком порядке?». Два товара могут иметь одинаковый CTR 0.12, но один из них нужно показать выше другого — и эта разница стоит денег.
Аналогия: представь, что ты организуешь концерт и должен составить сет-лист из 10 песен. Знать, что каждая песня «хорошая» (классификация) — недостаточно. Тебе нужно выбрать порядок: чем открыть, чем закрыть, куда поставить хит. Learning to Rank (LTR) — это семейство методов, которые учатся именно этому: предсказывать оптимальный порядок.
LTR используется повсеместно: поисковые движки (Google, Bing, Яндекс), рекомендательные системы, e-commerce (ранжирование товаров по запросу), feed-ранжирование в соцсетях. Если где-то есть список и пользователь видит только верхнюю часть — там есть LTR.
Большая картина: от признаков к ранжированному списку
LTR-модель принимает на вход запрос (query) и список кандидатов (documents) и возвращает отсортированный список, где наиболее релевантные элементы стоят выше. Вот весь pipeline за 4 шага:
Шаг 1. Сбор кандидатов. На предыдущих этапах (retrieval, candidate generation) отобрано 100-500 документов для данного запроса. Шаг 2. Извлечение признаков. Для каждой пары (query, document) считаем фичи: текстовые (BM25, TF-IDF), поведенческие (CTR по позициям), эмбеддинговые (косинус user↔item), кросс-фичи (query×document). Шаг 3. Скоринг. LTR-модель присваивает каждому кандидату скор — число, отражающее релевантность. Чем выше скор, тем выше документ в выдаче. Шаг 4. Сортировка. Кандидаты сортируются по скору. Верхние позиции — то, что увидит пользователь.

Ключевой вопрос LTR: как обучить модель скорингу, чтобы итоговая сортировка была оптимальной? Три подхода — pointwise, pairwise, listwise — отличаются именно этим: как формулируется функция потерь и какую «единицу» видит модель при обучении.
Pointwise: «оцени каждый документ отдельно»
Самый простой подход: задача ранжирования сводится к обычной регрессии или классификации. Для каждого документа модель предсказывает скор (relevance grade, CTR, оценку от 0 до 4), и мы сортируем по этому скору.
Например: у тебя есть датасет, где для каждого запроса каждому документу присвоена оценка релевантности от 0 (не релевантен) до 4 (идеальный ответ). Обучаем XGBoost с MSE-лоссом предсказывать эту оценку — всё, у нас есть LTR-модель. Сортируем документы по предсказанным оценкам.
Плюсы: простота. Берёшь любую регрессию/классификацию и получаешь ранжирование. Легко интерпретировать, легко дебажить. Минусы: модель не знает, что мы хотим порядок. MSE-лосс штрафует за ошибку в абсолютном скоре, а не за неправильный порядок. Если модель предсказала [3.1, 3.0] вместо [3.0, 3.1] — порядок перевёрнут, но MSE считает это мелкой ошибкой. Pointwise не учитывает контекст запроса: обучение не видит, что два документа конкурируют за одну позицию.
Pairwise: «кто из двух лучше?»
Вместо предсказания абсолютного скора pairwise-подход учится сравнивать: для каждой пары документов (d_i, d_j) одного запроса модель учится отвечать «d_i релевантнее d_j» или наоборот. Это ближе к реальной задаче ранжирования: нам важен не точный скор, а правильный порядок.
Аналогия: вместо того чтобы ставить абсолютные оценки каждому ресторану (4.2, 4.7, 3.8), ты просто говоришь: «Ресторан A лучше B, B лучше C». Из таких попарных сравнений выстраивается ранжирование.
RankNet: нейросетевые корни
RankNet (Burges et al., 2005) — один из первых pairwise-методов. Модель предсказывает скоры s_i и s_j для двух документов, а лосс-функция — кросс-энтропия от вероятности правильного порядка:
P_ij — предсказанная вероятность того, что документ i релевантнее j. σ — параметр масштабирования
Лосс RankNet — бинарная кросс-энтропия: если d_i действительно лучше d_j, штрафуем модель за низкую P_ij. Модель учится выставлять скоры так, чтобы попарный порядок был правильным.
LambdaRank: градиенты, которые знают про NDCG
Проблема RankNet: все пары равноценны. Перепутать 1-ю и 2-ю позиции так же «плохо», как перепутать 99-ю и 100-ю. Но пользователь видит только верх списка — ошибки наверху стоят дороже.
LambdaRank решает это элегантно: к градиенту RankNet домножается |ΔNDCG| — изменение NDCG при перестановке пары. Если перестановка d_i и d_j сильно меняет NDCG (например, она перемещает документ с позиции 1 на позицию 5) — градиент большой. Если перестановка на хвосте списка — градиент маленький.
Lambda-градиент: произведение градиента RankNet и изменения NDCG при перестановке пары (i, j)
Это ключевая идея LTR: NDCG недифференцируема (это функция от порядка, а порядок дискретен), но через lambda-градиенты мы создаём «суррогатный» градиент, который направляет модель к оптимизации NDCG. На практике это работает отлично — LambdaRank стабильно обходит RankNet по NDCG.
Listwise: «оптимизируй весь список целиком»
Listwise-подходы рассматривают весь список документов для одного запроса как единый обучающий пример. Вместо пар — работа со списком целиком.
Два основных направления: • ListNet — превращает скоры в распределение вероятностей (через softmax) и минимизирует KL-дивергенцию между предсказанным и истинным распределением. • Прямая оптимизация метрики (ApproxNDCG, SoftRank) — строит дифференцируемую аппроксимацию NDCG и оптимизирует её gradient descent-ом.
На практике чисто listwise-подходы не доминируют над pairwise с lambda-градиентами. Граница размыта: LambdaRank формально pairwise, но |ΔNDCG| учитывает весь список. Именно такие «гибридные» методы стали стандартом.
LambdaMART — стандарт индустрии
LambdaMART = Lambda-градиенты (из LambdaRank) + MART (Multiple Additive Regression Trees, т.е. градиентный бустинг на деревьях решений). Это, пожалуй, самый важный алгоритм LTR — он десятилетиями доминирует в поисковых системах и рекомендациях.
Как это работает: 1. Начинаем с нулевых скоров для всех документов. 2. Для каждой пары (d_i, d_j) с разной релевантностью считаем lambda-градиент: насколько нужно увеличить разницу скоров, с учётом влияния на NDCG. 3. Lambda-градиенты суммируются по всем парам для каждого документа — получаем «псевдо-остатки». 4. Строим дерево решений, предсказывающее эти остатки. 5. Добавляем дерево к ансамблю (с learning rate). 6. Повторяем 500-3000 итераций.
Почему LambdaMART — стандарт? • Скорость: GBDT (XGBoost, LightGBM, CatBoost) — одни из самых быстрых моделей. Обучение — минуты, инференс — микросекунды. • Фичи: деревья нативно работают с категориальными фичами, пропусками, нелинейностями. Не нужно нормализовать, не нужен feature engineering. • Качество: lambda-градиенты позволяют оптимизировать NDCG, несмотря на его недифференцируемость. На практике — один из лучших результатов на бенчмарках LTR. • Интерпретируемость: feature importance из деревьев показывает, какие фичи важны для ранжирования. • Надёжность: десятки лет в продакшене крупнейших систем.
from catboost import CatBoostRanker
# YetiRank — реализация LambdaMART в CatBoost
model = CatBoostRanker(
iterations=1000,
depth=6,
learning_rate=0.05,
loss_function='YetiRank', # lambda-градиенты + NDCG
# loss_function='PairLogitPairwise', # классический pairwise
)
# group_id — ID запроса: документы одного запроса сравниваются между собой
model.fit(X_train, y_train, group_id=query_ids_train,
eval_set=(X_val, y_val), eval_group_id=query_ids_val)
# Инференс: скоры для ранжирования
scores = model.predict(X_test)
# Сортируем документы по убыванию скора → ранжированный списокXGBoost, LightGBM и CatBoost для LTR
objective="rank:ndcg" или "rank:pairwise". Классика, но медленнее на больших данных.
• LightGBM: objective="lambdarank", metric="ndcg". Быстрее XGBoost благодаря histogram-based splits.
• CatBoost: loss_function="YetiRank" — собственная модификация LambdaMART. Нативная работа с категориальными фичами — удобно для LTR, где много категорий (город, категория товара).
Все три реализуют вариации LambdaMART.Признаки для ранжирования: три группы
LTR-модель ранжирования — самая «тяжёлая» стадия пайплайна, но работает с маленьким набором кандидатов (100-500). Поэтому может позволить себе дорогие признаки. Все фичи делятся на три группы:
Query features — зависят только от запроса: • Длина запроса (в токенах) • Частотность запроса (популярный или редкий) • Тип интента (навигационный, информационный, транзакционный) • Эмбеддинг запроса
Document features — зависят только от документа: • Качество документа (PageRank, число просмотров, рейтинг товара) • Свежесть (дата публикации) • Длина документа • CTR документа по всем запросам • Эмбеддинг документа из Two-Tower модели
Cross features — зависят от пары (query, document). Это самая ценная группа: • BM25 / TF-IDF score (текстовое совпадение) • Косинусное сходство эмбеддингов query и document • CTR пользователя в категории этого документа • Исторический CTR документа по похожим запросам • Скор из предыдущего этапа (retrieval score) • Время с последнего взаимодействия пользователя с этой категорией
Практическое правило
Position bias — когда данные врут
Пользователь кликает на первый результат намного чаще, чем на пятый — даже если пятый объективно лучше. Это position bias: CTR зависит не только от релевантности, но и от позиции показа. Если обучать LTR на «кликнул / не кликнул» наивно, модель выучит: «то, что было наверху, — хорошее». Она будет закреплять текущий порядок, а не находить оптимальный.
Аналогия: представь, что ты оцениваешь рестораны по числу посетителей. Ресторан на центральной улице получит больше посетителей, чем отличный ресторан в переулке — но не потому что он лучше, а потому что его позиция удобнее. Если обучаться на таких данных, модель будет считать центральные рестораны лучшими и ставить их ещё выше — замкнутый круг.
Как бороться с position bias
1. Inverse Propensity Weighting (IPW). Оцениваем вероятность клика в зависимости от позиции: p(click | position). Клик на позиции 5 «весит» больше, чем клик на позиции 1, потому что пользователь должен был пролистать — значит, документ действительно релевантен. Формально: каждый пример в лоссе домножается на 1/p(examination | position).
2. Рандомизация позиций. На небольшой части трафика (1-5%) показываем документы в случайном порядке. Это даёт «чистые» данные без position bias. Дорого (ухудшает UX на рандомизированном трафике), но даёт ground truth.
3. Position as feature. Добавляем позицию показа как признак в модель при обучении. На инференсе подставляем фиксированное значение (например, позицию 1 для всех) или убираем фичу. Модель учится «вычитать» эффект позиции.
4. Unbiased LTR. Семейство методов (regression EM, dual learning), которые моделируют position bias как латентную переменную и убирают его при обучении. Самый теоретически обоснованный подход, но сложнее в реализации.
Neural LTR: когда деревьев мало
LambdaMART на ручных фичах — мощный baseline, но у него есть предел: качество ограничено фичами, которые ты придумал. Neural LTR сдвигает парадигму: вместо ручных фичей модель учится из сырых данных (текст запроса + текст документа).
Cross-encoder — основной подход для Neural LTR. Запрос и документ конкатенируются и подаются в BERT (или другой трансформер) целиком: [CLS] query [SEP] document [SEP]. На выходе [CLS]-токен → линейный слой → скор релевантности.
Почему cross-encoder, а не bi-encoder (Two-Tower)? Bi-encoder считает эмбеддинги запроса и документа отдельно — быстро, но не может уловить тонкие взаимодействия. Cross-encoder видит запрос и документ одновременно: self-attention позволяет каждому слову запроса «смотреть» на каждое слово документа. Это значительно точнее, но и дороже: нужно прогнать BERT для каждой пары (query, document).
Типичный pipeline в продакшене: 1. Retrieval (bi-encoder / BM25): быстро отбираем 1000 кандидатов 2. LTR Stage 1 (LambdaMART на фичах): ранжируем 1000 → top-100 3. LTR Stage 2 (cross-encoder / Neural): переранжируем top-100 → top-10 Cross-encoder слишком дорог для 1000 документов, но идеально подходит для финального reranking на маленьком множестве.
from sentence_transformers import CrossEncoder
# Cross-encoder для reranking
reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
query = "как работает LambdaMART"
documents = [
"LambdaMART — алгоритм ранжирования на основе GBDT...",
"Градиентный бустинг — семейство ансамблевых методов...",
"BERT — языковая модель на основе трансформера...",
]
# Скоры для каждой пары (query, document)
pairs = [[query, doc] for doc in documents]
scores = reranker.predict(pairs)
# scores: [0.98, 0.23, 0.05] → первый документ наиболее релевантен
# Ранжируем
ranked = sorted(zip(documents, scores), key=lambda x: -x[1])Pointwise vs Pairwise vs Listwise — когда что
Pointwise — когда у тебя есть абсолютные оценки релевантности (0-4) или ты решаешь задачу CTR-предсказания. Простой baseline, часто «достаточно хорошо» для MVP. Pairwise (LambdaMART) — стандарт индустрии. Работает на данных с частичной разметкой (кликнул / не кликнул, без абсолютных оценок). Lambda-градиенты с |ΔNDCG| дают лучшую оптимизацию метрик ранжирования. Listwise — теоретически элегантнее, но на практике преимущество над LambdaMART невелико. Используется в исследованиях и в нейросетевых подходах (ApproxNDCG как лосс для нейросети). Neural (cross-encoder) — максимальное качество для текстовых задач, но дорогой: подходит для финального reranking на top-100.
🎯 На собеседовании
Junior
Middle
Senior
Собираем всё вместе
Learning to Rank — это задача, в которой важен порядок, а не абсолютный скор. Три подхода (pointwise → pairwise → listwise) постепенно усложняют лосс-функцию, чтобы лучше оптимизировать метрики ранжирования. LambdaMART (GBDT + lambda-градиенты с |ΔNDCG|) — рабочая лошадка индустрии: быстрый, точный, интерпретируемый.
Если запомнить одну вещь: LambdaMART обходит проблему недифференцируемости NDCG через lambda-градиенты, которые учитывают влияние перестановки каждой пары на итоговую метрику. Это позволяет обычным деревьям решений оптимизировать метрику ранжирования.
В продакшене LTR-модель — часть многоступенчатого пайплайна: retrieval отбирает кандидатов, LTR ранжирует, а neural reranker (cross-encoder) переранжирует top. Position bias — главный подводный камень: без его учёта модель закрепляет статус-кво вместо нахождения оптимального порядка.
Материалы
Классический обзор эволюции LTR: от RankNet через LambdaRank к LambdaMART. Обязателен к прочтению.
Практический разбор LTR в продакшене: фичи, CatBoost, position bias, A/B-тесты.
Курс по NLP с хорошим объяснением attention и ранжирования в контексте поиска.
Описание YetiRank, PairLogit и других лоссов для ранжирования в CatBoost.
Лекция Stanford по LTR: pointwise, pairwise, listwise с математикой и примерами.