Ранжирование (learning to rank)

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

Learning to rank (LTR) — это обучение модели не предсказывать абсолютную оценку, а правильно упорядочивать кандидатов по релевантности для конкретного пользователя.

Зачем отдельный этап ранжирования

Генерация кандидатов (CF, two-tower, популярность) даёт сотни-тысячи потенциально релевантных товаров — но быстро и грубо. Показать-то нужно десяток, и в правильном порядке. Ранжирование — это второй, точный этап: по каждому кандидату собирается много признаков, и обучаемая модель расставляет их в финальный порядок. Здесь точность важнее скорости, потому что кандидатов уже немного.

Признаки ранжирования

Сила LTR — в богатстве признаков. На вход модели подают всё, что есть:

  • Сигналы релевантности: скор из CF, близость эмбеддингов, контентное сходство.
  • Признаки товара: популярность, свежесть, цена, рейтинг, наличие.
  • Признаки пользователя: история, активность, сегмент.
  • Признаки пары и контекста: совпадение категории с историей, время суток, устройство.

Три семейства подходов

ПодходЧто предсказывает
PointwiseСкор каждого товара по отдельности (как регрессия/классификация)
PairwiseДля пары товаров — какой выше (например, RankNet, LambdaRank)
ListwiseКачество всего списка целиком (оптимизирует метрику вроде NDCG)

На практике золотой стандарт — градиентный бустинг для ранжирования (LambdaMART, реализован в XGBoost, LightGBM, CatBoost). Он мощный, работает с разнородными признаками и обучается на парных предпочтениях.

Простой линейный ранжировщик

Покажем суть pointwise-ранжирования на stdlib: взвешенная сумма признаков задаёт скор, по которому сортируем кандидатов.

# признаки кандидата: [cf_score, популярность, свежесть]
candidates = {
    "A": [0.9, 0.2, 0.1],
    "B": [0.4, 0.9, 0.8],
    "C": [0.7, 0.5, 0.9],
    "D": [0.6, 0.3, 0.2],
}
weights = [1.0, 0.5, 0.7]  # обучаются на данных; здесь заданы вручную

def score(feats):
    return sum(w * f for w, f in zip(weights, feats))

ranked = sorted(candidates, key=lambda x: -score(candidates[x]))
print("Финальный порядок:")
for item in ranked:
    print(f"  {item}: {round(score(candidates[item]), 3)}")

Вывод:

Финальный порядок:
  C: 1.58
  B: 1.41
  A: 1.07
  D: 0.89

Список уже отсортирован по скору: C (1.58) выше B (1.41), B выше A (1.07), A выше D (0.89). Скор — это просто взвешенная сумма признаков; меняя веса, мы меняем порядок.

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

Настоящий LTR обучает веса (или деревья бустинга) так, чтобы порядок предсказаний совпадал с «правильным» порядком из данных — например, кликнутый товар должен оказаться выше некликнутого. Listwise-методы прямо оптимизируют ранжирующие метрики (NDCG@k), что обычно даёт лучший результат, чем pointwise. Признаки нормируют, бустинг подбирает нелинейные комбинации автоматически — поэтому ручная установка весов из примера на проде заменяется обучением.

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

  • Путать генерацию кандидатов и ранжирование. Первое — широкий грубый отбор, второе — точная сортировка немногих; смешивать нельзя.
  • Оптимизировать не ту цель. Если важен порядок в топе, оптимизируйте ранжирующую метрику (NDCG), а не общую регрессию.
  • Утечка целевого признака. Случайно подать в признаки информацию о будущем клике — классическая ошибка, дающая обманчиво идеальный офлайн.

Итоги

  • Ранжирование — точный второй этап после грубой генерации кандидатов.
  • Сила LTR — в богатых признаках товара, пользователя, пары и контекста.
  • Подходы: pointwise, pairwise, listwise; на практике царит бустинг (LambdaMART).
  • Listwise-методы прямо оптимизируют ранжирующие метрики вроде NDCG.
Проверьте себя
1. Чем этап ранжирования отличается от генерации кандидатов?
AРанжирование грубо отбирает тысячи товаров, кандидаты — это финал
BГенерация кандидатов — быстрый широкий отбор, ранжирование — точная сортировка немногих по богатым признакам
CЭто один и тот же этап
DРанжирование не использует признаки
2. Какой метод считается практическим золотым стандартом для ранжирования рекомендаций?
AСлучайная сортировка
BГрадиентный бустинг для ранжирования (LambdaMART в XGBoost/LightGBM/CatBoost)
CСортировка по алфавиту
DТолько косинусная похожесть