- BrainTools - https://www.braintools.ru -

Ранжирование рекомендаций на главной Авито: как мы перешли от CatBoost к нейросетям

Всем привет! Меня зовут Ярослав Хныков, я DS Tech Lead в Авито [1]. Наша команда отвечает за рекомендации на главной странице Авито.

В статье расскажу, как мы шли от CatBoost к нейросетевому ранжированию: какие данные брали, почему первые АБ-тесты уходили в минус и что дало резкий прирост качества.

Будет полезно ML-инженерам и тимлидам, которые работают с ранжированием и рекомендательными системами.

В этой статье

Ранжирование рекомендаций на главной Авито: как мы перешли от CatBoost к нейросетям - 1

Как устроена лента рекомендаций Авито

Рекомендации на главной — первая точка касания пользователя с площадкой. На них приходится примерно половина всех просмотров объявлений и около 30% контактов покупателей с продавцами.

Устроено всё привычно. Когда приходит пользователь: 

1️⃣ Обращаемся к движкам кандидатогенерации — каждый нацелен на свой пользовательский сигнал — и из сотен миллионов объявлений получаем несколько тысяч самых релевантных. 

2️⃣ Обогащаем их признаками объявления и признаками пользователя, затем ранжируем. 

3️⃣ Отправляем ранжированную выдачу на слой диверсификации, который даёт финальное разнообразие ленты. Про это я писал в статье «Нейросетевая модель интересов пользователя» [9].

ML-архитектура рекомендаций

ML-архитектура рекомендаций

Почему CatBoost перестал давать рост

CatBoost появился в рекомендациях Авито в 2020 году. Вначале это была простая модель, но к 2026 году она сильно развилась.

2020

2026

Датасет

80 тыс. выдач

больше 1 млн выдач

Таргет

бинарный: был ли просмотр

составной, из целевых действий

Лосс

классификационный

ранжирующий

Признаки

19 простых счётчиков

около 200: признаки над эмбеддингами и нейросетевые предсказания

Несмотря на это, мы упёрлись в три ограничения:

🔴 Эффект «насыщения». Пробовали брать кратно больше данных, но качество не росло.

🔴 Эмбеддинги использовались не полностью. Появляется много мощных эмбеддингов для айтемов и пользователей. Но прямо подать их в модель нельзя, приходилось сжимать в dot-product’ы и счётчики — это субоптимально.

🔴 Ручные признаки стали узким местом. Чтобы подавать в CatBoost новый сигнал, нужно генерить много ручных признаков. Это неудобно, и здесь мы снова теряем часть информации.

💡 Нейросети позволяют обойти часть этих ограничений, поэтому мы посмотрели в их сторону. Предположение простое: есть много данных и хорошие эмбеддинги — обучим модель, и за счёт скейлинга она обыграет CatBoost. Забегая вперёд: получилось, но далеко не с первой попытки.

Тут еще больше контента

Как готовили данные для нейросети

За основу взяли датасет CatBoost — буквально показы объявлений на главной и таргет, который собирается из действий после показа. Сразу масштабировали его в 10 раз: до 10 миллионов выдач. Мы верили, что потенциал нейросети раскроется только на больших данных.

Дальше начинается то, чего с CatBoost делать не приходилось: весь препроцессинг у него под капотом, а для нейросети признаки нужно готовить руками. Это серьёзный недостаток. 

Вот как мы с ним живём:

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

В модели каждая фича смотрит в свою обучаемую эмбеддинг-таблицу по этому индексу. Трюки с хешированием нам не нужны: категориальных фичей немного и кардинальность у каждой невысокая.

Ранжирование рекомендаций на главной Авито: как мы перешли от CatBoost к нейросетям - 4

Вещественные признаки. Если подать их в сыром виде, столкнёмся с разным масштабом, хвостами и выбросами — надеяться, что модель под капотом обучит слой нормализации и окажется устойчивой к выбросам, рискованно. 

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

В датасете храним простую тройку: индекс левого бина, индекс правого и вес. Внутри модели интерполируем номер бина и нормализуем его на количество бинов для этой фичи. Получается число от 0 до 1, при этом сохраняется монотонная связь с исходным признаком: растёт признак — растёт закодированное значение.

