Авторы: Олег Лашинин (@fotol), Cергей Колесников (@Scitator).
Летом этого года на конференции SIGIR проводился Workshop On eCommerce, посвященный прогнозам намерений и рекомендаций. По традиции к воркшопу приурочили небольшое соревнование, посвященное использованию последних наработок в области RecSys. Мы в Tinkoff.AI решили немного развеяться и поучаствовать.
Если вам интересно, как решали поставленные задачи мы и другие участники, добро пожаловать под кат.
Вначале была задача
Перед началом соревнований был выпущен pre-print paper с подробным описанием темы и направления воркшопа. Прочитайте статью, если вам интересно, что волнует индустрию e-commerce и как в решении ее задач помогают соревнования.
Мы же постараемся объяснить всю суть за пару абзацев. Представьте себе стандартный онлайн-магазин книг и большой поток неавторизованных пользователей, потому что мало кто регистрируется ради покупки одной книги. У нас остается история поиска и хождения по сайту большого потока пользователей, даже если они не нашли интересующую книгу.
Суть соревнования: ответить на вопрос, как эффективно использовать логи данных, чтобы улучшить качество поиска и рекомендаций в таком сетапе.
Потом пришли данные, метрики и цели
Для решения задачи предложили использовать последовательности захешированных urls сайта (которые далее будем называть product_sku_hash), действия пользователя на этой странице (просмотр, добавление в корзину, удаление, покупка), поисковые запросы и результаты выдачи, описание свойств товаров.
Одна из целей соревнования — предсказать по истории сессии, с каким товаром будет взаимодействие в рамках сессии по некоторому известному ее началу. Чтобы оценить качество предсказаний в соревновании, использовались стандартные метрики ранжирования: MRR@20 и F1@20.
MRR@20 показывает средний ранг первого верно угаданного айтема среди списков длины до 20 включительно.
F1@20 показывала качество предсказания нескольких следующих элементов в сессии. F1@20, по аналогии с обычной F1-мерой, является средним гармоническим между Precision@K и Recall@K. Первая метрика использовалась для подсчета точности предсказания одного следующего элемента.
Поиск простых решений
Мы начали решать задачу с построения базовых простых эвристик, или бейзлайнов, которые на некоторых датасетах могут давать хорошее качество предсказания интересов пользователей.
Бейзлайны, которые мы выбрали для работы:
TopPopular — метод, который всем пользователям рекомендует товары в порядке уменьшения популярности. Этот метод, кстати, может выдавать разные результаты. Но мы просто сделали сортировку по количеству уникальных сессий, в которых встречался каждый товар. Мы начали участвовать в середине соревнования, поэтому скоры на лидерборде уже были сформированы. Качество TopPopular составило 0,0001 MRR@20, что было на несколько порядков меньше, чем у других участников.
TopPersonal — метод, который работает в два этапа. Выдает айтемы из истории пользователей, отсортированные по частоте, и дополняет список из рекомендаций TopPopular. Сила метода продемонстрирована в задаче предсказания следующей корзины, где метод TopPersonal показал метрики лучше, чем у более сложных моделей. Но на этом датасете TopPersonal не сработал. В тестовой части было мало продуктов, с которыми пользователь взаимодействовал в начале сессии.
Фреймворк с последовательными моделями session-rec представляет лучшие наработки разных методов в session-based рекомендациях. Содержит различные виды моделей:
простые бейзлайны: Association Rules, Sequential Rules;
методы ближайших соседей: Item-based kNN, Sequence and Time Aware Neighborhood;
сессионые алгоритмы: Gru4Rec, SR-GNN, NextItNet;
факторизационные методы: Factorized Personalized Markov Chains, Factored Item Similarity Models, Session-based Matrix Factorization.
Этот репозиторий открывает доступ к большому количеству имплементаций различных моделей. Однако есть ряд особенностей при использовании session-rec. Во-первых, это полное отсутствие какой-либо документации. Во-вторых — отсутствие общей архитектуры: каждый метод нес большое количество кастомной логики, которую необходимо было понять для запуска.
В связи с этими сложностями пришлось переписать их библиотеку для запуска интересующих нас бейзлайнов. Благодаря всем проведенным экспериментам у нас появился маленький session-rec-based фреймворк и скор ~0,12 MRR@20 (12-е место на лидерборде), что все еще не соответствовало нашим ожиданиям.
Хозяйке на заметку: самые простые baselines не всегда оказываются надежными решениями, хотя иногда простые алгоритмы работают лучше сложных, что доказано в известной статье Are We Really Making Much Progress.
Prod2vec — как word, только prod
С учетом того, что до дедлайна оставалось всего 2—3 недели, мы решили продолжить поиск baselines в другом пространстве решений, а именно Word2Vec.
Word2Vec — метод, хорошо известный в NLP-community еще с 2013 года, для обучения векторных представлений слов. Допустим, наши слова — это продукты, а предложения — это сессии пользователей. Тогда можно использовать все стандартные реализации w2v для фиксации векторных репрезентаций товаров. Полученные репрезентации можно использовать для поиска наиболее релевантных товаров к последнему в сессии. Они будут нашим предсказанием по покупке пользователя.
Самое интересное в этом подходе — это возможность использовать w2v-реализации из gensim. Она не требует от вас больших вычислительных ресурсов или сильной кастомизации. Полученный пайплайн спокойно обучался на небольшом CPU-сервере за 10 минут и получал сильный скор на лидерборде — примерно 0,16 MRR@20.
Мы внесли два простых улучшения:
Вместо рекомендации только одного айтема мы стали генерировать 20 наиболее релевантных айтемов. Так как MRR@K как функция от K является неубывающей функцией, то увеличение длин списков не могло ухудшить эту метрику.
Добавили не только просмотры продуктов, но и факт добавления их в корзину или даже покупку, чтобы у модели было больше данных.
И получили законные 0,18 MRR@20.
Улучшаем baseline
Решаем проблему холодного старта
Выбранное решение через prod2vec работало только на тех сессиях, где было хотя бы одно взаимодействие с продуктом (product_sku_hash). Однако треть сессий содержала только информацию о просмотре страниц, и prod2vec на этой части данных не работал. Сначала мы всем таким сессиям ставили одинаковые рекомендации топ-20 популярных айтемов.
Но подобная заглушка давала практически нулевой результат по MRR@20, если отсылать такие предсказания по всем сессиям. То есть на лидерборде мы видели примерно 0,18 MRR@20, причем на трети сессий значение приближалось к нулю.
Стало понятно, что нужно попытаться персонализировать рекомендации на сессиях, у которых не было ни одного взаимодействия с product_sku_hash. Однако в таких сессиях также была информация о хешированных адресах веб-страниц и времени их посещения.
Мы взяли все сессии из обучения и для каждой веб-страницы смотрели, какие продукты встречаются в следующих 10 элементах сессии. Каждому встретившемуся продукту мы добавляли вес 1/shift, где shift — количество действий после url. Это правило понадобилось, чтобы уменьшить вес более дальним продуктам, у которых могла быть более слабая связь с изначальной страницей, для которой мы будем строить предсказания.
Для каждого url создали словарь, в котором хранились суммарные веса встреченных рядом product_sku_hash. Этот словарь мы сортировали по убыванию весов и получали список из минимум 20 элементов. Больший вес у продукта говорил о том, что он часто встречается после нужного url, что совпадало с задачей предсказания следующего элемента. Главное — такой подход позволял быстро получать рекомендации только по url. Этот подход мы называли url2product.
Сессий без действий с продуктами было около одной трети от всего датасета. Рекомендация продуктов по популярности давала 0,001 MRR@20, тогда же как через url2product мы получали 0,02 MRR@20. Объединив предсказания через Prod2vec на ⅔ сессиях и url2product на оставшихся «холодных» сессиях, мы ожидаемо получили 0,209 MRR@20.
Используем блендинг
Какое соревнование без блендинга предсказаний? Сразу после появления url2product возник соблазн использовать его на всех данных.
Сессии с просмотренными страницами продуктов содержали информацию об адресах страниц, поэтому получить предсказания от url2product было вполне возможно. Оставалось выбрать способ объединения рекомендаций из двух моделей. Нужна была некоторая функция, которая на вход принимает два списка с отранжированными продуктами, на выходе возвращает только один список, который и будет в итоге использован для рекомендаций. Мы попробовали консервативный метод и метод усреднения рангов.
Метод усреднения рангов мог значительно корректировать итоговые списки рекомендаций. Для каждого айтема считали его ранг в двух списках и усредняли значение. Если элемент отсутствовал в одном из списков, то его ранг считался равным 20 — максимальной длине списка. На первое место здесь выходили те продукты, которые были рекомендованы сразу в двух алгоритмах. К сожалению, этот метод только ухудшал качество. Подобное можно объяснить тем, что url2product был все же слабее prod2vec, поэтому ему нельзя было доверять добавлять новые айтемы относительно предсказаний prod2vec.
Консервативный метод сохранял все айтемы из одного списка, но мог менять их порядок по информации из второго списка. У нас была уверенность, что prod2vec в целом выдает MRR@20 выше, чем url2product. Поэтому те данные, которые выдавал prod2vec, выкидывать из списка не хотелось. Мы сначала брали данные из списка prod2vec, которые присутствовали в списке url2product. Затем брали все оставшиеся данные из того же списка, которые в рекомендациях url2product для этой сессии отсутствовали. Разумеется, такой алгоритм мог влиять только на MRR@20, а на F1@20 влияния не было, так как список айтемов не менялся. Этот простой метод дал нам 0,216 по MRR@20, то есть добавил 0,016.
Фильтруем предсказания
Было еще одно простое улучшение, которое принесло нам прибавку в качестве предсказаний. Сам по себе prod2vec фактически предсказывал те товары, которые, возможно, встречались рядом в историях пользователей. Поэтому не удивительно, что в финальных предсказаниях попадались айтемы, которые уже были в сессии. Действительно, обучая prod2vec мы предсказывали как «правый» контекст — будущие айтемы в истории, так и «левый» — предшествующие объекты. Так как предсказывать надо было только будущие айтемы, мы сделали дополнительный фильтр. Сначала генерировали 100 наиболее подходящих продуктов, потом из этого списка удаляли то, что уже встречалось в рамках сессии. Также на такой ход мыслей навели результаты TopPersonal, который выдавал близкое к нулю качество работы. В итоге это дало нам еще примерно 0,01 MRR@20.
Итоги
Скомбинировав все улучшения выше, в первой стадии соревнования мы заняли 5 место из 19, а во второй — 4 из 11.
Основные доработки бейзлайна, которые повлияли на качество, мы привели в таблице ниже.
Что сделали? | MRR@20 | F1@20 |
Prod2vec бейзлайн | 0.16 | 0.041 |
Стали рекомендовать 20 айтемов, обучились на всех данных | 0.179 | 0.046 |
Добавили все типы взаимодействия с айтемами | 0.185 | 0.048 |
+ url2product | 0.209 | 0.057 |
+ блендинг | 0.216 | 0.057 |
Стали предсказывать только новые айтемы | 0.228 | 0.062 |
Кроме этого, мы пробовали другие решения. Однако, если они не указаны в таблице, значит, они не улучшали качество. Так, например, мы вдохновились статьей про тюнинг параметров у prod2vec, в которой показали возможную зависимость качества рекомендаций от гиперпараметров модели. Мы запустили перебор параметров на 100 наборов различных конфигураций prod2vec, но не смогли добиться значимого прироста в качестве и поэтому в итоге оставили дефолтные гиперпараметры для word2vec.
Еще мы пробовали заводить различные модели, которые работают у нас в ранжировании спецпредложений. Такие модели, как SASRec и BERT4Rec, давали слабое качество, как и у других участников соревнования.
Выводы
Для себя мы вынесли следующее:
Нужно рассчитывать свою мотивацию и время. Даже гиперракетное топливо от успешных статей имеет свойство заканчиваться — рассчитывайте свою мотивацию и время.
Нельзя доверять academic benchmark frameworks, нужно забраться внутрь и проверить корректность имплементации. Далеко не всегда в открытом доступе находится последняя рабочая версия.
Начинать лучше с baselines — как мы показали в этом соревновании, простой word2vec-подход все еще жив, и что главное — может быть успешно применен в задаче сессионных рекомендаций.
Не забывать смотреть в данные: простые статистические решения могут показать значимый прирост при совмещении с ML-решениями.
Ну и главное — не бояться пробовать и участвовать. Workshop competitions — отличное место для экспериментов и публикаций всего за пару недель.
Другие решения
После соревнования нам стало интересно, что же придумали разработчики NVIDIA, которые получили результат 0,27 MRR. Было интересно, в каком направлении копали другие исследователи и что они в итоге получили. Всего по итогу соревнования было принято шесть работ. Пять — посвященных предсказанию следующего события в сессии, и еще одна работа рассматривала вторую часть соревнования — предсказание факта совершения покупки. Кратко расскажем о том, что делали другие исследователи.
Сравнили 4 модели, основанные на трансформерах.
Кратко: попробовали 4 модели, основанные на трансформерах, — SASRec, BERT4Rec и два варианта модели KeBERT4Rec.
Результат: 0,13 MRR
Использовали графовую нейронную сеть.
Кратко: совсем другие результаты SASRec показал в этой работе. Авторы смогли разогнать модель уже до 0,16 MRR, добавив контекстные признаки по айтемам. Интересно, что они также обучили SRGNN (Sequential recommendation Graph Neural Network), которая также дала примерно 0,16 MRR.
Результат: 0,16 MRR.
Комментарий: Эту модель мы пробовали в рамках своих исследований, однако ее обучение занимало длительное время, и поэтому в соревновании решили ее не использовать.
Объединили LSTM и MF и получили второе место.
Кратко: в работе автор описывает ансамбль из двух моделей — LSTM и MF. Еще автор описывает простую нейросеть из полносвязных слоев, которая обрабатывает сессии без каких-либо продуктов. Примечательно, что автор использовал пятифолдовую кросс-валидацию и получил практически совпадающие результаты по ней в сравнении с лидербордом.
Результат: 0,22 MRR.
Составили граф между сессиями, веб-адресами страниц и продуктами.
Кратко: составили граф между сессиями, веб-адресами страниц и продуктами. Векторы узлов такого графа запоминали с помощью DeepWalk. Имея выученные векторы для сессий, авторы искали ближайшие векторы продуктов по косинусной близости. Этот подход чуть более умный, чем тот, что использовали мы, однако обучение векторов через DeepWalk требовало у них шесть часов, а наш подход рассчитывался за пару минут. В целом авторы также использовали GRU4Rec, который, согласно их результатам, давал 0,25 MRR. Однако не прописали в явном виде, использовали ли они в GRU4Rec предсказания на сессиях без действий с продуктами (то есть нам показалось, что 0,25 MRR показал GRU4Rec + cosine sim из DeepWalk). В любом случае интересно, что модель, основанная на GRU4Rec, показала лучший результат, чем SASRec, BERT4Rec из других статей.
Результат: 0,26 MRR.
Выиграли соревнование с помощью трансформеров.
Кратко: ребята из NVIDIA объединили все данные — просмотры, действия, поисковые запросы, картиночное и текстовое описания — в одну модель, модели объединили в ансамбль. В качестве основной архитектуры использовали Transformer-XL и XLNet. Интересно, что авторы использовали векторы поисковых запросов в качестве глобального контекста для всех элементов сессии.
Результат: 0,28 MRR.
На этом все, если вам интересны другие истории из жизни Tinkoff.AI — подписывайтесь на телеграм-канал.