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

Как мы поняли, что нам это нужно?

Об этом вам расскажу я, Паша, руководитель Data Science-направления в Центре развития финансовых технологий Россельхозбанка.

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

Читатель наших прошлых статей на Хабре уже знает, что у нас есть два маркетплейса «Своё Родное» — b2c-платформа по продаже фермерской продукции и «Своё Фермерство» — b2b-платформа для продажи (и не только) товаров для самих фермеров. Каждый товар при этом относится к определенной подкатегории. А все подкатегории, в свою очередь, распределены по категориям. Например, на «Своём Родном» коровье молоко относится к подкатегории «Молоко», которая лежит в категории «Молоко и яйца». А на «Своём Фермерстве» самосвал относится к подкатегории «Дорожно-строительная техника», которая лежит в категории «Спецтехника».

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

Из чего состоит пайплайн?

Пайплайн задачи категоризации представляет собой задачу определения категории/подкатегории товара (из текущего списка категорий на платформе) и состоит из трех этапов:

  1. Работа модуля парсинга внешних каталогов фермеров

  2. Работа модели категоризации (определение категории/подкатегории)

  3. Дообучение модели категоризации

То есть изначально мы имеем внешний товарный каталог в формате XML/YML. Модуль парсинга получает на вход каталог, выделяет из него атрибуты товаров (мы решили начать с простого и использовать лишь описания и наименования товара, этого для начала оказалось вполне достаточно). Далее полученные атрибуты поступают в модуль категоризации, на выходе модель определяет наиболее вероятную подкатегорию, по которой однозначно определяем категорию товара. Ну и само собой, мы умеем дообучать модель, если это необходимо. Общая схема пайплайна выглядит примерно так:

Схематическое описание работы модулей
Схематическое описание работы модулей

Этап #1. Модуль парсинга внешнего каталога

На этом модуле не будем особо акцентировать внимание, но отметим, что он получает на вход XML/YML файл внешнего каталога фермера и выделяет из него описания и наименования товаров в формате JSON. Парсер основан на модуле etree библиотеки lxml. Подробную документацию можно прочесть здесь: https://lxml.de/tutorial.html. Ниже приведен пример каталога, идущего на вход парсера и то, что имеет в итоге на выходе.

Пример внешнего каталога фермера:

<?xml version="1.0" encoding="windows-1251"?>
<yml_catalog date="2021-10-14 14:46">
	<shop>
		<name>...</name>
		<company>...</company>
		<url>...</url>
		<platform>...</platform>
		<currencies>
			...
		</currencies>
		<categories>
			...
		</categories>
		<offers>
			<offer id="194" available="true">
				<url>...</url>
				<price>2525</price>
				<currencyId>RUB</currencyId>
				<categoryId>53</categoryId>
				<picture>...</picture>
				<name>Икра лососевая Кеты (500 гр.) СВЕЖИЙ ВЫЛОВ 2020</name>
				<description>Вкусная красная икра горбуши из Камчатского края</description>
			</offer>
...

Пример выходного формата:

[
    {
        "product_name": "Икра лососевая Кеты (500 гр.) СВЕЖИЙ ВЫЛОВ 2020",
        "product_description": "Вкусная красная икра горбуши из Камчатского края"
    }, 
...
]

Для лучшего понимания весь дальнейший рассказ будем вести на примере платформы «Своё Родное».

Этап #2. Модуль категоризации

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

В качестве модели была выбрана комбинация, которая хорошо зарекомендовала себя при решении прошлых задач: блендинг предобученного Sentence RuBert от DeepPavlov) и Логистической регрессии на CountVectorizer фичах с некоторыми весами.

В настоящее время нами используются модели со следующими гиперпараметрами:

  1. Гиперпараметры Bert:

    1. Batch_size = 4

    2. Padding = True

    3. Truncation = False

    4. Num_epochs = 5

    5. Learning_rate =  5*10-6

  2. Логистическая Регрессия — со стандартными гиперпараметрами из sklearn

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

