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 по генерации эмбеддингов и обзоры технологий векторных БД с их сравнением и сценариями применения.
Теоретические вопросы
- В чём основная цель преобразования текста в эмбеддинги?
- Как эмбеддинги помогают измерять семантическую близость слов и предложений?
- Опишите процесс создания эмбеддингов слов и роль контекста в этом процессе.
- Как эмбеддинги улучшают семантический поиск по сравнению с подходом на основе ключевых слов?
- Какова роль эмбеддингов документа и запроса в процессе семантического поиска?
- Что такое векторное хранилище и почему оно важно для эффективного поиска?
- Какие критерии следует учитывать при выборе векторной базы данных?
- Почему Chroma удобна для прототипов и каковы её ограничения?
- Опишите пайплайн семантического поиска с использованием эмбеддингов и векторной базы данных.
- Как разбиение документов повышает гранулярность и релевантность поиска?
- Зачем генерировать эмбеддинги для чанков и в чём их значение для поиска?
- Зачем индексировать векторное хранилище при поиске схожести?
- Как работает обработка запроса и какие метрики сравнения используются?
- Как шаг генерации ответа улучшает пользовательский опыт (UX) в приложениях семантического поиска?
- Какие шаги необходимы для подготовки окружения?
- Приведите пример, где загрузка и разбиение текста критически важны для семантического поиска.
- Как генерация эмбеддингов «преобразует» текст и как продемонстрировать близость между эмбеддингами?
- Что следует учитывать при настройке Chroma для эффективного поиска?
- Как поиск схожести помогает находить релевантные фрагменты?
- Какие сбои типичны для семантического поиска и как их устранять для повышения точности?
Практические задания
Ниже представлены практические задания по эмбеддингам, векторным базам данных и семантическому поиску:
- Напишите функцию
generate_embeddings
, которая для списка строк возвращает список «эмбеддингов» (можно имитировать, например, длиной строки). - Реализуйте функцию
cosine_similarity
для вычисления косинусной близости между двумя векторами. - Создайте класс
SimpleVectorStore
с методамиadd_vector
иfind_most_similar
(на основе косинусной близости). - Разработайте скрипт: загрузите текст из файла, разбейте его на чанки заданного размера (например, 500 символов) и выведите их.
- Напишите функцию
query_processing
: сгенерируйте эмбеддинг запроса (используя заглушку), найдите ближайший чанк вSimpleVectorStore
и выведите его. - Реализуйте функцию
remove_duplicates
: из списка чанков верните новый список без дубликатов (по точному совпадению или порогу схожести). - Создайте скрипт: инициализируйте
SimpleVectorStore
, добавьте набор эмбеддингов (используя заглушки), выполните семантический поиск по примеру и выведите топ-3 результата. - Напишите функцию
embed_and_store_documents
: для списка чанков сгенерируйте эмбеддинги (используя заглушки), сохраните их вSimpleVectorStore
и верните это хранилище. - Реализуйте функцию
vector_store_persistence
: продемонстрируйте сохранение и загрузку состоянияSimpleVectorStore
(с использованием сериализации/десериализации). - Напишите функцию
evaluate_search_accuracy
: по списку запросов и ожидаемых чанков выполните поиск и посчитайте долю совпадений.