Оглавление

  1. Проблематика

  2. ML-модели

  3. Локальная LLM

  4. Эмпирические методы

  5. Сложности, с которыми мы столкнулись

  6. Реализованное решение

  7. Препроцессинг

  8. Полный пайплайн

  9. Полученное качество

  10. Дальнейшие планы развития

Проблематика

Начнем с того, для чего нам вообще понадобился классификатор номенклатуры? Основной профиль заказчиков, для которых назрел вопрос товарной аналитики продаж, – крупные металлотрейдеры. 

Исторически процессы у них сложились следующим образом: сделки ведутся в CRM-системе (например, Битрикс24), товары добавляются в табличную часть «Товары» сделки, с достаточной степенью детализации (название товара, количество, габариты, цена, налог, скидка). Однако сам формат названий не согласован и не стандартизирован, то есть каждый товар можно записать практически неограниченным количеством способов, в том числе с опечатками.

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

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

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

Какие варианты у нас были

1. ML-модель

Первым вариантом было обучение модели классификации. Для работы с названиями товаров можно использовать два семейства представлений текста: локальные и распределённые.

Локальные, или разреженные, представления — это, например, мешок слов, TF-IDF и символьные либо словные n-граммы. Такая модель опирается на конкретные фрагменты строки. Для нашей задачи это одновременно преимущество и ограничение. Точное совпадение марки стали, номера ГОСТа или сокращения может быть очень информативным, однако даже небольшая опечатка или новый вариант записи превращает знакомый признак в незнакомый.

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

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

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

2. Использование локальной LLM в связке с Python SDK

Второй вариант — передать классификацию локальной большой языковой модели. Мы подняли модель семейства Qwen (квантизованный Qwen 3.8-27b) через Ollama и вызывали её из Python: в запрос передавали исходное название товара, описание структуры каталога и требовали вернуть категорию в заданном формате.

На неоднозначных и небрежно записанных названиях LLM рассуждала гибче классической модели: могла распознать сокращение, восстановить пропущенный контекст и объяснить выбор. Например, для позиции «отвод для трубы 90° 57×3,5» модель должна была не просто заметить слово «труба», а определить, что главным видом товара является отвод, и вернуть его категорию, а не категорию труб.

Однако свободное рассуждение не гарантирует стабильной классификации. Модель могла выбирать разные ветви для похожих запросов, переоценивать общеупотребительное слово и игнорировать технически значимый индекс. Чтобы удержать результат в границах каталога, в промпте всё равно приходилось описывать приоритеты, исключения и допустимые классы — фактически переносить в него бизнес-правила. Передать модели сразу всю иерархию из более чем 1100 категорий тоже непрактично: сначала нужно отобрать подходящих кандидатов, а затем проверить, что ответ действительно существует в справочнике.

Для массива в 15+ млн позиций добавились эксплуатационные ограничения: время обработки, загрузка GPU, контроль формата ответа и повторяемость результата. Локальный запуск решал вопрос конфиденциальности данных, но не делал инференс бесплатным и не устранял необходимость в валидации.

Поэтому от LLM как от основного классификатора мы отказались. При этом подход остался полезным как запасной план — но об этом дальше.

3. Эмпирические методы (создание продвинутого бизнес правила)

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

Какие преимущества у этого подхода? Главное – абсолютная интерпретируемость результата. Никаких чёрных ящиков, если алгоритм ошибся, всегда можно установить почему и исправить ошибку («отминусовать» проблемные ключевые слова, изменить приоритеты ключей, добавить новые ключи и правила распределения).

Сравнение подходов
Сравнение подходов

С какими сложностями мы столкнулись?

1. Невозможность однозначно установить принадлежность товара к конкретному классу – всегда есть двойственность в выборе

Даже такой формальный документ как ГОСТ (возьмем для примера ГОСТ 28548-90, устанавливающий термины и определения понятий основных видов стальных труб) не исключает возможность отнести трубу как категории горячекатаных бесшовных стальных труб (по способу производства), так и к плоскоовальным стальным трубам (по форме сечения).

Пример классификации труб по ГОСТ
Пример классификации труб по ГОСТ

Чтобы устранить эту неоднозначность, классификатор должен точно определять, что является более важным характеризующим свойством трубы: её способ производства или форма сечения. И так по каждому виду / подвиду товарной группы.

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

