Кросс-модальный поиск с Sentence Transformers: RAG для текста и изображений | AiManual
AiManual Logo Ai / Manual.
09 Апр 2026 Гайд

Мультимодальные эмбеддинги и реранкеры: кросс-модальный поиск в RAG, который работает

Как использовать мультимодальные эмбеддинги и реранкеры Sentence Transformers для создания RAG-систем, работающих с текстом и изображениями. Пошаговый гайд.

Когда текст перестал быть главным

Ваш классический RAG умеет искать по тексту. Умеет работать с PDF, с документами. Но что делать с картинкой, которая стоит тысячу слов? Или с запросом, который описывает изображение? «Найди мне графики с ростом продаж за 2025 год» — и какого черта ваш векторный поиск по тексту ничего не находит, хотя в базе десятки таких графиков? (Потому что они в PNG, а не в описаниях alt-текста, которые кто-то забыл добавить).

Вот она, проблема. Мир мультимодален. Данные живут в разных измерениях — текст, изображения, аудио, видео. А ваш поисковый движок слеп на одно из них. Традиционные эмбеддинги (те самые, что из Harrier-OSS или zembed-1) работают в одной модальности. Они не понимают, что текст «рыжий кот на диване» и фотография этого кота — про одно и то же.

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

Мультимодальные эмбеддинги — это не магия, это алгебра

Забудьте про «просто скормим картинку нейросети». Мультимодальные эмбеддинги — это модели, которые учатся представлять информацию из разных модальностей в одном векторном пространстве. Текст и изображение переводятся в векторы, которые можно сравнивать между собой. Косинусная близость между вектором текстового запроса «диаграмма роста пользователей» и вектором PNG-графика будет высокой, если модель обучена правильно.

И тут на сцену выходит Sentence Transformers. Да, та самая библиотека, которая раньше ассоциировалась только с текстом. К 2026 году она стала полноценным фреймворком для мультимодальности. Они не просто обернули CLIP — они создали целую экосистему моделей и реранкеров, которые делают кросс-модальный поиск практичным, а не академическим упражнением.

💡
Кросс-модальный поиск — это когда запрос в одной модальности (текст) ищет релевантные данные в другой (изображения). Или наоборот. Мультимодальный RAG расширяет это, добавляя такой поиск в конвейер извлечения контекста для большой языковой модели.

Зачем здесь реранкер? Он же для текста!

Самый частый вопрос. Вы получили топ-20 изображений по текстовому запросу через косинусную близость эмбеддингов. Первые пять — релевантны, остальные — так себе. Реранкер (модель для переранжирования) — это второй этап, который смотрит на пары «запрос-кандидат» и точнее оценивает релевантность. Он обучается на более сложных задачах и часто использует кросс-энкодеры.

В мультимодальном контексте появились реранкеры, которые работают с парами «текст-изображение». Они не генерируют эмбеддинги для всего корпуса (это дорого), а только для конкретной пары на этапе ранжирования. Результат — качество поиска взлетает на 15-30%, особенно для сложных запросов. А в RAG каждый процент на счету, потому что от этого зависит, какую информацию получит LLM.

1 Ставим всё, что нужно. Правильно.

Не делайте pip install sentence-transformers[all]. Вы установите тонну ненужного. Нам нужен точный набор. И да, Python 3.10+ обязателен, потому что в более старых версиях некоторые зависимости новых моделей ломаются.

# Создаем чистую среду. Всегда.
python -m venv venv_multimodal
source venv_multimodal/bin/activate  # или .\\venv_multimodal\\Scripts\\activate на Windows

# Ядро
pip install sentence-transformers==3.0.0  # Актуально на 09.04.2026
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124  # Для CUDA 12.4, актуально в 2026

# Для работы с изображениями
pip install Pillow
pip install opencv-python-headless  # Если нужна продвинутая предобработка

# Для векторной БД в примере - Qdrant, но подойдет любая
pip install qdrant-client

Не используйте torch из системного питона. Не используйте pip install sentence-transformers без указания версии. Через месяц вы обновите что-то случайно, и пайплайн сломается. Фиксируйте версии в requirements.txt. Прямо сейчас.

2 Выбираем модель. Не ту, что на хабре хвалят.

На 09.04.2026 лидеры — это семейства OpenCLIP и собственные разработки Sentence Transformers. Но не берите первую попавшуюся. Смотрите на задачи:

  • Для общего поиска «текст-изображение»: sentence-transformers/clip-ViT-B-32-multilingual-v1 — хороший баланс скорости и качества, поддерживает множество языков.
  • Для максимальной точности: sentence-transformers/clip-ViT-H-14 — тяжелая, но если у вас GPU с памятью от 8 ГБ, то она того стоит.
  • Для специализированных данных (медицинские снимки, схемы): ищите fine-tuned версии на Hugging Face Model Hub. Например, microsoft/BiomedCLIP-PubMedBERT_256-vit_base_patch16_224 — для медицинских изображений.

