Embeddings: как LLM понимает смысл слов

opensourceaillmembeddingsnlpit
← Back to Blog

Введение: что такое embeddings

Embeddings — это векторные представления слов, предложений или документов. Каждая единица текста отображается в точку в многомерном пространстве, где близость означает семантическую близость.

"король" → [0.23, -0.45, 0.89, ..., 0.12]  (1536 измерений для GPT-4)
"queen"  → [0.21, -0.43, 0.91, ..., 0.14]  (очень близко к "король")
"банан"  → [0.87, 0.34, -0.12, ..., -0.56] (далеко от "король")

Почему это важно для LLM?

Embeddings — это первый слой любой нейросети, работающей с текстом:

Текст → Токенизация → Embedding Layer → Transformer Layers → Output
         [числа]        [векторы D]       [обработка]        [вероятности]

Без качественных embeddings transformer не сможет ничего понять.


Уровень 1: Словарные embeddings

One-Hot Encoding (самый простой подход)

Словарь: ["cat", "dog", "bird", "fish"]

"cat"    → [1, 0, 0, 0]
"dog"    → [0, 1, 0, 0]
"bird"   → [0, 0, 1, 0]
"fish"   → [0, 0, 0, 1]

Проблемы:

  1. Нет семантики: "cat" и "dog" так же далеки, как "cat" и "bird"
  2. Огромное пространство: словарь 50K токенов → вектор из 50K измерений
  3. Редкость: редкие слова имеют мало данных для обучения

Уровень 2: Dense Embeddings (Word2Vec, GloVe)

Идея: обучить векторы

Вместо hand-crafted one-hot, мы обучаем векторы так, чтобы близкие по смыслу слова были близки в пространстве.

Word2Vec (Google, 2013)

Два подхода:

Skip-gram: предсказывать контекст по слову

Контекст: "the cat sits on the mat"

Для слова "cat":
  Вход: "cat"
  Цель: предсказать "the", "sits", "on", "mat"

Для слова "dog":
  Вход: "dog"
  Цель: предсказать "the", "runs", "on", "grass"

→ "cat" и "dog" будут иметь похожие векторы!

CBOW (Continuous Bag of Words): предсказывать слово по контексту

Контекст: "the ___ sits on the mat"
Вход: ["the", "on", "the", "mat"]
Цель: предсказать "cat"

Результат обучения Word2Vec

Аналогии работают!

"король" - "мужчина" + "женщина" ≈ "королева"

Векторная арифметика:
  king - man + woman = [0.23, -0.45, 0.89, ...] - [0.12, 0.34, -0.23, ...] + [0.10, 0.36, -0.21, ...]
  = [0.21, -0.43, 0.91, ...] ≈ queen

GloVe (Stanford, 2014)

GloVe использует матрицу совместной встречаемости слов:

Матрица совместности:
        | cat | dog | runs | sits | food
--------|-----|-----|------|------|------
cat     |  0  |  3  |  1   |  15  |  8
dog     |  3  |  0  |  12  |  2   |  5
runs    |  1  |  12 |  0   |  1   |  0
sits    |  15 |  2  |  1   |  0   |  0
food    |  8  |  5  |  0   |  0   |  0

GloVe обучает embeddings так, чтобы:
  w_i^T * w_j + b_j = log(P(j|i))
  
где w_i — вектор слова i, P(j|i) — вероятность слова j в контексте i

Размерности embeddings

Модель           | Размерность | Словарь
-----------------|-------------|----------
Word2Vec         | 100-300     | ~1M
GloVe            | 50-843      | ~400K
FastText         | 100-300     | ~1M (+ подсловные n-граммы)

Уровень 3: Контекстуальные embeddings (BERT и далее)

Проблема статических embeddings

Word2Vec даёт один вектор на слово:

"bank" в "river bank" → [0.23, -0.45, ...]
"bank" в "bank account" → [0.23, -0.45, ...]  ← тот же вектор!

Но это разные значения!

BERT: контекстуальные embeddings

BERT генерирует разные векторы для одного слова в разном контексте:

"Banking on this bank's stability"

BERT token 1 "Banking":  [0.12, -0.34, 0.56, ...]  (финансовый контекст)
BERT token 4 "bank":     [0.23, -0.45, 0.67, ...]  (финансовый контекст)
BERT token 5 "'s":        [0.01,  0.12, 0.23, ...]
BERT token 6 "stability": [0.45,  0.23, -0.12, ...]

BERT token 1 "Banking":  [0.34, -0.12, 0.78, ...]  (другой контекст — другой вектор!)

Как BERT создаёт embeddings

Вход: "[CLS] cat sits on [MASK] mat [SEP]"

Компоненты:
1. Token embeddings:   [cat, sits, on, mask, mat] → векторы
2. Segment embeddings: [0, 0, 0, 0, 0]            (один сегмент)
3. Position embeddings: [0, 1, 2, 3, 4]           (позиции)

Сумма: token + segment + position → вход в transformer

Выход: 768-мерные векторы для каждого токена (BERT-base)

Уровень 4: Embeddings в LLM (Transformer)

Embedding Layer в GPT/Llama

В LLM embedding layer — это просто таблица поиска:

class EmbeddingLayer(nn.Module):
    def __init__(self, vocab_size=128256, hidden_size=4096):
        # Таблица: токен_id → вектор
        self.weight = nn.Embedding(vocab_size, hidden_size)
        # Вес: [128256, 4096] = ~512M параметров только для эмбеддинга!
    
    def forward(self, token_ids):
        # token_ids: [batch_size, seq_len]
        # output: [batch_size, seq_len, hidden_size]
        return self.weight(token_ids)

# Пример:
# Токены: [122, 353, 749, 0]
# → Векторы: [4096, 4096, 4096, 4096] (4 вектора по 4096 чисел)

