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

opensourceaillmembeddingsvectorit
← Back to Blog

Введение: от слов к числам

Embedding — это способ представить текст (слово, предложение, документ) как вектор чисел. Этот вектор захватывает смысл текста.

Факт: В embedding пространстве слова "король" - "мужчина" + "женщина" ≈ "королева".


Что такое Embedding?

Проблема

LLM работает с числами, а не словами.

Традиционный подход:
  Слово "король" → индекс 4829 → One-hot vector [0,0,1,0,...0]
  ✗ Вектор размерности 50000+
  ✗ Нет семантики
  ✗ "Король" и "королёк" не связаны

Embedding подход:
  Слово "король" → Dense vector [0.23, -0.45, 1.12, ...]
  ✓ Вектор размерности 768-4096
  ✓ Семантика захвачена
  ✓ "Король" и "королёк" близки

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

2D embedding пространство:

  "король" ●
           |
           |
  "королева" ●
           |
           |
  "принц" ●------● "принцесса"
           |
           |
  "мужчина" ●------● "женщина"
           |
           |
  "королёк" ●

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

Уровень 1: Word Embeddings

Word2Vec

Word2Vec (2013) — первый популярный метод word embeddings.

Два подхода:
1. CBOW (Continuous Bag of Words):
   Контекст → целевое слово
   
   "король сидит на [троне]" → "трон"

2. Skip-gram:
   Целевое слово → контекст
   
   "трон" → ["король", "сидит", "на"]

Word2Vec Example

from gensim.models import Word2Vec

# Обучение
sentences = [["король", "сидит", "на", "троне"],
             ["королева", "сидит", "на", "троне"],
             ["принц", "ждет", "принцессу"]]

model = Word2Vec(
    sentences=sentences,
    vector_size=100,  # размерность embedding
    window=5,
    min_count=1,
    workers=4
)

# Получение embedding
king_vector = model.wv['король']
# [0.234, -0.567, 1.234, ...]

# Аналогия
result = model.wv.most_similar(
    positive=['король', 'женщина'],
    negative=['мужчина'],
    topn=1
)
# [('королева', 0.89)]

GloVe

GloVe (Global Vectors, 2014) — матричная факторизация.

Матрица co-occurrence:
        король королева принц
король    0      5     2
королева   5      0     4
принц     2      3     0

GloVe факторизует эту матрицу в embeddings.

Уровень 2: Contextual Embeddings

Проблема Word2Vec

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

"банк" реки → [0.1, 0.2, 0.3, ...]
"банк" финансовый → [0.1, 0.2, 0.3, ...]
✗ Одинаковый вектор для разных значений

Contextual embeddings:
"банк" реки → [0.1, 0.5, 0.3, ...]
"банк" финансовый → [0.8, 0.2, 0.9, ...]
✓ Разные векторы для разных контекстов

BERT Embeddings

BERT (2018) — contextual word embeddings.

Input: [CLS] банк [SEP]
        [CLS] он [SEP]

BERT → для каждого токена свой embedding:

"банк" в "банк реки": [0.1, 0.5, 0.3, ...]
"банк" в "банк финансовый": [0.8, 0.2, 0.9, ...]

12/24 layers → можно взять embedding с любого слоя.

BERT Example

from transformers import BertTokenizer, BertModel
import torch

tokenizer = BertTokenizer.from_pretrained('bert-base-multilingual-cased')
model = BertModel.from_pretrained('bert-base-multilingual-cased')

text = "банк реки красивый"
inputs = tokenizer(text, return_tensors='pt')

with torch.no_grad():
    outputs = model(**inputs)

# last_hidden_state: [1, seq_len, 768]
last_hidden = outputs.last_hidden_state

# Embedding для каждого токена
token_embeddings = last_hidden[0]
# shape: [seq_len, 768]

Уровень 3: Sentence Embeddings

Sentence-BERT

Sentence-BERT (2019) — BERT для предложений.

Проблема: усреднение word embeddings не дает хорошего sentence embedding.

Решение: twin BERT network с training на similarity tasks.

