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

В этой статье я решил немного подробнее остановиться на базовых понятиях, чтобы говорить на одном языке с более широкой аудиторией. Поэтому, если вы - матёрый ML-щик и уже собаку съели на этих рекомендательных системах, то советую перескочить следующий блок (ну или хотя бы посмотрите на картинки, по-моему инфографика у дизайнеров вышла превосходной). Прошу:
Рекомендательные системы
Обсудим базу. Есть человек, о нём у нас имеется какая-то информация, обычно её не то, чтобы много. Нам известно о том, какие товары он просматривал, добавлял в избранное/корзину и, что самое важное – покупал. Если повезёт, также будем знать, какого он пола, возраста и в каком районе живёт. О товарах у нас также есть информация: категория, стоимость, вес, габариты, название, описание, КБЖУ в случае продуктов и т.д. Стоит помнить, что действия пользователя происходят в каком-то контексте, он зашёл на платформу в какой-то месяц, в какой-то день недели, в какое-то время дня, за окном у пользователя такая-то погода, и т.п. Всё это может повлиять на конечные предпочтения человека и соответствующие рекомендации. Далее вся эта информация передаётся пока что в некий чёрный ящик под названием рекомендательная система. Мы ещё не знаем, как она конкретно работает, но на выходе у неё стоит список товаров, которые показывается текущему пользователю в текущем контексте:

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

Первая разновидность – это статистические. Они основаны на сборе статистики о товарах и дальнейшем построении правил по типу: “С этим товаром чаще брали…”. Например, пользователи часто покупают бургер и картошку фри вместе. Что порекомендовать человеку, который пока что взял только один бургер? Очевидно, что картошку фри. Кстати говоря, моя предыдущая статья и была посвящена модификации одного популярного алгоритма этой породы.
С этим подходом есть несколько проблем: во-первых, не совсем понятно, как в такой системе продвигать новые товары, по которым ещё толком не успело накопиться достаточно статистики (проблема холодного старта). Во-вторых, в силу схемы подсчёта чаще будут рекомендоваться самые популярные товары, что не всегда хорошо. В-третьих, мы не учитываем индивидуальные предпочтения пользователя. Что, если пользователь ненавидит картошку фри и с бургером всегда берёт греческий йогурт? "Нет, ешь картошку!" - говорим мы ему из раза в раз. Такого нетипичного пользователя в подобной системе мы не отследим и легко потеряем.Далее идут коллаборативные рекомендательные системы. Тут уже поинтереснее: их суть заключается в том, что мы тем или иным образом начинаем отслеживать похожих друг на друга людей и рекомендуем им схожие товары.
Здесь проблема возникает, когда человек только зашёл на сервис и у него вообще ещё нет покупок (всё тот же холодный старт). Что в таком случае рекомендовать – непонятно. Плюсом нужно иметь уже очень много накопленной информации, чтобы обучить хоть какую-нибудь рабочую коллаборативную систему, иначе мы просто не сможем грамотно типизировать всех пользователей.Ну и последняя группа систем – это тренд, нейросетевые рекомендеры. Вообще говоря, как мы их обучим, так они и будут работать. Хотим предсказывать следующий товар в корзине – без проблем, предсказывать всю корзину целиком – да пожалуйста, хотим предсказать товар, на который пользователь скорее всего кликнет – тоже можно.
А в чём вопрос по итогу? Казалось бы, просто внедряйте нейросети и будет вам счастье. Но на самом деле и здесь не всё так просто. Во-первых, не любая архитектура будет хорошо решать поставленную перед ней задачу, нужно тщательно подойти к проработке модели и её обучению. Во-вторых, для обучения большей части моделей данных нужно столько, сколько есть только у самых крупных компаний. Здесь всё зависит от подхода и как раз-таки правильно выбранный подход сыграет решающую роль.
Стоит также упомянуть гибридные системы – это комбинация рассмотренных нами вариантов. Гибридные системы призваны компенсировать недостатки своих составных частей и по итогу дать лучший возможный результат. Например, коллаборативные и статистические рекомендеры отлично отрабатывают в связке, поскольку они способны нивелировать проблему холодного старта друг друга.
Настраиваемый автоэнкодер
Как появилась идея? На дворе было лето 2023 года, во всю гремели генеративные модели, в общем-то они и сейчас продолжают, и наша молодая команда в жажде поработать с нейросетями в известной задаче обломала крылья и долгое время толком ничего не могла придумать. Все разработанные архитектуры нейросетевых рекомендеров на нашем небольшом датасете давали отвратительные результаты и отправлялись в помойку.
И тут после очередной прочитанной статьи по Stable Diffusion внезапно появилась мысль: а что если нейросеть по заданному набору параметров будет предсказывать целый заказ в скрытом представлении (эмбединге) и только после этого декодировать его в ранжированный список товаров, т.е. как бы генерировать целый заказ? Отлично, подумали мы, давайте пробовать.
С декодером было всё ясно, эмбединг заказа после нескольких слоёв расширяем до списка всех товаров и прогоняем через софтмакс. А вот с энкодером поинтереснее. Что вообще из себя представляет эмбединг заказа? Эмбединги товаров, пользователей и категорий у нас уже были получены после сингулярного разложения соответствующих матриц. И так появилась простая, но элегантная идея: эмбединг заказа - это нормированная сумма эмбедингов входящих в него товаров в том же самом евклидовом пространстве. Стоит отметить, что это далеко не очевидная гипотеза, которая в нашем конкретном случае сработала неплохо. Мы могли бы реализовать end-to-end архитектуру, устранив вопрос интерпретации скрытого представления, но тем не менее выбрали раздельную модель обучения, и тут встаёт вопрос - зачем?
Суть в том, что у нас остаётся проблема холодного старта, а ещё хотелось бы делать рекомендации, основываясь на том, какой товар пользователь сейчас просматривает. Возможность интерпретации эмбединга заказа в данном случае жизненно необходима, поскольку нам потребуется так или иначе его видоизменять (донастраивать). Тут-то и появляется настраиваемый автоэнкодер! Продублирую картинку с ката:

