Привет! Меня зовут Павел Саликов, я Senior ML-инженер в команде Дубликатов Товаров Wildberries. В этой статье расскажу про наше решение матчинга товаров на маркетплейсе и про то, как удалось сделать его быстрым.
Что такое матчинг?
Цель матчинга — предложить пользователю идентичные товары другого продавца, чтобы можно было купить товар дешевле либо с более быстрой доставкой. Вот такие блоки вы можете видеть на сайте или в приложении:
Для этого мы используем:
картинки товаров
названия товаров
описание товаров
характеристики товаров.
Также в задаче матчинга иногда используют значение размеров. В нашем случае такого нет.
Все эти данные позволяют понять, что две белые футболки с котиком на самом деле один и тот же товар от разных продавцов.
Архитектура решения
В данной статье я опущу некоторые технические детали обучения моделей и остановлюсь подробнее на архитектуре нашего решения. Она содержит три больших этапа:
Поиск кандидатов. На данном этапе мы отбираем наиболее релевантных кандидатов в дубликаты товара. Причем точность этих кандидатов не так важна как полнота. Главное — отсечь самые нерелевантные товары и сохранить самые релевантные, так как только они будут участвовать в дальнейших шагах. На картинке ниже этот этап представлен блоками: Vit-H, e5-small multilingual finetuned, Faiss.
Классификация. На данном этапе мы скорим кандидатов с предыдущего этапа, чтобы получить только истинные пары дубликатов с наибольшей точностью. На картинке ниже этот этап представлен блоками: Bi-encoder и Cross-encoder.
Графовый анализ. На данном этапе мы составляем граф из пар, полученных на предыдущем шаге, и с его помощью дополнительно уточняем результат. На картинке ниже этот этап представлен блоком Community detection.
Как выглядит решение на проде?
Шаги нашего основного пайплайна запускаются в виде тасок на кластере с помощью Prefect. Также есть отдельный пайплайн, который рассчитывает картиночные эмбединги. Основной пайплайн синхронизируется с ним с помощью S3, а результаты работы всего решения выгружаются в базу данных Greenplum.
К БД обращаются остальные сервисы, которые хотят забрать дубликаты товаров. Среди них, например, API сайта, сервис аналитики, который поставляет нам онлайн-метрики и таргет, и некоторые другие сервисы.
Дополнительно отмечу, что скорость работы пайплайна достигается за счет итеративного подхода. Нам не нужно каждый раз получать все дубли с нуля — мы просто движемся итеративно, на каждой итерации смотрим, какие товары добавились или обновились на маркетплейсе, и находим дубликаты к ним среди текущих товаров.
Рассмотрим подробнее все описанные выше шаги.
От миллионов пар к тысячам: отбор кандидатов
Итак, вернемся на начальный этап поиска кандидатов. У нас есть миллионы товаров, которые потенциально могут быть дублями друг друга. Давайте сократим количество потенциальных дублей хотя бы на три порядка — от миллионов к тысячам.
В этом нам помогут текстовая и картиночная модели, которые преобразуют соответсвующие данные в эмбеддинги. В свою очередь эмбеддинги поступают в методы приближенного ближайшего поиска соседей (ANN), которые генерируют эти тысячи кандидатов.
Текстовая модель
При выборе текстовой модели мы ориентировались одновременно на скорость и качество. Важно, чтобы была возможность менять текстовое описание, которое мы подаем в модель, экспериментировать с ним и с аугментацией текста. Таким решением оказалась E5 Small.
Текстовая модель: e5-small-multilingual
Оптимальная по соотношению качество / скорость
Мультиязычная
Зафайнтюнена под данные Wildberries
Данные, которые мы подаем в эту модель: название, бренд, характеристики (характеристика + ее значение) и описание товара.
Картиночная модель
При выборе картиночной модели мы преследовали другую цель: выбрать наилучшую по качеству модель и переиспользовать ее. В дальнейшем аналогично текстовой модели мы проведем эксперименты с тюнингом модели и входящей картинки. Vit-H «из коробки» оказалась релевантна нашему запросу. Мы переиспользуем картиночные эмбеддинги, которые уже рассчитываются в Wildberries по этой модели.
Картиночная модель: Vit-H
Лучшая по качеству
Предобученна на LAION-2B
Зафайнтюнена на товарах из соревнования Google Universal Image Embedding
В качестве данных тут используется только первая картинка товара, масштабированная и нормализованная.
Поиск кандидатов
После того, как мы получили эмбеддинги товаров (текстовые и картиночные), нам нужно получить по ним искомых кандидатов. Для этого используем реализацию Faiss на GPU, что обеспечивает наиболее быстро обновляемый индекс товаров по сравнению с другими ANN. Дополнительное ускорение решение получает за счет итеративного подхода, так как кандидатов нужно найти только для обновленных товаров.
Также нужен был быстрый доступ до самих эмбеддингов. Для этого была реализована собственная структура данных на основе mmap.
Поиск кандидатов: Faiss + mmap
Быстро пересчитываемый индекс для поиска
Быстрый доступ до самих эмбеддингов
Оптимальное хранение
На этом этапе получилось перейти уже к тысячам потенциальных кандидатов товаров, но нужно лучше: спустимся на три порядка вниз и перейдем к единицам действительных дубликатов с помощью этапа, который у нас называется классификацией.
От тысяч пар к единицам: классификация пар
На этом этапе используются две модели: Bi-encoder и текстовый Cross-encoder.
Bi-encoder
В первую модель для пары товаров-кандидатов подаются их картиночные и текстовые эмбеддинги. В качестве Bi-encoder была выбрана достаточно простая модель — это линейные слои с drop-out и с нормализацией, обученные под каждую категорию отдельно.
В процессе экспериментов выяснилось, что не так важна сама модель, как правильно сформированный для нее batch данных. Его мы формируем таким образом, чтобы позитивные товары были дубликатами, а негативные товары — похожими товарами из той же категории. Смысл в том, чтобы отличать футболки не от холодильников, а идентичные футболки от похожих футболок.
Классификация: Bi-encoder
MLP, обученный под каждую категорию
В батче товары одной категории
Позитивные пары — товары из одной группы
Негативные пары — товары из разных групп
Cross-encoder
Прежде чем продолжить поясню, зачем нужна двухэтапная модель точности. Если использовать только Bi-encoder, как сначала и было, получаем качество хуже, а если только Cross-encoder — работу медленнее (это в несколько раз замедляет работу пайплайна, что мы не можем себе позволить). Поэтому остановились на таком сбалансированном решении: прогоняем сначала пары через Bi-encoder, отбираем их и лучшие пары уточняем с помощью Cross-encoder.
Итак, в Cross-encoder для пары товаров-кандидатов мы подаем их текстовые описания. Интересно, что в качестве данных здесь мы используем название, бренд и характеристики без описаний товаров. Почему без описаний? Они довольно шумные, хотя и помогают выделить как можно больше кандидатов и повысить полноту, но на этапе классификации описания товаров точность снижается. Текстовая модель под капотом — та же самая E5 Small Finetuned.
Классификация: Cross-encoder
Модель: E5 Small Finetuned
Данные: название + бренд + характеристики
Важно отметить таргет: у нас есть legacy-решение от продавцов, которые размечают SKU товаров. Также он постепенно дополняется с помощью разметки от асессоров, формируя таким образом наиболее актуальный таргет.
От пар к группам: анализ графов
Теперь перейдем от пар к группам. Мы можем остановиться на этапе классификации, так как у нас уже есть пары товаров, которые являются дубликатами. Но давайте разберем простой пример.
1 и 2 — одинаковые, идентичные товары, пара 1 и 3 тоже является идентичными товарами. То, что они идентичны, значит, что гарантированно товары из пары 2 и 3 тоже идентичны, то есть эту пару можно добавить к нашим результатам как дубль. Но это работает только тогда, когда модель работает со 100% точностью, а так как есть какие-то ошибки, они могут порождать следующие ситуации.
Например, есть одна группа из трех идентичных товаров (1, 2 и 3) и другая группа из трех идентичных товаров (4, 5 и 6). Модель точности разметила неверно то, что между этими группами товаров есть связь. Тогда, если по предыдущему транзитивному правилу все объединить в одну группу, точность сильно упадет (больше, чем в два раза), что нас не устраивает.
Чтобы решить эту проблему, мы используем алгоритмы выделения сообществ на графе, так называемые Community detection.
Все алгоритмы выделения сообществ предполагают, что внутри сообщества плотно связаны, а между сообществами связей мало. Таким образом, выделяя плотно связанные сообщества, мы добавляем дополнительные связи и объединяем отдельные связи в большую полносвязанную группу. И, с другой стороны, то свойство, что между сообществами связей мало, позволяет нам находить и обрезать слабые связи, которые, скорее всего, являются негативными примерами.
Результаты матчинга
В нашем пайплайне обрабатывается порядка 130 миллионов товаров, и для более чем трети из них мы находим дубликаты. Расскажу подробнее про метрики и про те изменения, которые оказали на них наибольшее влияние.
Оцениваются офлайн по таргету и онлайн с помощью асессоров:
Precision: доля верных пар среди всех дублей по результатам работы модели матчинга
Покрытие по GMV: Выручка всех товаров с дублями деленная на выручку по всем товарам
Оцениваются только онлайн:
Выручка: сумма товаров, купленных через блок дубликатов
Скорость: время на работу 1 итерации пайплайна
Переход к Mmap + Faiss позволил нам более чем в 10 раз ускорить наши решения. Также, переход от CatBoost к Bi-Encoder с правильно сформированным батчем тоже довольно сильно докинул в качестве в совокупности с моделью E5.
Важный шаг — это замена картиночной модели. Она помогла перейти от точности 80% к точности 90% без потери продуктовых метрик выручки и покрытия. Что тоже было очень важно, потому что выросло качество наших продуктов: пользователи хотят видеть в нашем блоке именно дубликаты, а не похожие товары.
Что касается Community detection: мы получили очень интересный результат. На самом деле, покрытие упало довольно существенно в связи с тем, что мы обрезали вот эти лишние связи, но при этом выручка выросла. Что показывает, что мы все-таки двигались в правильном направлении с применением Community detection.
Важный момент: на этом релизе у нас синхронизировались офлайн- и онлайн-метрика. Если раньше мы ориентировались на офлайн-метрику в 90% точности, а онлайн от нее немного отставала, то после релиза Community detection онлайн-метрика также держится на уровне 90%.
Также делюсь результатами последнего релиза, который был в A/B-тесте на момент моего выступления в рамках M2 Data Meetup. В нем мы добавили этап Cross-encoder в модель точности и увеличили стабильность работы пайплайна. Несмотря на то, что это замедлило скорость работы пайплана, прирост остальных метрик был слишком хорош, чтобы от него отказаться. Прямо сейчас мы работаем над дальнейшим ускорением нашего пайплана.
Эта и другие статьи про рекомендации в Wildberries выходят в Telegram-канале @wildrecsys. Подписывайтесь, чтобы не пропустить новые публикации!
А больше о том, как при помощи ML мы делаем маркетплейс лучше для продавцов и покупателей, рассказываем в Telegram-канале @wb_space. Там же делимся анонсами и полезными материалами от экспертов.
CitizenOfDreams
Ну вот пример с одного из маркетплейсов. Ищу некий товар некоей белорусской фирмы. Мне предлагают "то же самое, но дешевле", радостно делаю заказ и только тут замечаю, что это "то же самое" - нечто непонятное с отправкой из Китая и сворованным фото оригинального товара. Спасибо, блин, умный матчинг.