Этап #3. Модуль дообучения

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

Потому что нельзя, потому что нельзя…
Потому что нельзя, потому что нельзя…

Мы используем модуль дообучения в двух случаях:

  1. Когда на платформе появляются новые категории/подкатегории товаров

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

Этот модуль дообучения, в свою очередь, состоит из трех этапов:

  1. Кросс-валидация для определения оптимальной эпохи

  2. Определение наилучших весов моделей

  3. Обучение моделей на полной выборке новых данных

Кросс-валидация

Цель кросс-валидации — определить оптимальное количество эпох для дообучения в пункте 3 на полной выборке. Мы выбрали 5 стратифицированных фолдов, чтобы сохранить распределение классов, на каждом из фолдов обучали модель на 5 эпохах и следили за метриками на трейне и валидации. Для каждого фолда оптимальной эпохой считалась та, где значения метрик на трейне и валидации оказывались максимально близкими. Если же таких не находилось, то выбиралась эпоха, соответствующая максимальному значению метрики на валидации. В результате на каждом фолде получали наилучшую эпоху, а оптимальную (наилучшую по всем фолдам) эпоху вычисляли как ближайшее целое число к усредненному количеству эпох на каждом из фолдов. На наших данных оптимальным количеством эпох обычно было 3.

Определение наилучших весов

После получения предсказанных вероятностей подкатегорий BERT’а на оптимальной эпохе и Логистической регрессии перед нами стоит задача определения их оптимальных весов (в идеале тут мы хотим получить ненулевое значение хотя бы одного из весов).

Чтобы подобрать веса, мы итерируемся по весам BERT в промежутке от 0 до 1, Логистической регрессии приписываем 1 — вес BERT и максимизируем значение итоговой метрики.

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

Под метрикой мы понимаем следующие показатели:

  1. Sure Accuracy* — доля правильно предсказанных уверенных подкатегорий

  2. Coverage — охват датасета, на котором достигается наилучшее качество из п.1

*мы назвали метрику Sure Accuracy не просто так, а потому что считаем accuracy только для тех классов, в предсказании которых модель достаточно уверена.

Выбор метрики accuracy обусловлен тем, что в случае добавления новых категорий/подкатегорий, зачастую записей по ним очень мало, по сравнению с уже имеющимися категориями. Поэтому, даже если модель не научится определять небольшой класс, но при этом не потеряет обобщающие способности, то мы можем вручную разметить небольшое количество записей, нераспознанные моделью, и не беспокоиться за большие классы, которые разметит сама модель. Или мы можем вообще решить не применять её для автоматического определения таких классов (подкатегорий).

Для вычисления охвата датасета мы пользуемся следующим алгоритмом:

Мы ввели в алгоритм confidence_level — параметр, показывающий степень уверенности модели при предсказании той или иной подкатегории. То есть такой показатель, что предсказанные вероятности, которые превышают значения этого параметра, считаются уверенными. Далее мы смотрим на полученные вероятности и уровень уверенности, отсюда запоминаем количество уверенных и неуверенных предсказаний модели. Количество уверенных предсказаний мы делим на общее количество предсказаний и таким образом получаем охват датасета — величину в промежутке от 0 до 1, которая показывает, на каких категориях наша модель делает уверенные предсказания.

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

Схематичное описание расчета метрик
Схематичное описание расчета метрик

В результате работы этого алгоритма на каждом фолде для каждого веса BERT мы имеем уровень уверенности, качество и охват датасета. Полученные значения мы усредняем по фолдам. Как итог, мы стремимся достичь уровня качества (desirable_accuracy, который сами же и задаем — также задается в конфигурационном файле) на максимально возможном охвате датасета. 

Для визуального восприятия рисуется график в осях x — confidence_level, y — accuracy (и она же охват). Пунктирными линиями на графике нарисованы значения точности при фиксированном весе BERT в зависимости от значения confidence_level, а непрерывными линиями графики зависимости охвата от confidence_level так же отдельно для каждого фиксированного веса BERT.

