Привет! Меня зовут Никита Романов, и я техлид продуктов «Поиск по фото» и «Похожие по фото» в Wildberries. За спиной — более семи лет опыта в сфере CV.

В этой статье мы обсудим онлайн сервис «Поиск по фото» - архитектуру и основные компоненты — Image Retrieval, подбор текстовых тегов и уточнение текстом. Также обязательно поговорим о векторном индексе Qdrant, т.к. метрики и эксперименты мы тестируем в нём. Расскажем про результаты A/B-тестов и что уже в проде.

Как работает «Поиск по фото»

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

Это очень удобно, когда:

  • запрос пользователя специфический (нужен именно такой товар и никакой другой),

  • сложно / невозможно подобрать описание,

  • пользователь не знает название или производителя,

  • долго или лень вводить текст.

Когда пользователь загружает фотографию, мы пропускаем её через два основных пайплайна детекции.

Первый пайплайн — для детекции основных объектов. Это модель семейства YOLO, которая охватывает порядка 90% всех категорий на маркетплейсе: от одежды до мебели. Вторая модель — E2E OCR для детекции и распознавания артикулов — узкоспециализированный сценарий для пользователей, которые находят артикулы в пабликах на фото, очень удобно.

Когда пользователь выбирает предложенный кроп— переходим в общую эмбеддинговую модель. Сейчас мы используем SigLIP 2, и позже расскажу, почему именно её. От общей модели идут три основных пайплайна: модель Image Retrieval, то есть основной сценарий поиска, векторный индекс тегов и модель уточнения текстом.

После Feature Extractor Backbone идём в модель Image Retrieval. После неё — в векторный индекс Qdrant - в нем хранятся вектора всех товаров с маркетплейса. Осуществляем HNSW поиск в построенном индексе и получаем список всех кандидатов, далее обогащаем ключевыми характеристиками по товару: цена, количество отзывов, рейтинг и т. д. Ранжируем бустингом, переранжируем по логистике в онлайне и получаем финальную выдачу.

Почему мы используем именно Qdrant? Мы тестировали Redis, Milvus, Weaviate и другие открытые базы данных для векторного хранилища, но Qdrant показал лучшие результаты по нагрузке. Он держит максимальный RPS с минимальным Latency и из коробки поддерживает такую приятную вещь, как перестроение индекса на лету — это позволяет нам держать базу всегда актуальной.

В проде мы используем квантованные вектора 256 fp16. Время пайпа всего сервиса, от загрузки изображения до выдачи ответа пользователю, в среднем составляет 250мс в индексе размером 400млн товаров, которые мы предварительно векторизуем офлайн. База данных обновляется примерно пять раз в день.

Модель распознавания — основной сценарий

Всё начинается с данных — как и в любой задаче по машинному обучению. Мы собрали большой датасет WB 200M, который состоит из галерейных фотографий карточки товара от продавца и фотографий пользователей из отзывов — так мы составляем релевантные пары для задачи ImageRetrieval  

Что мы делаем с данными?

  1. Очищаем от дубликатов CLIP-like моделями. В нашем случае с задачей отлично справляется всё тот же SigLIP 2.

  2. Фильтруем от шумов и выбросов теми же CLIP-like моделями. Шумы — это размерная сетка, реклама, фото упаковки, фото другого товара и т. д.

  3. Отправляем в финальную команду разметки, где решается главная задача — мэтчинг двух фото на тему «Является ли объект на фото А объектом на фото Б».

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

4. Последний этап аугментации — детекция главного объекта. Во время обучения выборочно детектим главные объекты, чтобы эмбединговая модель  училась игнорировать  фон, лица моделей и прочий шум на фото карточки товара.

Почему мы выбрали такую модель? DFN, BLIP3, SigLIP — в прошлом году мы перебенчили почти большой пул моделей с HF и в этом году перешли на SigLIP2, который показывает лучшие результаты по нашим основным офлайн-метрикам: Precision, F1 и mAP.

Архитектура обучения модели: замороженный SigLIP 2, выходы идут в Attention-голову и далее в MRL с InfoNCE Loss.

