Привет! Мы — команда ML-разработчиков «Магнит Фудтех», входящей в состав бизнес-группы Магнит OMNI. 

Меня зовут Виктория Костерина, я тимлид команды. В этой статье мы вместе с моим коллегой, ML-инженером Богданом Тонанайским, рассказываем, как создавали систему автоматического сопоставления товаров между ассортиментом конкурентов и товарами «Магнита».

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

Сопоставить несопоставимое

В магазинах товары могут различаться форматами упаковки, объемом, весом и множеством других факторов. В офлайн-формате продукты стоят на полках, и их можно потрогать. В онлайне товары представлены карточками с текстовыми описаниями и картинками. Однако в базах данных разных ритейлеров даже одни и те же товары могут называться по-разному — как в примере ниже:

Coca-Cola Zero 330ml и Кока-Кола зеро 0.33 ж/б — товар один, названия разные.
Coca-Cola Zero 330ml и Кока-Кола зеро 0.33 ж/б — товар один, названия разные.

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

У нас в «Магните» стояла именно эта задача — автоматически сопоставлять товары из наших каталогов и каталогов конкурентов. Сотни тысяч SKU, разные форматы, опечатки, особенности описаний, — такое практически невозможно свести к ручной разметке. Более того, даже у одних и тех же ритейлеров названия могут меняться со временем.

Наивный путь

На первом этапе нам были доступны только текстовые заголовки товарных карточек. Идея была простая: что, если взять строковое расстояние Левенштейна, и просто искать ближайшие названия? В теории красиво, на практике — провал. Даже очень похожие строки могли означать совершенно разные товары. HitRate@10 оказался около 10%, а простые преобразования вроде приведения к нижнему регистру, стемминга и лемматизации хоть и улучшают метрику почти вдвое, но не дают пригодных результатов. Ниже представлено распределение коэффициента схожести текстов (fuzz ratio), где 100 соответствует полному совпадению.

Отсюда вывод: нормализация текстов важна, но не сделает из строковых методов пригодную систему.

Эмбеддинги приходят на помощь

После строковых методов мы обратились к эмбеддингам: векторизовали названия товаров языковыми моделями и брали топ по косинусной близости. Сырые LaBSE и ruBERT на наших данных сразу подтянули качество, HitRate@10 увеличился более чем в 2 раза. Построив распределение косинусной близости топового матча для успешных (матч в топ-10 результатов) и неуспешных (матч дальше) мы увидели следующую картину:

Уже на этих распределениях видно, что при определенном пороге отсечения по метрике (например, 0,995) можно увеличить качество сопоставленных пар при достаточно неплохом покрытии. А если «раздвинуть» эти два распределения, то это существенно повысит процент совпадений и устойчивость результатов. 

Картинка иллюстрирует общую идею. У нас появилась надежда, что fine-tuning даст серьёзный прирост.

Не только fine-tuning

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

Что это такое? Взглянув на самые длинные названия, мы увидели, что они содержат повторяющиеся подстроки, удаление которых улучшило картину:

Можно обучать

Мы выбрали компактную модель rubert-tiny2: баланс скорости и качества оказался оптимальным. Чтобы натренировать её на наших данных, применили contrastive learning с hard negatives. То есть мы специально скармливали парные примеры очень похожих, но разных товаров. В итоге модель научилась разносить и не путать такие пары, как, например, «Pepsi 0,5 л» и «Pepsi Max 0,5 л».

Эта дообученная модель дала ещё +25% к HitRate. Особенно сильно улучшилась точность на пограничных случаях.

LLM — фильтр последней надежды

Финальный пайплайн получился таким:

  1. Берём названия товаров конкурентов и «Магнита».

  2. Считаем эмбеддинги, ищем ближайших соседей, формируем топ из 5-10 кандидатов.

  3. Фильтруем по косинусному сходству.

  4. Самые спорные пары отправляем в LLM (DeepSeek, Qwen, YandexGPT).

LLM возвращает JSON: матч/частичный/нематч + уверенность + короткое объяснение. Так мы автоматизировали валидацию и разгрузили аналитиков.

Что мы поняли:

  1. Нормализация данных обязательна. Без неё даже простейшие методы работают плохо.

  2. Компактные трансформеры могут быть очень мощными, если их правильно дообучить.

  3. LLM отлично дополняют пайплайн, выступая последним фильтром.