Теперь мы уже видим, что это знакомая нам гибридная схема рекомендательной системы, которая объединяет в себе все 3 подхода. Про эмбединги входных параметров я уже пояснил, что они берутся из коллаборативного фильтра после сингулярного разложения матриц.
Нас сейчас интересует элегантно обведённая красным квадратом область. Здесь вырис��вывается основная идея. Блок со статистическим рекомендером достаёт нам ассоциативные правила к просматриваемому товару. Мы сортируем эти товары в порядке значимости, берём эмбединги первых n из них, домножаем на положительный коэффициент меньше единицы, приплюсовываем к эмбедингу заказа и нормируем полученный вектор. Вуаля! Получаем эмбединг заказа, смещённый в сторону просматриваемого товара. Теперь даже новый пользователь будет получать ассоциативные рекомендации с учётом контекста, в котором он находится. Здесь срабатывает то, что мы можем интерпретировать эмбединг заказа, как суммарный эмбединг входящих в него товаров, что собственно и позволяет делать такое вот смещение.
Оставшиеся нюансы
Остаются не освещёнными ещё несколько вопросов
- Что такое корзина в нашей схеме и как происходит обучение? Ведь корзина может быть только на инференсе, а на обучении у нас цельные заказы.
- Во-первых, корзина в данном случае это тоже отнормированный суммарный эмбединг входящих в неё товаров. Во-вторых, при обучении мы решили рандомно отсекать часть товаров из заказа, принимая полученный набор за корзину. Корзина может быть пустой, такой сценарий тоже учтён и отсечены могут быть вообще все товары разом.
- Какая лосс-функция используется для энкодера?
- Косинусное расстояние
- Какая лосс-функция используется для декодера и какой таргет?
- MSE с таргетом из нормированного multi-hot вектора заказа
- На схеме отображены все входные параметры в вашей системе?
- Нет, схема немного упрощена, мы еще учитываем семантику в виде эмбедингов названия и описания товаров, полученных из FastText. Так же передаём признаковый вектор изображения товара (мало ли кто-то больше любит видеть жёлтый цвет на картинке или определённую форму товаров)
- Как кодируете месяц, день недели и время?
- В виде пары синус-косинус соответствующей частоты. Например, время кодируем следующей парой чисел: , где h - время в часах от 0 до 23.
Результаты
Во-первых, мы объединили холодные и горячие рекомендации в одну систему. Когда система монолитна и ничего не болтается по-отдельности, этим проще управлять и модернизировать. Такой подход позволяет получать более стабильные результаты.
Во-вторых, рекомендации больше не нужно проставлять вручную, это высвобождает время маркетологов и программистов (очевидный плюс).
В-третьих, мы получили некую универсальную систему, ориентированную и на пользователя, и на товар, и на контекст.
Также стоит отметить, что такая система может быть обучена на не самом большом объёме данных в силу схемы генерации корзины в процессе обучения.
Спасибо за внимание, желаю всем обучаемых и производительных моделей!