Как работает слой эмбеддингов: от текста к ID токенов и векторам в LLM
Понятное для новичков объяснение слоя эмбеддингов: токенизация, таблицы поиска, обучаемые векторы, пример на 205 миллионов параметров и короткое демо на PyTorch с nn.Embedding.
Введите в LLM фразу "I love cats" — и модель так и не увидит трёх английских слов. Нейросети работают с числами. Как же эта короткая фраза превращается в то, что может обработать трансформер? Ответ начинается со слоя эмбеддингов.
1. От текста к ID токенов
Сначала идёт токенизация. Токенизатор разбивает текст на кусочки, называемые токенами, и присваивает каждому ID. Для простоты возьмём вот такой крошечный словарь:
"I" → 10
"love" → 25
"cats" → 47
Тогда предложение превращается в короткий список целых чисел:
"I love cats"
↓
[10, 25, 47]
Это ID токенов, и сами по себе они ничего не значат. Число 47 не говорит, что "cats" стоят сорок семь чего-либо. Это лишь индекс, метка, указывающая на одну запись в словаре. Прежде чем модель сможет сделать что-то полезное, нужен ещё один шаг.
2. От ID токенов к эмбеддингам
Этот шаг выполняет слой эмбеддингов. Представьте его как большую таблицу, где на каждый токен словаря приходится одна строка — вектор.
Допустим, в словаре всего 50 токенов и каждый токен описывается 4 числами. Каждая строка — это вектор эмбеддинга:
Embedding vector
Начало таблицы может выглядеть так:
Token 0 → [ 0.2, -0.4, 0.7, 0.1 ]
Token 1 → [ 0.6, 0.1, -0.3, 0.8 ]
...
Token 10 → [ 0.3, -0.2, 0.9, 0.4 ]
...
Token 25 → [-0.5, 0.7, 0.2, -0.1 ]
...
Token 47 → [ 0.8, 0.1, -0.6, 0.5 ]
...
Теперь возьмём входную последовательность:
[10, 25, 47]
Слой просто находит эти строки. Токен 10 получает свой вектор:
10 → [ 0.3, -0.2, 0.9, 0.4 ]
а два других — точно так же:
25 → [-0.5, 0.7, 0.2, -0.1 ]47 → [ 0.8, 0.1, -0.6, 0.5 ]
Итак, исходный текст проходит такие этапы:
"I love cats"
↓ Tokenizer[10, 25, 47] ↓ Embedding lookup[
[ 0.3, -0.2, 0.9, 0.4 ],
[-0.5, 0.7, 0.2, -0.1 ],
[ 0.8, 0.1, -0.6, 0.5 ]
] ↓Transformer
Вот и вся работа вкратце: слой эмбеддингов превращает ID токенов в векторы.
3. Почему векторы, а не просто ID?
Можно спросить, почему трансформеру нельзя подать ID напрямую. Дело в том, что ID — всего лишь метки. Посмотрите на три значения:
10
25
47
Разница между 25 и 47 ничего не говорит о том, как "love" связано с "cats". Векторы эмбеддингов другие, потому что это обученные представления. Во время обучения модель подстраивает их значения, пока они не начнут помогать трансформеру работать с языком.
4. Откуда берутся числа
Никто не вписывает значения матрицы эмбеддингов вручную. Они выучиваются при обучении. В начале векторы содержат значения без всякой языковой информации. Затем модель читает огромные объёмы текста и тренируется предсказывать следующий токен. Получив промпт вроде "I love cats because they are ___", она делает догадку.
Если догадка неверна, ошибка идёт назад по сети через обратное распространение. Векторы эмбеддингов обновляются вместе со всеми остальными весами. После обучения в матрице лежат представления, на которые модель научилась опираться.
5. Что представляет эмбеддинг
Возьмём вектор для "cats":
cats → [0.8, 0.1, -0.6, 0.5]
Так и хочется прочитать каждое число как именованное свойство, примерно так:
0.8 → animal
0.1 → size
-0.6 → shape
0.5 → plural
В общем случае эмбеддинги LLM устроены иначе. Смысл распределён по всем измерениям сразу. Одно измерение редко соответствует отдельному понятию, которое человек мог бы назвать. Представлением является весь вектор целиком.
6. Настоящие матрицы эмбеддингов огромны
Теперь перейдём от игрушечного примера к реалистичной модели с такими размерами:
Vocabulary size = 50,000
Embedding dimension = 4,096
Тогда в матрице эмбеддингов около 205 миллионов параметров, а это много. То, что выглядит как простая таблица поиска, может вмещать удивительно большую долю весов модели.
7. Что происходит дальше
Когда "I love cats" превратилась в набор векторов эмбеддингов, они поступают в трансформер. Вот как идёт поток:
"I love cats"
│
▼
Tokenizer
│
▼
[10, 25, 47]
│
▼
Embedding Layer
│
▼
[vector, vector, vector]
│
▼
Transformer
│
▼
Next-token prediction
Эмбеддинг — это мост между дискретными токенами и непрерывными числовыми представлениями, с которыми работает нейросеть.
8. Та же идея на Python
Чтобы показать это, хватит нескольких строк на PyTorch:
import torch
import torch.nn as nn
# Vocabulary contains 50 tokens
# Each token is represented by 4 numbers
embedding = nn.Embedding(
num_embeddings=50,
embedding_dim=4
)
# Token IDs for "I love cats"
tokens = torch.tensor([10, 25, 47])
# Look up their embeddings
vectors = embedding(tokens)
print(vectors)
print(vectors.shape)
Выведенный результат выглядит примерно так:
tensor([
[ 0.31, -0.21, 0.91, 0.42],
[-0.52, 0.73, 0.19, -0.12],
[ 0.79, 0.08, -0.61, 0.54]
])
А форма подтверждает: три токена по четыре числа:
torch.Size([3, 4])
По сути, слой эмбеддингов — это обучаемая таблица поиска. Если собрать всё вместе, конвейер выглядит так:
Text
↓
Tokenizer
↓
Token IDs
↓
Embedding lookup
↓
Vectors
Transformer
Токенизатор определяет, какие токены присутствуют. Слой эмбеддингов превращает их ID в обученные числовые представления. Трансформер берёт эти представления, разбирается в контексте и делает предсказания.
В следующий раз, встретив в определении модели такую строку:
nn.Embedding(vocab_size, embedding_dim)
читайте её как указание построить таблицу из vocab_size строк, где каждый токен получает собственный обучаемый вектор длиной embedding_dim.