Об Attention-голове можно поговорить отдельно. Она представляет собой Self-Attention Token Reducer и Attention Pooling: уменьшение размерности, нормализация Multi-Head Attention и на выходе вектор размера 512. Из любопытного: в Attention-голове есть UnLearnable Token — это необучаемый, но участвующий в обучении параметр, который мы инициализируем как матрицу средних по датасету Query-параметров с Multi-Head Attention.

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

Идём дальше — MRL InfoNCE Loss, матрёшка Representation Learning. Отличный подход для сжатия векторного пространства в нашей задаче.

Выходы с Attention-головы на предыдущем шаге мы пропускаем в MRL, где нарезаем вектор N-ного размера на векторы от 512 до 64 (в нашем случае). Затем независимо считаем InfoNCE Loss для каждого из этих векторов, берём среднее и получаем финальный Loss для обучения модели.

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

Результаты на выходе из обучения с MRL

Without MRL — обучение без MRL-подхода, просто выход с Attention-головы. Справа — результаты с MRL, где гиперпараметры модели были нарезаны от 128 до 1024. Прирост составил более 3% на всех ключевых офлайн-метриках, что однозначно приятно, ведь мы получили такие цифры бесплатно без дополнительных данных, просто используя MRL-подход.

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

Внизу — гиперпараметры обучения MRL-подхода. Например, средний график [64: 512] означает, что мы нарезали входной вектор размера 512 от 64 до 512, а по оси Y отложили одну из наших ключевых метрик.

Заметили, что две основные линии, которые обозначают размер векторов 256 и 512, находятся очень близко? Разница в значении появляется только на третьем знаке после запятой. Это достаточно  близкий результат метрик, поэтому в проде можно использовать 512 офлайн и 128 онлайн.

Погружаемся в текстовые теги

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

Пользователи используют теги для уточнения запроса, который они уже сделали ранее в качестве фотографии, или для Discovery, если нужна специфическая часть загруженного изображения. Может быть, важнее, чтобы подушка была квадратной, а не жёлтой. Или наоборот — интент у тегов бывает разный.

Дальше рассмотрим три подхода к генерации тегов, каждый из которых моя команда успела протестировать. Сразу отмечу: мы генерируем все теги офлайн. Почему? Потому что генерация онлайн через VLM и LLM — это дорого, и нет контроля качества.Также онлайн VLM может галлюцинировать и выдавать нерелевантные результаты.

Подход первый: генерация при помощи LLM. За основу мы брали название товара, текстовое описание карточки на маркетплейсе и всю доступную информацию. Пропускали это через Gemma-3 и получали конечный набор тегов. В описании обычно было много рекламы и шума, из-за чего приходилось проводить повторную обработку, чтобы получить чистый датасет.

Второй и третий подход очень похожи. В основе обоих — генерация при помощи VLM. Здесь мы пропускали изображения через Qwen 2.5, через API переводили результаты генерации и снова постпроцессили локально через LLM. Минусы очевидны: много этапов, и на каждом из них генерируется шум. Тем не менее в своё время этот подход показывал наилучшие результаты, особенно после выхода моделей Qwen 2.5 и Qwen 3. Рекомендую начинать с этого подхода, если планируете решать подобную задачу как бейзлайн.

Какие проблемы встречаются в работе с тегами:

  • эмоциональные описания,

  • семантическая несогласованность (все слова знакомые, но смысл ускользает),

  • опечатки (пропущенные окончания или лишние буквы в середине слова),

  • слова на другом языке.

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

При этом основной визуальный энкодер — всё тот же SigLIP2, который мы используем как фичер экстрактор во время обучения.

Как мы обучаем модель

Используем изображения из карточки товара и датасет из текстовых тегов, которые сгенерировали заранее, пропускаем их через соответствующие эмбеддеры, получаем набор сильных эмбеддингов и пропускаем его через MLP. Наша цель — спроецировать пространство из LLM или BERT Embedder в пространство SigLIP 2, то есть приблизить тексты к изображению, а не наоборот.

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

