Всем привет! Меня зовут Аня Власова, и я работаю ML-инженером в Купере (ex СберМаркет), а именно — в команде поиска. Сегодня я расскажу про нашу нейросетевую модель, которая стои́т на страже корректных поисковых запросов. Вы наверняка найдете пару инсайтов в этой статье, если тоже разрабатываете сервисы поиска или просто интересуетесь языковыми нейронками.
Зачем вообще автоисправление в поиске?
Ошибки в поисковых запросах — очень частое явление. Иногда пользователь не знает, как правильно пишется слово, иногда — случайно задевает не те клавиши, забывает поменять раскладку и т. д. Ну вы сами знаете, как это бывает :)
Учесть все возможные опечатки в SEO (поисковой оптимизации) карточки товара невозможно, поэтому без автоисправления поиск по запросу с опечаткой чаще всего не дает совсем ничего. Приходится повторно вводить запрос, а это — лишнее время и повод для раздражения. Тем временем семь из десяти товаров в Купере добавляются в корзину именно из поиска! Наша цель — помочь пользователю найти нужный товар как можно быстрее, и именно для этого требуется автоисправление опечаток.
Как опечаточник устроен сейчас
Все запросы от пользователей отправляются в поисковый движок — у нас это Elasticsearch. Если по введенному запросу есть какая-то выдача, то в дополнительных звеньях нет смысла и мы просто показываем пользователю товары. Если же выдачи нет, запрос должен пройти фильтр опечаточника и затем, в модифицированном виде, повторно отправиться в поисковый движок.
Если товаров нет в выдаче даже после работы опечаточника, приложение сообщает, что нужного товара нет в наличии.
Подробнее о том, как мы отбираем и ранжируем товары в поиске, можно прочитать в моей прошлой статье (кстати, у неё была номинаия на Технотекст 🙃).
На актуальном проде в качестве опечаточника у нас работает алгоритм SymSpell (вот ссылка на GitHub). Принцип его работы заключается в предобработке словаря:
Для каждого слова создается перечень возможных опечаток — ключей, которые могут из него получиться путем удаления одной или нескольких букв.
Если вводится слово с ошибкой, алгоритм генерирует ключи-удаления уже для него и идет сопоставлять их с предварительно созданным словарем опечаток.
Результаты ранжируются для выбора того исправления, которое с наибольшей вероятностью окажется верным. Ранжирование учитывает расстояние Левенштейна, то есть степень близости, и популярность (частоту ввода запросов в поисковую строку).
Объясню на пальцах. Для слова «торт» SymSpell сгенерирует такие однобуквенные удаления: «трт», «орт», «тот», «тор». Удаление «орт» также актуально, скажем, для слов «корт» и «борт», однако на запрос «иорт» алгоритм выдаст все-таки кондитерские изделия, потому что запрос «торт» гораздо более органичен для Купера (СберМаркета).
SymSpell очень быстрый, однако качество его работы не идеально. Чтобы повысить эффективность поиска, мы задумались над альтернативой.
Архитектура новой нейросетевой модели
Наше решение очень похоже на то, как решаются задачи по машинному переводу. Единственное отличие состоит в том, что у нас на входе и на выходе используется один язык.
Модель работает по принципу Seq2Seq (sequence-to-sequence), то есть сопоставляет одну последовательность данных с другой. Энкодером стала двунаправленная модель GRU, декодером — GRU с механизмом внимания. Помимо GRU, мы пробовали работать с более классической для Seq2Seq моделью — LSTM, — однако она обучалась гораздо дольше, не будучи при этом более результативной.
На вход мы подаем данные, разбитые на токены с помощью BPE. BPE-токенизация итеративно определяет наиболее часто встречающиеся в тексте сочетания символов. Каждый символ — отдельный токен. Частоты всех токенов подсчитываются, наиболее частотные пары токенов заменяются новыми токенами. В итоге формируется словарь токенов, который может включать как отдельные символы, так и комбинации.
Чем мы кормили нейронку
Для обучения нейросетевой модели нам нужны были пары: поисковый запрос с опечаткой + корректно написанный поисковый запрос.
Мы в Купере логируем только исходные запросы пользователей, и у нас нет информации о том, когда применялся опечаточник и как именно он исправлял запрос. Поэтому нам пришлось танцевать с бубном, чтобы заполучить требуемые пары.
У нас было три пути сбора данных для обучения:
Синтетическая генерация опечаток по названиям товаров.
Синтетическая генерация опечаток по корректным запросам (которые надо было достать из базы всех запросов).
Извлечение реальных пар.
Сейчас расскажу о них в подробностях.
Синтетическая генерация опечаток
Мы проанализировали поведение пользователей и выявили основные типы опечаток:
замена буквы в слове на соседнюю по клавиатуре или случайную — «gолоко» вместо «молоко»;
добавление лишней буквы — «молокео»;
случайное удаление буквы — «молоо»;
перестановка двух соседних букв слова местами — «млооко»;
дублирование буквы в слове — «ммолоко»;
отсутствие необходимого пробела — «пастеризованноемолоко»;
двойной пробел — «пастеризованное молоко»;
орфографические ошибки: «а» вместо «о», «и» вместо «е» и т. д.
Все типы опечаток генерировались по названиям товаров и корректным поисковым запросам. Также мы включали в обучение сами корректные запросы, чтобы модель обучалась их не трогать.
Названия товаров брались не целиком, а частями — чтобы они больше были похожи на то, что вводят пользователи. Мы извлекали униграмму (первое слово), биграмму (первые два слова) и триграмму (первые три слова). Для товара с названием «Молоко 2,5% пастеризованное 930 мл БЗМЖ» это «молоко», «молоко 2,5%» и «молоко 2,5% пастеризованное».
Чтобы извлечь корректные поисковые запросы из базы всех запросов в Купере (СберМаркете), мы прогоняли базу через открытые модели для исправления опечаток. Если большинство моделей не изменяли запрос, он маркировался как правильно набранный.
Извлечение реальных пар
Это самое интересное!
Сначала мы собирали все запросы, которые в течение небольшого промежутка времени вводились одним пользователем в одном магазине и в рамках одного заказа. Если пользователь заходил в Купер (СберМаркет) и поочередно за пять минут вбивал «малоко», «молоко», «молоко п», «молоко простоквашино», «хлеб» и «масло», эти запросы мы объединяли в одну группу.
Далее среди запросов нужно было найти те, которые отличались друг от друга одним или двумя символами. В нашем примере это запросы «малоко» и «молоко». Пары, где один запрос являлся частью другого (как «молоко» и «молоко п»), не рассматривались.
Среди получившихся пар мы находили более популярный запрос — который вводился пользователями чаще («молоко»). Он считался кандидатом в корректно написанные. Наименее популярный запрос («малоко») считался кандидатом в написанные с опечаткой.
N.B. Иногда популярный запрос — это тоже запрос с опечаткой. Поэтому результаты сравнения частотности добавлялись в датасет для обучения только после проверки на упомянутых ранее открытых моделях для исправления опечаток: наиболее популярный запрос должен был после прогона остаться неизменным, а наименее популярный — исправиться в наиболее популярный.
Реальные опечатки пользователей принесли нам самый большой прирост в метриках качества нейросетевой модели.
Дополнительные техники обучения
Также для улучшения качества и сокращения времени обучения мы применяли Teacher Forcing и Masked Language Modeling.
Teacher Forcing
Основная идея Teacher Forcing состоит в том, чтобы использовать истинные значения последовательности для лучшей обучаемости модели. Это минимизирует ошибки, которые могут накапливаться при генерации последовательностей на основе предыдущих собственных предсказаний.
На каждом шаге обучения мы уменьшали долю объектов, для которых применяли Teacher Forcing, чтобы сделать модель устойчивой к новым данным и её собственным ошибкам.
Masked Language Modeling
Метод предварительного обучения нейронных сетей, когда мы случайным образом маскируем часть входных токенов и просим модель предсказать замаскированные токены. В нашем случае сначала шло предварительное обучение на полных названиях товаров, а уже потом модель работала на искомое исправление опечаток.
И что, действительно стало лучше?
Да! Наша модель показала себя лучше, чем текущий продовый алгоритм SymSpell, причем как на сгенерированных опечатках, так и на реальных запросах пользователей. Финальная оценка, которую вы видите на скрине под этим абзацем, проводилась именно на реальных запросах.
+17% качественных исправлений — это ничего себе, правда? Новая модель дает просадку по времени в сравнении с продовым решением, однако разница не критична, потому что мы ожидаем, что система целиком будет отвечать пользователю за 500 миллисекунд.
Для обучения модели мы использовали данные за 2023 год, а для тестирования — за 2024-й. При таком подходе данные в обучающих и тестовых выборках могут повторяться, что соотносится с реальностью, ведь юзеры могут допускать как повторяющиеся, так и новые ошибки.
Сейчас мы тестируем новую модель уже в проде. В планах все-таки поработать над сокращением времени работы и улучшить качество исправления опечаток для отдельных вертикалей (в частности для аптек, где своя специфика поисковых запросов). Также в будущих версиях мы учтем транслитерацию и неправильную раскладку клавиатуры.
Надеюсь, что мой опыт окажется полезным для вас! Готова ответить на вопросы в комментариях :)
Кстати, впервые об этой теме я рассказывала на митапе, посвященому DS-решения в нашем сервиса поиска. Там есть ещё две крутые темы, если инетресно — ловите:
ML-команда Купера (ex СберМаркет) ведет tg-канал «ML доставляет» — там мы рассказываем, как создаём доставку будущего с помощью DS, NLP, CV. А если хочешь больше узнать, про нашу инженерную культуру в целом, подписывайся на tg-канал Купер.тех и на наш YouTube. А также слушай подкаст «Для tech и этих» от наших it-менеджеров.