Ранжирование рекомендаций на главной Авито: как мы перешли от CatBoost к нейросетям - 5

Эмбеддинги. Хранить сырые вектора в датасете накладно. Здесь выручает наблюдение, что один и тот же айтем повторяется между выдачами — он может встречаться в десятках и сотнях тысяч.

Поэтому вектора уникальных айтемов мы выносим в отдельный memmap-массив, а в датасете храним лишь индексы. В модели для каждой версии эмбеддинга используем собственную проекцию: версий много, и проекция нужна, чтобы привести всё в единую размерность и единое пространство.

Ранжирование рекомендаций на главной Авито: как мы перешли от CatBoost к нейросетям - 6

Пять итераций от MVP до прода

Модель №1. Быстро запустились. Мы решили, что в 2026 году нет смысла имплементировать всё самостоятельно, и попросили ИИ-агента набросать первую версию. Он сразу реализовал сложный энкодинг фичей и завёз DCNv2 со смесью экспертов и low-rank аппроксимациями. Обучили на бинарную кросс-энтропию — результаты оказались ужасными. 

Модель сильно проигрывала CatBoost, и из-за начальной сложности мы не могли понять, что пошло не так: проблема в препроцессинге или в модели? Оценить вклад каждой компоненты было нечем.

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

Для первых экспериментов мы использовали ONNX Runtime прямо внутри нашего сервиса на Go и поняли, что отскорить нейронкой все 5 000 кандидатов со всеми признаками и эмбеддингами не сможем.

Модель 1: быстрый старт

Модель 1: быстрый старт

Так появилась схема, которая живёт у нас до сих пор: CatBoost работает на L2 и отдаёт топ-300 лучших айтемов, а нейроранкер на L3 меняет порядок внутри этого топа. 

Мы используем раннее связывание кандидатов и признаков — по опыту [11] оно ощутимо мощнее позднего, которое применяем в других моделях. Но у раннего связывания есть цена: посчитать его для очень большого пула кандидатов не получится, поэтому нейроранкер и работает только с топ-300, которые предварительно отобрал CatBoost.

На latency это тоже сказалось: инференс модели добавлял около 30 мс — не так много. Позже мы перенесли модель в Triton на GPU: инференс там быстрее, хотя появляются накладные расходы на передачу тензоров по сети. В сумме по всему пайплайну это добавило порядка 6% к latency.

🏁 Первый А/Б-тест показал ожидаемое: 

  • −3% NDCG в офлайне 

  • −6% пользователей с контактами из рекомендаций в онлайне

Зато мы проверили продакшн-контур, закрыли интеграционные баги, получили первый бейзлайн и пошли его улучшать.

Модель №2. Убрали лишнюю сложность. Начали с максимально простой архитектуры: вещественные и категориальные признаки, а поверх них — обычный MLP. На наших данных такая модель показала себя лучше, чем более сложная архитектура из предыдущей итерации.

Заодно поменяли лосс. Бинарная кросс-энтропия оптимизирует скор каждого кандидата отдельно, а в рекомендациях важен не скор конкретного кандидата, а итоговый порядок всей выдачи. Поэтому перешли на listwise-лосс. В статьях он встречается под названием ListMLE. 

Идея простая: сортируем кандидатов по значению таргета по убыванию. На первом шаге модель должна выбрать лучший объект, на втором — лучший среди оставшихся, и так далее. Это можно представить как последовательную кросс-энтропию: на каждом шаге softmax задаёт распределение вероятностей по ещё не выбранным кандидатам. При этом сам лосс считается без итераций — мы заранее строим треугольную маску оставшихся айтемов и вычисляем все softmax-шаги параллельно.

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

🏁 Результаты второго А/Б:

  •  −1,5% NDCG

  •  −3% пользователей с контактами

Мы всё ещё заметно проигрывали, но офлайн улучшился — и онлайн потянулся за ним. Важный для нас шаг: значит, гипотезы можно проверять офлайн, и проверять их надо последовательно.

Модель 2: убрали лишнюю сложность

Модель 2: убрали лишнюю сложность

Модель №3. Встроили эмбеддинги. Мы поддержали проекцию для каждой версии эмбеддинга и задизайнили слой попарных интеракций, внутри которого считали всевозможные dot-product’ы попавших туда векторов.

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

