Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Первые эмбеддинги: практика генерации
first_embeddings

Первые эмбеддинги: практика генерации

Учимся генерировать эмбеддинги для текста: выбор модели, пакетная обработка, кэширование. Много кода, минимум теории.

Первые эмбеддинги: практика генерации

Научитесь генерировать эмбеддинги для текста: выбор модели, пакетная обработка, кэширование. Много кода, минимум теории.

#Что вы сделаете в этой теме

За 45 минут вы:

  1. Поймёте, что такое эмбеддинги и как они работают
  2. Научитесь выбирать модель под задачу
  3. Напишете генератор эмбеддингов с пакетной обработкой
  4. Реализуете кэширование для экономии времени
  5. Протестируете разные модели на своих данных

#Что такое эмбеддинг (простыми словами)

Эмбеддинг — это вектор чисел, представляющий текст в многомерном пространстве.

"кот сидит на коврике" → [0.12, -0.45, 0.78, 0.33, ..., 0.21] # 384 числа

Ключевое свойство: семантически близкие тексты имеют близкие векторы.

"кот на коврике" → [0.12, -0.45, 0.78, ...] "кошка на подстилке" → [0.15, -0.42, 0.75, ...] # Близко к первому! "автомобиль едет" → [0.89, 0.23, -0.12, ...] # Далеко от первого

Расстояние между векторами показывает семантическую близость текстов.

#Шаг 1: Базовая генерация эмбеддингов

Начнём с простого примера:

from sentence_transformers import SentenceTransformer # Загружаем модель model = SentenceTransformer('all-MiniLM-L6-v2') # Один текст text = "Машинное обучение — подраздел искусственного интеллекта" embedding = model.encode(text) print(f"Размерность: {embedding.shape}") # (384,) print(f"Первые 10 чисел: {embedding[:10]}") # Несколько текстов texts = [ "Машинное обучение", "Глубокое обучение", "Искусственный интеллект", "Анализ данных" ] embeddings = model.encode(texts) print(f"\nМатрица эмбеддингов: {embeddings.shape}") # (4, 384)

Вывод:

Размерность: (384,)
Первые 10 чисел: [0.023 -0.045 0.078 ...]

Матрица эмбеддингов: (4, 384)

#Шаг 2: Выбор модели

Разные модели — разное качество, скорость, размерность.

#Топ моделей для английского

МодельРазмерностьСкоростьКачествоКогда использовать
all-MiniLM-L6-v2384⚡⚡⚡ Очень быстро👍 ХорошееПрототипы, демо, небольшие проекты
all-mpnet-base-v2768⚡⚡ Средне👍👍 ОтличноеПродакшен, высокое качество
all-distilroberta-v1768⚡⚡⚡ Быстро👍👍 ХорошееБаланс скорости и качества

#Для русского языка

МодельРазмерностьЯзыкиКогда использовать
paraphrase-multilingual-mpnet-base-v276850+Многоязычные проекты
rubert-tiny2312RU, ENБыстрые решения для русского

#Практический тест моделей

Напишите скрипт для сравнения:

import time from sentence_transformers import SentenceTransformer, util models_to_test = { "MiniLM": "all-MiniLM-L6-v2", "MPNet": "all-mpnet-base-v2", "DistilRoBERTa": "all-distilroberta-v1", } test_pairs = [ ("кот сидит на коврике", "кошка лежит на подстилке"), ("Python программирование", "веб-разработка"), ("машинное обучение", "искусственный интеллект"), ("как дела?", "привет, как настроение?"), ] print("Сравнение моделей:\n") for name, model_name in models_to_test.items(): print(f"{'='*50}") print(f"{name} ({model_name})") print(f"{'='*50}") model = SentenceTransformer(model_name) # Тест скорости start = time.time() for _ in range(50): model.encode("тестовый текст для замера скорости") elapsed = time.time() - start speed = 50 / elapsed print(f" Скорость: {speed:.1f} текстов/сек") print(f" Размерность: {model.get_sentence_embedding_dimension()}") # Тест качества (косинусное сходство для пар) print(f"\n Косинусное сходство для пар:") for text1, text2 in test_pairs: emb1 = model.encode(text1, convert_to_tensor=True) emb2 = model.encode(text2, convert_to_tensor=True) sim = util.cos_sim(emb1, emb2).item() # Обрезаем текст для вывода t1 = text1[:25] + "..." if len(text1) > 25 else text1 t2 = text2[:25] + "..." if len(text2) > 25 else text2 print(f" '{t1}' ↔ '{t2}': {sim:.3f}") print()

