Позиционные кодировки: зачем модели порядок (запускаемый пример)
«Кот поймал мышь» и «Мышь поймала кота» состоят из одних слов, но смысл разный. Этот урок — про то, как модель узнаёт порядок токенов.
Позиционная кодировка — это добавляемая к эмбеддингу токена информация о его месте в последовательности, без которой модель не различала бы порядок слов.
Внимание само по себе слепо к порядку
Заглянем чуть вперёд: механизм внимания (раздел 3) обрабатывает токены как множество, а не как упорядоченный список. Он смотрит, какие токены на какие похожи, но «не знает», какой шёл первым. Если бы мы просто перемешали слова, для голого внимания почти ничего бы не изменилось. А для языка порядок критичен: подлежащее и дополнение, отрицание, время — всё держится на позициях.
Решение: добавить позицию в вектор
Раз сам механизм порядок не видит, информацию о позиции вшивают прямо в векторы. К эмбеддингу каждого токена прибавляют (или присоединяют) вектор, зависящий от его позиции. Тогда «кот на позиции 1» и «кот на позиции 5» приходят в слой внимания уже разными — и модель может учитывать порядок.
Синусоидальные кодировки
В оригинальном трансформере позицию кодировали набором синусов и косинусов разной частоты. Для позиции pos и измерения i: чётные измерения — синус, нечётные — косинус, а частота плавно меняется по измерениям. Посчитаем такие векторы для первых позиций.
import math
# Синусоидальные позиционные кодировки (как в оригинальном трансформере).
# Для позиции pos и измерения i: чётные -> sin, нечётные -> cos.
d_model = 4 # размерность вектора
def positional_encoding(pos, d_model):
pe = []
for i in range(d_model):
k = i // 2
angle = pos / (10000 ** (2 * k / d_model))
pe.append(math.sin(angle) if i % 2 == 0 else math.cos(angle))
return pe
for pos in range(4):
vec = positional_encoding(pos, d_model)
nums = ", ".join(f"{v:+.3f}" for v in vec)
print(f"позиция {pos}: [{nums}]")
Вывод:
позиция 0: [+0.000, +1.000, +0.000, +1.000] позиция 1: [+0.841, +0.540, +0.010, +1.000] позиция 2: [+0.909, -0.416, +0.020, +1.000] позиция 3: [+0.141, -0.990, +0.030, +1.000]
Заметьте: у каждой позиции получается свой уникальный узор из чисел. Низкие измерения меняются быстро (различают соседние позиции), высокие — медленно (различают далёкие). Такая схема позволяет модели судить и об абсолютной позиции, и об относительных расстояниях между токенами — и работает даже для последовательностей длиннее тех, что встречались при обучении.
Современные варианты
Синусоиды — классика, но не единственный способ:
| Подход | Идея |
| Синусоидальные | фиксированные синусы/косинусы, добавляются к эмбеддингу |
| Обучаемые | вектор позиции — обучаемый параметр, как эмбеддинг |
| Относительные / RoPE | кодируют расстояние между токенами; помогают с длинным контекстом |
Современные большие модели чаще используют относительные кодировки (например, RoPE) — они лучше переносятся на длинные тексты. Но идея одна: без явной информации о позиции внимание было бы слепо к порядку слов.
Итог
- Механизм внимания сам по себе не различает порядок токенов — для него это множество.
- Позиционные кодировки вшивают информацию о месте токена прямо в его вектор.
- Синусоидальные кодировки дают каждой позиции уникальный узор разных частот.
- Современные модели часто применяют относительные кодировки (RoPE) для длинного контекста.