Быстрый способ тренировать линейные модели

Конечно, MLP — самая простая голова, чтобы быстро обучать модели и получать бейзлайны. Но есть трюк, как решить задачу ещё проще — использовать Closed-Form Solution на основе вот такой формулы:

Да и тут есть недостатки. Эта формула очень требовательна к оперативной памяти, поэтому мы решаем задачу накоплением батчей. То есть берём части большого векторного массива изображений и текстов, перемножаем частично в памяти, аккумулируем и финально перемножаем накопленные батчи в памяти: X.T@X перемножаем на X.T@Y и получаем оптимальное решение.

Второй трюк, который мы используем, — ортогональная инициализация. Она повышает сходимость модели в 4 раза и добавляет 2–3% к финальным метрикам. Берём рандомную Q-матрицу и ортогонально инициализируем первый слой, потом берём такую же Q-матрицу и инициализируем уже транспонированный слой.

Примерно так выглядит наша MLP-голова:

На этой задаче считаем следующие метрики: Word Recall Rate, Word Error Rate и Retrieval Metrics. Каждая из них достаточно информативна, но есть тонкости. В Word Recall Rate мы начинаем поощрять за более длинные теги, что иногда вызывает галлюцинации у модели. А метрики Error Rate и Retrieval Metrics нам, наоборот, приходится штрафовать за дополнительные уточнения, которые иногда оказываются полезными для пользователя.

Так может выглядеть результат:

Загрузив фото топа с белым кружевом, получаем само описание кружева и даже аксессуаров на девушке. (Кстати, благодаря нашей модели я узнал слово «tribal», которое описывает узор на топе справа).

Уточнение текстом

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

Карточка товара на маркетплейсе — это набор одних и тех же товаров с разными характеристиками. Хороший пример и бейзлайн для такой задачи — джойстик, который просто представлен в разных цветах:

Данные можно также генерировать при помощи VLM, скормив ей две картинки с задачей: «Опиши, чем отличаются два изображения». Так мы получим ещё один триплет: два изображения + сгенерированное VLM отличие.

Ещё один интересный способ, который может улучшить результат — данные триплета текста. Берём текстовый энкодер SigLIP2 и пропускаем наименования с карточек товара. Получаем три эмбеддинга: текстовый якорный, целевой и эмбеддинг изменения. Тут надо быть аккуратным с данными — они могут быть шумными или мало информативными

Как в итоге выглядит эта задача во время обучения?

Пропускаем картинку и текст изменения через MLP-голову и приближаем MLP-голову к целевой картинке и целевому SigLIP2-пространству. Во время инференса отправляем эмбеддинг в Image Retrieval, где уже ищем в Qdrant основные предварительно подсчитанные векторы. Если мы хотим переиспользовать SigLIP2-эмбеддинг и пропускаем его через голову модификации текстом (где отдельно добавляем текст), то проецируем его в исходное SigLIP2-пространство, которое можем повторно спроецировать в Image Retrieval.

Не нужно пересчитывать всю базу данных и изобретать велосипед. Все эмбеддинги для товаров уже посчитаны, и они будут в том же пространстве модели Image Retrieval, хотя вход у них может быть разный.

Посмотрим на результат:

Слева мы ищем фигурку Эльзы из мультфильма «Холодное сердце», имея фото фигурки Рапунцель. Модель находит игрушку того же бренда — круто! Справа ищем планку памяти для компьютера. Модель немного умеет в оптическое распознавание символов, поэтому понимает, что требуется планка на 1 333 МГц, и успешно её находит.

Метрики офлайн те же, что для TagRetrieval: Hitrate, MRR и NDCG.

Метрики в онлайне

Мы проводили A/B-тест для модели Image Retrieval с Attention-головой на векторах размера 128 и 256, чтобы посмотреть, как будет отличаться качество в онлайне на бизнес-метриках. Спойлер: сильно.

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

В статье мы рассмотрели ключевые аспекты текущей реализации Поиска по фото, рассказали про лучшие практики и поделились фактическими результатами.

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

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