Генерация мультимодальных эмбеддингов для семантического поиска

30 минут Урок 10

Введение: Почему ключевые слова больше не работают

Добро пожаловать во второй модуль курса. Сегодня мы переходим от простой генерации текста к архитектурной основе современных AI-систем — мультимодальным эмбеддингам (embeddings).

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

Здесь на сцену выходят эмбеддинги Gemini. Это не просто числа, это «координаты смысла». В мире Gemini слово «молоток», слово «гвоздь» и фотография строительной площадки находятся в одном математическом пространстве, близко друг к другу.

В этом уроке мы научимся:

  • Превращать текст, изображения и видео в векторы.
  • Понимать разницу между типами задач (Task Types) для повышения точности.
  • Строить семантический поиск, который «понимает» суть, а не просто ищет совпадения букв.

Концепция единого векторного пространства

Главная суперсила Gemini 3 API — это мультимодальность на уровне архитектуры. Раньше вам приходилось использовать одну модель для текста (например, BERT), другую для картинок (ResNet или CLIP), и затем пытаться как-то «сшить» их результаты.

Gemini создаёт единое векторное пространство. Представьте себе огромную многомерную карту (обычно 768 измерений или больше).

  • В одной точке этой карты находится вектор текста «пушистый кот».
  • Совсем рядом находится вектор фотографии вашего домашнего питомца.
  • Чуть дальше — вектор видеоролика, где лев играет с мячом (потому что это тоже кошачьи, но контекст другой).

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

python
import google.generativeai as genai
import os

# Настройка API ключа
# В реальном проекте используйте переменные окружения для безопасности
os.environ["GOOGLE_API_KEY"] = "ВАШ_КЛЮЧ_GEMINI"
genai.configure(api_key=os.environ["GOOGLE_API_KEY"])

# Для работы с эмбеддингами мы используем специализированную модель.
# В линейке Gemini это часто 'models/text-embedding-004' или мультимодальные вариации.
MODEL_NAME = 'models/text-embedding-004'

text_to_embed = "Архитектура нейронных сетей требует глубокого понимания линейной алгебры."

# Базовый вызов API для генерации эмбеддинга
result = genai.embed_content(
    model=MODEL_NAME,
    content=text_to_embed,
    task_type="retrieval_document", # Важный параметр, который мы разберем далее
    title="AI Education" # Опционально, помогает модели понять контекст документа
)

# Результат - это список чисел (вектор)
embedding_vector = result['embedding']

print(f"Размерность вектора: {len(embedding_vector)}")
print(f"Первые 5 значений: {embedding_vector[:5]}")

Критически важный нюанс: Task Types

Многие разработчики упускают этот момент и получают посредственные результаты поиска. Gemini API требует, чтобы вы явно указали, для чего вы генерируете эмбеддинг. Вектор для «вопроса» и вектор для «ответа» должны строиться немного по-разному, чтобы идеально совпадать в математическом пространстве.

Основные типы задач (task_type):

  • RETRIEVAL_QUERY: Используйте это для текста, который вводит пользователь (запрос). Например: «Как приготовить лазанью?».
  • RETRIEVAL_DOCUMENT: Используйте это для данных, которые вы храните в базе знаний (документы). Например: статья с рецептом лазаньи.
  • SEMANTIC_SIMILARITY: Для сравнения двух текстов на схожесть (например, проверка на плагиат или дубликаты).
  • CLASSIFICATION: Если векторы будут использоваться как входные данные для классификатора.

Золотое правило: Если вы делаете систему «Вопрос-Ответ» (RAG), запросы пользователя эмбеддим с retrieval_query, а базу знаний — с retrieval_document. Это асимметричный поиск, и он работает гораздо лучше.

Работа с изображениями и мультимодальность

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

Представьте интернет-магазин. У вас есть 50,000 фото платьев. Вам не нужно вручную подписывать «красное», «длинное», «вечернее». Вы просто превращаете все фото в векторы. Когда пользователь пишет «вечернее платье алого цвета», вектор его текста автоматически укажет на векторы нужных изображений.

Для передачи изображений в API обычно используется либо формат PIL (Python Imaging Library), либо ссылки на Google Cloud Storage (для больших объемов видео).

python
import PIL.Image

# Загружаем изображение (локально или по сети)
img_path = 'cat_on_sofa.jpg'
# Создаем заглушку изображения для примера, в реальности - загрузка файла
# image = PIL.Image.open(img_path)

# Внимание: для изображений мы используем мультимодальную модель
# На момент выхода Gemini 3, название модели может быть 'models/multimodal-embedding-001'
# или аналогичным.
MULTIMODAL_MODEL = 'models/embedding-001' 

