Привет, Хабр! Меня зовут Данил Комаров, я дата-сайентист в команде персонализации Lamoda Tech. Уже больше года мы меняем подход к рекомендациям на главной странице, делая их персонализированными. Я расскажу, как мы внедряли и масштабировали решение, переводили его из оффлайна в онлайн, и бустили систему на разных слоях.

Изменения главной страницы и появление рекомендаций

Ежемесячно на Lamoda заходят 17 миллионов пользователей, которые делают сотни тысяч заказов, а мы оперируем более чем 700 000 товарами в стоке. Этот объем поддерживают более тысячи сотрудников Lamoda Tech, 40 из которых работают в отделе Data Science. Чтобы пользователи получали лучший опыт и возвращались к нам, мы постоянно развиваем продукт. Речь пойдет о главной странице, в которую мы внедрили персонализацию на основе интересов пользователя. 

Примерно так выглядела главная страница Lamoda до мая 2024 года. На тот момент на ней уже были элементы, привлекающие внимание — баннеры, сторис и дизайнерские подборки. 

Но нам, как крупному fashion-tech ритейлеру, не хватало персонализации контента в первой точке входа в приложение. Поэтому весной 2024 года мы задумались о создании рекомендаций на главной и уже в мае запустили первый А/В-тест с виджетом из 72 товаров на главных страницах — женской и мужской. Виджет располагался в верхней части главной и управлялся горизонтальным скроллом. 

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

  • АLS (Alternating Least Squares) — коллаборативная фильтрация,

  • Bestsellers — топ продаж за последние дни. 

Для АLS проводили обучение на четырех типах интеракций: 

  • посещение продуктовых страниц — product_page

  • добавление в избранное — fav_add

  • добавление в корзину — cart_add,

  • заказы — order.

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

Обучив ALS, мы получили эмбеддинги пользователей и эмбеддинги товаров. По этим сущностям нам нужно было получить рекомендации. Напомню, что мы имеем большой каталог товаров, поэтому искать точным поиском соседей рекомендации для пользователя не представлялось возможным. Поэтому мы проводили инференс с помощью приближенного поиска соседей — HNSW (Hierarchical Navigable Small World)

Помимо ALS использовали второй источник кандидатов — бестселлеры, то есть топ продаж за последние дни.

Смешав оба движка, мы получили жадно отобранную и неразнообразную выдачу. Чтобы исправить ситуацию, приняли решение создать отдельный бизнес-слой с двумя компонентами: 

  • диверсификация с помощью подхода MMR (Maximal Marginal Relevance)

  • бизнес-логика на некоторые позиции в ленте. 

Мы настроили выполнение пайплайна по расписанию: один раз в сутки рассчитывали рекомендации в оффлайне и заливали в наше key-value хранилище.

В A/B-тесте мы не получили прокрас нашей целевой метрики — покупок, но увидели прокрас прокси-метрики, увеличив конверсию добавления в корзину на 0,3%.

Выводы:

  • Мы увидели заинтересованность пользователей в подборке и изменение их взаимодействия с нашим сервисом. 

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

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

  • UI виджета несовершенен: не все пользователи понимали, как его использовать и листать товары. 

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

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

На ней появилась полноценная персональная лента рекомендаций, а сторис и рекламные баннеры превратились в небольшие вставки между товарной лентой. 

Мы запустили A/B-тест старой главной против новой и получили очень хорошие прокрасы по конверсии добавления в корзину — +1,2%, и по конверсии в покупку — +1,1%.

Отдельно отмечу, что время между сессиями в приложении сократилось почти на целый процент! Пользователи стали чаще возвращаться на Lamoda, а это значит, что рекомендации на главной помогли  изменить не только их взаимодействие с сервисом, но и возвращаемость к нам (retention). 

В начале осени 2024 года мы раскатили новую главную на 100% наших пользователей.

Масштабирование и переход части системы в онлайн 

Если посмотреть на верхний таббар в нашем приложении, можно заметить, что на самом деле у нас не одна, а целых шесть главных страниц — «Все», «Премиум», «Дизайнеры», «Спорт», «Красота» и «Дом», и они отличаются для гендеров женщин и мужчин.

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

