Привет! С вами Ярослав Хныков — senior ML engineer в Авито. В статье расскажу, как мы повысили разнообразие и релевантность рекомендаций на главной странице.
Покажу, как появляется выдача с однотипными рекомендациями, чем здесь помогает простой «блендер» категорий и как мы прокачали его с помощью модели интересов пользователя, основанной на трансформерах. В конце — результаты A/B-тестов, метрики и рекомендации, которые вы сможете забрать к себе в продукт.
Статья будет особенно интересна специалистам, которые работают с рекомендательными системами.

Содержание
Рекомендации на главной Авито: как всё устроено и какие возникают проблемы
Рекомендации на главной странице — это бесконечная персональная лента объявлений. Через неё проходит примерно 50% всех просмотров и 30% общего числа контактов покупателей с продавцами. Это первая точка входа и место, где легко «залипнуть», — но только если лента не превращается в однообразный поток похожих карточек.
Как выглядит ML-архитектура на главной Авито:
двухуровневая система: кандидатогенерация → ранжирование;
5 кандидатогенераторов: 1 работает офлайн и реагирует на последние действия пользователя с задержкой в несколько часов, а 4 действуют онлайн и подстраиваются мгновенно;
финальное ранжирование CatBoost с богатым набором фич: от простых счётчиков до скорингов трансформенных моделей.

Часто такой системы достаточно, чтобы показать действительно качественные рекомендации. Но есть проблема — «склейка» однотипных карточек объявлений.
Если мы просто отсортируем предложения по скору ранжирования и отдадим это в качестве выдачи, пользователь столкнётся с достаточно однообразной лентой.
Дело в том, что ранжирующая модель скорит объявления независимо, поэтому похожие предложения получают очень близкие скоры и после сортировки «склеиваются» в блоки. При тысячах кандидатов на входе ранжирования весь топ легко забивается одной-двумя категориями. Из-за этого снижается суммарная полезность ленты.
Представьте, что вам на первой позиции показали Айфон, а на второй позиции вам показали тот же Айфон, но другого цвета. Тогда вся добавочная ценность второго предложения заключается только в смене цвета. Некоторые предложения могут и вовсе повторяться, а пользователю может быть интересен не только Айфон.
Что получаем в итоге:
Убывающая полезность: второй и последующие подряд Айфоны дают мизерную добавочную ценность.
Узкое покрытие интересов: другие потенциально релевантные объявления, которые могут закрыть потребности пользователя, не пробиваются в топ.
Снижение «интриги»: пользователь видит одно и то же. В итоге однотипная выдача может побудить его уйти из сервиса.
Чтобы улучшить ситуацию, мы решили дополнить систему выдачи рекомендаций дополнительным «слоем», направленным на повышение разнообразия.
Внедрили блендер категорий на основе интересов пользователя
Его цель — не «сломать» релевантность, а предотвратить склейку однотипных карточек.

Давайте обсудим принцип его работы:
1. Группируем объявления по категориям, сохраняя внутри категорий порядок, который задаётся моделью ранжирования. Для примера я взял три раздела: Питомцы, Недвижимость, Авто.
2. Считаем интерес пользователя к категориям. Интерес — это счётчик событий с затуханием по времени. Для его расчёта мы забираем историю пользователя из онлайн-хранилища истории. Каждому типу события назначаем вес в зависимости от его важности. Например, добавление в избранное — более важный показатель пользовательского интереса, чем клик.
Далее мы складываем эти веса, дополнительно добавляя экспоненциальный дисконт по времени. Таким образом, отдаём приоритет более свежим событиям, чтобы быстрее реагировать на смену пользовательских предпочтений.

3. Сэмплируем категорию из распределения категорийных интересов и забираем лучшее из доступных объявлений в ней.
4. Повторяем до заполнения ленты.
Разнообразие можно регулировать сглаживанием распределения — чем сильнее сглаживание, тем больше разнообразия получим на выходе.
Благодаря такому простому механизму мы из блочно-однотипной выдачи получили достаточно разнообразную выдачу по категориям.