def get_multimodal_embedding(text=None, image=None):
    """
    Генерирует эмбеддинг для текста, изображения или их комбинации.
    """
    if image and text:
        content = [text, image]
    elif image:
        content = image
    else:
        content = text
        
    result = genai.embed_content(
        model=MULTIMODAL_MODEL,
        content=content,
        task_type="retrieval_document" # Предполагаем, что индексируем картинку в базу
    )
    return result['embedding']

# Пример использования (псевдокод загрузки)
# image_vector = get_multimodal_embedding(image=image)
# text_query_vector = get_multimodal_embedding(text="ленивый кот отдыхает")

# Если мы сравним image_vector и text_query_vector, дистанция будет минимальной.

Математика поиска: Косинусное сходство

Как нам понять, что два вектора близки? Мы не используем обычную линейку (Евклидово расстояние), потому что в многомерных пространствах с нормализованными векторами важнее угол между ними, а не расстояние между концами стрелок.

Мы используем Косинусное сходство (Cosine Similarity).

  • Значение 1.0: Векторы идентичны (направлены в одну сторону).
  • Значение 0.0: Векторы перпендикулярны (абсолютно разные темы, не связаны).
  • Значение -1.0: Векторы противоположны по смыслу (антонимы, хотя в эмбеддингах это встречается редко и зависит от обучения).

Формула проста: это скалярное произведение (Dot Product) двух векторов. Поскольку эмбеддинги Gemini обычно нормализованы (их длина равна 1), скалярное произведение и есть косинусное сходство.

python
import numpy as np

def find_best_match(query_embedding, database_embeddings):
    """
    Находит наиболее похожий документ в базе.
    
    Args:
        query_embedding: Вектор запроса (список float)
        database_embeddings: Список словарей {'id': str, 'vector': list}
        
    Returns:
        ID лучшего совпадения и score.
    """
    # Превращаем запрос в numpy массив для скорости
    query_vec = np.array(query_embedding)
    
    best_score = -1.0
    best_doc_id = None
    
    for doc in database_embeddings:
        doc_vec = np.array(doc['vector'])
        
        # Вычисляем скалярное произведение (Dot Product)
        # Для нормализованных векторов это эквивалентно косинусному сходству
        score = np.dot(query_vec, doc_vec)
        
        if score > best_score:
            best_score = score
            best_doc_id = doc['id']
            
    return best_doc_id, best_score

# Пример данных
db = [
    {'id': 'doc1', 'vector': [0.1, 0.2, 0.9]}, # Условно: статья про космос
    {'id': 'doc2', 'vector': [0.8, 0.1, 0.1]}  # Условно: рецепт пирога
]

# Запрос: "Как испечь шарлотку?" -> вектор будет похож на [0.85, 0.1, 0.05]
query = [0.85, 0.1, 0.05]

best_id, score = find_best_match(query, db)
print(f"Победитель: {best_id} со счетом {score:.4f}")

Оптимизация и лучшие практики (Production Ready)

Когда вы переходите от экспериментов к реальному продукту, скорость и стоимость становятся критичными. Вот несколько советов для работы с Gemini API:

  1. Пакетная обработка (Batching): Никогда не отправляйте тексты на эмбеддинг по одному в цикле. API позволяет отправлять списки текстов (batch). Это в разы быстрее и экономит квоты. Метод embed_content принимает список строк в поле content.
  2. Сжатие размерности (Matryoshka Embeddings): Некоторые новые модели поддерживают усечение векторов. Вы можете отбросить часть чисел с конца вектора, незначительно потеряв в точности, но выиграв в скорости поиска и объеме хранилища. Gemini 3 оптимизирован для работы с усеченными размерностями.
  3. Векторные базы данных: В примере выше мы использовали список и цикл. Это работает для 1000 документов. Если у вас миллион записей, используйте специализированные БД: Chroma, Pinecone, Qdrant или pgvector (расширение для PostgreSQL). Они используют алгоритмы приблизительного поиска (HNSW), которые на порядки быстрее полного перебора.

Упражнение

Создайте мини-поисковик 'Умная Галерея'. <br>1. Представьте, что у вас есть список из трех описаний картин: 'Звездная ночь над городом', 'Портрет дамы с загадочной улыбкой', 'Тающие часы в пустыне'. <br>2. Сгенерируйте эмбеддинги для этих описаний, используя 'retrieval_document'. <br>3. Примите поисковый запрос 'сюрреализм и время' (используйте 'retrieval_query'). <br>4. Найдите описание с максимальным косинусным сходством.

Вопрос

Вы создаете систему поддержки пользователей. У вас есть база знаний со статьями и чат-бот, куда пользователи пишут вопросы. Какой task_type следует использовать для эмбеддинга вопросов пользователей?