Сопоставление каталогов продукции: автоматический массовый подбор с использованием токенизации

от автора

Задача широко знакома в узких кругах: наш каталог товаров встречается с каталогом контрагента — по сути одни и те же позиции, но названы по-разному. Надо найти совпадения и предоставить коллегам список подходящих наших артикулов для каждой их позиции.

В разобранном ниже случае это картриджи: 22 тысячи записей у контрагента против сотен тысяч наших номенклатур. Для такой задачи матерый программист берёт Elasticsearch, алгоритмы нечёткого поиска и тратит много времени, иногда в меру матерясь. Здесь подбор ведется с помощью токенизации, запросами в стиле no-code и без ИИ.

Принцип сопоставления по токенам

Принцип сопоставления по токенам

Загрузка данных

Каталоги грузим в таблицы базы данных как есть, ETL разбирает Excel — видит листы, понимает, какие колонки брать, показывает, сколько строк будет загружено.

Распознавание данных

Распознавание данных

ETL — стандартная вещь, но этот шаг надо упомянуть, потому что сопоставление надо воспринимать не в отрыве от всей остальной активности, включая тайминги.

Теперь данные хранятся в таблицах БД. Обе таблицы, наш каталог и контрагентский, грузятся одинаково, данные можно догружать по мере их поступления. Таблицы нужно создать вместе с нужными справочниками, в нашем случае — со справочниками токенов.

Таблица загруженного каталога контрагента

Таблица загруженного каталога контрагента

Токенизация

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

Используем конструктор запросов — аналог SQL для ленивых, он сам подсказывает связанные таблицы и связывает их, а мы просто добавляем нужные колонки в набор, составляем SELECT. В запросе есть формула, которая бьет строку наименования позиции на токены, а затем записывает ссылки на эти токены для токенизируемой записи номенклатуры. Это был самый сложный момент квеста, потому что здесь надо было заставить ИИ-чат написать выражение для разбиения фразы на группы букв и цифр.

Получилась такая регулярка, которую сам бы я, например, написал быстрее, чем с ИИ, но это безумно лень, кроме того — у нас no-code:

REGEXP_REPLACE(  REGEXP_REPLACE(    REGEXP_REPLACE(      REGEXP_REPLACE(        REGEXP_REPLACE(          REGEXP_REPLACE(            REGEXP_REPLACE(              REGEXP_REPLACE(                REPLACE(                  Наименование, '&', ''                ),                 '(^| )(для|за|под|без|не|в|к|от|с|на|и)( |$)',                 ' '              ),               '([0-9])([a-zA-Zа-яА-ЯёЁ])',               '$1,$2'            ),             '([a-zA-Zа-яА-ЯёЁ])([0-9])',             '$1,$2'          ),           '[^a-zA-Z0-9а-яА-ЯёЁ]+',           ','        ),         ',,+',         ','      ),       '^,|,$',       ''    ),     '([0-9]{3,}),(страниц|стр|ст|с|к)',     '$1стр'  ),   '([0-9]{3,})стр',   '$1стр')

Сейчас я гляжу на это выражение и есть к чему придраться, но пусть будет так.

Логика простая, идем изнутри наружу:

  1. Выпиливаем предлоги и союзы — «для», «за», «под», «без», «не», «в», «к», «от», «с», «на», «и» — если они стоят отдельным словом. То есть «Бумага для печати А4» превращается в «Бумага печати А4» — всё что не несёт смысла для сопоставления выбрасываем.

  2. Дальше пара зеркальных регулярок, которые бьют стыки цифр и букв. Одна ловит «3страницы» (цифра сразу за буквой нет, тут наоборот — буква после цифры) и втыкает между ними запятую: «3,страницы». Другая — буква-потом-цифра, тоже разделяется запятой. Смысл: «А4» не должно слипаться в один токен, его надо развести на «А» и «4» как отдельные смысловые куски.

  3. Потом рубим вообще всё, что не буква и не цифра — превращаем всё в запятую. Теперь строка уже не текст, а просто список слов, разделенных запятыми и удаляем повторы.

  4. Теперь идет особый случай для этой категории товара — про страницы. Если нашли число от трёх цифр и длиннее, а сразу за ним через запятую идёт «страниц», «стр», «ст», «с» или «к» — оно склеивает это обратно, например, в «500стр» без запятой между ними. Таким образом мы не спутаем токены моделей T-800 и T-1000 с ресурсом копирования в 800 и 1000 страниц у каких-то других моделей. Это специфика картриджей — ресурс страниц, а для кошачьего корма будут граммы, для акустики — Герцы и так далее.

Для разных категорий товаров мы можем сделать немного разные правила токенизации и запускать эти запросы с фильтрами по категориям.

Подобранные токены сохраняются в базе данных — следует сохранить отобранные и вычисленные значения в поля связанных таблиц. Те токены, которых ещё нет, создать в таблице токенов. В нашем случае сохраняемое значение задано в поле SET, здесь надо нажать Выполнить, и все токены сохранятся.

Пара деталей: здесь мы обрабатываем по 10000 строк за раз, иначе браузеру тяжело, и стоит фильтр на пустую колонку токены, потому что мы токенизируем только то, что ещё не токенизировалось. В следующий запуск будет новая порция строк и так 3 раза (для 22000 записей).

Таблица токенов

