- BrainTools - https://www.braintools.ru -

LLM‑разметка в поиске: от эксперимента к инструменту

Привет! Меня зовут Александр Баранов, я аналитик данных в команде поиска Купера. Цель этого рассказа, поделиться наработками в деле оптимизации разметки текстовых данных при помощи большой языковой модели (LLM). Если после прочтения вы захотите что-то добавить или спросить, буду только рад!

LLM‑разметка в поиске: от эксперимента к инструменту - 1

Как устроен поиск товаров

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

LLM‑разметка в поиске: от эксперимента к инструменту - 2

Итак, пользователи вводят запрос в строку поиска, и мы обязаны сделать их опыт [1] комфортным. Для этого:

  • исправляем все возможные опечатки;

  • обогащаем запрос синонимами, чтобы учесть разные варианты формулировок;

  • достаем кандидатов из поискового индекса и переранжируем их.

Полученную выборку показываем людям.

По-хорошему, разметка текстовых данных нужна на каждом из этих этапов. Сначала, для исправления опечаток — чтобы оценить, корректно ли система исправила запрос. Затем, для генерации кандидатов — чтобы понять, логично [2] ли расширять запрос именно этими синонимами. А после реранкинга — чтобы убедиться, что мы показываем действительно релевантные товары.

Делать это вручную — неудобно, сложно и дорого. Чтобы разметить данные, надо выгрузить их и отправить на краудсорсинговую платформу. Потом долго ждать, пока асессоры возьмут задачу в работу и сделают ее. Наконец, такие задания требуют немалого бюджета, что тоже не очень приятно.

Вот эти сложности и стали стимулом [3] поискать более быстрый и экономичный способ разметки. С учетом того, что большие языковые модели обладают бесконечными знаниями, способны работать круглосуточно и без выходных, к тому же быстрее и дешевле живых асессоров, нам показалось логичным, попытаться делегировать эту работу (или хотя бы ее часть) им.

Внедрение LLM в поиск 

Давайте последовательно пройдем по всем этапам. 

Исправление опечаток

Elastic* — полнотекстовая система, это означает, что после лемматизации она находит документы исключительно с такими же токенами. Если пользователь вводит запрос с опечаткой, а все искомые товары содержат только правильное написание, система просто не найдет такой товар. 

Скрытый текст

* Elastic — открытая поисковая система (аналитическая платформа на основе Lucene), которая индексирует данные и находит документы (в вашем случае — товары) по введенному запросу.

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

Казалось бы, что еще нужно? Тем не менее есть проблема: вместе с правильными исправлениями в словарь может попадать мусор. Например, в паре «колея» и «Корея» — расстояние Левенштейна* довольно низкое, меняется лишь один символ, однако полностью утрачивается смысл. Чтобы контролировать этот момент, мы и применяем большие языковые модели. Они автоматически оценивают, правильно ли сработало исправление.

Скрытый текст

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

Мы тестировали разные модели: Mistral, GigaChat, DeepSeek, их комбинации, например, голосование моделей, сценарии с переоценкой, когда одна модель давала помимо ответа степень уверенности в своем ответе. 

Если уверенность была низкой, разметка передавалась другой модели. Кто, как вы думаете, победил? Правильно — победил GigaChat. У него оказался не только самый быстрый инференс, он к тому же прекрасно работал с русским языком, показывая стабильно высокую точность.

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

Синонимы: расширять осторожно

Следующий вызов, после того как мы исправили опечатки — объяснить системе, что «помидор» и «томат» на самом деле обозначают один и тот же объект. Набирая в строке «помидор», пользователь ожидает увидеть в товарной выдаче товары, логично связанные с термином «томат». Но, поскольку (как мы уже зафиксировали выше) Elastic осуществляет исключительно полнотекстовый поиск, «помидор» и «томат» для нее — различные токены, поэтому система не находит и не показывает товары, в названии которых присутствует «томат». Для этого в ней предусмотрен механизм синонимов.

Синонимы бывают Index time (время индексации) и Query time (время запроса): 

  • первые применяются на этапе индексации товаров:

  • вторые — непосредственно во время запроса. 

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

Пример: «помидор, томат, помидорка» — все эти термины считаются равнозначными. Если пользователь ищет «помидор», найдутся товары с названием «томат» и «помидорка». И наоборот — ищет «томат», найдутся «помидор» и «помидорка». Связь работает в обе стороны.

Или с направлениями (односторонние синонимы), когда термины расширяются по направленным связям, но не в обратную сторону. 

Пример: «помидор => красный овощ». Здесь стрелка показывает направление. Если пользователь ищет «помидор», система его расширит на «красный овощ» и найдет товары с этим описанием. Но если кто-то ищет «красный овощ», система не будет искать «помидор». Связь работает только в одну сторону.

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

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

Пайплайн соответствия синонимов аналогичен валидации опечаток, правда, с особенностями. Промпт здесь подготовлен именно для рядов синонимов:

  • подробно описывает, что считать синонимом, а что нет;

  • учитывает корнер-кейсы; 

  • содержит инструкцию по обращению с транслитерациями и переводами;

  • знает, в каком варианте предоставить ответ. 

После того как ответ от модели получен, мы фильтруем валидные синонимы и отправляем запрос на слияние изменений (merge request) в Git-репозиторий со словарями.