Input: "Кот сидит на коврике"
       → Sentence-BERT → [0.23, -0.45, 1.12, ...] (768-dim)

Input: "Кот сидит на подушке"
       → Sentence-BERT → [0.21, -0.43, 1.10, ...] (768-dim)

Similarity: cosine([0.23, -0.45, 1.12], [0.21, -0.43, 1.10]) = 0.97

Sentence-BERT Example

from sentence_transformers import SentenceTransformer, util

model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')

sentences = [
    "Кот сидит на коврике",
    "Кот сидит на подушке",
    "Собака играет во дворе"
]

embeddings = model.encode(sentences)
# shape: [3, 768]

# Cosine similarity
similarity = util.cos_sim(embeddings[0], embeddings[1])
# tensor(0.9234)

Уровень 4: Modern Embedding Models

E5 (Embedding, 2022)

E5 от Meta:
- "text_embedding: {text}"
- Обучен на 1B+ парах
- Поддерживает разные языки

E5-base: 220M parameters, 768-dim
E5-large: 335M parameters, 1024-dim

Пример:
  Input: "text_embedding: что такое LLM?"
  Output: [0.12, -0.34, 0.56, ...] (1024-dim)

GTE (General Text Embedding, 2023)

GTE от Alibaba:
- Базирован на BERT architecture
- 330M parameters
- 768-dim embedding

GTE-base:
  - Быстрый
  - Хороший для general tasks
  
GTE-large:
  - Точный
  - Лучше для retrieval

Snowflake Arctic Embedding

Snowflake Arctic Embedding Models:
- 27M parameters (very lightweight!)
- 1024-dim embedding
- Отлично для CPU inference

# Размер модели: ~100MB
# Inference: <1ms на предложение (CPU)

Уровень 5: Embedding Dimensions

Размерность и качество

Размерность | Точность | Скорость | Память
-----------|----------|----------|--------
128        | 75%      | 100%     | 1x
256        | 85%      | 80%      | 2x
512        | 90%      | 60%      | 4x
768        | 93%      | 50%      | 6x
1024       | 95%      | 40%      | 8x
3072       | 97%      | 20%      | 24x

Выбор: 768 — золотая середина

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

# Trade-off analysis
dimensions = [128, 256, 512, 768, 1024]
accuracies = [0.75, 0.85, 0.90, 0.93, 0.95]
latencies = [1.0, 1.2, 1.5, 2.0, 3.0]  # ms

# Для retrieval: 768 достаточно
# Для classification: 1024 лучше
# Для edge devices: 128-256

Уровень 6: Normalization

L2 Normalization

L2 normalization: ||v|| = 1

v = [3, 4]
||v|| = sqrt(9 + 16) = 5
v_normalized = [3/5, 4/5] = [0.6, 0.8]

Зачем?
- Cosine similarity = dot product normalized vectors
- Упрощает вычисления
- Стабилизирует training

Cosine Similarity

cosine(A, B) = (A · B) / (||A|| * ||B||)

A = [1, 0, 1]
B = [1, 0, 1]
cosine = (1+0+1) / (sqrt(2) * sqrt(2)) = 2/2 = 1.0  ✓

A = [1, 0, 1]
B = [-1, 0, -1]
cosine = (-1+0-1) / (sqrt(2) * sqrt(2)) = -2/2 = -1.0  ✗

A = [1, 0, 1]
B = [0, 1, 0]
cosine = (0+0+0) / (sqrt(2) * sqrt(1)) = 0  ⊥

Normalization Impact

Без normalization:
  Similarity range: [-5, 5]
  Зависит от magnitude

С L2 normalization:
  Similarity range: [-1, 1]
  Стандартный scale
  Cosine = dot product

Уровень 7: Evaluation

Retrieval Evaluation

Metrics:
- MRR (Mean Reciprocal Rank): средний обратный ранг
- NDCG (Normalized DCG): качество ранжирования
- Recall@K: найден ли relevant документ среди top-K

E5-base на MS MARCO:
  MRR@10: 38.5
  NDCG@10: 45.2
  
GTE-base на MS MARCO:
  MRR@10: 39.1
  NDCG@10: 46.0