2. Название товара состоит из множества взаимоисключающих (зачастую) частей

Из каких частей может состоять артикул:

  • вид товара (труба, лист)

  • категорийная принадлежность (бесшовная, холоднокатаная)

  • цель применения (водогазопроводная, для нефтепроводов)

  • материал (сталь, медь)

  • марка металла (09Г2С, Ст20)

  • свойство доп. обработки (перфорированная, оребренная, ППУ)

  • тип и номер стандарта (ГОСТ, ОСТ, АТК, ТУ, EN, DIN, JIS)

При этом наличие одной из частей можно неявно исключить другие. Например, исходя из названия «Труба 32х1,5 8732-78» неявно подразумевается, что:

  • Категорийная принадлежность – бесшовная горячедеформированная;

  • Материал – сталь;

  • Тип стандарта – ГОСТ.

Декартово произведение всех таких комбинаций просто огромно.

3. Высокая кардинальность свойств товаров

Существует огромной количество вариаций, которыми можно записать одно и то же свойство: б/ш, бесшовная, бс/ш, бесш., б\ш бс.\ш, и т.д.

Вот несколько реальных примеров, как менеджеры записывали бесшовную трубу:

Пользовательская запись "трубы бесшовной", Ч1
Пользовательская запись "трубы бесшовной", Ч1
Пользовательская запись "трубы бесшовной", Ч2
Пользовательская запись "трубы бесшовной", Ч2

4. Ошибки пользовательского ввода (опечатки)

Эта проблема отчасти связана с предыдущим пунктом – при полном отсутствии контроля ввода менеджеры опечатываются, эти опечатки нужно корректно выявлять и обрабатывать.

Примеры опечаток в пользовательском вводе
Примеры опечаток в пользовательском вводе

5. Пересечения и конфликты ключей

Чем осложнено простое перечисление ключей? Возникают конфликты ключевых слов при классификации. Например, «отвод для трубы» может быть присвоен категории «Трубы», хотя должен относиться к категории «Отводы».


Как выглядит реализованное решение

Как я и сказал ранее, из трёх вариантов мы выбрали именно создание продвинутых бизнес-правил.

Какие виды правил были созданы

  1. Ключевое слово – обязательное, наследуется от узла-родителя к узлу-потомку с учетом наследования весов;

  2. Вес ключевого слова – влияет на общую сумму баллов, которая присвоена классу-кандидату. Чем более специфичный ключ, тем больший вес ему лучше присвоить;

  3. Слово-блокер – при наличии в товаре слова-блокера исключается возможность присвоения класса и его дочерних элементов товару. Наследуются от узла-родителя к узлу-потомку;

  4. Слово-требование – ключ, обязательный к наличию в товаре, чтобы класс (или его дочерние элементы) могли быть присвоены данному товару. Наследуются от узла-родителя к узлу-потомку.

Поддерживаемые «операторы» для записи бизнес правил

! – оператор перезаписи

Если от узла-родителя к узлу-потомку пришли ключи, которые необходимо переписать на уровне потомка, используется оператор перезаписи: !фильтр, !фильтры, !грязевик, !грязевики.

^ – оператор начала строки

Если необходимо, чтобы ключевое слово было первым в названии товара. 

Итоговая категорийная матрица бизнес-правил

Итоговая матрица содержит 22 категории верхнего уровня и 1100+ категорий всего:

Матрица бизнес-правил
Матрица бизнес-правил

Препроцессинг данных

Предварительная подготовка товаров состоит из трех частей:

  1. Детекция опечаток – считает по словарю отслеживаемых ключей расстояние Левенштейна и предлагает наиболее вероятные кандидаты на опечатки;

  2. Маппинг марок – сопоставляет марку стали с материалом;

  3. Маппинг ГОСТов – сопоставляет ГОСТ изделия с типом / маркой / материалом.

Например, у нас есть исходный товар:

БРС по типу "CAMLOCK" DN 80 PN 25 12Х18Н10Т

Прогоняем его через обработчик пользовательского ввода и в результате получаем скорректированную версию названия товара:

БРС по типу "CAMLOCK" DN 80 PN 25 12Х18Н10Т | cam-lock | стальной


Полный пайплайн

Шаг 1. Преподготовка данных

Исправляем опечатки, маппим марки и ГОСТы, обработка батчами:

Пример скрипта предобработки
Пример скрипта предобработки