Сделали это с таким условием, чтобы в следующей итерации посоревноваться в A/B-тесте между бестселлерами и нашим рекомендательным алгоритмом на других табах. 

Категорийное разнообразие кандидатов

Начиная переиспользовать подход для других табов главных, мы столкнулись с классической проблемой категорийного разнообразия. Например, в категории «Спорт» доля обуви оказалась выше в два раза, чем в среднем по всему стоку.

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

Среди разных способов решения выбрали наиболее простой — квотирование. Мы выделили определенную квоту под кандидатов из каждой категории самого высокого уровня, и начали смотреть, какие категории пользователи посещали в разделе «Спорт» за последние N дней. На основе этого сформировали места в итоговую квоту для кандидатов: 50% одежды, 30% обуви, 10% аксессуаров и 10% сумок. 

В конечном счете мы объединили два подхода — квотирование и диверсификацию с помощью MMR. 

Мы получили разнообразные ленты, удовлетворяющие нашим потребностям, и после этого запустили A/B-тест, сопоставив бестселлеры и наш пайплайн по формированию рекомендаций. 

Мы получили хорошие прокрасы: конверсию добавления в корзину увеличили на 0,4%, а конверсию в покупку на 0,5%. Этот тест помог нам выровняться по алгоритму на всех главных страницах, чего мы и добивались.

Добавление реранкера

До настоящего момента я никак не упоминал тяжелое ранжирование, оно же модель второго уровня или реранкер. Дело в том, что у нас есть retrieval-часть и бизнес-логика с диверсификацией, но мы никак не учитывали такие типы фичей, как пользовательские, товарные и парные. А это важные сигналы о том, как сделать рекомендации гораздо мощнее. Поэтому мы и начали разрабатывать реранкер — сначала в оффлайне (с инференсом в батч режиме 1 раз в сутки по расписанию).

Оффлайн-реранкер, постановка обучения

  1. Мы собрали данные для обучения с фронтенда. В момент, когда пользователь заходит в приложение на главную, к нам прилетают разные виды событий. Одними из них являются просмотры (“impressions”). Именно они стали нашими группами для ранжирования. 

  2. Собрали товарные, пользовательские и парные признаки из фича-стора. На тот момент у нас уже были регулярные джобы, которые считали ежедневно разные виды признаков с нашего сервиса.

  3. Таргет: добавление в корзину. Это своего рода прокси к заказам и покупкам. Таких сигналов много, поэтому они отлично подошли для нашей первой итерации обучения. 

  4. Оффлайн-метрика: NDCG@60.

Для обучения мы использовали CatBoost с Pairwise Yetirank loss, и в результате получили 22% прироста по метрике относительно прода.

Посмотрев на важность признаков (Feature importance), мы заметили, какие из них оказались важными для реранкера — многие ожидаемо проникли в топ:

  1. Ранг ALS

  2. Количество отзывов

  3. Посещение товара за последние N дней

  4. Время жизни товара

  5. Интерес к цвету

  6. Интерес к бренду

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

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

Мы снова запустили A/B-тест – теперь уже с реранкером. В итоге получили приросты метрик, сопоставимые с запуском главной страницы, прирост конверсии добавления в корзину составил +0,7%, а конверсии в покупку — +1%.

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

Переход в онлайн

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

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

  2. Мы были ограничены функциональностью рекомендаций и никак не ловили онлайн-контекст. Каждый день пересчитывали рекомендации с учетом новой истории, но в течение нового дня в  каждой сессии пользователь совершал действия, которые мы могли учесть только на следующий день.

  3. Потому что могли :) К этому времени у нас в каталоге уже был онлайн-реранкер, а также реранкер в других полках рекомендаций. Конечно, это стоило переиспользовать. 

Стоит описать, как выглядело взаимодействие сервиса и пользователя до появления онлайн-слоя. Когда пользователь открывал приложение Lamoda, с устройств шел запрос в сервис рекомендаций. Тот забирал уже готовую подборку из нашего key-value хранилища Aerospike, обрабатывал и фильтровал ее при необходимости, и отдавал снова на устройства для отображения пользователю.