Реранкеры — отдельная история. На момент апреля 2026 года модель cross-encoder/ms-marco-MiniLM-L-12-v2 устарела для текста, а для мультимодальности смотрите на Salesforce/blip-itm-rerank или специализированные sentence-transformers/clip-ViT-B-32-multilingual-v1-rerank.

💡
Всегда проверяйте дату последнего обновления модели на Hugging Face. Модель 2023 года, даже если она популярна, будет хуже, чем новая, но менее известная модель 2025-2026 годов. Контекстные окна, качество эмбеддингов и поддержка новых форматов данных — всё это улучшается.

3 Готовим данные. Не просто ресайз.

Ошибка номер один: скормить модели сырые изображения разных размеров и форматов. Модели ожидают определенного препроцессинга. И нет, это не просто resize((224,224)).

from PIL import Image
from sentence_transformers import SentenceTransformer, util
import torch

# Загружаем модель для эмбеддингов изображений и текста
# Актуальная мультимодальная модель на 2026 год
model = SentenceTransformer('sentence-transformers/clip-ViT-B-32-multilingual-v1', device='cuda' if torch.cuda.is_available() else 'cpu')

# Функция для загрузки и предобработки изображения
def load_and_preprocess_image(image_path):
    image = Image.open(image_path).convert('RGB')
    # Модель сама выполнит нужный препроцессинг (нормализацию, ресайз) при вызове encode
    return image

# Для текста ничего особенного не нужно
text = "График роста доходов по кварталам 2025 года"

# Получаем эмбеддинги
image_emb = model.encode(load_and_preprocess_image("chart.png"))
text_emb = model.encode(text)

# Считаем схожесть - это и есть кросс-модальная близость
similarity = util.cos_sim(text_emb, image_emb)
print(f"Схожесть текста и изображения: {similarity.item():.4f}")

Важно: если у вас тысячи изображений, не делайте encode по одному. Используйте батчи. И кэшируйте результаты, чтобы не вычислять эмбеддинги каждый раз при перезапуске. Для этого идеально подходит модульный подход к RAG пайплайну, где эмбеддинг-модуль вынесен отдельно.

4 Индексируем в векторной БД. С умом.

Здесь ловушка: мультимодальные эмбеддинги обычно имеют размерность 512 или 768. Это не 1536, как у text-embedding-3-large от OpenAI, но и не 384. Проверьте, какая размерность у вашей модели (model.get_sentence_embedding_dimension()).

from qdrant_client import QdrantClient
from qdrant_client.http import models
import uuid

# Подключаемся к Qdrant (локально или облако)
client = QdrantClient(host="localhost", port=6333)

collection_name = "multimodal_docs"

# Создаем коллекцию с правильной размерностью
# Для clip-ViT-B-32 размерность 512
client.recreate_collection(
    collection_name=collection_name,
    vectors_config=models.VectorParams(
        size=512,  # Указываем точное значение!
        distance=models.Distance.COSINE
    )
)

# Предположим, у нас есть список словарей с данными
documents = [
    {"id": 1, "image_path": "charts/sales_q1_2025.png", "description": "График продаж за первый квартал 2025"},
    {"id": 2, "image_path": "screenshots/dashboard.png", "description": "Дашборд с метриками активности пользователей"},
    # ...
]

points = []
for doc in documents:
    # Генерируем эмбеддинг для изображения
    img = load_and_preprocess_image(doc["image_path"])
    img_emb = model.encode(img).tolist()
    
    # Создаем точку для Qdrant
    point = models.PointStruct(
        id=doc["id"],
        vector=img_emb,
        payload={
            "image_path": doc["image_path"],
            "description": doc["description"],
            "type": "image"  # Метаданные для фильтрации
        }
    )
    points.append(point)

# Загружаем батчем
client.upsert(collection_name=collection_name, points=points)

Почему не стоит хранить эмбеддинги текста и изображений в одной коллекции? Можно, но тогда нужно размечать в payload тип модальности. А если у вас гибридный документ (текст + картинка), то лучше создать отдельный эмбеддинг для каждого и связывать через общий ID. Или использовать продвинутые стратегии, как в мультимодальном RAG 2025.

5 Ищем. И потом переранжируем.

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

from sentence_transformers import CrossEncoder

# Поиск
query = "Скриншот дашборда с DAU и MAU"
query_emb = model.encode(query)

search_result = client.search(
    collection_name=collection_name,
    query_vector=query_emb.tolist(),
    limit=10  # Берем больше кандидатов, чем нужно, для реранкинга
)