Токены при создании попадают в общую справочную таблицу. Немного модернизируем эту таблицу, добавив каждому токену признаки, которые обязательно должны совпасть: товар, марка, модель. Подтянув этот признак в запрос, мы можем отдельно анализировать совпадение всех критичных параметров и вычислять вес (качество) сопоставления для сортировки по этому значению.

Также добавим туда поле Синоним, которое пока не используется, но в следующих итерациях будет служить для схлопывания идентичных токенов.

Запрос на сопоставление

Так, токенизировали, молодцы. Дальше надо сделать выборку и объединение многие-ко-многим по совпадающим ссылкам на токены. Делаем ещё один запрос — накидываем в запрос колонки каталога контрагента, потом таблицу токенов, наконец — колонки нашего каталога и колонки токенов Товар, Марка, Модель (у них повышенный вес). Также формулой считаем выражение ТММ, которое истинно если совпали 3 ключевых признака: Товар, Марка, Модель.

К выбранным колонкам можно применять любые действия, доступные в SQL

К выбранным колонкам можно применять любые действия, доступные в SQL

Мы применяем функцию суммирования количества токенов для каждого совпадения, а результат запроса упорядочиваем по убыванию. Для каждой позиции получается список совпадений — ограничим его 10 значениями, используя аналог ключа LIMIT.

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

После первого прохода каталог из 22 тысяч позиций можно скормить ИИ и получить точное сопоставление, потому что теперь этот объем влезет в контекст агента. До этого перебор комбинаций 22 тысяч против сотен тысяч был невозможен. Теперь можно добавить в запрос вычисляемую колонку ТММ и использовать её как флаг совпадения обязательных признаков.

Как считается оценка точности

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

  1. количество совпавших токенов — сколько одинаковых слов в двух наименованиях;

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

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

Ручное сопоставление

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

Совпадения подсвечиваются — зелёным отмечены строки, где совпали марка, модель и тип устройства. Если зелёного справа нет, что-то одно не совпало. Здесь мы оцениваем качество сопоставления, понимаем причины ошибок и пути их устранения.

Очевидно, сопоставление не 100% точно, но по первым результатам тестирования мы уже решаем задачу, экономя много человеко-часов.

Массовый автоматический подбор

Основной рабочий режим — массовый подбор по всему каталогу. На пульте видны все позиции контрагента, у которых ещё нет пары. Запуск идет в несколько потоков, это рабочее место ищет совпадения для каждой позиции и сразу записывает их. В таблице появляются подобранный артикул и альтернативные кандидаты, у лучшего варианта — самая высокая оценка.

Скорость — 120-160 сопоставлений в минуту. На полных 22 тысячах позиций это пара-тройка часов на весь каталог. Число потоков это рабочее место регулирует само, глядя на прирост/деградацию скорости сопоставления.

Готовый результат выгружается отдельным запросом: подобранный артикул плюс все альтернативы — например, в Excel. Почти во всех инструментах то же доступно в JSON через API, если результат нужно передать во внешнюю систему.

Доуточнение языковой моделью

При небольшом объёме данных, как вот эти 22 тысячи строк, результат можно дополнительно проверить языковой моделью. Токенное сопоставление берёт на себя тяжёлую часть и отдаёт по каждой позиции короткий список кандидатов, а из этого шорт-листа модель выберет только то, что совпадает точно. Надо только запросом подтянуть не артикулы, а сами названия. Получается быстро и недорого (Deepseek делает это бесплатно) потому что модель работает уже по отобранным парам, а не по всему каталогу целиком.

Итоги

Сопоставление каталогов с помощью токенизации — дешевый альтернативный вариант с приемлемой точностью. Этот подход не требует Elasticsearch и профессиональных разработчиков.

Весь цикл — загрузка, токенизация, сопоставление, массовый автоподбор, проверка, выгрузка — можно собрать таблицами и запросами в no-code инструментах. Разметку токенов мы делаем запросом, регулярные выражения берёт на себя ИИ, а спорные пары при необходимости можно доразобрать языковой моделью.

Оговорки

No-code сейчас выродился, и даже далекие от IT пользователи сейчас пишут код руками ИИ-агентов, поэтому код всё-таки будет, но теперь он не пишется людьми, называется вайбкодингом и в рамках этой заметки мы не считаем это программированием.

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

То же можно собрать на Python: pandas для таблиц, регулярки для токенизации, rapidfuzz для нечёткого поиска, TfidfVectorizer для взвешивания. В промышленных решениях будет работать очень быстро и гибко, но будет немного ручной возни с памятью .

Elasticsearch — другой маршрут. Каталоги организовать как индексы, настроить анализаторы, сделать more-like-this или fuzzy-запросы. Получаем ранжирование из коробки и, пожалуй, максимальную скорость поиска. Плата — отдельная инфраструктура и время на маппинги, но если поток каталогов регулярный и есть бюджет — вполне оправдано.

Knime или Alteryx — визуальные ETL, где можно собрать пайплайн без кода. Regex-очистка, токенизация, fuzzy-соединения — всё есть. Но полный цикл с интерактивной сверкой и массовым автоподбором в несколько потоков придётся собирать из узлов самому, единого рабочего места из коробки нет. Если уже работаете в этих средах — пробуйте, если нет — может оказаться тяжеловато.

ссылка на оригинал статьи https://habr.com/ru/articles/1055368/