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

Требуется:

  1. построить умную рекомендательную систему, чтобы при заходе на сайт, руки сами тянулись к заветной кнопке «Купить»;

  2. облегчить пользователю поиск товаров даже с самыми нестандартными запросами;

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

Ресурсы: команда ИИ из 6 человек, полгода работы, графовая база Neo4j, векторный поиск, генеративный ИИ и безграничное терпение бизнес-команд.

Эта статья о том, как мы это сделали и с какими трудностями столкнулись на своем пути.

Какие бывают рекомендации

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

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

  • какие товары обычно смотрят или покупают совместно с тем, что сейчас смотрит, покупает или добавил в избранное пользователь;

  • какие товары похожи по своим характеристикам на текущий;

  • порекомендовать аксессуары для данного товара;

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

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

В этом случае мы можем:

  • рекомендовать ему в первую очередь те товары, что он отложил в избранное, но подзабыл;

  • найти пользователей, которые имеют схожий профиль по купленным товарам, и на основе их совокупности предпочтений сделать рекомендацию текущему пользователю;

  • сегментировать пользователя по частоте, объему и стоимости покупок и сделать рекомендацию по покупкам схожей аудитории;

  • строить рекомендации исходя из пользовательского поведения.

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

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

О том, что бизнесу не достаточно иметь волшебную кнопку

Итак, алгоритм рекомендации выбран, возможно, мы даже уже отобрали для показа топ-N товаров. Можно отдавать их на фронт? А вот и нет. Как известно, самым продаваемым товаром в магазине независимо от ассортимента является пакет, но с позиции бизнеса рекомендовать его покупку недопустимо, поэтому поверх найденных взаимосвязей в обязательном порядке накладываются ограничения.

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

Как же сделать так, чтобы пользователь увидел именно то, что мы хотим показать ему в первую очередь, но при этом не отпугнуть его лавиной ненужного спама?

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

  • возможность выбора рекомендательных алгоритмов;

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

  • проведение A/B тестирования, например, выбрать регион, которому будет показываться рекомендация именно в таком наборе алгоритмов, фильтров и сортировок;

  • исключение товаров и категорий — никаких пакетов, товаров, которые пользователь уже купил или только что положил в корзину и т. п.;

  • выбор конкретного места размещения рекомендации на сайте и ее публикация для показа.

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

Настройки панели прилетают в рекомендательную систему двумя частями. Первая часть включает в себя то, что актуально для всех пользователей, например, выбранные для рекомендации категории. Вторая часть правила приходит непосредственно в момент рекомендации и включает пользовательские особенности — город показа, ID пользователя и просматриваемого товара, дополнительные характеристики, например, VIN-номер автомобиля, если человек ищет автозапчасти. Благодаря второй части алгоритмы определяют ограничения, накладываемые на предрассчитанные данные. Например, алгоритм показа похожих товаров заранее располагает данными о том, какие товары похожи между собой, но именно в момент рекомендации учитывает их наличие рядом с пользователем, вместе с подходящими ему размерами, если вдруг они нам известны.

Почему именно граф

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

Мы выбрали графы, потому что у графовых рекомендаций есть ряд преимуществ:

  • все взаимосвязи заранее построены в графе, поэтому минимизированы операции, требующие расчетов в реальном времени;

  • графам достаточно небольшого объема данных, благодаря чему упрощается холодный старт рекомендаций для нового пользователя;

  • доступна визуализация и наглядное объяснение, почему выбрана та или иная позиция товара для рекомендации;

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

  • графы могут учитывать историю, быть динамическими;

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

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

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

Графовый RAG

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

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

Не графом единым

Граф с помощью взаимосвязей покрывает большинство рекомендательных алгоритмов, но один все-таки потребовал применения дополнительных инструментов — это алгоритм похожих товаров. Не просто товаров, которые покупают как взаимозаменяемые, а обладающих большим набором схожих характеристик. Их можно было бы разместить в свойствах узлов графа, но для ускорения расчетов мы предпочли применение векторного поиска. Для каждого товара были отобраны значимые свойства, а затем на основе векторного сходства каждый товар получил топ-K похожих, и уже эта схожесть перенесена в граф в виде связей.

Трудности на пути к успеху. Динамические запросы

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

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

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

Трудности на пути к успеху. Быстродействие

Рекомендации должны работать с такой скоростью, чтобы успевать реагировать на каждое действие пользователя, пока тот не успел покинуть страницу или проскроллить панель с рекомендацией за пределы видимости. Поэтому нам пришлось потрудиться и над тем, как обеспечить быструю выдачу. Neo4j заявляет в своих преимуществах гораздо более высокую производительность по сравнению с SQL-базами в задачах, когда требуется выполнить множество join. Но более высокая производительность еще не означает, что все будет летать из коробки. Очень важно было правильно построить запросы и связи, и на эту тему нам удалось найти полезные гайды тут и тут.

На скорость влияет то, где расположить свойство — в узле или в связи между узлами. Связи между узлами изначально ищутся быстрее. Но, если нужно дотянуться за данными через несколько связей, например, для конкретного пользователя найти его чеки, товары, которые он покупал в этих чеках, а в этих товарах сделать фильтр по определенной категории или свойству, то бывает удобнее простроить связь от пользователя напрямую к конечной точке или дать узлу пользователя дополнительную характеристику «предпочитаемая категория». При построении связей не стоит увлекаться созданием суперузлов, это узлы с особенно большим количеством связей. Такими могли бы быть узлы пола пользователей — мужской и женский, поделившие между собой связи от всех пользователей магазина. Это антипаттерн, который сильно замедляет работу базы.

P.S. Если вам интересны новости про генеративный ИИ, LLM, мультиагентов, я рассказываю об этом в своем Телеграм канале https://t.me/generative_ai_ru

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


  1. CitizenOfDreams
    26.06.2024 03:24
    +1

    К чОрту ваши рекомендательные системы (вы положили в корзину туалетную бумагу? может быть, вас также заинтересует наждачная?). Сделайте мне в магазине хороший параметрический поиск, и я буду покупать в этом магазине вдвое больше и вдвое чаще.


  1. DenSigma
    26.06.2024 03:24

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


    1. lad_habrablog
      26.06.2024 03:24
      +1

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