Результаты A/B-теста:
Давайте посмотрим, что это даёт пользователю. Мы запустили простой A/B-тест, где сравнили базовую систему рекомендаций до и после применения блендера. В результате получили:
+2,5% пользователей, совершивших контакт с продавцом. На масштабах Авито — это сотни тысяч дополнительных покупателей в сутки.
+4% пользователей, которые совершили контакт в новой для себя категории.
Рост кросс-категорийности обеспечен тем, что мы начинаем чаще показывать в топе менее очевидные, но всё ещё релевантные для пользователя категории.
Ограничения формулы интересов
Пока улучшали формулу, столкнулись с определёнными ограничениями:
Она не работает для новых категорий. Интерес определён лишь для тех категорий, с которыми пользователь уже взаимодействовал. Новым для пользователя категориям приходится присваивать константный ненулевой вес, не учитывая их реальную популярность.
Не строит связи между категориями. Например, пользователь искал хомячка и даже уже успел купить его. Но блендер не «понимает», что пора показать клетки и корм. Комплементарность не моделируется.
Медленно переключает контекст. Если пользователь резко сместил фокус, интерес к новой теме долго «догоняет» старую из-за инерции накопления событий.
Сложно встраивать дополнительные факторы. Например, мы хотели также учитывать источник события. Действия с поисковой выдачей — более явный сигнал пользовательского интереса, так как пользователь сам предварительно указал свою потребность в поисковой строке. Однако встраивание дополнительного фактора сводится к дополнительному подбору веса, что не очень удобно.
Мы решили разработать ML-модель интересов пользователя, чтобы обойти ограничения блендера. Заменили ручную формулу на модель, предсказывающую распределение интересов по категориям — то есть сразу дающую «правильные» веса для блендера с учётом контекста.
Расскажу, как строился весь процесс.
Собрали датасет для обучения ML-модели
Ориентируемся на реальные моменты посещения главной. Что защищает нас от различных ликов.
В качестве инпута берём историю пользователя на момент посещения главной.
В качестве таргета берём пропорции целевых событий: добавление в избранное и контактные события по категориям в следующие 7 дней. С этим гиперпараметром можно экспериментировать, мы, например, остановились на компромиссном варианте — неделя.
Если взять слишком короткий промежуток, модель будет менее склонна предсказывать комплиментарные категории. Если же, наоборот, слишком длинный — модель начнёт скатываться в популярное, теряя связь с историей пользователя.

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

Выбрали ML-модель
В качестве модели решили использовать Transformer Encoder, потому что он:
гибкий с точки зрения добавления фичей и экспериментов с таргетами;
хорошо учитывает последовательный сигнал — важно в нашей задаче;
имеет довольно низкую задержку (latency) для использования в real-time: применение небольшого трансформера на CPU укладывается в 10–20 мс.

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

Позиция: используем стандартные обучаемые позиционные эмбеддинги. Единственная тонкость заключается в том, что мы кодируем историю от последнего события к первому. Мы знаем, что последние события — самые важные в истории пользователя, поэтому хотим сохранить для них некоторую инвариантность.
Возраст события: здесь мы хотели сохранить непрерывность из прежней формулы интересов, поэтому использовали следующий подход.
Возраст события в секундах (∆T) линейно интерполируется в диапазон: [-1, 1], где 1 — текущий момент, а -1 — некоторый момент в прошлом. В нашем случае — 2 месяца назад. В результате получается такая формула:
Это значение затем пропускается через небольшой MLP для обучения нелинейной функции затухания, а его вывод проецируется в общую размерность модели.

Чтобы закодировать событие полностью, нам достаточно всё сложить: эмбеддинги позиции, возраста и атрибуты. Далее нормируем результат и подаём в Transformer Encoder.

