Некоторое время назад IT в «Леруа Мерлен» претерпело довольно много изменений. Было перепилено довольно много систем, практически все писались с нуля. Одной из таких систем  была Публикационная Платформа, которая занималась передачей данных о продуктах на сайт и пришла на замену старой системе OPUS. Помимо всего прочего, в этой системе были микросервисы, отвечающие за рекомендации. Я не буду тратить время читателя на объяснение старой логики наполнения рекомендаций, скажу лишь, что первую половину 2020 года на официальном сайте крупнейшего ритейлера можно было увидеть следующее. Как замену для Рихтовочный набор — набор молотков и наковален для исправления деформаций кузова автомобиля. сайт на полном серьёзе предлагал прикупить заготовку для выпиливания лобзиком. А к мусорному мешку предлагалось прикупить защитный комбинезон и респиратор, и это ещё до пандемии) В общем, было весело всем, кроме бизнеса. Встал вопрос о том, как исправить ситуацию. В то время в нашей команде дата-сайентистов не было, поэтому задача ушла простым разработчикам. И вот что мы придумали. У нас есть много специалистов, которые любят свою работу и хорошо знают свой товар. Они на 100% знают, что можно предложить в качестве замены, если искомого товара нет в наличии (похожие товары) или что ещё может понадобиться дополнительно (сопутствующие товары). Большинство этих сотрудников работает в магазинах и ежедневно консультирует наших клиентов как раз по этим вопросам, но мы не можем попросить их составлять для нас рекомендации: им есть чем заняться. Специфика ритейла заключается в том, что за год меняется до 20% ассортимента. А товаров очень много. Вбивать всё это руками — не вариант, нужен другой подход. Мы подумали, что наш клиент, совершая покупку, каждый раз сам решает, подходят ли эти обои именно к такому плинтусу или это зеркало к тумбе для ванной. А в сложных моментах его консультируют наши профессионалы. И он голосует за своё решение рублём, покупая у нас товар, — почему бы не использовать эти данные? Таким образом, мы переформулировали нашу задачу так:  как из массива чеков получить информацию о том, какой товар к какому подходит? Алгоритм, который мы использовали, был разработан Google в 2013 году и называется Word2Vec. Как следует из его названия, он переводит «слово» в «вектор» на основании контекста, на котором его обучают. При этом после обучения «схожие по контексту» слова будут иметь близкие векторы, а различные — далёкие. Идея перевода слов в векторы может показаться нетривиальной, тут можно привести следующую аналогию.  Допустим, есть 2 машины, BMW и Запорожец. Рассмотрим их с точки зрения габаритных характеристик. Возьмём ширину и высоту каждого и построим на двухмерной плоскости векторы, соответствующие этим машинам. Получится примерно следующее: Расстояние между двумя векторами — это степень «похожести» двух автомобилей. Тут мы видим, что 2 машины не так уж и сильно отличаются друг от друга, если просто стоят и занимают место в гараже. Если же мы добавим к контексту скорость, то получим совершенно другую картину: Теперь 2 вектора имеют куда большее расстояние, и мы понимаем, что автомобили в этом контексте уже значительно отличаются друг от друга. Это пример того, как разные сущности в зависимости от контекста могут быть представлены различными векторами. Для желающих подробно погрузиться в теорию на Хабре есть много статей по этой теме, например, тут и тут. Мы же вернёмся к нашим рекомендациям. Как я уже говорил, наш контекст, т. е. то, на чём мы будем обучать нашу модель, — это чеки. А именно текстовые файлы, каждая строчка в которых это набор артикулов товаров через пробел, которые в одном чеке были куплены: Возникает вопрос: как в этих чеках наша модель сможет найти какие-то закономерности?Можно представить себе следующую аналогию. Допустим, есть 2 чека: В этих чеках все товары одинаковые, кроме 13227536 и 33444222. Что это означает?С некой долей вероятности мы можем предположить, что было 2 клиента, которые решали одну и ту же задачу (т. к. все, кроме одного, товары одинаковые), и они её решили, но решили по-разному. Т. е. с некой долей вероятности можно предположить, что товар 13227536 может быть заменён на 33444222, т. е. это похожие товары.  Теперь рассмотрим другой случай: Тут чек номер 2 включает в себя чек номер 1. С некой долей вероятности мы можем предположить, что также были 2 клиента, решавшие одну задачу, но клиент, оформивший второй чек, решил купить больше товаров. Тут мы можем предположить, что товары, отмеченные фиолетовым, 12355478 78856433 являются сопутствующими товарами к товарам, которые отмечены жёлтым. Теперь перейдём к обещанным двум строчкам кода. Код для обучения модели будет выглядеть следующим образом и будет представлять из себя не что иное, как вызов библиотечной функции gensim. Здесь везде используются дефолтные значения. Для того, чтобы получить рекомендации к товару с product_id, нам нужно попросить модель вернуть какое-то количество айдишников продуктов, векторы которых являются ближайшими к вектору продукта product_id. Это весь движок. На этой строчке модель вернёт 2000 продуктов, но, как показано выше, там будут и похожие товары, и товары сопутствующие. Для того, чтобы отделить зёрна от плевел, нам придётся написать ещё 2 строчки: Исходя из нашей доменной области, мы можем предположить, что один товар может заменить другой, только если у него тот же тип. Т. е. молоток мы можем заменить молотком, а не микроскопом. Поэтому из similar нужно взять товары, тип которых совпадает с типом product_id. Сопутствующие же товары, наоборот, должны быть другого типа. Вот и вся любовь. А/Б-тест движка для похожих товаров показал общий прирост конверсии аж на 10% и на следующий же день был установлен в продакшн на 100% трафика. Хотелось бы заметить, что это не потому, что подход какой-то революционный, просто скорее всего то, что было раньше, никуда не годилось.  А/Б-тест для сопутствующих товаров показал прирост конверсии только в определённых категориях. И мы знаем почему: наши сотрудники часть сопутствующих товаров выставляют руками, а живая экспертиза довольно часто оказывается лучше машинного обучения, особенно если алгоритм такой незамысловатый. Тем не менее практика показывает, что мы можем рассматривать машинное обучение как помощник для людей, например, там, где у коллег не хватает ресурсов. Из вышенаписанного мы можем сделать вывод, что эксперимент был удачный. А также из-за простоты модели понятно, что этому всему ещё есть куда расти. Как я уже говорил, основной плюс в том, что это быстро и может проработать какое-то время пока, не придут настоящие дата-сайентисты и не сделают как надо. А они к нам, кстати, пришли, но об этом я расскажу в другой раз. Из минусов подхода можно перечислить: Word2Vec вряд ли подойдёт для рекомендаций фильмов, музыки, книг и т. д. Это не про предпочтение определённого автора или жанра. В специфике DIY товары покупаются комплектом и под проект: купил обои — тебе точно нужен клей. Необходимость в клее не исчезнет, а вот боевики могут в какой-то период разонравиться, и хорошая рекомендательная система должна это как можно раньше диагностировать. Клиент, оказывается, не всегда прав. Есть хороший пример пары товаров, которые не подходят друг другу, но которые клиент всё равно покупает вместе, потому что «названия похожие». Потом, конечно, возвращает (наш движок, кстати, не учитывает возврат), но рекомендовать эти товары на официальном сайте компания себе позволить не может. Такие моменты нужно обходить в ручном режиме. Голый Word2Vec в данной статье учитывает только историю продаж. И не учитывает ничего больше. Это плохо. Например, существует много других способов находить похожие товары — по схожести фотографий, похожему названию и т. д. У такого подхода проблема с новинками. Пока новый товар не купят достаточное количество раз связки не сформируются.