Эмбеддинги токенов: вектор на токен (запускаемая близость)
Токены — это номера, но модели нужны числа со смыслом. Этот урок про эмбеддинги: как каждому токену сопоставляют вектор, в котором близость означает похожесть смысла.
Эмбеддинг (embedding) — это обучаемый вектор чисел, сопоставленный токену; близкие по смыслу токены получают близкие векторы.
Сначала наивно: one-hot
Самый прямой способ превратить токен в числа — one-hot вектор: длина = размер словаря, на месте нужного токена единица, везде нули.
vocab = ["кот", "пёс", "молоко", "лает", "мяукает"]
def one_hot(word):
return [1 if w == word else 0 for w in vocab]
print("One-hot векторы (по одному на токен словаря):")
for w in vocab:
print(f" {w:9} -> {one_hot(w)}")
print()
print("Проблема: размер вектора = размер словаря.")
print("Для словаря в 50000 токенов это вектор из 50000 нулей и одной единицы.")
print("И любые два слова одинаково 'далеки' — близости смыслов нет.")
Вывод:
One-hot векторы (по одному на токен словаря): кот -> [1, 0, 0, 0, 0] пёс -> [0, 1, 0, 0, 0] молоко -> [0, 0, 1, 0, 0] лает -> [0, 0, 0, 1, 0] мяукает -> [0, 0, 0, 0, 1] Проблема: размер вектора = размер словаря. Для словаря в 50000 токенов это вектор из 50000 нулей и одной единицы. И любые два слова одинаково 'далеки' — близости смыслов нет.
Проблем две. Во-первых, для словаря в 50 000 токенов это вектор из 50 000 чисел — расточительно. Во-вторых, и это важнее, любые два разных токена в one-hot одинаково «далеки» друг от друга: «кот» и «кошка» так же не похожи, как «кот» и «молоко». Никакой информации о смысле здесь нет.
Эмбеддинги: плотные векторы со смыслом
Вместо этого каждому токену сопоставляют плотный вектор из нескольких сотен чисел (например, 768). Эти числа не задаются вручную — они обучаются вместе со всей моделью. В процессе обучения векторы выстраиваются так, что токены, употребляемые в похожих контекстах, оказываются рядом в пространстве. «Кот» и «кошка» съезжаются близко, «банан» — далеко.
Технически это просто таблица: матрица размером «словарь × размерность». Номер токена — это номер строки, а строка — его эмбеддинг. Превращение токена в вектор — это поиск строки в таблице.
Как измерить «близость»
Похожесть двух векторов чаще всего меряют косинусной близостью — косинусом угла между ними. Значение от −1 до 1: ближе к 1 — векторы смотрят в одну сторону (похожи), около 0 — не связаны. Зададим игрушечные эмбеддинги вручную и посчитаем близость.
import math
# Игрушечные эмбеддинги: каждому слову вручную задан вектор из 3 чисел.
# Близкие по смыслу слова мы намеренно поместили рядом в пространстве.
embeddings = {
"кот": [0.9, 0.1, 0.2],
"кошка": [0.8, 0.2, 0.1],
"собака": [0.7, 0.3, 0.2],
"банан": [0.1, 0.9, 0.8],
"яблоко": [0.2, 0.8, 0.9],
}
def dot(a, b):
return sum(x * y for x, y in zip(a, b))
def cosine(a, b):
return dot(a, b) / (math.sqrt(dot(a, a)) * math.sqrt(dot(b, b)))
target = "кот"
print(f"Близость к слову '{target}':")
scores = []
for word, vec in embeddings.items():
if word == target:
continue
scores.append((word, cosine(embeddings[target], vec)))
for word, score in sorted(scores, key=lambda p: p[1], reverse=True):
print(f" {word:7} {score:.3f}")
Вывод:
Близость к слову 'кот': кошка 0.987 собака 0.959 яблоко 0.389 банан 0.303
«Кот» оказался ближе всего к «кошке» и «собаке» (животные), а «банан» и «яблоко» — далеко. В настоящей модели эти векторы никто не задаёт руками: они сами выстраиваются за время обучения так, чтобы помогать предсказывать следующий токен.
Важная оговорка
Эмбеддинг токена из таблицы — статичный: один и тот же токен «банк» получает одинаковый стартовый вектор в «банк реки» и «денежный банк». Различать смыслы по контексту — задача следующих слоёв (внимания), которые «дорабатывают» эти векторы. Об этом — раздел про attention.
Итог
- One-hot — расточителен и не несёт смысла: все токены одинаково «далеки».
- Эмбеддинг — плотный обучаемый вектор; похожие по смыслу токены оказываются рядом.
- Близость векторов измеряют косинусной близостью (косинусом угла).
- Эмбеддинг из таблицы статичен; уточняют смысл по контексту уже слои внимания.