Embeddings: как превратить текст в числа, модели для создания эмбеддингов и работа с ними
Текст — это не просто строка. Это точка в многомерном пространстве смыслов.
Компьютеры не понимают слова. Они понимают числа. Embedding — это способ превратить текст (слово, предложение, абзац) в вектор чисел, который capture-ит смысл текста, а не просто его поверхность.
Ключевая идея embeddings: семантически близкие тексты находятся близко в векторном пространстве. Если вычислить расстояние между векторами двух похожих предложений — оно будет маленьким. Если между векторами совершенно разных текстов — большим.
Это и есть основа RAG: мы превращаем вопрос пользователя в вектор, ищем ближайшие векторы чанков в базе и получаем релевантные документы по смыслу, а не по ключевым словам.
Embedding-модель — это нейронная сеть, обученная так, чтобы похожие тексты имели близкие векторы. На вход она принимает текст, на выходе отдаёт массив чисел фиксированной длины.
# Пример: embedding через sentence-transformers
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("all-MiniLM-L6-v2")
texts = [
"Кошка сидит на диване",
"Кот расположился на софе",
"Акции Apple выросли на 5 процентов"
]
embeddings = model.encode(texts)
print(f"Размерность каждого embedding: {embeddings.shape[1]}")
# Размерность: 384 — фиксированное число для этой моделиМодель all-MiniLM-L6-v2 создаёт векторы размерности 384. Это значит, что любой текст — хоть одно слово, хоть целый абзац — превращается в 384 числа.
Разные модели создают embeddings разной размерности:
| Модель | Размерность | Скорость | Качество |
|---|---|---|---|
all-MiniLM-L6-v2 | 384 | Быстрая | Хорошее для большинства задач |
all-mpnet-base-v2 | 768 | Средняя | Высокое качество |
multilingual-e5-large | 1024 | Медленная | Отличное, поддержка многих языков |
OpenAI text-embedding-3-small | 1536 | API-запрос | Высокое |
OpenAI text-embedding-3-large | 3072 | API-запрос | Очень высокое |
Большая размерность не всегда означает лучшее качество. Для русского языка и большинства RAG-задач модели с 384–768 измерениями вполне достаточно.
После того как мы получили векторы, нужно понять, насколько они похожи. Основная метрика — косинусное сходство (cosine similarity).
Косинусное сходство измеряет угол между двумя векторами:
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
# Два embedding-вектора
vec1 = np.array([[0.1, 0.5, -0.3, 0.8]])
vec2 = np.array([[0.15, 0.48, -0.28, 0.75]])
vec3 = np.array([[-0.5, 0.1, 0.9, -0.2]])
sim_12 = cosine_similarity(vec1, vec2)[0][0]
sim_13 = cosine_similarity(vec1, vec3)[0][0]
print(f"Сходство 1-2: {sim_12:.4f}") # ~0.99 — очень похожи
print(f"Сходство 1-3: {sim_13:.4f}") # ~0.5 — менее похожиВ RAG мы используем косинусное сходство, чтобы ранжировать чанки по релевантности запросу. Чем выше сходство — тем релевантнее чанк.
Есть два способа получать embeddings:
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("intfloat/multilingual-e5-small")
embedding = model.encode("Привет, мир!")Плюсы:
multilingual-e5 понимают русскийМинусы:
from openai import OpenAI
client = OpenAI(api_key="your-key")
response = client.embeddings.create(
model="text-embedding-3-small",
input="Привет, мир!"
)
embedding = response.data[0].embeddingПлюсы:
Минусы:
Для русского языка важно использовать мультиязычные модели. Не все embedding-модели поддерживают кириллицу.
from sentence_transformers import SentenceTransformer
# Мультиязычная модель — работает с русским текстом
model = SentenceTransformer("intfloat/multilingual-e5-small")
rus_texts = ["Как дела?", "Привет мир", "Векторные представления текста"]
embeddings = model.encode(rus_texts)
print(f"Количество embeddings: {len(embeddings)}")
print(f"Размерность: {embeddings.shape[1]}")Модели, работающие с русским языком:
intfloat/multilingual-e5-small (384) — лёгкая, хорошая для стартаintfloat/multilingual-e5-base (768) — баланс скорости и качестваintfloat/multilingual-e5-large (1024) — лучшее качество, но медленнееsentence-transformers/paraphrase-multilingual-mpnet-base-v2 (768)Некоторые векторные базы данных (например, Chroma по умолчанию) ожидают нормализованные векторы. Нормализация — это приведение вектора к единичной длине, чтобы сравнивать только направление, а не абсолютные значения.
import numpy as np
vector = np.array([3.0, 4.0])
normalized = vector / np.linalg.norm(vector)
print(f"Длина нормализованного вектора: {np.linalg.norm(normalized):.4f}")
# Длина: 1.0000Хорошая новость: большинство современных embedding-библиотек и векторных БД делают нормализацию автоматически.
Вот полный пример использования embeddings для семантического поиска:
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
# 1. Загружаем модель
model = SentenceTransformer("intfloat/multilingual-e5-small")
# 2. Наша «база документов»
documents = [
"Отпуск оформляется через портал HR-системы",
"Больничный лист нужно отнести в бухгалтерию",
"Новый офис откроется на улице Ленина в январе",
"Зарплата выплачивается дважды в месяц",
"Корпоративный проездной можно получить в отделе кадров"
]
# 3. Создаём embeddings для всех документов
doc_embeddings = model.encode(documents)
# 4. Вопрос пользователя
query = "Как получить медицинскую страховку?"
query_embedding = model.encode([query])
# 5. Находим самый похожий документ
similarities = cosine_similarity(query_embedding, doc_embeddings)[0]
most_similar_idx = np.argmax(similarities)
print(f"Вопрос: {query}")
print(f"Самый релевантный документ: {documents[most_similar_idx]}")
print(f"Сходство: {similarities[most_similar_idx]:.4f}")Этот пример демонстрирует суть RAG-retrieval: вопрос и документы представлены как векторы, и мы находим ближайший по косинусному сходству.
При индексации тысяч документов вызывать модель для каждого текста по отдельности неэффективно. Лучше использовать батчи:
def embed_in_batches(texts, model, batch_size=32):
"""Создаёт embeddings батчами для экономии памяти."""
all_embeddings = []
for i in range(0, len(texts), batch_size):
batch = texts[i:i + batch_size]
batch_embeddings = model.encode(batch, show_progress_bar=False)
all_embeddings.extend(batch_embeddings.tolist())
return all_embeddings
# Пример использования
large_doc_list = ["Документ 1", "Документ 2"] * 100 # 200 документов
embeddings = embed_in_batches(large_doc_list, model, batch_size=64)
print(f"Создано {len(embeddings)} embeddings")Batch-обработка особенно важна при работе с GPU — она загружает видеокарту полностью и ускоряет процесс в разы.
sentence-transformers) бесплатны и приватны, API (OpenAI) дают высокое качествоmultilingual-e5)Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.