Зависимость качества, уровня уверенности и охвата в зависимости от весов модели
Зависимость качества, уровня уверенности и охвата в зависимости от весов модели

Поясним, как читать этот график. Для каждого веса BERT мы выбираем accuracy (пунктирные линии), наиболее близкую к желаемой или в случае, когда полученное качество не меньше желаемого, мы берем все точки пересечения с графиком. Для каждой такой точки мы получаем confidence_level (по оси абсцисс). Непрерывные линии ниже показывают охват датасета в зависимости от уровня уверенности, их пересечение с уровнем уверенности (которое мы получили ранее) определяет наилучший охват датасета для текущего accuracy. Проделав эту процедуру для каждого веса BERT, мы выбираем наилучший охват, вес и качество, которые будут соответствовать некоторому уровню confidence_level.

В итоге в нашем эксперименте, оптимальный вес BERT составил 0.03, выбранный уровень уверенности получился 0.71, а качество достигло 0.95 на 71% датасета.

Обучение моделей на полной выборке новых данных

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

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

Сравнение различных подходов

BERT и Логистическая регрессия показывают довольно хорошее качество, но при этом требуют много времени на полное обучение. На случай, когда нужен более быстрый результат были разработаны еще 2 модели: TinyBERT + Логистическая регрессия и SVC в связке все с той же Логистической регрессией.

Tiny Bert обучается также, как и обычный BERT, а SVC обучается на фичах, полученных с помощью CountVectorizer, то есть на тех же фичах, что и Логистическая Регрессия. В целом, весь пайплайн отрабатывает таким же образом, как и ранее описанный.

Нами было проведено сравнение качества и времени различных подходов: здесь, как и следовало ожидать, быстрее всех обучается SVC+LR, TinyBert+LR, а на втором месте и больше всего времени на обучение затрачивает BERT+LR.

Лучшие результаты экспериментов представлены в таблице ниже:

Модель

Охват при 95% точности

weighted f1 score

macro f1 score

Accuracy на всем датасете

Время обучения на CPU

BERT + LR

74%

0.94

0.84

0.85

~ 24 часа

BERT-tiny + LR

67%

0.94

0.80

0.84

~ 5 часов

SVC + LR

63%

0.94

0.81

0.83

~ 30 минут

В настоящее время нами используется блендинг BERT + Logistic Regression, но если окажется так, что качество предсказаний более легких моделей подходит бизнес-команде, отвечающей за саму площадку и продукт, то мы можем легко подменить одну модель на другую в нашем пайплайне и продолжить использовать его дальше. Кроме того, мы проводили эксперименты с другими моделями, однако они показали худший результат, чем отобранные нами модели.

Есть ли планы по улучшению модели?

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

  1. Обогатим описание и наименование товара другими атрибутами

  2. Попробуем построить продуктовые эмбеддинги товаров (Product2Vec), учитывая все атрибуты

  3. Попробуем добавить также информацию об изображениях товара

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


  1. Nual
    17.12.2021 19:34
    +1

    ваа, сильно сильно!


  1. Nual
    18.12.2021 14:38
    +1

    Подскажите, пожалуйста, а вы в команду людей не набираете? Аналитики/исследователи?


    1. PDudukin Автор
      20.12.2021 11:03
      +1

      Добрый день! Периодически набираем, конечно. Вакансии аналитиков данных/исследователей данных обычно публикуем в ODS-сообществе в Slack. Но можно и просто связаться со мной - рассмотрим.


  1. YRIXCHATANGO
    18.12.2021 19:48

    фермеров очень много и загружать их товарные каталоги вручную такое себе занятие,

    а сами фермеры свои товары не загружают? и выбрать категорию товара не могут? странный маркетплейс

    и оптимизацией фронта никто не занимается?


    1. YRIXCHATANGO
      19.12.2021 09:45

      чет нет ответа


    1. YRIXCHATANGO
      19.12.2021 09:53

      толпа долбоящеров которая работает на РХСБ просто пиепец

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