Feature-rich ranking: лестница классов моделей (Фаза 8)

D2b сознательно остановился на логистической регрессии. Здесь — следующий вопрос production-ранжирования: сколько платит смена класса модели на тех же фичах? И отдельно — сколько платит другой апгрейд над той же базой сигналов: явные попарные взаимодействия, где класс модели не меняется. Всё остальное зафиксировано, ничего не выбрано по финальной метрике, а выводы проверены разрезом revisit/novel и парным интервалом для разности.

⚠ Все ранкеры лестницы обучаются и оцениваются офлайн на реальном логе Brightkite (~2.02M событий); страница показывает готовый маленький артефакт. Бустинг (sklearn HistGB) на сервер не едет — как и весь Brightkite-трек, VPS видит только JSON.

загрузка артефакта лестницы ранкеров…

Теория простым языком

Ранжирование в проде — это почти всегда табличная задача: десятки-сотни фичей про пару (юзер, айтем) и вопрос «какой класс модели выжмет из них больше». Этот модуль отвечает на него так, как положено в инженерии: раскладывает апгрейд на два независимых эксперимента, фиксирует всё остальное — и меряет, включая срез, на котором агрегат врёт, и парный интервал, без которого дельта не имеет смысла.

▸Зачем менять класс модели, если фичи те же
БылоD2b: логистическая регрессия на 8 фичах — сознательно простейший ранкер, весь интеллект в фичах (D2b).
ПроблемаЛинейная модель складывает вклады фичей независимо: «geo важно, ТОЛЬКО ЕСЛИ истории нет», «pop полезен, ТОЛЬКО ЕСЛИ transition молчит» — такие условные правила ей недоступны, сколько фичей ни подавай.
ИдеяДеревья решений строят такие правила по построению (split по одной фиче внутри ветки другой), а градиентный бустинг складывает сотни слабых деревьев в сильный ранкер. Взаимодействия находятся сами — не надо перечислять пары руками.
Стало лучшеНа тех же фичах агрегат не сдвигается отличимо от нуля, зато разрез показывает встречную динамику: бустинг теряет попадания на возвратах и набирает их на местах, НОВЫХ ДЛЯ ПОЛЬЗОВАТЕЛЯ — и только этот выигрыш переживает парный bootstrap (таблицы выше). Нелинейность платит там, где нет персональной истории.
Осталось слабымМодель стала заметно менее прозрачной: коэффициентов нет, важности только через permutation/SHAP и только с контрактом замера; обучение и тюнинг дороже; на revisit-доминированном агрегате апгрейд не виден — без среза его не продать.
ДальшеДальше по лестнице индустрии: LambdaMART (обучение прямо на ранжирующую метрику), Wide&Deep / DeepFM / DCN-v2 (нейро-взаимодействия на кросс-фичах) — та же логика, больше ёмкость.

Две оси апгрейда, которые нельзя складывать в одну лестницу

Над одной и той же базой сигналов здесь стоят два разных эксперимента, и это главная дисциплинарная мысль модуля.
1. Смена класса модели: логрег на 8 исходных фичах → GBDT на тех же 8 фичах. Признаки идентичны, меняется только способ их использовать — дельту можно приписать классу.
2. Смена представления признаков: логрег на 8 фичах → логрег на 8 + 28 попарных произведениях. Класс модели не меняется; это попытка купить нелинейность, оставаясь линейным. Приписывать эту дельту «смене класса модели» — ошибка, которая стояла на этой странице до первого раунда ревью.
У второй оси есть собственная ловушка, которую мы померили: широкая модель при том же learning rate сходится медленнее, поэтому «добавили взаимодействия» и «дали оптимизатору больше итераций» очень легко перепутать. В таблице чувствительности выше видно, что число попаданий на новых местах гуляет от одного правила останова к другому сильнее, чем весь заявленный прирост — значит, эффект взаимодействий на этих данных не установлен, и так и надо говорить. Вывод не «interaction features бесполезны», а «ручной перебор пар требует отбора, регуляризации и проверки по срезам — ровно той работы, которую хочется отдать модели».

Лестница классов моделей для табличного ранжирования

1. Линейная модель — baseline с дешёвой интерпретацией: стандартизованный коэффициент ≈ условный эффект (с оговорками из D2b). Потолок: аддитивность.
2. GBDT (у нас sklearn HistGradientBoosting; в индустрии XGBoost/LightGBM/CatBoost) — рабочая лошадка табличного ранжирования/CTR и очень сильный baseline: находит взаимодействия и нелинейности сам, устойчив к масштабам фичей, быстро обучается. Важно: наш HistGB используется как pointwise-скорер — это не LambdaMART и не listwise ранкер, ranking-objective сюда не зашит. И это бустинг «из коробки»: подбора гиперпараметров у нас не было вообще, так что таблица говорит «GBDT с дефолтами против логрега», а не «лучший достижимый GBDT».
3. Нейро-ранкеры (Wide&Deep, DeepFM, DCN-v2) — когда фичей тысячи, много категориальных с огромной кардинальностью (id-эмбеддинги), нужен multi-task и данных очень много; в больших рекомендательных системах они давно в проде. На маленьких табличках чаще проигрывают бустингу — «нейросеть ≠ лучше» (урок NeuMF) действует и здесь.