Теперь же у нас добавился новый сервис – Recommend Reranker. Это backend-сервис на Go, который внутри себя использует нашу самописную библиотеку Reranker. В этом сервисе происходит много разных этапов для переранжирования ленты, но отмечу две наиболее важные компоненты:

  • онлайн-инференс CatBoost-модели, в результате которого получаем список товаров-кандидатов, отранжированный по убыванию скоров,

  • MMR-диверсификация — по аналогии с оффлайном. 

В качестве оффлайн-обвязки вокруг этого у нас есть достаточно большой Hadoop-кластер. На нем ежедневно считаются разные фичи, обучаются модели генерации кандидатов и учатся разные версии реранкеров. Все  эти процессы шедулятся в Airflow. Артефакты выполнения всех джоб мы загружаем в наши key-value хранилища Aerospike или Redis, а также заливаем бинарные файлы обученной модели CatBoost в бакет S3 и ключ версии модели в etcd. 

Путь к онлайн-персонализации

Быстрая победа, или внедрение реактивных кандидатов

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

Теперь рассмотрим, как это мы реализовали у себя.

К уже существующим сервисам рекомендаций и реранкера мы добавили  еще один сервис под названием Prism. Он написан на Go и содержит в себе много полезных функциональностей, одна из которых API онлайн профиля пользователя. Этот API позволяет по запросу получить самую актуальную онлайн-информацию — например, какие последние страницы посещал пользователь, какие товары добавил в корзину и так далее. Зная эту информацию, мы можем сформировать тех самых реактивных кандидатов.

Как мы формировали реактивных кандидатов:

  1. Взяли последние посещенные товары пользователем из API профиля пользователя.

  2. По этим товарам из key-value хранилища достали товары, похожие на последние посещенные.

  3. Засэмплировали и добавили эти похожие товары в общий список всех кандидатов.

  4. Отправили всех кандидатов в реранкер.

Запустили A/B-тест с добавлением реактивных кандидатов и увидели прокрасы не только метрик верхней воронки из рекомендательной ленты на главной, но и целевых денежных метрик.

После того, как мы получили быструю победу от внедрения такого решения, решили раскатывать функциональность на 100% аудитории пользователей.

После переезда реранкера на онлайн-рельсы мы открыли себе путь к онлайн-персонализации не только на уровне кандидатов, но и на уровне фичей на этапе ранжирования. Однако на нашем пути лежал один барьер.

Барьер перехода к онлайн-персонализации

Для начала стоит пояснить, как мы собирали датасет для обучения реранкера. У нас есть кликстрим — можно сказать, это все логи приложения и сайта, которые генерируют ежедневно наши пользователи. В этом большом кликстриме есть “impressions”, то есть просмотры определенного блока приложения. Когда пользователь заходит на главную страницу, с устройств к нам начинают лететь те самые события просмотров товаров в ленте рекомендаций. Для нас это будущие группы для ранжирования. 

Помимо этого ежедневно в Airflow у нас запускаются джобы, которые считают разные виды признаков по пользователям и товарам, результат которых складывается в наш Feature Storage.

В итоге события просмотров мы соединяем с признаками и получаем датасет для обучения реранкера.

Давайте разберем плюсы и минусы такого подхода.

Плюс (он один):

  • Рабочий подход для старта системы. Мы жили с этим подходом с самого начала, и до какого-то времени он действительно работал. Простая и понятная механика позволяет собрать датасет для обучения ранжирующих моделей и сразу приносить эффекты от внедрения дополнительного уровня в систему.

А теперь минусы:

  • Фичи на обучении могут отличаться от тех, что приходят в момент запроса на бэкенде. Если посмотреть на картинку выше, можно заметить, что мы делаем join кликстрима с фичами по конкретной дате расчета. Все фичи из Feature Storage также проливаются в key-value хранилище, в которое ходят бэкенд-сервисы. Если пользователь заходит на главную в разное время дня, фичи в момент каждого нового запроса могут отличаться друг от друга (например, залились новые фичи или какие-то пришли Null-ами). В этот момент то, на что мы обучаем и то, на что применяем модель, начинает отличаться — так делать не очень хорошо. 

  • Мы ограничены добавлением новых онлайн-фичей. Датасет при таком способе, как описано выше, состоял исключительно из фичей, посчитанных в оффлайне по истории взаимодействия пользователя с сервисом. А все самые горячие онлайн-фичи обычно считаются на бэкенде, что мы никак не могли учесть.