Clustering Evaluation

Clustering quality:
- Silhouette Score: [-1, 1] (лучше = больше)
- Davies-Bouldin Index: [0, ∞) (лучше = меньше)

Embedding quality → clustering quality
Лучшие embeddings → лучшие кластеры

Уровень 8: Local Embedding Models

Ollama for Embeddings

import ollama

# Получение embedding через Ollama
response = ollama.embeddings(
    model='nomic-embed-text',
    input='Что такое LLM?'
)

# embedding: list[float] (768-dim)
print(response['embedding'][:10])
# [0.123, -0.456, 0.789, ...]

Hugging Face Transformers

from sentence_transformers import SentenceTransformer

# Локальная модель
model = SentenceTransformer('sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2')

# Batch encoding
texts = [
    "Документ 1",
    "Документ 2",
    "Документ 3"
]

embeddings = model.encode(texts, batch_size=32)
# shape: [3, 384]

# Сохранение
model.save('./local-embedding-model')

ONNX Embeddings

from onnxruntime import InferenceSession

# ONNX модель — быстрая на CPU
session = InferenceSession('embedding-model.onnx')

inputs = tokenizer(texts, return_tensors='onnx')
outputs = session.run(None, inputs)

embeddings = outputs[0]
# shape: [batch_size, 768]
# Inference: ~0.5ms per sentence (CPU)

Уровень 9: Applications

Semantic Search

# Semantic Search Pipeline
class SemanticSearch:
    def __init__(self, model_name: str):
        self.model = SentenceTransformer(model_name)
        self.documents = []
        self.embeddings = None
    
    def add_documents(self, docs: list[str]):
        self.documents = docs
        self.embeddings = self.model.encode(docs)
    
    def search(self, query: str, top_k: int = 5) -> list[tuple[str, float]]:
        query_embedding = self.model.encode(query)
        similarities = util.cos_sim(query_embedding, self.embeddings)[0]
        top_indices = similarities.topk(top_k).indices
        
        return [
            (self.documents[i], similarities[i].item())
            for i in top_indices
        ]

# Usage
search = SemanticSearch('paraphrase-multilingual-MiniLM-L12-v2')
search.add_documents(documents)
results = search.search("что такое машинное обучение")

Duplicate Detection

def find_duplicates(texts: list[str], threshold: float = 0.95):
    """Найти дубликаты по similarity threshold"""
    embeddings = model.encode(texts)
    duplicates = []
    
    for i in range(len(texts)):
        for j in range(i+1, len(texts)):
            sim = util.cos_sim(embeddings[i], embeddings[j]).item()
            if sim > threshold:
                duplicates.append((i, j, sim))
    
    return duplicates

Recommendation

def recommend_items(user_embedding, item_embeddings, top_k=10):
    """Рекомендация по user embedding"""
    scores = util.cos_sim(user_embedding, item_embeddings)[0]
    top_indices = scores.topk(top_k).indices
    return top_indices

Уровень 10: Best Practices

Model Selection

Выбор модели:

Для русского языка:
  - paraphrase-multilingual-MiniLM-L12-v2
  - sentence-transformers/paraphrase-multilingual-mpnet-base-v2
  - BAAI/bge-m3

Для английского:
  - sentence-transformers/all-MiniLM-L6-v2
  - E5-base
  - GTE-large

Для edge:
  - Snowflake Arctic Embedding (27M params)
  - MiniLM (22M params)

Production Tips

Production Embedding Pipeline:
1. Batch encoding (не по одному!)
2. Кэширование embeddings
3. ONNX для CPU inference
4. Normalization при сохранении
5.定期 re-evaluation accuracy

Заключение

Embedding models — фундамент для semantic search, retrieval и многого другого.

Ключевые выводы:

  1. Word embeddings (Word2Vec) → contextual (BERT) → sentence (SBERT)
  2. Размерность 768 — хороший баланс
  3. L2 normalization + cosine similarity — стандарт
  4. Локальные модели работают отлично для большинства задач
  5. Batch processing критичен для production

Ресурсы: