
Представьте ситуацию: вам захотелось заказать раков. Что будете делать? Есть несколько вариантов, один из них — поискать подходящее заведение с доставкой в социальных сетях. Там можно найти локальную компанию с хорошими отзывами и приемлемыми ценами. Вы заходите ВКонтакте, открываете поиск по сообществам, вбиваете запрос «заказ раков» и получаете... подборку сообществ по астрологии. Совпадение по тексту есть, паблики популярные, можно сказать, что алгоритмы справились. Вы узнали о влиянии планет на вашу судьбу, но остались без раков.
Казалось бы, запрос звучал очевидно, но для классического текстового поиска это задача со звёздочкой. На помощь приходит семантический поиск — технология, которая обещает понять не только буквы в запросе, но и смыслы, стоящие за ними.
Меня зовут Арсений Расов, я тимлид ML-инженеров в команде поиска AI VK. В этой статье расскажу, как мы с командой внедряли семантический поиск по сообществам ВКонтакте и почему задача, рассчитанная на два месяца, заняла полгода. Рассмотрим современные NLP-технологии в продакшене и поговорим про непредсказуемость проверенных алгоритмов за пределами Jupyter Notebook.
Что такое хороший поиск и как его измерить
Начнём с фундаментального вопроса: чем хороший поиск отличается от плохого? На первый взгляд — скоростью, точностью и предсказуемостью. Но ключевая разница в другом: хороший ищет то, что пользователь хочет найти, а не то, что написано в запросе. Эти смыслы часто разделяет пропасть в виде опечаток, сленговых выражений, неточных формулировок и необычных названий сообществ.

Поиск ВКонтакте — это не просто фича, это одна из ключевых точек входа в сервисы соцсети. Качество поиска напрямую влияет на бизнес-метрики, поэтому наша команда постоянно работает над улучшениями. Чтобы отследить, насколько хорошо мы понимаем пользователей, нам тоже нужны собственные критерии оценки.
Есть два типа метрик:
Асессорская оценка. Пул запросов прогоняется через поиск, полученная выдача отдаётся асессорам, которые оценивают релевантность от 1 до 5. С этими данными можно делать что угодно, но чаще всего мы считаем NDCG — метрика учитывает положение объекта в выдаче и сильнее штрафует за ошибки в топе результатов. Сложность NDCG в том, что её трудно объяснить бизнесу, поэтому параллельно мы смотрим на Precision@K — процент релевантных объектов в топ-k выдаче.
Онлайн-метрики. Они учитывают реальное поведение пользователей: долю запросов с кликами, долю кликов с целевыми действиями (например, вступлениями в сообщество или отправкой сообщения) и долю выдач без результата. Асессорская оценка показывает, насколько хорошо мы находим то, что буквально написано в запросе без учёта реального желания пользователя. Онлайн-метрики показывают, насколько хорошо мы угадываем, что хочет найти пользователь, даже если он не написал этого в запросе прямым текстом.

Компании обычно балансируют между двумя подходами в зависимости от текущих приоритетов. Мы больше пользовались онлайн-метриками, поэтому дальше я буду рассказывать об опыте с учётом этого типа оценки.
Почему классический поиск не справляется
Вызовы, с которыми мы сталкиваемся, работая с поиском, в рамках статьи можно разделить на две категории. Они связаны с семантическим разрывом между тем, что пишет пользователь, и тем, как называются сообщества:
Запрос не соответствует названию сообщества. Пользователь может ввести текст с опечатками, использовать сленг или не знать точного названия. А у разыскиваемого паблика при этом креативное название, с помощью простых запросов его не найти
Широкие запросы. Пользователь задаёт общее направление поиска и хочет получить подборку, которая соответствует этому интенту. Но сообщества могут не содержать его в названии: например, «Стендап на ТНТ» вряд ли напишет, что это юмористическое шоу, поиску нужно догадаться самостоятельно

Решать такие задачи с помощью методов вроде BM25 или проверки на соответствие запроса названию сообщества крайне сложно. Нужно что-то более изысканное, что-то, что понимает смысл, а не просто считает совпадающие слова.
Архитектура поиска: куда встроить семантику
Сначала давайте посмотрим, из чего состоит поиск на верхнем уровне. Классический включает два ключевых этапа: черновое ранжирование (отбор кандидатов) и чистовое (финальное) ранжирование.

На черновом этапе мы отбираем кандидатов для дальнейшего ранжирования, сужаем пул сообществ с миллионов до тысяч или сотен. Здесь важна скорость и эффективность, поэтому используются либо простые эвристики типа сортировки по BM25, либо лёгкие ML-модели — неглубокий бустинг с ограниченным количеством фич и деревьев.
На чистовом этапе запускаем глубокий бустинг с большим количеством деревьев на всех доступных признаках. Так формируется финальная выдача, которую видит пользователь.
Тут возникает стратегический вопрос: какой из этих этапов обогащать семантикой в первую очередь? Интуиция подсказывает выбрать чистовой поиск — там финально формируется качество и появляется больше возможностей для тонкой настройки. Но мы сделали ставку на другой вариант, и на это были свои причины.
Если на черновом этапе мы не находим релевантные документы, потому что нет текстовых пересечений, то чистовая модель их никогда не увидит, какой бы умной ни была. Это как искать ключи под фонарём не потому, что там потерял, а потому, что там светло. Семантический поиск на черновом этапе позволяет расширить пул кандидатов за счёт документов, которые релевантны по смыслу, но не имеют прямых текстовых совпадений с запросом.
Как работает семантический поиск
Идея семантического (он же векторный, он же нечёткий) поиска привлекательна в своей простоте. У нас есть тексты запросов и тексты названий сообществ. Мы преобразовываем их с помощью нейросетевой модели в векторы эмбеддингов — числовые представления в многомерном пространстве. Здесь похожие по смыслу тексты находятся близко друг к другу, а непохожие — далеко. Запросы оказываются близко к релевантным документам и далеко от нерелевантных.

Реализовав такую модель, мы можем заранее посчитать эмбеддинги для всех сообществ и проиндексировать их векторно. Для запросов — сделать сервис, который в реальном времени будет считать эмбеддинги. Дальше с помощью алгоритмов поиска ближайших соседей (в нашем случае HNSW) находим релевантных кандидатов и добавляем их к результатам чернового отбора.
Звучит логично: современные методы NLP уже позволяют работать не только с морфологией, но и с семантикой в любых текстовых технологиях. Что может пойти не так?
Первая попытка: наивный оптимизм
Мы выбрали TinyBERT — дистиллированную версию BERT, обученную на русских текстах. Это относительно маленькая модель, но была причина взять именно её: на этапе Proof of Concept вычислительных ресурсов не хватало для чего-то более масштабного. Мы надеялись справиться с TinyBERT.

С таргетом для обучения тоже пришлось изворачиваться — асессорской разметки у нас не было, поэтому использовали то, что доступно из логов. Можно было взять клики, но у них есть ложные срабатывания: пользователь кликнул, посмотрел и ушёл разочарованный. Мы выбрали вступления в сообщества как более надёжный сигнал релевантности. Если человек вступил, значит, нашёл то, что искал. Да, данных меньше, но за несколько месяцев логов набралось достаточно.
Для обучения использовали InfoNCE Loss — функцию потерь, которая в батче из пар «запрос — релевантное сообщество» пытается приблизить в векторном пространстве элементы каждой пары и отдалить запрос от сообществ из других пар. Элегантное решение для обучения без явных негативных примеров.

Модель обучили, индекс построили, интеграцию сделали. На черновом этапе дополнили существующий отбор по BM25 кандидатами. Оставалось только запустить A/B-тест и радоваться результатам.

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

Первый полноценный A/B-тест показал не лучшие результаты. Процент запросов с пустой выдачей снизился, как и планировалось, но мы падали по кликам в топ-5 и проседали по кликам с вступлениями.

Причина таких результатов — мы думали только о черновом этапе. Наша умная ранжирующая модель никогда не видела документы, которые проходили по семантике, но не проходили по BM25. Для неё эти кандидаты были непонятными сущностями без истории и контекста. Она не знала, что с ними делать, и ставила куда попало, ломая всю выдачу.
Итерация 1: обучаем ранжировщик
Мы нашли решение: собрать данные из эксперимента, добавить их в обучающую выборку и переобучить ранжирующую модель. Так она узнает про новые типы кандидатов. Заодно добавили косинусное расстояние между векторами запроса и сообщества как фичу, чтобы ранжировщик мог использовать семантическую информацию напрямую.