Эти нюансы натолкнули нас на изменение парадигмы работы с фичами.

Обучение реранкера на залогированных фичах

Как стала выглядеть механика сбора датасета для обучения:

  • Данные для обучения (просмотры) мы также продолжили собирать с устройств. 

  • Фичи для обучения начали логировать на бэкенде и перетаскивать в Hadoop. 

Архитектурно это выглядит так: 

  1. Внутри сервиса Recommend Reranker мы научились формировать сообщения — в нашем случае это просто большие json-файлы с набором фичей и всей мета-информацией в момент запуска модели.

  2. Эти сообщения отправляем в один из топиков нашего кластера Kafka. 

  3. На топик Kafka подписан Spark Streaming, который батчами вычитывает эти сообщения и перекладывает в Hadoop. 

  4. В Hadoop над этими данными проводятся data quality проверки, и после данные вытягиваются в плоские таблицы с необходимыми полями. Теперь эти таблицы являются источниками фичей для обучения, в них находятся те самые фичи, которые были в момент запроса и скоринга модели на бэкенде. 

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

А теперь снова вернемся к оффлайн-уровню. 

Улучшение оффлайн-уровня кандидатов

Система с новыми кандидатами стала выглядеть так — из общего стока происходит оффлайн отбор кандидатов, а далее идет онлайн-слой: добавление реактивных кандидатов, реранкер и бизнес-слой с диверсификацией. 

Но одного движка на оффлайн-уровне мало, нам хотелось больше и мы добавили еще два: SANSA и SASRec. Про SASRec опустим: мы еще экспериментируем с ним, скоро будем запускать тесты. А вот SANSA рассмотрим подробнее.

При упоминании автоэнкодеров в рекомендациях первым делом на ум приходит EASE — известная в индустрии модель. Почему мы не используем ее?

Ответ простой: у нас слишком много товаров в каталоге. EASE способна работать в каталогах до нескольких десятков тысяч айтемов. Далее ей становится тяжело: она очень требовательна к оперативной памяти машины, на которой вычисляется, что приводит к проблемам обучения. 

Ребята из компании GLAMI решили эту проблему: придумали модель SANSA — Scalable Approximate NonSymmetric Autoencoder.

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

Ознакомиться с кодом модели можно на GitHub.

SANSA: персональные товарные кандидаты, постановка обучения 

  • Постановка: user х item, как в целом в ALS, обучение в разрезе gender x country. 

  • Глубина данных: шесть месяцев.

  • Интеракции: бинарные, определяем, был добавлен товар в корзину или нет. 

  • Оффлайн-метрика: Recall@200. 

Мы увидели прирост по метрикам около 10% относительно продового ALS. И конечно, захотели сразу провести А/В-тест. 

Здесь хочу подсветить то, как можно сравнивать кандидатов на уровне оффлайна. Если вы хотите сравнить оффлайн-движок на проде и новый разработанный алгоритм, без глобального контроля или более хитрых способов сделать это практически невозможно. У продового движка будут заведомо завышенные метрики. Как с этим бороться?

Почти никак. Если вы получаете на новом движке метрики, сопоставимые с теми, что крутятся в проде, движок точно следует тестировать в A/B, это может принести результаты. Не менее важно смотреть и на то, как меняется разнообразие брендов в новом движке и насколько популярны те айтемы, которые появляются. Если новый движок начинает подкидывать в рекомендации не очень популярные, но персонализированные товары, это тоже здорово — у SANSA есть такой интересный эффект по сравнению с тем же ALS. 

Дополнительные применения SANSA

SANSA очень гибкая и легкая модель, которую можно обучать в разных постановках, не только User-To-Item, а, например, User-To-Brand, чтобы предсказывать комплиментарные бренды. Для нас это важно, так как мы собираем не только историю пользователя по самим товарам, но и его взаимодействие с брендами.

Также модель можно обучать в постановке Basket-To-Item. Это рекомендации комплиментарных или upsell-товаров. В данном случае под Basket можем иметь в виду и непосредственно корзину, и заказы.