Пример вывода:

==================================================
MiniLM (all-MiniLM-L6-v2)
==================================================
  Скорость: 142.3 текстов/сек
  Размерность: 384

  Косинусное сходство для пар:
    'кот сидит на коврике' ↔ 'кошка лежит на подсти...': 0.812
    'Python программирование' ↔ 'веб-разработка': 0.345
    'машинное обучение' ↔ 'искусственный интеллект': 0.723
    'как дела?' ↔ 'привет, как настроение?': 0.654

Как интерпретировать:

  • Сходство 0.7+ — очень похожие тексты
  • Сходство 0.4–0.7 — умеренная связь
  • Сходство < 0.4 — слабая связь

#Шаг 3: Пакетная обработка (Batch Processing)

Генерация эмбеддингов для тысяч документов по одному — медленно. Используйте батчи!

#❌ Медленно: по одному

texts = ["документ"] * 10000 # 10 тысяч документов embeddings = [] for text in texts: embedding = model.encode(text) # 10000 отдельных вызовов! embeddings.append(embedding) # Время: ~70 секунд для 10K документов

#✅ Быстро: пакетная обработка

def batch_encode(model, texts, batch_size=32): """ Генерация эмбеддингов батчами для экономии памяти и ускорения. Args: model: Модель sentence-transformers texts: Список текстов batch_size: Размер батча """ all_embeddings = [] for i in range(0, len(texts), batch_size): batch = texts[i:i + batch_size] batch_embeddings = model.encode( batch, batch_size=batch_size, show_progress_bar=True # Прогресс-бар ) all_embeddings.append(batch_embeddings) return np.vstack(all_embeddings) # Использование import numpy as np texts = ["документ"] * 10000 # 10 тысяч документов embeddings = batch_encode(model, texts, batch_size=64) print(f"Матрица: {embeddings.shape}") # (10000, 384) # Время: ~15 секунд для 10K документов (в 4-5 раз быстрее!)

#Оптимизация размера батча

# Маленький батч (16) — меньше памяти, медленнее embeddings = batch_encode(model, texts, batch_size=16) # Средний батч (64) — баланс (рекомендуется) embeddings = batch_encode(model, texts, batch_size=64) # Большой батч (256) — быстрее, но больше памяти embeddings = batch_encode(model, texts, batch_size=256)

Рекомендации:

  • < 1000 документов: batch_size=32
  • 1000–10000: batch_size=64
  • 10000: batch_size=128–256 (если хватает памяти)

#Шаг 4: Кэширование эмбеддингов

Генерация эмбеддингов — дорогая операция. Кэшируйте результаты!

#Простой кэш на диске

import hashlib import json import numpy as np from pathlib import Path class EmbeddingCache: """Кэширование эмбеддингов на диске.""" def __init__(self, cache_dir=".cache/embeddings"): self.cache_dir = Path(cache_dir) self.cache_dir.mkdir(parents=True, exist_ok=True) def _get_cache_key(self, text: str) -> str: """Хэш текста как ключ кэша.""" return hashlib.md5(text.encode('utf-8')).hexdigest() def get(self, text: str) -> np.ndarray | None: """Получить из кэша или None.""" key = self._get_cache_key(text) cache_file = self.cache_dir / f"{key}.npy" if cache_file.exists(): return np.load(cache_file) return None def set(self, text: str, embedding: np.ndarray): """Сохранить в кэш.""" key = self._get_cache_key(text) cache_file = self.cache_dir / f"{key}.npy" np.save(cache_file, embedding) def get_or_compute(self, model, text: str) -> np.ndarray: """Получить из кэша или вычислить.""" embedding = self.get(text) if embedding is None: embedding = model.encode(text) self.set(text, embedding) return embedding # Использование cache = EmbeddingCache() # Первый вызов — генерация (медленно) embedding1 = cache.get_or_compute(model, "важный документ") # Второй вызов — из кэша (мгновенно!) embedding2 = cache.get_or_compute(model, "важный документ")

#Кэш для пакетной обработки

def batch_encode_with_cache(model, texts, cache, batch_size=64): """Пакетная генерация с кэшированием.""" all_embeddings = [] cache_hits = 0 cache_misses = 0 for i in range(0, len(texts), batch_size): batch = texts[i:i + batch_size] batch_embeddings = [] for text in batch: emb = cache.get(text) if emb is not None: batch_embeddings.append(emb) cache_hits += 1 else: cache_misses += 1 emb = model.encode(text) cache.set(text, emb) batch_embeddings.append(emb) all_embeddings.append(np.array(batch_embeddings)) print(f"Кэш: {cache_hits} попаданий, {cache_misses} промахов") return np.vstack(all_embeddings)

#Шаг 5: Обработка длинных текстов

Модели имеют ограничение на длину текста (обычно 512 токенов).

#Проблема: текст слишком длинный

long_text = "Очень длинный документ..." * 1000 # 10000 символов # ❌ Модель обрежет текст или выдаст ошибку embedding = model.encode(long_text)

#Решение 1: Разбиение на чанки

def chunk_text(text, max_length=500): """ Разбиение длинного текста на чанки. Args: text: Исходный текст max_length: Максимальная длина чанка в символах """ chunks = [] # Разбиваем по предложениям sentences = text.replace('!', '.').replace('?', '.').split('.') current_chunk = "" for sentence in sentences: sentence = sentence.strip() if not sentence: continue if len(current_chunk) + len(sentence) < max_length: current_chunk += " " + sentence else: if current_chunk: chunks.append(current_chunk.strip()) current_chunk = sentence if current_chunk: chunks.append(current_chunk.strip()) return chunks # Использование long_text = "Длинный документ..." * 1000 chunks = chunk_text(long_text, max_length=500) # Генерируем эмбеддинги для каждого чанка chunk_embeddings = model.encode(chunks) # Усредняем для получения одного вектора документа document_embedding = np.mean(chunk_embeddings, axis=0)

#Решение 2: Использование моделей с большим контекстом

# Модель с контекстом 1024 токена model = SentenceTransformer('msmarco-MiniLM-L-6-v3') # Модель с контекстом 512 токенов (стандарт) model = SentenceTransformer('all-MiniLM-L6-v2')

#Шаг 6: Нормализация векторов

Для косинусного сходства удобно нормализовать векторы.

from sklearn.preprocessing import normalize # Генерация embeddings = model.encode(texts) # L2-нормализация normalized_embeddings = normalize(embeddings) # Теперь скалярное произведение = косинусное сходство from numpy import dot similarity = dot(normalized_embeddings[0], normalized_embeddings[1]) print(f"Сходство: {similarity:.3f}")

#Шаг 7: Визуализация эмбеддингов

Посмотрите, как векторы группируются в пространстве.

import matplotlib.pyplot as plt from sklearn.manifold import TSNE def visualize_embeddings(embeddings, labels=None, title="Векторное пространство"): """ Визуализация эмбеддингов в 2D с помощью t-SNE. Args: embeddings: Матрица эмбеддингов (n_samples, n_dimensions) labels: Метки для раскраски точек (опционально) title: Заголовок графика """ # Снижение размерности до 2D tsne = TSNE(n_components=2, random_state=42, perplexity=5) embeddings_2d = tsne.fit_transform(embeddings) plt.figure(figsize=(10, 8)) if labels is not None: scatter = plt.scatter( embeddings_2d[:, 0], embeddings_2d[:, 1], c=labels, cmap='viridis', alpha=0.6, s=100 ) plt.colorbar(scatter, label='Категория') else: plt.scatter(embeddings_2d[:, 0], embeddings_2d[:, 1], alpha=0.6, s=100) plt.title(title) plt.xlabel("Компонента 1") plt.ylabel("Компонента 2") plt.grid(True, alpha=0.3) plt.tight_layout() plt.show() # Использование texts = [ "кот кошка питомец", "собака пёс питомец", "автомобиль машина транспорт", "мотоцикл байк транспорт", "Python программирование код", "JavaScript веб программирование" ] embeddings = model.encode(texts) labels = [0, 0, 1, 1, 2, 2] # Категории: животные, транспорт, код visualize_embeddings(embeddings, labels, "Группировка текстов")

#Практическое задание: генератор эмбеддингов

Создайте утилиту для генерации эмбеддингов с кэшированием:

# embedding_generator.py import numpy as np from sentence_transformers import SentenceTransformer from pathlib import Path import hashlib import json class EmbeddingGenerator: """Генератор эмбеддингов с кэшированием и пакетной обработкой.""" def __init__( self, model_name: str = 'all-MiniLM-L6-v2', cache_dir: str = ".cache/embeddings", batch_size: int = 64 ): self.model = SentenceTransformer(model_name) self.batch_size = batch_size self.cache_dir = Path(cache_dir) self.cache_dir.mkdir(parents=True, exist_ok=True) # Статистика self.cache_hits = 0 self.cache_misses = 0 def _get_cache_key(self, text: str) -> str: return hashlib.md5(text.encode('utf-8')).hexdigest() def _get_cache_path(self, key: str) -> Path: return self.cache_dir / f"{key}.npy" def get_cached(self, text: str) -> np.ndarray | None: key = self._get_cache_key(text) cache_path = self._get_cache_path(key) if cache_path.exists(): self.cache_hits += 1 return np.load(cache_path) self.cache_misses += 1 return None def save_cache(self, text: str, embedding: np.ndarray): key = self._get_cache_key(text) cache_path = self._get_cache_path(key) np.save(cache_path, embedding) def encode_single(self, text: str) -> np.ndarray: """Эмбеддинг для одного текста.""" cached = self.get_cached(text) if cached is not None: return cached embedding = self.model.encode(text) self.save_cache(text, embedding) return embedding def encode_batch(self, texts: list[str]) -> np.ndarray: """Пакетная генерация с кэшированием.""" all_embeddings = [] for i in range(0, len(texts), self.batch_size): batch = texts[i:i + self.batch_size] batch_embeddings = [] for text in batch: emb = self.get_cached(text) if emb is not None: batch_embeddings.append(emb) else: emb = self.model.encode(text) self.save_cache(text, emb) batch_embeddings.append(emb) all_embeddings.append(np.array(batch_embeddings)) return np.vstack(all_embeddings) def get_stats(self) -> dict: """Статистика кэша.""" total = self.cache_hits + self.cache_misses hit_rate = (self.cache_hits / total * 100) if total > 0 else 0 return { "cache_hits": self.cache_hits, "cache_misses": self.cache_misses, "hit_rate": f"{hit_rate:.1f}%" } # Использование if __name__ == "__main__": generator = EmbeddingGenerator( model_name='all-MiniLM-L6-v2', batch_size=64 ) documents = [ "Python — язык программирования", "Django — веб-фреймворк", "Flask — микрофреймворк", "FastAPI — современный фреймворк", ] embeddings = generator.encode_batch(documents) print(f"Сгенерировано эмбеддингов: {embeddings.shape}") print(f"Статистика: {generator.get_stats()}")

#Типичные ошибки

#1. Сравнение эмбеддингов от разных моделей

# ❌ НЕЛЬЗЯ model1 = SentenceTransformer('all-MiniLM-L6-v2') model2 = SentenceTransformer('all-mpnet-base-v2') emb1 = model1.encode("текст") emb2 = model2.encode("текст") # Эти векторы в разных пространствах — сравнение бессмысленно!

Правило: Одна модель на весь проект.

#2. Игнорирование кэширования

# ❌ Генерация каждый раз for query in queries: embedding = model.encode(query) # 1000 вызовов! # ✅ Кэширование for query in queries: embedding = cache.get_or_compute(model, query) # Повторные — из кэша

#3. Обработка длинных текстов без разбиения

# ❌ Модель обрежет текст long_text = "..." * 10000 embedding = model.encode(long_text) # ✅ Разбиение на чанки chunks = chunk_text(long_text) chunk_embeddings = model.encode(chunks) embedding = np.mean(chunk_embeddings, axis=0)

#4. Отсутствие нормализации для косинусного сходства

# ❌ embeddings = model.encode(texts) similarity = np.dot(embeddings[0], embeddings[1]) # ✅ from sklearn.preprocessing import normalize normalized = normalize(embeddings) similarity = np.dot(normalized[0], normalized[1])

#Чеклист: вы научились

  • Генерировать эмбеддинги для одного текста и списка
  • Выбирать модель под задачу (скорость vs качество)
  • Использовать пакетную обработку для больших объёмов
  • Кэшировать эмбеддинги на диске
  • Разбивать длинные тексты на чанки
  • Нормализовать векторы для косинусного сходства
  • Визуализировать эмбеддинги в 2D

#Что дальше

Теперь вы умеете генерировать эмбеддинги. Следующий шаг — Метрики сходства:

  • Cosine, Euclidean, Dot Product — в чём разница
  • Когда какую метрику использовать
  • Как интерпретировать результаты

#Домашнее задание

  1. Создайте файл my_embeddings.py с генератором эмбеддингов
  2. Возьмите 20–30 документов на вашу тему (статьи, посты, документация)
  3. Сгенерируйте эмбеддинги с кэшированием
  4. Визуализируйте результаты в 2D
  5. Найдите 3 пары документов с highest similarity

Готовы? Открывайте similarity_metrics!

Далее: Метрики сходства: когда какую использовать