Масштаб embedding layer

Модель          | Vocab Size | Hidden Size | Параметры
----------------|------------|-------------|----------
GPT-2           | 50,257     | 768         | ~38M
GPT-3 125M      | 50,257     | 768         | ~38M
Llama 3 8B      | 128,256    | 4,096       | ~512M
Llama 3 70B     | 128,256    | 8,192       | ~1B
GPT-4 (оценка)  | ~150,000   | ~16,384     | ~2.4B

Embedding layer может составлять 10-20% всех параметров модели!


Визуализация embeddings

t-SNE и PCA

from sklearn.manifold import TSNE
import matplotlib.pyplot as plt

# Получаем embeddings
embeddings = model.token_embedding.weight.detach().numpy()  # [50257, 768]

# Снижаем до 2D
tsne = TSNE(n_components=2, random_state=42)
embeddings_2d = tsne.fit_transform(embeddings)

# Строим
plt.scatter(embeddings_2d[:, 0], embeddings_2d[:, 1], alpha=0.1)
plt.show()

Что видно на графике?

Кластеры формируются по:
1. Части речи: существительные, глаголы, прилагательные
2. Тематике: животные, еда, технологии, география
3. Грамматике: единственное/множественное число, прошедшее/будущее время

Пример кластера "животные":
  cat, dog, bird, fish, horse, cow, sheep, elephant, ...

Пример кластера "глаголы действия":
  run, walk, jump, fly, swim, climb, crawl, ...

Специализированные embeddings

Sentence embeddings

Для сравнения предложений и поиска похожих:

Model              | Размер | Описание
-------------------|--------|----------
sentence-transformers | 768 | BERT-based для предложений
OpenAI text-embedding-3-small | 1536 | GPT-based
OpenAI text-embedding-3-large | 3072 | Более точный
Cohere embed-multilingual-v3 | 1024 | 100+ языков

Использование

from sentence_transformers import SentenceTransformer

model = SentenceTransformer('all-MiniLM-L6-v2')

sentences = [
    "Кошка сидит на ковре",
    "Кот находится на коврике",
    "Собака бегает по парку",
    "The cat sits on the mat"
]

embeddings = model.encode(sentences)

# Поиск похожих
from sklearn.metrics.pairwise import cosine_similarity

print(cosine_similarity([embeddings[0]], embeddings))
# [[0.92, 0.89, 0.23, 0.78]]
# "Кошка сидит на ковре" ближе к "Кот находится на коврике" (0.89)

Embeddings для RAG и векторных баз данных

Как embeddings используются в RAG

1. Индексация:
   Документ → Чанки → Embeddings → Векторная БД
   
2. Поиск:
   Запрос → Embedding → Поиск похожих → Контекст для LLM
   
3. Генерация:
   LLM + контекст → Ответ

Векторные БД

Система           | Тип        | Особенности
------------------|------------|---------------------------
FAISS             | ANN        | Быстрый, от Meta
Pinecone          | Managed    | Полностью управляемый
Weaviate          | Hybrid     | Векторы + графы + фильтры
Milvus            | Distributed| Масштабируемый
Chroma            | Local      | Простой, для разработки
Qdrant            | Hybrid     | Сэмплирование + фильтры

Cosine Similarity — метрика близости

cosine_similarity(a, b) = (a · b) / (||a|| × ||b||)

Результат: [-1, 1]
  1  = идентичные направления
  0  = ортогональные (независимые)
 -1  = противоположные направления

Пример:
  a = [1, 0, 0]
  b = [0.9, 0.1, 0]
  cosine = (0.9) / (1 × 1) = 0.9  ← очень похожи

Практические аспекты

Выбор размерности

Меньше размерность:
  + Быстрее вычисления
  + Меньше память
  - Меньше информации

Больше размерность:
  + Больше информации
  + Лучше для сложных задач
  - Медленнее, больше память

Нормализация embeddings

# L2 нормализация (для cosine similarity)
import torch

def normalize(embeddings):
    norms = torch.norm(embeddings, p=2, dim=-1, keepdim=True)
    return embeddings / norms

# После нормализации:
# ||embedding|| = 1
# cosine_similarity(a, b) = a · b (проецирование)

Квантование embeddings

Float32:  4 байта на измерение
Float16:  2 байта на измерение
Int8:     1 байт на измерение
Int4:     0.5 байта на измерение

Embedding 4096 dim:
  Float32:  16 KB
  Float16:  8 KB
  Int8:     4 KB
  Int4:     2 KB

Будущее embeddings

Мультимодальные embeddings

CLIP (OpenAI):
  Текст: "кошка на ковре" → [0.23, -0.45, ...] (768 dim)
  Изображение: [фото кошки] → [0.25, -0.43, ...] (768 dim)
  
  Cosine similarity = 0.92 ← текст и изображение близки!

DALL-E, Stable Diffusion:
  Текст → embedding → генерация изображения

Dynamic embeddings

Вместо фиксированных векторов — генерация на лету:

Традиционный подход:
  lookup[token_id] → fixed_vector

Dynamic подход:
  small_network(token_id, context) → adaptive_vector
  
→ Вектор адаптируется под контекст, но без полного transformer

Итоги

  • Embeddings — это векторные представления текста в многомерном пространстве
  • Word2Vec/GloVe — статические, один вектор на слово
  • BERT — контекстуальные, разные векторы для одного слова
  • В LLM embedding layer — это таблица поиска, до 20% параметров модели
  • Sentence embeddings используются в RAG и векторных БД
  • Cosine similarity — основная метрика близости
  • Мультимодальные embeddings объединяют текст, изображения, аудио
  • Квантование embeddings экономит память без значительной потери качества