🏁 И это сработало неплохо: 

  • В офлайне модель впервые обошла CatBoost: +0,7% NDCG

  • Целевая метрика — количество пользователей с контактами — снова упала, на 1,8%

  • Позитивный сигнал: пользователей с целевыми действиями, куда помимо контактов входит, например, добавление в избранное, стало больше на 0,7%

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

🏁 Смотрим офлайн-метрики в бизнес-разрезах:

  • −1,9% NDCG по контактам

  • +0,8% по целевым действиям

Контакты мы всё ещё ранжируем хуже CatBoost, а тотальная метрика растёт за счёт лучшего ранжирования добавлений в избранное — их в датасете намного больше. 

Вывод: смотрите на метрики в разрезах, иначе тотал замаскирует проблему с главной бизнес-метрикой.

Модель 3: важно смотреть на разрезы

Модель 3: важно смотреть на разрезы

Модель №4. Отбалансировали контакты. Оказалось, что если выкинуть хитрый слой попарных интеракций и подавать эмбеддинги напрямую в голову, тотальное качество не улучшается, но разрез по контактам растёт.

Дополнительно внедрили в лосс веса, которые сильнее штрафуют за неправильное ранжирование контактов. Game changer’ом это не стало, но офлайн-метрики баланса чуть улучшило.

🏁 В четвёртом А/Б модель наконец сравнялась с CatBoost: 

  • −0,6% пользователей с контактами

  • +0,6% с целевыми действиями

Контакты вышли в серую зону. Мы построили нейросеть, но сильного скачка качества не было.

В этом же АБ проверили ещё один подход: та же модель, но в топ-300 мы смешали ранги CatBoost и нейроранкера, чтобы получить финальное ранжирование. Приём известный по соревнованиям: модели разной природы хорошо компенсируют ошибки [12] друг друга, и при блендинге общее качество, как правило, растёт.

🏁 Метрики потянулись: 

  • +0,7% пользователей с контактами 

  • +0,9% пользователей с целевыми действиями из рекомендаций

  • +0,4% пользователей с целевыми действиями по всему Авито 

Результат скромный, но такое изменение мы решили раскатить. У него была и важная инженерная подоплёка. Коэффициенты мы осознанно не подбирали — хотели сохранить вклад каждой модели одинаковым, иначе получилась бы сильная завязка на конкретные версии. А так CatBoost и нейроранкер можно развивать независимо: улучшаешь любую из них — сразу видишь продуктовый эффект, а удачные идеи по фичам и датасетам переносятся из одной модели в другую. И всё это время у нас остаётся стабильный продакшн-вариант.

Также отбалансировали loss: добавили веса и повысили значимость контактов. Изменение не дало значимого роста total NDCG, но улучшило баланс action/contact

Также отбалансировали loss: добавили веса и повысили значимость контактов. Изменение не дало значимого роста total NDCG, но улучшило баланс action/contact

Модель №5. Добавили историю пользователя в трансформер. История пользователя — вероятно, ключевой сигнал для рекомендаций. Один и тот же айтем может быть и хорошим, и плохим в зависимости от того, что человек недавно смотрел и чем интересовался. А у нас теперь есть нейросеть — ей можно скормить последовательность событий и получить представление каждого кандидата, которое зависит от истории.

Схема модели 5

Схема модели 5

Сначала мы отфильтровали историю до полезных целевых действий и в качестве бейзлайна ограничили её ёмкость 50 событиями. Клики добавлять не стали: они могли бы вытеснить из истории более значимые сигналы пользовательского интента.

Каждое событие в истории связано с конкретным айтемом. Например, пользователь мог проконтактировать по объявлению о конкретной собаке. Для такого события у нас есть эмбеддинги айтема, информация о его позиции в истории, а также дополнительные признаки. Например, тип события и источник перехода: рекомендации, поиск или внешний трафик.

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

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

💡 Дальше — трюк, который и делает всю схему рабочей. Мы склеиваем историю и кандидатов в один общий вход трансформера и, прогнав его, получаем новые скрытые состояния кандидатов, уже обусловленные на историю пользователя. Это позволяет не прогонять трансформер под каждого кандидата отдельно — хватает одного прогона на всю выдачу.

История и кандидаты в одном трансформере

