Конспект 9: Embeddings - Глубокое погружение
Оглавление
- Введение в Embeddings
- Что такое embedding?
- Типы embeddings
- Современные embedding модели
- Расстояния и сходство
- Dimensionality reduction
- Chunking и preprocessing
- Semantic search
- Embedding caching и optimization
- Практические примеры
- Лучшие практики
Введение в Embeddings
Что такое Embeddings?
Определение: Embedding — это численное представление текста в виде вектора в многомерном пространстве.
Суть:
Текст: "N8N это платформа для автоматизации"
↓
Embedding модель
↓
Вектор: [0.123, -0.456, 0.789, ..., -0.321]
(1536 измерений для text-embedding-3-large)
Зачем нужны embeddings?
Проблема: LLM не могут сравнивать текст напрямую
Решение: Преобразовать текст в числа (вектора)
С числами можно:
✅ Считать расстояние между текстами
✅ Найти похожие тексты (semantic search)
✅ Кластеризовать документы
✅ Использовать для RAG
✅ Делать рекомендации
✅ Детектировать дубли
История embeddings
2013: Word2Vec (слова в вектора)
2018: ELMo (контекстные embeddings)
2018: BERT (двунаправленные)
2019: GPT embeddings
2023: Text-embedding-3 (последнее SOTA)
2024: Multimodal embeddings (текст + изображения)
Что такое embedding?
Визуальное представление
Трехмерное пространство (для наглядности, на самом деле 1536D):
↑ dimension 3
|
"N8N" ●
| \
| "automation platform" ●
| /
"workflow" ●
|
"code" ●
|
"AI" ●─────────────→ dimension 1
\
\
────→ dimension 2
"N8N" близко к "automation platform" (семантически похожи)
"N8N" далеко от "AI" (другая тема)
Как создается embedding?
Процесс:
Входной текст
↓
Токенизация (разбить на токены)
↓
Token embeddings (каждый токен → вектор)
↓
Attention layers (взвешивание важности)
↓
Pooling layer (объединение в один вектор)
↓
Normalization (приведение к единичной длине)
↓
Выходной вектор (1536D)
Размерность embedding
Маленькие embeddings (128-256D):
- Быстрые вычисления
- Экономия памяти
- Подходят для simple tasks
Средние (512-768D):
- Баланс скорости и качества
- Стандарт для многих задач
Большие (1024-1536D):
- Максимальное качество
- Медленнее
- Дороже (больше память)
Пример:
text-embedding-3-small: 512D
text-embedding-3-large: 1536D
Типы embeddings
1. Text Embeddings
Для текстов любого размера:
Пример моделей:
- OpenAI text-embedding-3-small ($0.02/1M tokens)
- OpenAI text-embedding-3-large ($0.13/1M tokens)
- Cohere embed-english-v3.0
- Jina embeddings
- BGE (open source, бесплатно)
Использование:
1. Embedding документов в vector DB
2. Semantic search
3. RAG системы
4. Clustering
2. Image Embeddings
Для изображений:
Пример моделей:
- CLIP (OpenAI)
- ViT (Vision Transformer)
- ImageNet embeddings
Использование:
- Image search (поиск похожих картинок)
- Content-based recommendations
- Image clustering
3. Multimodal Embeddings
Для текста + изображений вместе:
Пример моделей:
- CLIP
- LLaVA embeddings
- Jina multimodal embeddings
Использование:
- Search in documents with images
- Cross-modal retrieval
- Image understanding
4. Sentence Embeddings
Специально для предложений (не отдельных слов):
Пример моделей:
- all-MiniLM-L6-v2 (small, fast)
- all-mpnet-base-v2 (medium)
- gte-large (большой, качественный)
Особенность: - Могут сравнивать целые предложения - Понимают семантику лучше word embeddings
5. Instruction-tuned Embeddings
Embeddings которые понимают инструкции:
text_embedding = model.embed(
text="N8N это платформа",
instruction="Represent this for semantic search"
)
Пример: - BGE-large-en-v1.5 с инструкциями - e5-large-v2
Современные embedding модели
Сравнение моделей
| Модель | Размерность | Цена | Скорость | Качество | Особенности |
|---|---|---|---|---|---|
| text-embedding-3-small | 512 | $0.02/1M | Быстро | ⭐⭐⭐⭐ | Компактная |
| text-embedding-3-large | 1536 | $0.13/1M | Средне | ⭐⭐⭐⭐⭐ | SOTA качество |
| Cohere embed-3 | 1024 | $0.10/1M | Средне | ⭐⭐⭐⭐⭐ | Multi-language |
| BGE-large | 1024 | Бесплатно | Быстро | ⭐⭐⭐⭐ | Open source |
| all-MiniLM-L6-v2 | 384 | Бесплатно | Очень быстро | ⭐⭐⭐ | Для локального |
| Jina embeddings | 768 | $0.10/1M | Средне | ⭐⭐⭐⭐ | Long context |
Выбор модели
Если бюджет есть и нужно лучшее качество:
→ text-embedding-3-large (OpenAI)
→ Cohere embed-3
Если нужна локальная модель (приватность):
→ BGE-large-en-v1.5
→ gte-large-en-v1.5
Если нужна скорость:
→ text-embedding-3-small
→ all-MiniLM-L6-v2
Если нужен очень длинный контекст (>512 tokens):
→ Jina embeddings (up to 8K tokens)
Цена embeddings
OpenAI:
text-embedding-3-small: $0.02 per 1M tokens
text-embedding-3-large: $0.13 per 1M tokens
Пример:
1000 документов × 500 токенов = 500K токенов
Стоимость: 500K × $0.02 / 1M = $0.01
Cohere:
embed-english-v3.0: ~$0.10 per 1M tokens
Open source (локально):
BGE, Jina, all-MiniLM: $0 (но GPU затраты)
Расстояния и сходство
Euclidean Distance (Евклидово расстояние)
Формула:
d = sqrt((x1-y1)² + (x2-y2)² + ... + (xn-yn)²)
Пример в 2D:
Точка A: (1, 2)
Точка B: (4, 6)
Расстояние = sqrt((4-1)² + (6-2)²) = sqrt(9 + 16) = sqrt(25) = 5
Плюсы: - Интуитивно понятно - Работает для низких размерностей
Минусы: - Медленно для высоких размерностей - Зависит от масштаба
Cosine Similarity (Косинусное сходство)
Формула:
similarity = (A · B) / (||A|| * ||B||)
где:
A · B = сумма произведений компонент
||A|| = длина вектора A
Особенность: - Сравнивает углы между векторами, не расстояния - Результат: -1 до 1 (обычно 0 до 1 для текста) - 1 = идентичные направления (похожие) - 0 = перпендикулярные (не похожи) - -1 = противоположные
Пример:
Вектор A: [1, 0, 0]
Вектор B: [1, 0, 0]
Cosine similarity = 1 (идентичны)
Вектор C: [0, 1, 0]
Cosine similarity A-C = 0 (перпендикулярны)
Вектор D: [-1, 0, 0]
Cosine similarity A-D = -1 (противоположны)
Почему cosine лучше для embeddings: - Не зависит от длины вектора - Быстрее вычисляется - Более стабильный результат - Стандарт в NLP
Другие метрики
Dot Product (Скалярное произведение):
similarity = A · B
- Быстрее всех
- Работает если embeddings нормализованы
Manhattan Distance (L1):
d = |x1-y1| + |x2-y2| + ... + |xn-yn|
- Быстрее Euclidean для высоких размерностей
Практическое применение
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
# Embeddings (пример)
text1_embedding = np.array([0.1, 0.2, 0.3])
text2_embedding = np.array([0.1, 0.2, 0.3])
text3_embedding = np.array([0.9, 0.8, 0.7])
# Cosine similarity
sim_1_2 = cosine_similarity([text1_embedding], [text2_embedding])[0][0]
sim_1_3 = cosine_similarity([text1_embedding], [text3_embedding])[0][0]
print(f"Text 1 vs Text 2: {sim_1_2:.3f}") # ~1.0 (идентичны)
print(f"Text 1 vs Text 3: {sim_1_3:.3f}") # ~0.5 (похожи)
Dimensionality reduction
Зачем уменьшать размерность?
Проблема: 1536D embedding слишком большой
- Медленный поиск
- Много памяти
- Дорогое хранилище в vector DB
Решение: Сжать до 256-512D без потери качества
Техники reduction
1. Random Projection
Идея: Просто взять случайные оси в пространстве
Original: 1536D
Random projection: 256D
Быстро, но может потерять информацию
Код:
from sklearn.random_projection import SparseRandomProjection
rp = SparseRandomProjection(n_components=256)
reduced_embeddings = rp.fit_transform(embeddings)
2. PCA (Principal Component Analysis)
Идея: Найти оси с наибольшей дисперсией
1. Вычислить главные компоненты
2. Оставить топ-256 компонентов
3. Спроецировать на них
Код:
from sklearn.decomposition import PCA
pca = PCA(n_components=256)
reduced_embeddings = pca.fit_transform(embeddings)
# Сохранить для использования на новых данных
import pickle
pickle.dump(pca, open('pca_model.pkl', 'wb'))
Плюсы: - Сохраняет максимум информации - Оптимальное сжатие
Минусы: - Медленнее random projection - Нужно обучать на данных
3. UMAP (Uniform Manifold Approximation and Projection)
Идея: Сохранить локальные и глобальные структуры
Результат: более интерпретируемое пространство
Хорошо для визуализации и анализа
Код:
import umap
reducer = umap.UMAP(n_components=2)
embedding_2d = reducer.fit_transform(embeddings)
# Визуализация
import matplotlib.pyplot as plt
plt.scatter(embedding_2d[:, 0], embedding_2d[:, 1])
plt.show()
4. OpenAI Embedding Shortening
Новая фишка OpenAI (December 2024):
Можно урезать embedding до меньшего размера
без переживаний
embeddings = client.embeddings.create(
input="...",
model="text-embedding-3-large",
dimensions=256 # Вместо 1536!
)
Выигрыш: - Цена: -80% (меньше хранилища, передачи) - Качество: только -5% - Скорость: +6x быстрее
Chunking и preprocessing
Размер chunk
Слишком маленькие (50-100 токенов):
❌ Теряется контекст
❌ Много embeddings (дорого)
❌ Слишком много matches
Оптимальные (200-400 токенов):
✅ Хороший баланс
✅ Контекст сохранен
✅ Разумное количество matches
Слишком большие (1000+ токенов):
❌ Может быть слишком много шума
❌ Дороже хранить в vector DB
❌ Меньше точность поиска
Стратегии chunking
1. Overlap (Перекрытие)
Документ: [1, 2, 3, 4, 5, 6, 7, 8]
Chunk size: 4
Overlap: 2
Chunk 1: [1, 2, 3, 4]
Chunk 2: [3, 4, 5, 6]
Chunk 3: [5, 6, 7, 8]
Плюсы:
- Сохраняется контекст на границах
- Лучше поиск
Минусы:
- Дубликаты информации
2. Smart chunking (по структуре)
Документ:
# Заголовок 1
Some text...
# Заголовок 2
Some text...
Chunking:
Chunk 1: [# Заголовок 1 + Some text...]
Chunk 2: [# Заголовок 2 + Some text...]
Плюсы:
- Семантически coherent chunks
- Лучше качество embeddings
3. Recursive chunking
Попробуй разбить по:
1. Параграфам
2. Предложениям
3. Словам
Используй первый успешный способ
Код (LangChain):
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=400,
chunk_overlap=50,
separators=["\n\n", "\n", ".", " ", ""]
)
chunks = splitter.split_text(text)
Semantic search
Как работает semantic search?
1. Пользователь: "Как работает N8N?"
↓
2. Embedding запроса
↓
3. Поиск похожих embeddings в vector DB
↓
4. Вернуть топ-K результатов
↓
5. Ранжировать по релевантности
↓
6. Пользователю: релевантные документы
Реализация с Qdrant
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct
import openai
# 1. Инициализация
client = QdrantClient(":memory:") # или remote URL
# 2. Создать collection
client.create_collection(
collection_name="documents",
vectors_config=VectorParams(size=1536, distance=Distance.COSINE)
)
# 3. Embedding и загрузка документов
documents = [
"N8N is an automation platform",
"Workflow automation is important",
...
]
points = []
for i, doc in enumerate(documents):
embedding = openai.Embedding.create(
input=doc,
model="text-embedding-3-large"
)["data"][0]["embedding"]
points.append(
PointStruct(
id=i,
vector=embedding,
payload={"text": doc}
)
)
client.upsert(collection_name="documents", points=points)
# 4. Поиск
query = "How to automate workflows?"
query_embedding = openai.Embedding.create(
input=query,
model="text-embedding-3-large"
)["data"][0]["embedding"]
results = client.search(
collection_name="documents",
query_vector=query_embedding,
limit=3 # Топ-3 результата
)
# 5. Вывод результатов
for result in results:
print(f"Score: {result.score:.3f}")
print(f"Text: {result.payload['text']}")
Re-ranking (Улучшение результатов)
Проблема: Semantic search может вернуть не совсем релевантные результаты
Решение: Второй pass с более сложной моделью
# 1. Быстрый поиск с embeddings (топ-50)
fast_results = client.search(..., limit=50)
# 2. Re-rank с более сложной моделью (топ-5)
from sentence_transformers import CrossEncoder
model = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
pairs = [[query, result.payload['text']] for result in fast_results]
scores = model.predict(pairs)
# Отсортировать по новым скорам
ranked_results = sorted(
zip(fast_results, scores),
key=lambda x: x[1],
reverse=True
)[:5]
Embedding caching и optimization
Caching embeddings
Проблема: Embedding generation дорого ($0.02/1M tokens)
Решение: Кэш embeddings
import hashlib
import json
from redis import Redis
redis_client = Redis()
def get_embedding(text, model="text-embedding-3-large"):
# 1. Проверить кэш
cache_key = f"embedding:{hashlib.md5(text.encode()).hexdigest()}"
cached = redis_client.get(cache_key)
if cached:
return json.loads(cached)
# 2. Если не в кэше, создать
embedding = openai.Embedding.create(
input=text,
model=model
)["data"][0]["embedding"]
# 3. Сохранить в кэш (TTL 30 дней)
redis_client.setex(
cache_key,
86400 * 30, # 30 days
json.dumps(embedding)
)
return embedding
Batch embedding
Вместо:
# Медленно: 1000 отдельных вызовов
embeddings = [get_embedding(doc) for doc in docs]
Используй batch:
# Быстро: 10 batch вызовов
def batch_embeddings(texts, batch_size=100):
embeddings = []
for i in range(0, len(texts), batch_size):
batch = texts[i:i+batch_size]
response = openai.Embedding.create(
input=batch,
model="text-embedding-3-large"
)
embeddings.extend([e["embedding"] for e in response["data"]])
return embeddings
embeddings = batch_embeddings(docs, batch_size=100)
Embedding dimensionality optimization
# Вместо 1536D, использовать 256D
embeddings = client.embeddings.create(
input=texts,
model="text-embedding-3-large",
dimensions=256 # -80% памяти, цены, скорости!
)
Практические примеры
Пример 1: Document Q&A с embeddings
import openai
from qdrant_client import QdrantClient
# 1. Подготовить документы
documents = load_documents("knowledge_base/")
# 2. Embedding и загрузка в Qdrant
qdrant = QdrantClient("http://localhost:6333")
store_embeddings(qdrant, documents)
# 3. Q&A функция
def answer_question(question):
# Embedding вопроса
question_emb = openai.Embedding.create(
input=question,
model="text-embedding-3-large"
)["data"][0]["embedding"]
# Поиск релевантных документов
results = qdrant.search(
collection_name="docs",
query_vector=question_emb,
limit=3
)
# Составить контекст
context = "\n".join([r.payload['text'] for r in results])
# LLM для ответа
response = openai.ChatCompletion.create(
model="gpt-4-turbo",
messages=[
{"role": "system", "content": "Answer based on context"},
{"role": "user", "content": f"Context: {context}\n\nQ: {question}"}
]
)
return response.choices[0].message.content
Пример 2: Semantic similarity check
def find_similar_documents(document_id, top_k=5):
"""Найти похожие документы"""
# Получить embedding исходного документа
original_doc = documents[document_id]
original_emb = get_embedding(original_doc)
# Поиск в vector DB
results = qdrant.search(
collection_name="docs",
query_vector=original_emb,
limit=top_k + 1 # +1 чтобы исключить сам документ
)
# Фильтровать: исключить исходный документ
similar = [r for r in results if r.id != document_id]
return similar[:top_k]
Пример 3: Clustering с embeddings
from sklearn.cluster import KMeans
import numpy as np
# 1. Получить embeddings всех документов
embeddings = np.array([get_embedding(doc) for doc in documents])
# 2. KMeans clustering
kmeans = KMeans(n_clusters=10, random_state=42)
clusters = kmeans.fit_predict(embeddings)
# 3. Группировать документы
from collections import defaultdict
grouped = defaultdict(list)
for doc, cluster in zip(documents, clusters):
grouped[cluster].append(doc)
# Вывод
for cluster_id, docs in grouped.items():
print(f"\nCluster {cluster_id}:")
for doc in docs[:3]: # Первые 3
print(f" - {doc[:100]}...")
Лучшие практики
Do's ✅
-
Используйте text-embedding-3-large для quality
Если бюджет есть: лучший результат -
Кэшируйте embeddings
Embedding одного документа дважды = пустая трата денег -
Используйте chunking с overlap
Перекрытие сохраняет контекст на границах -
Уменьшайте размерность
text-embedding-3-large → 256D экономит 80% памяти -
Batch embeddings
100 документов сразу дешевле чем по одному -
Мониторьте embedding качество
Проверяйте что поиск возвращает релевантные результаты
Don'ts ❌
-
Не используйте word embeddings для семантического поиска
Используйте sentence/paragraph embeddings -
Не игнорируйте chunking
Неправильный чанкинг = плохой поиск -
Не забывайте про normalization
Нормализованные embeddings работают лучше -
Не переиспользуйте старые embeddings
Если обновили embedding модель, переэмбедируйте данные -
Не пренебрегайте re-ranking
Для критичных приложений: второй pass с более сложной моделью
Чек-лист embeddings
Подготовка:
☑️ Выбрана embedding модель (OpenAI, Cohere, BGE)
☑️ Документы очищены и предпроцессированы
☑️ Определен размер chunk (200-400 tokens)
☑️ Настроено overlap (50-100 tokens)
Генерация:
☑️ Embeddings генерируются в batch
☑️ Кэширование настроено (Redis/SQLite)
☑️ Мониторируется стоимость
☑️ Логируется процесс
Хранилище:
☑️ Vector DB выбрана (Qdrant, Pinecone, Weaviate)
☑️ Метаданные сохранены (source, date, id)
☑️ Индексы созданы для быстрого поиска
☑️ Настроена репликация/backup
Поиск:
☑️ Semantic search работает
☑️ Re-ranking реализован (if needed)
☑️ Результаты проверены (manual QA)
☑️ Performance тестирован (скорость, accuracy)
Maintenance:
☑️ Мониторинг качества embeddings
☑️ Периодическое обновление (новые документы)
☑️ Экспериментирование с моделями
☑️ A/B тестирование search результатов
Дата создания: December 2025
Версия: 1.0
Автор: Pavel
Применение: Semantic Search, RAG, Vector Databases, Information Retrieval