Шаг 2. Классификация

Предсказываем класс по бизнес правилам:

Пример скрипта классификации
Пример скрипта классификации

Шаг 3. Артикулизация

Присваиваем артикулы, обработка батчами:

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

Оцениваем полученное качество

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

Методика оценки

Контрольную выборку формируем из 1,5 млн. товарных позиций, которые не использовались при разработке правил. Эталонную категорию для каждой позиции подтверждал специалист. Чтобы одинаковые или почти одинаковые названия не попали одновременно в разработку и контроль, дубли предварительно объединялись в группы и целиком относились только к одной выборке.

Точность конечной категории

Основная метрика — доля товаров, для которых классификатор выбрал правильную конечную категорию:

\mathrm{Accuracy}_{leaf}=\frac{1}{N}\sum_{i=1}^{N}{1}(\hat{y}_i=y_i)\approx87\%,

где y_i — эталонная категория товара, \hat{y}_i — результат классификатора, а 1 — индикатор совпадения.

Качество по отдельным классам

Общая точность не показывает, насколько хорошо распознаются редкие категории. Поэтому для каждого класса c отдельно вычисляются precision, recall и F1:

\mathrm{Precision}_{c}=\frac{TP_c}{TP_c+FP_c},\ \mathrm{Recall}_{c}=\frac{TP_c}{TP_c+FN_c}\mathrm{F1}_{c}=2\frac{\mathrm{Precision}_{c}\cdot\mathrm{Recall}_{c}}{\mathrm{Precision}_{c}+\mathrm{Recall}_{c}}

Взвешенный F1 с учетом доли каждого класса в выборке:

\mathrm{F1}_{weighted}=\sum_{c=1}^{C}\frac{n_c}{N}\mathrm{F1}_{c}\approx87\%,

где n_c — число объектов класса c.

Точность на верхнем уровне каталога

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

\mathrm{Accuracy}_{lvl\ d=1}=\frac{1}{N}\sum_{i=1}^{N}{1}\!\left(\operatorname{ancestor}_{d}(\hat{y}_i)=\operatorname{ancestor}_{d}(y_i)\right)\approx96\%

Точность автоматической классификации и покрытие

В продакшене безопаснее не присваивать категорию сомнительному товару, чем записать заведомо неверный результат. Поэтому классификатор может автоматически принимать решение только при уверенности выше порога \tau.

Покрытие (доля товаров, которую система классифицирует автоматически):

\mathrm{Coverage}(\tau)=\frac{1}{N}\sum_{i=1}^{N}{1}(s_i\geq\tau)\approx86\%,

где s_i — нормированный отрыв (по сумме набранных весов) победившего класса от ближайшего кандидата.

Точность автоматической классификации, рассчитанная только среди принятых решений:

\mathrm{SelectiveAccuracy}(\tau)=\frac{\sum_{i=1}^{N}{1}(s_i\geq\tau){1}(\hat{y}_i=y_i)}{\sum_{i=1}^{N}{1}(s_i\geq\tau)}\approx93\%

То есть на контрольной выборке взвешенный F1 классификатора составил 87%. На верхнем уровне каталога категория определялась правильно в 96% случаев. При выбранном пороге уверенности система автоматически классифицировала 86% товаров; точность среди принятых решений составила 93%. Таким образом, около 80% всех товаров обрабатывались автоматически и корректно, и только 14% неоднозначных позиций требовали дополнительной проверки.

Дальнейшие планы развития инструмента

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

Первое направление — связать нормализованную номенклатуру с базой поставщиков. Когда менеджер добавляет товар в сделку Битрикс24, система сможет определить его категорию и сразу показать подходящих поставщиков, историю закупок и другие доступные сведения. Это сократит ручной поиск и позволит использовать накопленные данные независимо от того, как товар был записан в конкретной сделке.

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

Третье направление — гибридная обработка нераспознанных товаров. Если бизнес-правила не нашли категорию или разница между лучшими кандидатами слишком мала, позицию можно передать локальной LLM. Модель получит не весь каталог, а короткий список кандидатов и объяснение сработавших признаков. Ответ пройдёт автоматическую проверку либо отправится эксперту, а подтверждённое исправление пополнит словарь и набор правил. LLM не станет заменой текущего решения, а альтернативным механизмом работы с наиболее сложными случаями.

Архитектура гибридной модели
Архитектура гибридной модели

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