# Теперь реранкинг
# Загружаем мультимодальный реранкер (актуальная модель на 2026 год)
reranker = CrossEncoder('Salesforce/blip-itm-rerank', max_length=512)

# Готовим пары [запрос, описание_кандидата] для реранкера
# Реранкер может работать с текстовыми описаниями кандидатов, а не с самими изображениями (это быстрее)
pairs = [[query, hit.payload["description"]] for hit in search_result]

# Получаем скоре релевантности для каждой пары
rerank_scores = reranker.predict(pairs)

# Сортируем результаты по скорам реранкера
reranked_results = [
    {"hit": hit, "rerank_score": score}
    for hit, score in zip(search_result, rerank_scores)
]
reranked_results.sort(key=lambda x: x["rerank_score"], reverse=True)

# Берем топ-3 после реранкинга
top_k = 3
final_results = reranked_results[:top_k]

for res in final_results:
    print(f"Путь: {res['hit'].payload['image_path']}, Реранк-скор: {res['rerank_score']:.4f}")

Реранкер работает с парами «текст-текст» (запрос и описание изображения). Это быстрее, чем повторно кодировать изображение. Но если описаний нет, можно использовать реранкер, который принимает напрямую эмбеддинги — ищите такие модели, они появляются.

6 Встраиваем в RAG. Не забываем про контекст.

Теперь у вас есть релевантные изображения (или их описания). Как передать это LLM? Самый простой способ — добавить в промпт текстовые описания найденных изображений. Но если вам нужно, чтобы модель "видела" изображение, то нужно использовать мультимодальную LLM (например, GPT-4V, Claude 3.5 Sonnet или открытые аналоги).

Пайплайн выглядит так:

  1. Пользователь задает вопрос.
  2. Система выполняет кросс-модальный поиск (текст -> изображения) с реранкингом.
  3. Из найденных изображений извлекаются текстовые метаданные (путь, описание) и, при необходимости, сами изображения кодируются в base64.
  4. В промпт для мультимодальной LLM добавляются текстовые описания и, возможно, ссылки на изображения или base64.
  5. LLM дает ответ, основанный на мультимодальном контексте.

Это уже не просто RAG, а Agentic RAG с гибридным поиском, где агенту доступны разные типы данных.

Где всё ломается. Предупреждён — вооружён.

  • Память GPU. Модели типа ViT-H-14 жрут память. Индексируйте на мощной машине, а для инференса используйте квантование (например, с помощью техник, описанных для pplx-embed). Или используйте CPU — будет медленнее, но стабильнее.
  • Несоответствие распределений. Модель обучалась на одном наборе данных (например, Flickr30k), а ваши изображения — медицинские рентгеновские снимки. Качество упадет. Ищите дообученные модели или делайте fine-tuning на своих данных.
  • Текстовые описания изображений. Если в вашей базе у изображений нет текстовых описаний, реранкеру нечего будет анализировать. Либо генерируйте описания с помощью image captioning модели (BLIP, OFA), либо используйте реранкеры, работающие напрямую с эмбеддингами.
  • Скорость. Кросс-энкодеры (реранкеры) работают медленнее, чем би-энкодеры (для первичного поиска). Не применяйте их ко всему корпусу. Только к топ-N кандидатам от быстрого поиска.

Вопросы, которые вы хотели задать, но боялись

Вопрос Ответ
Можно ли искать по изображению, чтобы найти текст? Да, это обратный кросс-модальный поиск. Закодируйте изображение-запрос, найдите ближайшие текстовые эмбеддинги в коллекции. Работает ровно так же.
Sentence Transformers или чистый CLIP от OpenAI? Sentence Transformers дает удобный API, поддержку множества моделей и реранкеров. Чистый CLIP (через OpenAI API) — это черный ящик, дороже и без возможности fine-tuning. Для контроля и кастомизации — однозначно Sentence Transformers.
Как добавить аудио и видео? Принцип тот же, но нужны модели, которые эмбеддят аудио и видео в то же пространство. Ищите мультимодальные модели «текст-аудио» (например, CLAP) или используйте отдельные энкодеры, а потом комбинируйте эмбеддинги (но это сложнее).
Какая векторная БД лучше? Любая, которая поддерживает cosine similarity и фильтрацию по метаданным. Qdrant, Weaviate, Pinecone, pgvector. Выбор зависит от масштаба и инфраструктуры. Для начала — Qdrant локально.

И последнее — зачем всё это?

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

Начните с малого: выберите одну папку со скриншотами, проиндексируйте, попробуйте найти по ним через текстовые запросы. Увидите, как это меняет представление о том, что можно искать. И потом уже масштабируйте на весь пайплайн, который должен укладываться в 400 мс.

Дерзайте. И не забудьте зафиксировать версии библиотек.

Подписаться на канал