Pointwise / pairwise / listwise — Наш ранкер — pointwise: он независимо присваивает score каждой паре «пользователь × кандидат», позитивом служит следующий чек-ин, негативами — сэмплированные кандидаты. Кликов и impression-aware CTR-задачи в Brightkite нет, поэтому «учим P(клик)» — неверное описание того, что здесь происходит. Pairwise (BPR, RankNet) учит «позитив выше негатива», listwise (LambdaMART, LambdaRank) — прямо двигает NDCG списка. В проде pointwise часто достаточно, и его скор можно калибровать — но только при impression-aware логах и корректной поправке на схему сэмплирования негативов. В этом модуле калибровки нет и быть не может: лог positive-only, обучающие пары сэмплированы, поэтому наш скор — ranking score, а не вероятность чек-ина. При целевой метрике порядка pairwise/listwise objectives могут дать прирост за счёт лучшего совпадения с ranking-задачей, но не гарантируют его; механику pairwise мы уже строили в BPR.
Важности без коэффициентов — У деревьев нет весов, которые можно прочитать. Permutation importance — перемешать колонку и посмотреть, насколько выросла функция потерь: честно про «на что модель опирается», но коррелированные фичи делят вклад непредсказуемо (как и коэффициенты в D2b — урок переносится). SHAP даёт по-примерные атрибуции той же природы. Важности без контракта замера не значат ничего, поэтому свой мы печатаем рядом с таблицей: выборка, единица, метрика, число повторов, seed — и прямое признание, что выборка у нас не отложенная. Для ranking-эффекта честнее абляции с переобучением + user-level eval (как в D2b) — самый честный ответ и самый дорогой.
Парный bootstrap для разности — Когда две модели оценены на одних и тех же пользователях, сравнение парное. Два отдельных доверительных интервала для двух долей — это не интервал для их разности: непересекающиеся интервалы достаточны, но не необходимы, а пересекающиеся ничего не доказывают. Правильно — ресэмплировать пользователей совместно и на каждой реплике считать саму разность Δ. Именно так посчитаны интервалы в таблице парных дельт, и именно поэтому мы больше не пишем, что бустинг «обходит co-visitation»: разница там — около одного попадания, и её интервал накрывает ноль.

Как это связано с остальным учебником

Это Фаза 8 — первая ступень полного capstone: класс ранкера обновлён, фичи и протокол — те же, что в D2b и воронке E3. Следующие ступени: B4 feature store (фичи становятся главным активом — им нужна инфраструктура: point-in-time корректность, train/serve consistency; утечку такого рода мы уже ловили в D2b) и B6 re-rank constraints (квоты, eligibility, разнообразие поверх ранкера). Потом — сборка полного capstone.

⚠️ Что может пойти не так

  • Судить апгрейд модели по агрегированной метрике — она складывает выигрыш на одном срезе с проигрышем на другом и показывает их сумму; сравнение классов моделей без срезов почти бессмысленно.
  • Приписывать смене класса модели дельту ступени, где класс не менялся: добавление попарных произведений — это другой эксперимент, про представление признаков.
  • Сравнивать модели по двум отдельным доверительным интервалам вместо интервала для их разности — при общей eval-когорте нужен парный bootstrap по пользователям.
  • Подбирать число итераций (или любой другой гиперпараметр) по финальной метрике — это выбор модели по тесту; у широкой логрегрессии именно так и получался «прирост», который не пережил объявленного правила останова.
  • Читать permutation importance как вклад в Recall@10 — она посчитана на pair-level objective, на не отложенной выборке, и коррелированные фичи вдобавок делят важность произвольно (recency/seen/personal_freq здесь сильно связаны).
  • Путать «новое для пользователя» с «новым в каталоге»: наш novel-срез — про таргет вне истории юзера; холодный АЙТЕМ (без взаимодействий вообще) — это B5, другая задача.
  • Забывать, что у срезов разные потолки ретривала: на новых местах до ранкера доезжает меньше половины таргетов, и сравнивать его результат надо с этим потолком, а не с единицей.
  • Ждать от смены класса модели прорыва сквозь потолок retrieval — он не пробивается ранкером в принципе; хочешь выше — чини кандидатов (E3).
  • Тащить нейро-ранкер на маленькую табличку, потому что «в статьях DeepFM» — сначала бустинг-baseline; нейро платит при тысячах фичей и id-эмбеддингах.
  • Забыть про стоимость: GBDT дороже логрега в обучении и тюнинге, а офлайн-прирост в доли процента может не окупить сложность — решает продукт, не лидерборд.

🧠 Проверь себя: Бустинг дал доли процента к агрегированному Recall@10 против логрега. Менеджер спрашивает: «стоит ли катить?» Какой ответ правильный?

Где это в дорожной карте

Фаза 8 — Feature-rich ranking, ступень 1 из 3 на пути к полному capstone (дальше B4 feature store → B6 re-rank constraints → сборка). Теоретическая лестница фазы — LambdaMART и нейро-ранкеры — намечена выше; практическая ценность здесь — дисциплина сравнения: разделить оси апгрейда, выписать контракт моделей, ничего не выбирать по тесту, мерить по срезам и парным интервалом, учесть стоимость.

Что дальше

До финальной сборки не хватает ещё 7 модулей по этому пути.

Порядок здесь — рекомендация из карты курса, ничего не блокируется. Отметка «прочитано» хранится только в этом браузере.

Источники