Мы — платформенная команда ML-инженеров в Магнит OMNI: бизнес-группе, объединяющей сервис доставки «Магнит Фудтех», маркетплейс «Магнит Маркет», рекламную платформу AdTech и программу лояльности «Магнит Плюс».

Введение

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

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

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

Ограничимся упрощенной схемой поиска для понимания:

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

  • Если катпред «полный», то поиск перенаправляется в категорию из каталога. Далее мы ранжируем выдачу с помощью отдельной механики «категориального ранжирования», разбор которой мы в этой статье опустим

  • Если же катпред «неполный», то поиск происходит по стандартному пайплайну, но фильтруется товарами конкретной категории.

Вместе с катпредами наша схема поиска обновляется:

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

Например, вот сравнение запроса «iphone 16 pro» с катпредом и без:

Без катпреда
Без катпреда
С катпредом на категорию «Электроника/Смартфоны и аксессуары/Смартфоны»
С катпредом на категорию «Электроника/Смартфоны и аксессуары/Смартфоны»

Благодаря катпреду поиск происходит только среди смартфонов, что приводит к:

  • увеличению релевантности поисковой выдачи;

  • сокращению времени ответа на запрос

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

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

Решение: создать ML-алгоритм.

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

Структура товарных категорий представляет собой иерархическое дерево, в корне которого находится абстрактный узел «Все категории». От него отходят основные разделы каталога, каждый из которых имеет собственные дочерние узлы. В зависимости от глубины и детализации ассортимента, дерево обычно содержит 3–5 уровней, заканчивающихся листовыми категориями:

Ниже представлено количество категорий каждого уровня:

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

Источники:

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

Таргет — категория, которую нужно предсказать, — имеет, как было сказано выше, 5 уровней. Чтобы учесть это, мы должны декомпозировать пятиуровневый Category_id в 5 чисел - id каждого уровня отдельно. Для категории 5 уровня Category_id превратится в 5 ненулевых чисел, для категории 3 уровня — в 3 числа и 2 нуля, которые соответствуют 4 и 5 уровням. Это нужно, чтобы на каждом объекте ансамбль мог пройти все 5 ступеней или выйти на предсказание ранее:

Таким образом, наши данные выглядят как «поисковой запрос, который векторизирован – (Category_lvl1_id, Category_lvl2_id, Category_lvl3_id, Category_lvl4_id, Category_lvl5_id)», где признак – эмбеддинг, а таргет – 5 классифицируемых id.

Выбор архитектуры модели

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

И хотя на первый взгляд задача решается по алгоритму «взять BERT» и, «получив числа», «применить софтмакс», все же кажется, что можно добиться лучшего качества быстрее, используя нативную архитектуру задачи.

То есть мы могли бы использовать логическую связь, которая спускается по дереву категории товаров, в обучении нашей модели. Как именно это сделать? Если мы посмотрим на список категорий 1 уровня: 

А также на типичные запросы пользователей:

То мы увидим, что много запросов легко соотносится к конкретной категории первого уровня: «махеев джем груша» — «Продукты», «телефон realme c 75» — «Электроника», потому что их всего 21. Категории же следующих уровней — 2–5 — более разнообразны и сложны семантически.

  1. Предположим, получили модель, которая классифицирует поисковый запрос на 1 из 21 категорий первого уровня. 

  2. Взглянем на задачу по-новому: если определили категорию первого уровня — «Продукты» для того же запроса «махеев джем груша», то можем с большей легкостью определить категорию второго уровня. 

  3. Продолжая рассуждения, определим третью, четвёртую, и, наконец, пятую. 

С помощью такого алгоритма решаем следующие проблемы:

  • знания о предыдущих уровнях категории помогают в предсказании следующих уровней;

  • изначальная сложная задача классификации — попробовать разрешить ~3500 классов с ходу – превратилась в 5 классификаций полегче.

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

Если мы вглядимся в структуру этой модели, то увидим, что это – DirectedAcyclicGraph,или  DAG-структура, в честь которой названа статья.

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

Вот как выглядит обновленная схема:

Используя такую структуру, мы получим несколько преимуществ:

  • число классов на ступени N ограничиваем количеством категорий уровня N;

  • позволит более сложным ступеням – 3 и 4 – использовать уверенные предсказания предыдущих моделей, получив, таким образом, своего рода Attention;

  • облегчим на каждой ступени выход из алгоритма: если на этой ступени модель не предсказала категорию достаточно уверенно (по впоследствии определенному вручную threshold), мы не подаем объект на следующую ступень, а просто заканчиваем инференс объекта выборки;

  • инвариантность к базовым алгоритмам: это может быть любая классифицирующая модель. В силу гибкой настройки мы выбрали градиентные бустинги.

Из минусов такого подхода стоит отметить:

  • необходимость настройки каждой базовой модели по отдельности с разными целевыми метриками: первые модели в ансамбле должны иметь больший Recall, в то время как, начиная с третьей модели, важнее становится Precision (скорее особенность, чем минус);

  • авторегрессионность ансамбля: обычно такая проблема решается с помощью BeamSearch;

  • такой последовательный ансамбль не может быть примитивно распараллелен.

Выше упоминали механику выхода из модели:

  • при обучении используем только те объекты, которые на каждой ступени предсказаны более уверенно, скор которых выше threshold;

  • при инференсе, когда был так же уверенно предсказан конец категории — то есть id=0 на 3--5 ступени. 

Визуализируем эту логику, добавив ее в нашу схему:

Обучение и валидация модели

Было собрано ~7 млн пар «Поисковый запрос — 5 id уровней категорий».

Написали модуль для перебора гиперпараметров каждой модели для обучения ансамбля, чтобы решить проблему внимания к каждой базовой модели. В первых моделях ансамбля целились в Recall, в последних - в Precision.

Метрики

Accuracy

Precision

Recall

F1-score

L1_category

0.997

0.997

0.997

0.997

L2_category

0.996

0.996

0.996

0.996

L3_category

0.994

0.994

0.994

0.994

L4_category

0.995

0.996

0.995

0.996

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

Эксперимент и инференс

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

«браслет бижутерия» -> «Все категории/Аксессуары/Женские аксессуары/Бижутерные украшения/Браслеты»

Это логичное поведение на основе пользовательских данных, но не то, которое мы ожидали от модели. 

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

Проведя второй эксперимент со всеми изменениями, зафиксировали статзначимый прирост денежных метрик.
После чего раскатили модель на всех пользователей и написали Airflow-DAG для автоматического инференса данных, чтобы новые запросы попадали в катпредовые правила.

Заключение

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

Успехов!

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