Результаты улучшились: мы почти перестали терять клики с вступлениями, но всё ещё проседали по кликам в топе. До продакшена было далеко, но появилась уверенность, что мы движемся в правильном направлении.

Итерация 2: оверсемплинг спешит на помощь
Мы продлили эксперимент, чтобы собрать больше данных и применить оверсемплинг. В особую категорию объектов выделили сообщества, в которые вступали, — с низким BM25, но высокой семантической близостью. Насемплилось много таких примеров. Мы показали ранжировщику: «Смотри, вот эти странные с точки зрения текстовых метрик документы на самом деле релевантны».

После этого выросли клики со вступлениями — плюс 5%, чуть больше половины от годовой цели. С небольшой просадкой по кликам в топ-5 мы решили выкатиться в продакшен. Это был первый релиз ВКонтакте с семантическим поиском.

Итерация 3: больше семантики богу семантики
К этому моменту стало ясно, что одной косинусной близости недостаточно. Ранжирующая модель должна смотреть на семантику с разных сторон, понимать тексты на уровне смыслов через множество разных сигналов. Мы решили не выдумывать новые метрики для той же модели, а обучить принципиально другие модели на других данных и использовать их предсказания как дополнительные фичи.
Пошли в сторону категоризации, чтобы классифицировать сообщества и запросы по тематикам, а после сравнить их по согласованности. Порядка 1 миллиона пабликов ВКонтакте уже размечены по 60 категориям. Причём это multi-label классификация, когда одно сообщество может относиться к нескольким категориям.

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

Мы использовали RuBERT для обеих задач, достигли 77% Precision@3 для сообществ и 70% для запросов. В качестве фич для ранжировщика использовали модуль разности между предсказанными вероятностями категорий для запроса и сообщества — своеобразную меру семантической согласованности. Итого 60 новых фич, которые смотрят на семантику под другим углом.

Что в финале
Минус 35% запросов без выдачи, плюс 8% в доле кликов с вступлениями и 2% к кликам в топ-5. Мы достигли того, чего хотели: научили поиск понимать не только буквы, но и смыслы — не только то, что пользователь написал, но и то, что он имел в виду.

Ещё планируем обучить Retrieval-модель вместо TinyBERT — что-нибудь из семейства E5, уже на асессорских данных, а не на кликах с вступлениями. Тогда мы сможем:
разделить retrieval-часть и расчёт семантических фич. Так получится экспериментировать с векторным поиском и не ломать каждый раз распределение признаков для ранжировщика
использовать асессорскую оценку для классификации запросов вместо наших эвристик с агрегацией логов
Выводы
Полгода работы, десятки экспериментов, сотни часов анализа — и вот какие простые истины мы вынесли из этого опыта.
Во-первых, любой алгоритм может повести себя непредсказуемо в продакшене. Хорошие скоры на офлайн-оценке не гарантируют, что метрики останутся положительными после запуска в рабочей среде. Это нормально. Это часть процесса, к ней нужно быть готовым.
Во-вторых, мгновенного профита после внедрения новой технологии не бывает. Придётся костылить, добавлять дополнительную логику и модели, итерироваться снова и снова. Красивое решение из статьи на arXiv в реальности обрастает таким количеством практических доработок, что авторы оригинальной идеи его бы не узнали.
В-третьих, сложная система вроде поиска требует множества сигналов. Нельзя решить все проблемы одной серебряной пулей, даже если эта пуля — крутая нейросеть. Нужны сигналы из разных доменов, разные подходы к пониманию семантики, множество источников информации. В финальной модели ранжирования важно правильно скомбинировать эти сигналы: всё учесть и сбалансировать.
Семантический поиск — это мощная технология, которая меняет подход к поиску информации. Наш путь от концепции до работающего решения в продакшене оказался длиннее и извилистее, чем мы ожидали. Зато теперь, когда пользователь ищет раков, он находит именно раков, а не предсказания астрологов. И ради этого стоило потратить полгода на борьбу с реальностью.
SolidSnack
Мужики, просто крик души - обычный поиск ВК то работает, кхм... раком. А тут уже поиск сервисов с AI подъехал...