История и кандидаты в одном трансформере

Отдельный интересный момент — attention. Чтобы получить представление кандидата, которое зависит от истории, мы позволили кандидатам смотреть на историю. Но заметили, что если позволять кандидатам смотреть ещё и друг на друга, качество модели тоже растёт — примерно на 0,6%.

Кандидаты получают контекст истории

Кандидаты получают контекст истории

Теперь предсказание для каждого кандидата зависит не только от него самого, но и от конкурентного контекста: какие ещё объявления есть в выдаче, среди каких альтернатив модель выбирает. Магии здесь нет — это обычный self-attention с маской.

Новое представление кандидатов, которые знают об истории, уходит в параллельную версию DCN, а учится модель на все тот же лосс — ListMLE. 

Масштаб модели сейчас скромный — около 2,5 млн параметров. В ближайшие полгода выходить на сотни миллионов не планируем: сначала хотим выжать максимум из архитектуры.

Жми сюда!

Что получилось в онлайне

В офлайн-метриках мы видели сильный рост:

  •  +7,2% NDCG по целевым действиям

  • +4,9% по контактам

Он перешёл в онлайн:

  • +4,3% пользователей с контактами из рекомендаций

  • +3,3% пользователей с целевыми действиями

  • +0,8% пользователей, совершивших сделку

Последнее — самое важное. Эффект пророс в общую метрику Авито. Это одно из самых эффективных изменений в рекомендациях за последние годы.

История дала скачок качества

История дала скачок качества

Три вывода, которые можно взять

💡 Нейросети дают заметный эффект, если их правильно готовить. Придётся масштабировать датасет, настраивать признаки, подбирать лосс и адаптировать архитектуру под ваши данные — ручной работы здесь больше, чем с CatBoost.

💡 Офлайн-метрики должны вести к онлайн-результату. Для нейросетей это особенно важно: они гибкие, поэтому появляется много степеней свободы и гипотез, которые хочется проверить. Все гипотезы в онлайне проверить невозможно. Поэтому на ранних этапах важно понять, есть ли мэтч между офлайном и онлайном, — а если нет, разбираться в датасетах и особенностях метрик.

💡 Начать лучше с простых идей. Так появляется лучшее понимание того, как развивается технология. И, скорее всего, в продакшн не попадёт избыточно сложное решение там, где ту же задачу можно решить проще.

Вся статья кратко

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

Переход на нейросети пришлось делать постепенно: от простого MLP и ListMLE до эмбеддингов, блендинга с CatBoost и полноценного нейроранкера.

Главный скачок дала история пользователя: 50 последних целевых событий вместе с кандидатами подаются в трансформер, а attention учитывает и историю, и контекст выдачи.

В онлайне модель дала +4,3% пользователей с контактами из рекомендаций и +0,8% пользователей, совершивших сделку.

Мы пишем про Data Science не только на Хабре, но и в телеграме. Переходите в канал «Доска AI-объявлений» [14] — там много интересного про DS в Авито.

Кликни здесь и узнаешь

Автор: Yaroslav_Khnykov

Источник [16]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/36647

URLs in this post:

[1] Авито: https://clc.to/LV3FBg

[2] Как устроена лента рекомендаций Авито: #section1

[3] Почему CatBoost перестал давать рост: #section2

[4] Как мы готовили данные для нейросети: #section3

[5] Пять итераций от MVP до прода: #section4

[6] Что получилось в онлайне: #section5

[7] Три вывода, которые можно взять: #section6

[8] Вся статья кратко: #section7

[9] «Нейросетевая модель интересов пользователя»: https://habr.com/ru/companies/avito/articles/974682/

[10] Тут еще больше контента: https://telegram.me/+ShQQPXymxoViNzFi

[11] опыту: http://www.braintools.ru/article/6952

[12] ошибки: http://www.braintools.ru/article/4192

[13] Жми сюда!: https://clc.to/MDY_jw

[14] «Доска AI-объявлений»: https://t.me/DS_avitotech

[15] Кликни здесь и узнаешь: https://clc.to/vtMlJg

[16] Источник: https://habr.com/ru/companies/avito/articles/1090584/?utm_campaign=1090584&utm_source=habrahabr&utm_medium=rss

www.BrainTools.ru

Rambler's Top100