Всем привет! Меня зовут Иван Максимов, я работаю Lead Data Scientist’ом в команде рекомендаций и A/B-тестирования Delivery Club. Это первая из серии статей про нашу рекомендательную систему. Я расскажу о том, как мы определили проблемы предыдущего подхода к рекомендациям, и как начали строить новый: с оптимизацией рекомендаций сразу под несколько бизнес-метрик.  Статья будет интересна data scientist’ам и менеджерам продуктов, которые хотят с нуля построить систему рекомендации контента. Команда рекомендаций и A/B-тестирования окончательно сформировалась к началу 2021 года. В это время ситуация с рекомендациями в ленте ресторанов была достаточно сложной. Основная модель была сделана еще в 2017 году командой Поиска Mail.ru. На тот момент Delivery Club был относительно небольшой компанией, поэтому аутсорс ML был весьма разумен. Стоит отдать должное коллегам из Поиска Mail.ru: их модель до сих пор хороша с точки зрения машинного обучения, а на 2017 год могла считаться чуть ли не SOTA (state-of-the-art) в индустрии.  За время существования модели начали добавляться бизнес-правила. Менеджеры продуктов тестировали поднятие в топ ленты ресторанов с хорошей комбинацией популярность-рейтинг (этот процесс мы называем «бусты», от англ. boost), а команда Поиска Mail.ru тестировала последний заказ пользователя. Эти изменения давали статистически значимый рост метрик в А/В-тестах, и на начало 2021 года использовались в продакшене. Не удивляйтесь: согласно нашумевшей статье, наилучшие ML-модели рекомендаций не всегда могут побить хорошо составленные базовые модели (baseline’ы). Поэтому в индустрии широко используются базовые модели; к ним относится, например, топ популярных фильмов в Netflix, последние поисковые запросы в Google и многие другие. Такое сочетание ML-модели и baseline’ов развивалось инкрементально: каждое новое изменение внедрялось только при успешном A/B-тесте. Но в какой-то момент существенный рост офлайн ML-метрик перестал давать рост бизнес-метрик в A/B. Одновременно с этим менеджеры продуктов начали ставить перед системой рекомендаций вопросы, на которые не было простых ответов: Мы тратим деньги на привлечение нового пользователя, а он уходит после 1-2 заказов, почему? Почему я сделал уже два заказа и не вижу персональных рекомендаций? Почему у всех очень похожие рекомендации? Как продвигать на платформе не очень популярные, но качественные рестораны? Вроде бы есть рестораны с быстрым временем доставки, но они не в топе ленты, почему? Почему мне рекомендуют то, что я никогда не ем? Давайте разберемся, почему замедлился рост бизнес-метрик и у менеджеров продуктов стало возникать всё больше вопросов. Для этого нужно понять, каким ML-проблемам соответствуют вопросы бизнеса. Cold Start. Delivery Club быстро растет, поэтому мы сталкиваемся с массовой проблемой холодного старта (cold start problem):  Регулярный и большой приток новых пользователей.  Существенная часть клиентов заказывает в тех ресторанах, где заказывала раньше (три заказа в одном ресторане дают нам всего один элемент в матрице user-item). В результате заметная доля пользователей совершала заказы не более чем в двух ресторанах, и для них качество рекомендаций снижается из-за нехватки информации для обучения модели. С этой ML-проблемой связаны вопросы 1 и 2. Exploration. С начала Covid-19 количество ресторанов на платформе выросло более чем вдвое, это добавило сложностей. Раньше пользователю в конкретной локации с разумным временем доставки было доступно 20-30 ресторанов, а теперь намного больше 100. Пролистать такое количество предложений сложно, поэтому большая часть заказов приходится на первые несколько десятков позиций в выдаче. Для простоты восприятия я буду говорить, что большая часть заказов приходится на топ-20 позиций. Рекомендательные ML-модели учатся на истории действий пользователей, поэтому склонны ставить в топ-20 те рестораны, которые ранее хорошо себя показали. А чтобы ставить в топ-20 новые рестораны с небольшим количеством заказов, у модели мало данных. Такая ситуация может привести к оттоку заведений с платформы. Задачу выбора между рекомендацией проверенных ресторанов, похожих на те, в которых пользователь уже заказывал, и получением данных о новых заведениях в data science называют exploration-exploitation tradeoff. И с этой ML-проблемой связаны вопросы 3 и 4. Давайте разберем, как релевантность и разнообразие зависят от информации о пользователе, и когда возникают проблемы холодного старта и выбора между релевантностью и разнообразием. Эти знания пригодятся нам в дальнейшем. На графике изображены кривые релевантности (exploitation) и разнообразия (exploration) в зависимости от доступной информации о пользователе. Для простоты объем информации будем измерять количеством покупок пользователя: чем их больше, тем больше действий совершено в приложении, и значит больше информации мы можем получить из логов. До первой покупки о пользователе практически ничего не известно. На этом этапе мы не можем рекомендовать что-то персональное и ограничиваемся вариацией «топ популярных», поэтому и релевантность, и разнообразие рекомендаций низкое. Эта ситуация называется холодный старт (cold start) для пользователя. По мере роста количества информации о пользователе можно рекомендовать более персонализированные товары из узких ниш. Это сопровождается ростом одновременно релевантности и разнообразия. Но, начиная с Х покупок, рекомендательный алгоритм может запереть пользователя в «пузыре» его собственных предпочтений (filter bubble). При этом релевантность растет, а разнообразие рекомендаций падает — ситуация выбора между этими параметрами (exploration-exploitation tradeoff). Классический пример чрезмерного сдвига в сторону релевантности: после просмотра или покупки шкафа пользователь начинает видеть контекстную рекламу, состоящую только из шкафов.  Недоступность некоторых источников данных. Так как ML-модель ранжирования была сделана внешней командой и достаточно давно, то у нее не было доступа к важным фичам:  зоны доставки ресторанов; оценки и отзывы клиентов после заказа; прогноз времени доставки в реальном времени; подробная информация о блюдах в меню; данные о ресторанах из внешних источников. Это существенно ограничило развитие ML-модели. Без данных о зонах доставки ресторанов не получится сделать корректный negative sampling для формирования таргета. Данные о блюдах в меню могли бы помочь рекомендовать новые рестораны за счет контентного подхода. С этими сложностями связаны вопросы 5 и 6. Итак, мы перевели вопросы бизнеса в ML-проблемы. Но в чём же недостаток текущей рекомендательной системы? В реальном мире однозначного ответа не существует. По нашей теории, мы попали в точку локального оптимума. В текущем решении ML-модель и последний заказ решали проблему exploitation, а поднятие в топ ресторанов по комбинации популярность + рейтинг («бусты») решало проблему холодного старта и exploration. При этом, на начало 2021 года мы находились в точке, где баланс exploration-exploitation локально оптимален, а процесс рекомендаций новых ресторанов (холодный старт) работает недостаточно хорошо. В результате весь топ ленты ресторанов был занят либо прошлыми заказами, либо очень популярными сетевыми ресторанами с высоким рейтингом. Интересный факт из наших последующих исследований: если «в лоб» обучить на наших данных классическую модель рекомендаций (допустим, ALS), то она вырождается в одну из базовых моделей: популярные рестораны либо прошлые заказы! При околонулевом коэффициенте регуляризации модель переобучается на прошлые покупки и вырождается в baseline «рекомендация прошлых покупок». При повышении коэффициента модель вырождается в baseline «рекомендация самых популярных ресторанов». Такая же ситуация возникает, если мы применяем альтернативные методы регуляризации: понижение размерности скрытого слоя и другие. Разумный баланс, когда мы рекомендуем новые для пользователя и не слишком популярные рестораны, найти практически невозможно.  В конце 2020 года бизнес осознал, что текущая комбинация ML-модели и «бустов» уже не так хорошо справляется со всеми задачами. Поэтому решили придумать новый подход к рекомендации ресторанов. В начале 2021 года под такую задачу и сформировали нашу команду. Трансформация ленты — это не только улучшение ML-модели ранжирования ресторанов, но и огромная работа по изменению дизайна, ускорению бэкенда и изменению бизнес-логики. Большое спасибо всем командам, которые принимали в этом участие! Однако в этой статье я сосредоточусь именно на ML-части. Чтобы выйти из точки локального оптимума, нужно придумать, как существенно лучше решить хотя бы одну из основных проблем: холодный старт, exploitation или exploration. Можно было попробовать решать эти задачи внутри одной ML-модели. По этому пути пошли некоторые крупные зарубежные игроки. Например, Uber Eats балансирует между «справедливостью» маркетплейса и конверсией в модели второго уровня. А крупный индийский игрок Swiggy недавно выпустил статью про то, как они внедрили многокритериальную оптимизацию в свою основную модель — градиентный бустинг над деревьями. Но если внимательно изучить опыт Uber Eats и Swiggy, то окажется, что путь от простых эвристик до продвинутых моделей занял у них около двух лет.  Из предыдущих исследований и A/B-тестов мы знаем, что в рекомендациях ресторанов существуют сильные базовые модели: популярные рестораны, высокий рейтинг, прошлые покупки, рестораны с самым низким временем доставки и т.д. Каждая из этих моделей предназначена для решения одной из трех проблем: прошлые покупки решают проблему exploitation, низкое время доставки — exploration, а популярные рестораны и рейтинг — холодного старта. Поэтому мы разделили единую ленту рекомендаций на несколько блоков, каждый из которых будет решать только одну из проблем — exploitation, exploration или cold start. Для каждого рекомендательного блока хотели сделать отдельную узкоспециализированную ML-модель. Наша гипотеза заключалась в том, что это может дать существенный рост метрик в каждой отдельной задаче и не займет много времени на разработку. После первых итераций такими блоками рекомендаций стали: блок популярных ресторанов из категории «Фастфуд» (холодный старт); карусель ресторанов «Вы заказывали» (exploitation); лента ресторанов (exploration). Я расскажу про первые два пункта: наш подход к решению проблемы холодного старта и улучшению сценария exploitation.  По истории заказов заметно, что фастфуд настолько популярный сегмент, что практически любая рекомендательная система помещала бы такие заведения в топ. Также этот сегмент один из самых популярных среди новых пользователей. При этом популярные фастфуд-сети стабильно занимают первые позиции в ленте ресторанов, увеличивая время на её прокрутку. Поэтому мы решили сделать отдельный блок с популярными фастфуд-заведениями на нашей платформе. В первой итерации для этого не использовали машинное обучение, а вручную выбрали наиболее популярные по истории заказов. Идея довольно простая: заведения в этом блоке достаточно известны, поэтому для них не нужно делать большую карточку ресторана в ленте, достаточно маленького логотипа — он и так достаточно узнаваем. В результате такого изменения мы не снизили количество заказов из популярных фастфуд-сетей, но увеличили просмотры других ресторанов. Самый понятный сценарий exploitation в нашем приложении — это рекомендации тех ресторанов, в которых пользователь уже заказывал ранее. Существенная часть заказов совершается именно по такому сценарию. К тому же A/B-тест поднятия в топ ленты ресторана, в котором пользователь совершил последний заказ, уже показал статистически значимый результат и был давно внедрен в продакшен. Выглядит довольно логичной гипотеза о том, что если мы упростим сценарий перезаказа, то это повысит его удобство и улучшит бизнес-метрики.  Чтобы воплотить это в жизнь мы решили сделать отдельную карусель «Вы заказывали» (горизонтальную ленту рекомендаций), в которой находятся все рестораны, где пользователь ранее заказывал. Эта карусель содержит до 20 карточек заведений и расположена над основной лентой рекомендаций. Таким образом: Пользователь получает быстрый доступ к наиболее релевантным ему ресторанам. Так как все прошлые рестораны собраны в отдельной «карусели», из ленты их можно исключить.  Во-первых, это сократит количество мест, которое занимали прошлые заказы с N до 1. Во-вторых, лента будет полностью работать на решение проблемы exploration, а задачей ML-модели в ленте станет предсказание нового для пользователя ресторана, в котором он закажет. Для такой карусели мы можем сделать отдельную модель, прогнозируя, в каком ресторане из истории пользователя он закажет в следующий раз — потенциально можем улучшить опыт пользователя. В теории звучит довольно логично и просто, но у реализации такой карусели и ML-модели сортировки ресторанов для неё есть несколько особенностей.  Во-первых, нам нужно отображать рекомендации в карусели в реальном времени. Техническое ограничение от команды разработки: 99-ый перцентиль времени ответа не более 100 мс под нагрузкой до 1 тыс. RPS. Поэтому мы решили делать offline-модель, которая заранее рассчитывает рекомендации для всех пользователей и обновляет их два раза в неделю. Во-вторых, пользователь может заказывать в совершенно разных заведениях на разные адреса. Например, на работу — бизнес-ланч, а домой — суши и пиццу. Этот нюанс мы учли тем, что считали прогноз не для пользователя, а для пары пользователь-локация. Но ведь нам нужно заранее рассчитать все рекомендации. И если пользователей несколько миллионов, то всех пар пользователь-локация — миллиарды. Хранить в каком-нибудь key-value хранилище такое большое количество ключей проблематично. Мы решили обойти эту сложность так: округляем координаты до двух знаков после запятой (очень приближенно получаем квадраты 1х1 км, хотя на самом деле это не совсем квадраты из-за формы Земли). В продакшене нужно лишь округлить координату пользователя, чтобы получить соответствующий квадрат. Но даже таких квадратов слишком много, чтобы рассчитать все возможные комбинации пользователь-локация. Поэтому мы делали рекомендации только для комбинаций из прошлых заказов. А на случай, если пользователь решит совершить заказ в новой локации, у нас есть fallback рекомендаций на среднестатистического пользователя: из модели убираем все фичи, связанные с локацией. В-третьих, хотелось бы иметь некие «логичные» рекомендации, потому что карусель «Вы заказывали» — это первый блок, который видит пользователь. Поэтому мы наложили в модели ограничение на монотонность по большинству из признаков. Например, вероятность следующей покупки в этом ресторане должна быть тем больше, чем: больше заказов в этом ресторане у пользователя;  меньше прошло времени с последнего заказа в этом ресторане; лучше было качество доставки (обратное от % опозданий, % сбоя в доставке). В качестве модели мы использовали LightGBM. На тестовых данных она дает 74% precision@1. Из топовых признаков: количество заказов пользователя в ресторане, средняя возвращаемость пользователей в ресторан, расстояние от пользователя до ресторана.  Интересно, что признаки, связанные с давностью заказа, даже не входят в топ-5 по feature importance из shap. Более того, модель, которая сортирует рестораны в карусели по давности последнего заказа, показывает себя хуже по precision@1, чем случайная сортировка! Хотя ранее в проде в топе ленты отображался ресторан, в котором пользователя сделал последний заказ. Это аналог ML-модели только на одном признаке — времени с последнего заказа в ресторане с ограничением на монотонность. Справедливости ради стоит добавить, что такая же ситуация сохраняется и с остальными метриками, которые мы мониторим — precision@2 и MAP. Весь процесс изменений занял всего несколько месяцев. По большому счёту, мы выделили сценарии перезаказа и заказа в популярных ресторанах в отдельные рекомендательные блоки. В итоге получили не только рост ключевых бизнес-метрик, но и достаточно сильные изменения нашей главной страницы, которые видны даже визуально: Во время экспериментов над моделями и форматами рекомендаций мы выучили два важных урока.  Во-первых, все компании и приложения разные. То, что работает в одних организациях, может не работать в других. В нашем случае лента рекомендаций, которая крайне популярна в индустрии (её используют Facebook, Instagram, Twitter и многие другие), подходила не под все потребности пользователей. Оказалось, что люди хотели бы видеть явные сценарии заказа (например, «Вы заказывали»), и удовлетворяя такие желания мы действительно улучшаем бизнес-метрики.  Во-вторых, чтобы повышать качество рекомендаций не обязательно двигаться в сторону SOTA-моделей. Даже достаточно простые эвристики («Популярный фастфуд») могут показывать себя очень хорошо, если разместить их в правильном месте и с подходящим дизайном. Мы только в начале пути улучшения рекомендаций в Delivery Club. Ещё предстоит множество экспериментов с моделями и форматами рекомендаций. И мы обязательно поделимся новостями в следующих статьях! Кстати, у меня есть Telegram-канал ML4Value о том, как пройти непростой путь от ML-модели до ощутимой пользы бизнесу: как выбирать таргеты в рекомендательных системы, ускорять А/В-тесты и многое другое. Как мы строили платформу А/В-тестирования в Delivery Club. Эволюция прогноза времени доставки в Delivery Club. Как мы автоматизировали отрисовку зон доставки ресторанов. ML team lead @ Delivery club | Тг-канал @ml4value