Ближайшие планы на будущее

Сейчас мы работаем над несколькими обновлениями:

1. Обогащение системы размером пользователя. 

Для пользователя важно видеть вещи в подходящем для него размере, и мы его отпугнем, если будем показывать в рекомендациях товары большего или меньшего размера.

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

2. Добавление сессионных онлайн-фичей в реранкер.

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

3. Уменьшение ранее просмотренных товаров в ленте.

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

4. Мониторинг метрик здоровья рекомендаций. 

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

Так становится проще понять, например, то, как и почему поменялась глубина скролла выдачи или клики. Это нужно начать мониторить как можно раньше. 

Выводы

  • В 2025 году, когда LLM и трансформеры в индустрии сильно шумят, все еще работают и приносят результат простые решения. Если вы начинаете что-то делать с нуля, обязательно присмотритесь к простым подходам. Даже это принесет интересные инсайты и эффекты.

  • Переходить в онлайн непросто, но необходимо. После того, как вы построили базовый многоуровневый пайплайн, который работает, встают вопросы оптимизаций и следующих шагов развития. Часто их приходится решать написанием новых сервисов – онлайн-инференс реранкера, кастомные библиотеки по диверсификации ленты, отдельный сервис с онлайн-профилем пользователя и так далее. Но это все поможет вывести ваши рекомендации на совершенно новый уровень.

  • Важно позаботиться о логировании событий со всех сервисов. Для машинного обучения данные – это золото. Если на стороне логов с приложения есть какие-то потери или баги, дальнейшая разработка сколь угодно сильных моделей почти бесполезна, поэтому очень важно проверить правильность логирования данных и метрик со всех сервисов, которые потом используются в моделях и не только.

  • Главная страница — ключевая точка входа для персонализации. Если вы думаете, строить ли вообще рекомендации на главной — определенно да. 

Если вы использовали какие-то подходы из статьи в своей практике и хотите подискутировать, или у вас есть вопросы – пишите в комментариях, буду рад ответить и обсудить интересующие детали :-)

Комментарии (2)


  1. ChePeter
    05.09.2025 07:05

    Очень интересная статья.

    Весь прогноз и все рекомендации по старым покупкам и старому поведению юзеров. Это как маршалы готовятся к прошлой войне. Мода меняется быстрее, чем вы думаете, а Ламода учится продавать по своим старым сделкам.

    Весь расчет про тех, кто уже на Ламоде

    1. Взяли последние посещенные товары пользователем из API профиля пользователя.

    2. По этим товарам из key-value хранилища достали товары, похожие на последние посещенные.

    Только они уже тут и они уже заняты поиском, они знают зачем пришли и что хотят унести. Вы их хотите переобучить - мол ищите не тут и не так, а так,как мы вас научим ?!

    Такую тактику пробовал и досконально проверял - результат будет такой "или вас будут игнорить, или посылать и уйдут"

    Начисто отсутствует в основе рассуждений математика. Если это теорвер, "попробуем предсказать поведение случайности" и будем сегментировать и ранжировать, а далее подсказывать. То такую тактику тоже досконально проверял. Там нет независимых случайных величин, никаких и совсем. Там теорвер не применим в предсказаниях. Очень редко когда кто выбирает в моде то, что он не соотнесет с мнением других. Там существенно зависимые случайные величины.

    А вот какая в основе математика - молчок. Набрали программ разных и смешали в кучу.

    Самы эффективный и похожий на правду инструмент, это что-то типа кристалической решетки - все с связаны с соседями и смотрят на их моду.

    Наверное место Ламоды в торговле стоками, тогда имеет смысл продавать прошлые идеи по прошлым лекалам.

    А в настоящей моде нужна другая математика и другой подход.


    1. itisdanil Автор
      05.09.2025 07:05

      Петр, благодарю за обратную связь. Согласен, что предсказание интересов по стилю — сложная задача, выходящая за рамки классических методов.

      В статье я не ставил цель расписать математику используемых алгоритмов (ей посвящены десятки других статей), а поделился нашим опытом  — описанные решения проверены на A/B-тестах и дают у нас результат на метриках. Мы постоянно улучшаем алгоритмы, тестируя новые подходы на трафике наших пользователей.