Embedding Models: как LLM понимает смысл слов
opensourceaillmembeddingsvectorit
Введение: от слов к числам
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 и многого другого.
Ключевые выводы:
- Word embeddings (Word2Vec) → contextual (BERT) → sentence (SBERT)
- Размерность 768 — хороший баланс
- L2 normalization + cosine similarity — стандарт
- Локальные модели работают отлично для большинства задач
- Batch processing критичен для production
Ресурсы: