Перейти к содержанию

2.4 Сила эмбеддингов

Эмбеддинги — это числовые представления текста, где словам, предложениям и документам сопоставляются векторы в многомерном пространстве, а близкие по смыслу тексты оказываются геометрически рядом. Такие представления рождаются в результате обучения моделей на больших корпусах: модель учится связывать слово с его контекстом и улавливать семантические связи, поэтому синонимы и лексемы, встречающиеся в сходных контекстах, располагаются рядом. Благодаря этому семантический поиск выходит за пределы точного совпадения «ключевых слов»: мы считаем эмбеддинг для каждого документа (или чанка) и для пользовательского запроса, сравниваем близость векторов по косинусной или другой метрике и ранжируем материалы по степени смысловой близости, даже если точных вхождений нет. В результате меняется сам подход к анализу, хранению и поиску — взаимодействия становятся осмысленнее, а рекомендации точнее.

Поверх эмбеддингов работают векторные хранилища — базы, оптимизированные для хранения векторов и быстрого поиска ближайших соседей. Они используют специализированные индексы и алгоритмы, чтобы отвечать на запросы схожести в больших массивах данных, и подходят как для исследовательских задач, так и для продакшена. При выборе решения ориентируются на размер данных (от in-memory вариантов для небольших наборов до распределённых систем для масштаба), требования к персистентности (нужна ли надёжная запись на диск или достаточно временного хранения для прототипов) и сценарий использования (лаборатория versus боевая среда). Для быстрых прототипов часто выбирают Chroma — лёгкое in-memory хранилище; в долгосрочных и масштабных сценариях уместны распределённые и облачные векторные СУБД. В типовом пайплайне семантического поиска документы сперва разбиваются на осмысленные фрагменты, затем для каждого чанка считаются эмбеддинги и индексируются; на запрос рассчитывается его эмбеддинг, выполняется поиск ближайших фрагментов, после чего извлечённые части вместе с запросом подаются в LLM для генерации связного ответа.

Перед углублением в эмбеддинги и векторные базы данных подготовим окружение: выполним импорты, прочтём ключи API и базовую конфигурацию.

import os
from openai import OpenAI
import sys
from dotenv import load_dotenv, find_dotenv

# Расширяем системный путь для включения директории проекта
sys.path.append('../..')

# Загружаем переменные окружения
load_dotenv(find_dotenv())

# Настраиваем ключ OpenAI API
client = OpenAI()

Далее загрузим документы и разобьём их на семантически осмысленные фрагменты — так удобнее управлять данными и готовить их к созданию эмбеддингов. Для демонстрации используем серию PDF‑документов (в примере встречается и «шум» в виде дубликатов):

# Импортируем класс PyPDFLoader из библиотеки langchain
from langchain.document_loaders import PyPDFLoader

# Инициализируем список загрузчиков PDF, каждый из которых представляет конкретный документ лекции
pdf_document_loaders = [
    PyPDFLoader("docs/doc1.pdf"),
    PyPDFLoader("docs/doc2.pdf"),
    PyPDFLoader("docs/doc3.pdf"),
]

# Создаём пустой список для хранения содержимого каждого загруженного документа
loaded_documents_content = []

# Перебираем каждый загрузчик PDF в списке для загрузки документов
for document_loader in pdf_document_loaders:
    # Используем метод load каждого экземпляра PyPDFLoader для загрузки содержимого документа
    # и расширяем список loaded_documents_content результатом
    loaded_documents_content.extend(document_loader.load())

# На этом этапе loaded_documents_content содержит содержимое всех указанных PDF-документов

После загрузки разделим документы на чанки, чтобы повысить управляемость и эффективность последующих шагов:

from langchain.text_splitter import RecursiveCharacterTextSplitter

# Настраиваем и применяем разделитель текста
document_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1500,
    chunk_overlap=150
)
document_splits = document_splitter.split_documents(documents)

Теперь рассчитаем эмбеддинги для каждого чанка: текст преобразуется в векторы, отражающие его смысловое значение.

from langchain_openai import OpenAIEmbeddings
import numpy as np

embedding_generator = OpenAIEmbeddings()

# Примеры предложений для генерации эмбеддингов
sentence_examples = ["I like dogs", "I like canines", "The weather is ugly outside"]

# Генерируем эмбеддинги для каждого предложения
embeddings = [embedding_generator.embed_query(sentence) for sentence in sentence_examples]

# Демонстрируем сходство с помощью скалярного произведения
similarity_dog_canine = np.dot(embeddings[0], embeddings[1])
similarity_dog_weather = np.dot(embeddings[0], embeddings[2])

Далее проиндексируем полученные векторы в векторном хранилище, чтобы выполнять быстрый поиск схожести. Для демонстраций подойдёт Chroma — легковесное in‑memory решение:

from langchain.vectorstores import Chroma

# Определяем директорию для сохранения векторного хранилища
persist_directory = 'docs/chroma/'