Основные параметры модели:
Параметр |
Значение |
Размерность вектора |
64 |
Максимальная длина истории |
512 событий |
Количество слоёв энкодера |
2 |
Число параметров |
Чуть больше 1 миллиона |
Функция активации |
HardSwish — даёт 1.5× ускорения на CPU |
Функция потерь |
Cross Entropy Loss по распределению категорий |
Сгладили предикты новой модели и запустили A/B-тест
Из-за того, что в обучающих данных покупатели за целевой период часто активны лишь в одной-двух категориях, предсказания модели получаются немного вырожденными.
Чтобы лента не становилась из-за этого однообразной, мы используем температурный софтмакс и подбираем температуру на реальных данных.

Температуру мы подбираем через имитацию продового трафика, чтобы выровняться по релевантности на реальных пользовательских выдачах.
Что увидели по метрикам
Валидация:
в рамках офлайн-валидации мы увидели рост метрик ранжирования категорий на 5–7% по сравнению с формулой-эвристикой, которую использовали ранее;
модель научилась строить более логичные связи между категориями. Например, после просмотра микрофона она определяет интерес к Музыкальным инструментам:

А просмотр футбольных бутс в категории Обувь говорит ей, что пользователю может быть интересен «Спорт и отдых»:

кроме того, модель корректно учитывает источники событий: данные из поиска оказывают большее влияние, чем из ленты рекомендаций.
A/B-тест:
Дополнительно к эвристике: +1% пользователей, совершивших контакт.
+0,6% пользователей, совершивших контакт в новой категории.
– 2,5% скрытий рекомендации с причиной «несоответствие категории» — при бóльшем разнообразии.
Метрики нас порадовали, и мы раскатили модель в продакшен.
Вся статья кратко
Давайте повторим:
Мы начали с простой проблемы: однотипная выдача на главной странице снижала полезность ленты и «сжимала» пространство пользовательских интересов.
На первом шаге мы внедрили блендер категорий — он дал ощутимый прирост: больше контактов, больше кросс-категорийных взаимодействий и заметно более разнообразная лента.
-
Затем, чтобы уйти от ограничений ручной формулы интересов и научиться учитывать контекст, связи между категориями и источник событий, мы перешли на модель интересов на базе Transformer Encoder.
Она компактная, работает на CPU в real-time, корректно кодирует историю пользователя и предсказывает распределение категорий, которое значительно улучшает работу блендера.
Итоги A/B-тестов подтвердили эффект:
рост контактов и взаимодействий в новых категориях;
снижение скрытий «несоответствие категории» при большем разнообразии;
улучшение офлайн-метрик ранжирования на 5–7%.
Ну и несколько важных уроков:
В реальном продукте важна не только средняя релевантность ваших рекомендаций, но и то, как вы представляете их пользователю.
В рекомендациях важно работать не только над релевантностью, но и над тем, как именно выдача собирается и показывается пользователю.
Простые решения дают быстрый прирост, а усложнение модели оправдано только тогда, когда даёт устойчивый выигрыш в метриках и качестве продукта.
Ещё больше контента на тему data science — в канале: «Доска AI-объявлений». Заходите, там интересно. А если хотите вместе с нами адаптироваться в мире стремительно развивающихся технологий — присоединяйтесь к командам. Свежие вакансии есть на нашем карьерном сайте.
Комментарии (12)

Dakar
09.12.2025 16:27А вы уже догадались убирать из рекомендаций заказанное/купленное?
И может не надо кидаться с рекомендациями по результатам одного открытого объявления? Так случайно или мимоходом откроешь какую-нибудь фигню и потом долго любуешься на поток такой фигни.

Devastator82
09.12.2025 16:27Можно на объявлении на главной щелкнуть на три точки и выбрать «Не интересует категория» и она исключится из рекомендации

Veddary
09.12.2025 16:27Из всего вышесказанного я лишний раз убедился, что Авито совершенно не заинтересован с максимальной точностью помочь найти и купить мне то, за чем я на него пришел. Вместо того, чтобы, наконец, нормально структурировать разделы (чего стоит никак не убираемая до сих пор смесь из комплектующих для ПК и ноутбуков в одном разделе - хотя это совсем разные товары с разной аудиторией) и повысить полезность сайта для целеустремленного покупателя, который ищет конкретную вещь - да тот же айфон нужного цвета, вы обеспечиваете максимальное разнообразие отображения мало полезной фигни. У вашего сайта явно какие-то тяжёлые галлюцинации о том, что он маркетплейс, а не доска объявлений.