И наконец последний слой валидации — бот в GitLab. Он ищет разницу (diff) в merge request с добавлением синонимов, и найдя ее, отправляет в языковую модель, которая предлагает подсказки-варианты для подозрительных мест. Автор merge request может принять или отклонить их буквально одной кнопкой. Да, помимо дополнительной валидации, наш сценарий предоставляет также генеративную часть — модель может предлож��ть что-то исправить или добавить.

Релевантность: как ESCI упрощает жизнь

В его основе модель CatBoost, которая учитывает как предрассчитанную косинусную близость эмбеддингов запроса и товаров, так и различные конверсии, статистики на уровне магазинов, пользователей и SKU. 

Когда в модель вносятся какие-то изменения, мы смотрим на актуальную метрику качества ранжирования в поиске (NDCG) относительно пользовательских сигналов — игноров, кликов, добавлений в корзину, покупок. При этом не забываем [5] мониторить смысловое соответствие. 

Для этого товары из поисковой выдачи размечаем на четыре класса:

  • Высокая релевантность (Vital) — товар полностью подходит запросу. Совпали все ключевые характеристики, если они были указаны.

  • Средняя релевантность (Rel Plus) — некоторые характеристики не совпали, но в целом товар подойдет на замену большинству пользователей.

  • Низкая релевантность (Rel Minus) — не совпали какие-то существенные характеристики. На замену товар подойдет лишь небольшой части пользователей.

  • Отсутствие релевантности (Irrelevant) — товар не подходит ни в каком виде. В идеале эти позиции вообще не должны появляться в выдаче при поиске. Если они появляются, это значит, что модель ранжирования работает плохо.

LLM‑разметка в поиске: от эксперимента к инструменту - 3

Вроде хорошо, однако подход с такой классификацией несет проблему. Дело в том, что промежуточные классы Rel Plus и Rel Minus достаточно размыты и часто требуют субъективной оценки: системе сложно понять, подойдет ли товар на замену большинству пользователей или небольшой части. Обычно в подобных ситуациях асессоры не приходили к консенсусу, да и большие языковые модели ошибались тоже.

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

В ESCI четыре класса:

  • Exact — точное совпадение, товар полностью соответствует запросу.

  • Substitute — заменитель, товар не тот же самый, но его можно разумно купить вместо искомого.

  • Complementary — комплиментарный, товар дополняет искомый (аксессуар, расходник, сопутствующее).

  • Irrelevant — нерелевантный, товар к запросу не относится.

Главная идея ESCI в том, что классы разделены по смыслу более четко, чем шкалы типа «почти подходит большинству / меньшинству», поэтому и людям, и LLM проще работать с разметкой. В нашем случае — это помогло. Точность LLM-разметки стала выше: на сбалансированной выборке, где этих классов поровну, она выросла с 0,55 до 0,7, а в промежуточных классах стало меньше ошибок.

Батчи, кеш и контроль качества

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

Пример:

  • Неэффективно (обработка по одному):

text

Запрос 1: "Проверь синоним: помидор → томат"

Запрос 2: "Проверь синоним: яблоко → фрукт"

Запрос 3: "Проверь синоним: кот → животное"
LLM‑разметка в поиске: от эксперимента к инструменту - 4 [6]
  • Эффективно (батчевая обработка):

text

Один запрос: "Проверь синонимы:

1. помидор → томат

2. яблоко → фрукт

3. кот → животное"
LLM‑разметка в поиске: от эксперимента к инструменту - 5 [6]

Для нас это важно по нескольким причинам:

  • экономия токенов — инструкция пишется один раз вместо трех;

  • экономия времени — один API-запрос, а не три;

  • та же точность — на тестах выяснилось, что батчевая обработка не снижает точность.

В GigaChat input (вход) и output (выход) тарифицируются одинаково, поэтому много повторяющихся инструкций в каждом запросе было бы транжирством токенов. LLM получает и отдает batch, полученный JSON легко распарсить и извлечь оттуда нужную информацию. 

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

В основе решения лежит базовый для разметчиков класс LLM Labeler, от которого легко наследуются другие языковые модели. За кеширование отвечают контекстные менеджеры — при инициализации они подгружают кеш с S3, а после выхода из контекста отправляют его обратно. Разметчик релевантности также обращается к внутренним сервисам, чтобы собрать метаданные про SKU (Stock Keeping Unit) — уникальный код товара.

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

Заключение: выводы и roadmap

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

Из последнего — был построен пайплайн автоматической разметки и мониторинга качества поиска. Алгоритм следующий:

  • берем семпл реальных запросов за вчерашний день;

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

  • результат сохраняем в базу; 

  • подключаем BI для визуализации трендов и аномалий;

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

Числовые значения изменены и приведены в иллюстративных целях 

Числовые значения изменены и приведены в иллюстративных целях 

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

На этом все! Если у вас появились вопросы или желание поделить��я опытом в LLM, с удовольствием пообщаюсь в комментариях!

Автор: Alexandr_Baranov

Источник [7]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/25447

URLs in this post:

[1] опыт: http://www.braintools.ru/article/6952

[2] логично: http://www.braintools.ru/article/7640

[3] стимулом: http://www.braintools.ru/article/5596

[4] ссылке: https://telegra.ph/Prompty-dlya-razmetki-11-14

[5] забываем: http://www.braintools.ru/article/333

[6] Image: https://sourcecraft.dev/

[7] Источник: https://habr.com/ru/companies/kuper/articles/993588/?utm_source=habrahabr&utm_medium=rss&utm_campaign=993588

www.BrainTools.ru

Rambler's Top100