Куда двигаться дальше

Сейчас мы хотим попробовать использовать LLM не только как фильтр, но и как генератор матчей и авторазметки. А ещё планируем внедрить semi-supervised обучение с обратной связью от аналитиков и интегрировать результаты в систему ценообразования.

История продолжается :)

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


  1. VladimirFarshatov
    10.11.2025 06:42

    2001 год. Access-97. Автоснабженец, сопоставляющий прайс-листы до 10 поставщиков товаров в компьютерный магазин с номенкклатурой от 5-10 тыс. позиций каждый. Забавно, почему все (и в то время) начинали с "расстояния Левенштейна", ни разу не задумываясь о том что 1(одна буква) меняет "смысл" слова: "рука-река" к примеру.

    Но .. даже так можно вполне. Странно что у вас HiRate оказался всего 10%. Немножко эвристики, и помнится достигали 82% совпадения.Зачетнее было позже: сайт товарный агрегатор (2011?) когда на sql.ru начинал выкладывать свои наработки по распознаванию. Самая зачетная товарная строчка в прайс-листе поставщика была: "куп. верх. 2 черепа, жен. - ххх руб.", применение предпочтительных сокращений бухгалтера этого поставщика, разворачивание по словарю и ву-а-ля, фраза превращалась в "Купальник женский, только верх, с рисунком 2 черепа".

    У Вас, случаем не поэтому *ошибки парсинга) самые высокие цены в округе на тот же самый товар? Сколько ни сопоставлял - Магнит в проигрыше.

    Впрочем, мода на МЛ, ИИ, она такая.. спасибо за статью. Всё это делалось в период 2000-2013гг от "Парадокс", до ..


    1. DvoiNic
      10.11.2025 06:42

      "куп. верх. 2 черепа, жен. - ххх руб."

      А вот так? ПЮРМЯС ТЁМА ГОВКАБРИС ЖБ100? Это Пюре мясное "Тёма" с говядиной, кабачками и рисом, железная банка 100 граммов


      1. VladimirFarshatov
        10.11.2025 06:42

        Тут важен контекст товарной строки, собственно из-за его игнорирования часто были казусы, пока не дошло. Если предположить, что контекст "детское питание" (из расшифровки, не очевидно конечно же) то "пюрмяс" по Зализняку может выдать только "пюре" (нет вменяемых прилагательных, на память), "мясо", "мясн(ой..)". Первым существительное имеет больший вес чем наоборот (ГОСТ в помощь, был такой). Соответственно второй слог - прилагательное: "Пюре мясное". Тут никакой сложности нет. Тёма - имя, возможно торговая марка. Тоже норм. А вот с "говкабрис", раскладка по слогам и поиск по Зализняку, догадываюсь может дать больше вариаций как уточнение :)

        "ЖБ100" - одно из типовых сокращений упаковки, вопросов вообще нет. Сложнее сокращение "л." -- литры, листы и т.п.

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

        Собственно, тогда и пришел к фундаментальному пониманию что "смысла, как вещи в себе, не существует в природе, всё есть системы обозначений", то есть "смысл" как таковой есть приписанное свойство той или иной системе обозначений. И что ключевой ролью в трактовке смысла набора обозначений является "контекст", который часто пропущен или вытаскивается из неких сторонних моментов, зачастую совсем левых. Как тут - строка товара и продуктов детского питания.


        1. VladimirFarshatov
          10.11.2025 06:42

          Перплексити. Собственно рассуждение в стиле тогдашней программы парсинга на скуле:
          ""ПЮРМЯС ТЁМА ГОВКАБРИС ЖБ100" — это мясное детское пюре из говядины торговой марки «Тёма», упакованное в жестяную банку объемом 100 грамм. .. Состав включает говядину, подсолнечное масло, рисовую крупу и воду. .. Слог "каб" в сокращении "говкабрис" из названия, вероятно, означает "кабачок". Это связано с тем, что слово "говкабрис" в данном контексте является составным сокращением, обозначающим состав детского пюре: "гов" — говядина, "каб" — кабачок, "рис" — рис."
          Очень похоже на алгоритм: разбить всё по слогам, найти слова, выделить из них те, которые относятся к контексту товарной группы или над группы, найти ключевое существительное и выделить дополнение в описании. Примерно так оно и работало тогда.