criminalist
09.12.2025 16:27Вчера буквально сидел и 30 минут наблюдал картину как жена искала раздел подходящий ))
А кроме того:
Платные объявления которые не работают. Объективно, либо звонки не проходят, либо сообщения не доходят (тех поддержка вообще обычный bash скрипт).
За два с половиной года понял одно, тут рыбы нет, платные не платные, иксы и прочие просто не работает, возможно тематика, (моя чип тюнинг и диагностика авто)
Сколько пытаюсь добиться понимания, от поддержки один ответ, регион всему вина.

bromand
09.12.2025 16:27Как насчёт учёта времени просмотра и действий пользователя при открытии карточки? К примеру, если пользователь открыл карточку товара и сразу же её закрыл, то её можно не учитывать для последующего расчёта интереса. Но если пользователь открыл карточку, полистал информацию о товаре, открыл профиль продавца, то тогда начать отдавать больший приоритет категориям этого товара.
Возможно, запись времени просмотра карточек и действий пользователя будет проблематичным, но я считаю что это может ещё сильнее повысить эффективность вашего алгоритма.

Devastator82
09.12.2025 16:27На главной неплохой в целом результат, но зачем мне показывать на главной то что:
Я уже смотрел.
Я уже купил (купил конкретно этот товар у этого продавца с авито досиавкой).
То что кто-то купил с авито доставкой и я, следовательно, купить не смогу.
И еще вопрос. Не вы ли занимаетесь подборками, которые приходят в личные сообщения? Там вообще мрак. Процитирую свое старое сообщение:
Каждое лето я ищу квартиру у моря на лето для своей семьи. Я сохраняю поиск с датами, количеством комнат (не менее трех), наличием лифта, минимальной площадью кухни (не менее 12), конкретным городом (Геленджик). После этого мне начинает сыпаться от Авито «Вы такое искали»:
Студии в Сочи
Однушки в Новороссийске
Двушки в Ставрополе
Трешки в Геленджике (неужели?!) но недоступные на мои даты.
Про минимальные размеры кухни и лифт вообще молчу они просто игнорируются. Такое чувство, что вся предложка строится по принципу «квартира Краснодарский край».
Кстати, к главной это тоже относится. Если вы даете рекомендации по краткосрочной аренде - почему бы не учитывать даты поиска?

bkar
09.12.2025 16:27Я уже купил (купил конкретно этот товар у этого продавца с авито доставкой)
Знаком с одним анализом этой проблемы в одной очень специфической сфере (типо - есть ли смысл магазину свадебных платьев показывать свадебные платья той, которая только что уже одно купила). Оказывается, что очень даже может быть.
Для самых вовлеченных клиентов сайт магазина это прежде всего новостной портал, а не прайслист. И их очень и очень интересут, сколько стоит точно такое же, но с перламутровыми пуговицами. Или, какое такое же, но совсем и совершенно другое они могли бы купить за эти деньги. Причем, сами бы за такой информацией может и не зашли, но, если подкинуть, то залипают. Может, и не хотят жратьчтодают, но и перестать не могут.
Ну а тому, кто зашел на сайт, несоизмеримо дешевле показать ещё и что-то покупибельное, чем затащить его на сайт из просторов сети.

Devastator82
09.12.2025 16:27Спасибо за интересный кейс, но я в своем сообщении имел в виду ситуацию, когда я у условного Васи на Авито купил с доставкой единственный у него айфон. Вася его еще не отправил и объявление не снял. И вот мне в рекомендациях показывают васино объявление с фактически уже моим айфоном.

katletmedown
09.12.2025 16:27Расскажите, пожалуйста, подробнее о метриках, какие отслеживаете, как оцениваете важность разных метрик при принятии решения по А/Б.
coden12
Почему то иногда поиск принудительно сужается до какого-то раздела и из корневого не доступен. Переход в корневой можно вручную осуществить только в поисковой строке браузера.