Конспект 9: Embeddings - Глубокое погружение

Pavel 13.12.2025 22:12 11 просмотров

Оглавление

  1. Введение в Embeddings
  2. Что такое embedding?
  3. Типы embeddings
  4. Современные embedding модели
  5. Расстояния и сходство
  6. Dimensionality reduction
  7. Chunking и preprocessing
  8. Semantic search
  9. Embedding caching и optimization
  10. Практические примеры
  11. Лучшие практики

Введение в 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?

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 ✅

  1. Используйте text-embedding-3-large для quality Если бюджет есть: лучший результат

  2. Кэшируйте embeddings Embedding одного документа дважды = пустая трата денег

  3. Используйте chunking с overlap Перекрытие сохраняет контекст на границах

  4. Уменьшайте размерность text-embedding-3-large → 256D экономит 80% памяти

  5. Batch embeddings 100 документов сразу дешевле чем по одному

  6. Мониторьте embedding качество Проверяйте что поиск возвращает релевантные результаты

Don'ts ❌

  1. Не используйте word embeddings для семантического поиска Используйте sentence/paragraph embeddings

  2. Не игнорируйте chunking Неправильный чанкинг = плохой поиск

  3. Не забывайте про normalization Нормализованные embeddings работают лучше

  4. Не переиспользуйте старые embeddings Если обновили embedding модель, переэмбедируйте данные

  5. Не пренебрегайте 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

Комментарии (0)

Для добавления комментария необходимо войти в аккаунт

Войти / Зарегистрироваться