# Очищаем любые существующие данные в директории сохранения
!rm -rf ./docs/chroma

# Инициализируем и заполняем векторное хранилище разбиениями документов и их эмбеддингами
vector_database = Chroma.from_documents(
    documents=document_splits,
    embedding=embedding_generator,
    persist_directory=persist_directory
)

Теперь выполним поиск по схожести — это и есть практическое преимущество связки эмбеддинги + векторная БД: быстрый отбор наиболее релевантных фрагментов под запрос.

# Пример запроса
query = "Is there an email I can ask for help?"

# Получаем топ-3 наиболее похожих фрагмента документа
retrieved_documents = vector_database.similarity_search(query, k=3)

# Изучаем содержимое лучшего результата
print(retrieved_documents[0].page_content)

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

# Пример запроса с режимом сбоя
query_matlab = "What did they say about MATLAB?"

# Выявление дублирующихся фрагментов в результатах поиска
retrieved_documents_matlab = vector_database

.similarity_search(query_matlab, k=5)

Далее можно рассмотреть стратегии устранения таких сбоев, чтобы извлекать и релевантные, и достаточно различающиеся фрагменты. В сумме эмбеддинги и векторные базы данных образуют мощную связку для семантического поиска по большим корпусам: корректная подготовка текста, осмысленная индексация и быстрые механизмы поиска ближайших векторов позволяют строить системы, которые понимают сложные запросы; анализ сбоев и дополнительные техники повышают устойчивость и точность. Для углубления полезны документация OpenAI API по генерации эмбеддингов и обзоры технологий векторных БД с их сравнением и сценариями применения.

Теоретические вопросы

  1. В чём основная цель преобразования текста в эмбеддинги?
  2. Как эмбеддинги помогают измерять семантическую близость слов и предложений?
  3. Опишите процесс создания эмбеддингов слов и роль контекста в этом процессе.
  4. Как эмбеддинги улучшают семантический поиск по сравнению с подходом на основе ключевых слов?
  5. Какова роль эмбеддингов документа и запроса в процессе семантического поиска?
  6. Что такое векторное хранилище и почему оно важно для эффективного поиска?
  7. Какие критерии следует учитывать при выборе векторной базы данных?
  8. Почему Chroma удобна для прототипов и каковы её ограничения?
  9. Опишите пайплайн семантического поиска с использованием эмбеддингов и векторной базы данных.
  10. Как разбиение документов повышает гранулярность и релевантность поиска?
  11. Зачем генерировать эмбеддинги для чанков и в чём их значение для поиска?
  12. Зачем индексировать векторное хранилище при поиске схожести?
  13. Как работает обработка запроса и какие метрики сравнения используются?
  14. Как шаг генерации ответа улучшает пользовательский опыт (UX) в приложениях семантического поиска?
  15. Какие шаги необходимы для подготовки окружения?
  16. Приведите пример, где загрузка и разбиение текста критически важны для семантического поиска.
  17. Как генерация эмбеддингов «преобразует» текст и как продемонстрировать близость между эмбеддингами?
  18. Что следует учитывать при настройке Chroma для эффективного поиска?
  19. Как поиск схожести помогает находить релевантные фрагменты?
  20. Какие сбои типичны для семантического поиска и как их устранять для повышения точности?

Практические задания

Ниже представлены практические задания по эмбеддингам, векторным базам данных и семантическому поиску:

  1. Напишите функцию generate_embeddings, которая для списка строк возвращает список «эмбеддингов» (можно имитировать, например, длиной строки).
  2. Реализуйте функцию cosine_similarity для вычисления косинусной близости между двумя векторами.
  3. Создайте класс SimpleVectorStore с методами add_vector и find_most_similar (на основе косинусной близости).
  4. Разработайте скрипт: загрузите текст из файла, разбейте его на чанки заданного размера (например, 500 символов) и выведите их.
  5. Напишите функцию query_processing: сгенерируйте эмбеддинг запроса (используя заглушку), найдите ближайший чанк в SimpleVectorStore и выведите его.
  6. Реализуйте функцию remove_duplicates: из списка чанков верните новый список без дубликатов (по точному совпадению или порогу схожести).
  7. Создайте скрипт: инициализируйте SimpleVectorStore, добавьте набор эмбеддингов (используя заглушки), выполните семантический поиск по примеру и выведите топ-3 результата.
  8. Напишите функцию embed_and_store_documents: для списка чанков сгенерируйте эмбеддинги (используя заглушки), сохраните их в SimpleVectorStore и верните это хранилище.
  9. Реализуйте функцию vector_store_persistence: продемонстрируйте сохранение и загрузку состояния SimpleVectorStore (с использованием сериализации/десериализации).
  10. Напишите функцию evaluate_search_accuracy: по списку запросов и ожидаемых чанков выполните поиск и посчитайте долю совпадений.