Значение как координаты: от эмбеддингов к векторным базам данных и RAG
Эмбеддинги, косинусное сходство, векторные базы данных и цикл RAG объясняются четырьмя короткими предложениями о собаках, кошках и Париже, чтобы стимулировать клики при поиске по смыслу.
Источники: Meaning as Coordinates: Semantic Search, Vector Databases, and RAG
Поле поиска, которое не возвращает ничего при поиске фразы «хорошая собака для активного человека», несмотря на то что база знаний полна советов по уходу за собаками, наглядно показывает, в чём заключается недостаток точного совпадения ключевых слов. Для устранения этой проблемы необходимо рассматривать смысл как точку в пространстве. Именно эта идея лежит в основе семантического поиска, векторных баз данных и технологий генерации с улучшением результатов поиска, и один небольшой пример достаточен, чтобы понять всё это.
Пример, используемый на протяжении всего текста
Представьте себе миниатюрную базу знаний, состоящую из четырёх предложений:
1. Dogs are loyal and need daily walks and exercise
2. Cats are independent pets that groom themselves
3. Golden retrievers are friendly family dogs
4. The capital of France is Paris
Пользователь спрашивает:
"What's a good pet for an active person who exercises a lot?"
В вопросе никогда не встречаются слова «собака» или «гулять». Простой поиск по ключевым словам не принесёт ничего полезного. Тем не менее машина всё равно может дать правильный ответ, потому что она сравнивает положение текстов в пространстве значений, а не те слова, которые у них общие.
Похожие значения, близкие точки
Вектор — это просто ряд чисел, который кодирует смысл фрагмента текста. Поскольку похожие значения порождают похожие векторы, поиск связанных текстов превращается в поиск близких точек. Это превращает языковую задачу в геометрическую, что лежит в основе современных систем поиска, рекомендаций и технологии RAG, позволяющей языковой модели обращаться к внешней библиотеке для получения информации и тем самым перестать выдумывать факты.
Когда значение представляет собой набор координат, работа с значением означает работу с расстоянием и направлением, в чём компьютеры отличаются высокой эффективностью.
Эмбеддинги для целых отрывков
Векторы на уровне слов недостаточны для поиска в документах. Для предложения, абзаца или страницы требуется отдельный вектор. Модели типа Transformer могут его генерировать: при вводе фрагмента текста они возвращают один вектор, обычно называемый эмбеддингом, который отражает его смысл.
"Dogs need daily walks" ──► [ 0.91, 0.08, -0.30, ... ] (e.g. 384 numbers)
"The capital is Paris" ──► [-0.05, 0.62, 0.44, ... ]
Эмбеддинг предложения сжимает отрывок в список чисел фиксированной длины. Два текста на одну и ту же тему дают схожие списки, а несвязанные тексты — сильно отличающиеся. Отдельные числа не имеют никакого смысла для человека. Важно то, как они взаимосвязаны, и их никто не читает напрямую. Их просто сравнивают.
Образ, который нужно запомнить
Представьте каждый текст как точку в пространстве. Тексты с похожим смыслом находятся рядом друг с другом, а несвязанные — далеко друг от друга.
(a simplified 2-D "meaning space")
dog walks • • golden retriever ← the "dogs" neighborhood
\ /
(pets)
cat grooming • • Paris ← far away, different topic
На этой упрощённой карте два предложения о собаках находятся рядом друг с другом, предложение о кошке расположено неподалёку от них, поскольку тоже касается домашнего животного, а предложение о Париже находится совсем в другом регионе. В реальных пространствах вкладки имеют сотни измерений, но принцип остаётся тем же: связанные значения соответствуют близко расположенным точкам. Всё, что следует далее, — это способы нахождения ближайших точек.
Поиск по ключевым словам против семантического поиска
Поиск по ключевым словам сопоставляет слова. При поиске по слову «физические упражнения» будет проигнорирован документ, в котором говорится о ежедневных прогулках, поскольку слова не совпадают. Этот метод быстр, но буквальный, и он упускает смысл.
Семантический поиск сопоставляет значения. Запрос преобразуется в вектор, и возвращаются документы, векторы которых наиболее близки к нему, независимо от того, какие слова в них используются. Вот рейтинг, полученный для примера с использованием реальных значений сходства:
Query: "a good pet for an active person who exercises"
0.999 ############################# Golden retrievers are friendly family dogs
0.995 ############################# Dogs are loyal and need daily walks and exercise
0.398 ########### Cats are independent pets that groom themselves
0.059 # The capital of France is Paris
В этом запросе отсутствуют общие ключевые слова с предложениями о собаках, однако оба показывают самые высокие оценки — примерно 1,0. Предложение о кошке находится посередине, поскольку кошка тоже считается домашним животным, а предложение о Париже имеет оценку близкую к нулю. Поиск по ключевым словам не принес бы никаких результатов. Именно поэтому семантический поиск кажется более умным: он понимает, что активный человек, занимающийся спортом, и собака, нуждающаяся в ежедневных прогулках, — это практически одно и то же. Разница между поиском по ключевым словам и семантическим поиском заключается в разнице между отсутствием результатов и получением именно того ответа, который был нужен.
Оценка близости с помощью косинусного сходства
Обычным инструментом для оценки степени близости двух векторов является косинусное сходство. Он учитывает угол между двумя стрелками, игнорируя их длину.
- Стрелки, направленные в одну сторону, дают сходство близкое к 1, что означает очень схожий смысл.
- Стрелки, направленные под прямым углом, дают сходство близкое к 0, что означает отсутствие связи.
- Стрелки, указывающие в противоположные направления, указывают на сходство близкое к -1, что означает противоположное значение.
Небольшой угол между запросом и документом о собаке выглядит так:
^ query
│ /
│/ small angle → cosine ≈ 1 (similar)
└──────► dog doc
А теперь широкий обзор документа из Парижа:
^ query
│
└──────► Paris doc big angle → cosine ≈ 0 (unrelated)
Вместо длины используется угол, чтобы обеспечить справедливое сравнение длинных и коротких текстов. Математические вычисления вписываются в одну строку: умножите векторы друг на друга и нормализуйте результат.
import numpy as np
def cosine(a, b):
return a @ b / (np.linalg.norm(a) * np.linalg.norm(b))
Эта небольшая функция лежит в основе каждого балла в приведённом выше рейтинге. Баллы варьируются от -1 до 1, причём значение, близкое к 1, указывает на то, что два текста означают одно и то же.
Векторные базы данных для поиска в масштабе
В примере представлены четыре документа. В реальных системах их количество достигает миллионов или миллиардов — это могут быть любые заявки на поддержку, страницы продуктов или абзацы корпоративной документации. Поиск по миллиарду векторов по одному при каждом запросе слишком медленный.
Для этой цели существуют векторные базы данных. Это хранилища, созданные для эффективного выполнения одной задачи: принятия вектора запроса и быстрого возвращения наиболее близких к нему хранимых векторов. Они используют метод приближённого поиска ближайшего соседа — семейство умных индексов, которые находят практически наиболее близкие результаты, пропуская большинство сравнений. Небольшая потеря точности компенсируется значительным ускорением работы.
documents ─► embed ─► [ vector database ]
│ stores millions of vectors
│ + a smart index (ANN)
query ─► embed ─────────► │
▼
top-k nearest matches (in milliseconds)
Документы проходят через модель вжимания один раз и сохраняются вместе с индексом. Когда поступает вопрос, он тоже преобразуется с помощью модели вжимания, и база данных мгновенно возвращает k наиболее подходящих результатов, независимо от количества хранящихся векторов. К таким решениям относятся FAISS, Pinecone, Weaviate, Chroma и Milvus, а также pgvector — расширение, позволяющее использовать поиск по векторам в обычном PostgreSQL. Детали могут отличаться, но все они хранят вжимания и быстро находят близкие к ним элементы. Короче говоря, векторная база данных — это поисковая система для смыслов.
В чём ошибаются модели простого языка
Большие языковые модели впечатляют, но у них есть три серьёзных недостатка:
- Галлюцинации. Когда они не уверены, они выдают плавные, уверенные, но неверные ответы.
- Предел знаний. Модель знает только то, что содержалось в данных обучения, зафиксированных в определённую ранее дату.
- Отсутствие знаний о ваших данных. Внутренние документы, личные заметки и записи прошлой недели остаются незнакомыми для модели.
You: "What's our refund policy?"
Plain LLM: "Typically, refunds are offered within 30 days..." ← a guess, not YOUR policy
Когда спрашивают о политике возврата средств, простая модель отвечает из памяти, делая правдоподобные предположения, а не согласно фактической политике компании. Это опасно в случае с фактической или конфиденциальной информацией. Необходимо предоставлять точные факты именно тогда, когда модель отвечает, без необходимости переобучения, и RAG именно это и делает.
Генерация с усилением за счёт поиска
RAG объединяет всё вышеописанное в один цикл. Вместо того чтобы просить модель отвечать из памяти, сначала с помощью семантического поиска находят соответствующие документы, а затем просят модель ответить на основе их содержимого. Название отражает суть: поиск находит релевантный текст, «усиление» означает его добавление в запрос, а генерация позволяет модели написать ответ.
1. Question ─► embed ─► search the vector DB ─► top relevant chunks
│
2. Build a prompt: "Using ONLY the context below, answer the question.
Context: [retrieved chunks]
Question: [user's question]"
│
3. LLM generates an answer grounded in the retrieved text ─► with sources
Проведём пример на практике:
Question: "What's a good pet for an active person?"
→ retrieves docs 1 & 3 (the dog sentences, from Figure 3)
→ prompt: "Using this context: [Dogs need daily walks and exercise;
Golden retrievers are friendly family dogs] — answer the question."
→ LLM: "A dog — especially a golden retriever — is a great fit, since
they need daily walks and exercise. (Source: docs 1, 3)"
Теперь ответ имеет прочную основу. Он основан на реальных документах, находящихся под контролем владельца, и позволяет указать источник факта. Гораздо меньше выдуманных ответов проскальзывает сквозь систему. RAG — это семантический поиск в сочетании с языковой моделью: сначала извлекаются нужные факты, затем модель формулирует ответ.
Практическая схема работы и её основная ловушка
У RAG есть два этапа. Настройка выполняется один раз, а запросы обрабатываются каждый раз, когда кто-то задаёт вопрос.
── SETUP (once) ── ── QUERY (every question) ──
documents user question
│ split into chunks │ embed
│ embed each chunk │ search vector DB
│ store in vector DB │ get top-k chunks
▼ │ build prompt with chunks
[ vector database ] ◄───── search ──────┤ send to LLM
▼
grounded answer
Документы делятся на фрагменты, встраиваются и хранятся один раз. Каждый вопрос также встраивается, сопоставляется с хранилищем, и лучшие фрагменты передаются в модель для формирования окончательного ответа.
Проблема заключается в самом процессе разделения документов на фрагменты перед их встраиванием. Слишком крупные фрагменты сливают несколько тем в один вектор, что приводит к нечетким сопоставлениям. Слишком маленькие фрагменты теряют контекст, из-за чего ответ может оказаться неполным. Выбор размера фрагментов является одним из важнейших практических решений в рабочей системе RAG, и это напоминание о том, что простая подготовка данных определяет всё последующее.
То же самое на коде
Часть системы RAG, отвечающая за поиск, представляющая собой простой семантический поисковик, требует всего нескольких строк кода на NumPy. Она принимает векторы некоторых документов плюс вектор запроса и сортирует документы по косинусной похожести:
import numpy as np
def cosine(a, b):
return a @ b / (np.linalg.norm(a) * np.linalg.norm(b))def search(query_vec, doc_vecs, docs, k=2):
scores = [(cosine(query_vec, v), d) for v, d in zip(doc_vecs, docs)]
return sorted(scores, reverse=True)[:k] # top-k most similar
При вводе данных о четырёх домашних животных и векторах Paris, а также запроса о активных людях сначала возвращаются два документа о собаках, что соответствует ранее показанной иерархии результатов. Это и есть семантический поиск в нескольких строках кода.
В реальном проекте векторы не создаются вручную. Вместо этого используется модель генерации векторов и хранилище векторов, причём код выглядит примерно так:
# Illustrative real-world usage (libraries download a model, so not run here)
from sentence_transformers import SentenceTransformer
import chromadb
model = SentenceTransformer("all-MiniLM-L6-v2") # turns text into vectors
db = chromadb.Client().create_collection("pets")db.add(documents=my_docs, embeddings=model.encode(my_docs).tolist(),
ids=[str(i) for i in range(len(my_docs))])q = "a good pet for an active person"
hits = db.query(query_embeddings=model.encode([q]).tolist(), n_results=2)
# hits now holds the top-2 most semantically similar documents
Библиотеки выполняют основную работу, но в основе всего лежит тот же поиск по косинусному сходству. Семантический поиск — это всего лишь несколько строк математических формул для векторов, а реальные инструменты обеспечивают высокую скорость и масштабируемость этой идеи.
Весь процесс
Текст начинается с символов и токенов, затем преобразуется в эмбеддинги, после чего получает контекст благодаря механизму внимания в трансформере, и в конечном итоге превращается в поисковую карту знаний, делающую языковые модели точными и полезными.
raw text
→ characters / tokens / embeddings [article 1: From Characters to Meaning]
→ attention turns them into context-aware
vectors inside a transformer [article 2: Attention, Explained Simply]
→ those vectors become coordinates we can
store, search, and retrieve [article 3: this one]
→ semantic search (find by meaning)
→ vector databases (search at scale)
→ RAG (ground an LLM in real data)
Основная идея, проходящая сквозь всё это, — это развитие одной концепции: смысл — это точка в пространстве. Сначала это касается слов, затем предложений в контексте, и, наконец, целых библиотек документов, которые можно искать и анализировать.
Стоит также помнить ещё одно различие. RAG предоставляет модели доступ к знаниям без изменения самой модели, подобно тому, как передаются ей записки для прочтения в момент ответа. Тонкая настройка — это другой подход, при котором внутренние веса модели корректируются путём обучения на примерах. RAG предоставляет знания для прочтения, тогда как тонкая настройка изменяет саму модель. Умение выбрать подходящий метод — это само по себе практический навык.
Связанная литература
- Практические заметки: Ограничения векторного поиска в RAG — косинусное сходство и связанные аспекты — Пошаговое руководство по использованию Практических заметок: Ограничения векторного поиска в RAG — косинусное сходство и связанные аспекты: шаблоны контрактов, проверок и готовых фрагментов кода для команд, внедряющих эту практику.
- Практические заметки: Векторные базы данных для производственного RAG: индексация, гибридный поиск — Пошаговое руководство по использованию Практических заметок: Векторные базы данных для производственного RAG: индексация, гибридный поиск: шаблоны контрактов, проверок и готовых фрагментов кода для команд, внедряющих эту практику.