Кодировщик трансформера
Кодировщик модели трансформера (transformer) [1] принимает на вход эмбеддингов элементов входной последовательности и выдаёт уточнённых эмбеддингов для каждого элемента с учётом его контекста (других элементов последовательности).
Кодировщик состоит от последовательных применений блоков кодировщика, каждый раз - со своими параметрами. В оригинальной статье [1] бралось .
Блок кодировщика
Схема одного блока кодировщика представлена ниже [2] для перевода входного предложения "Thinking machines":
Первый блок кодировщика принимает -мерные эмбеддинги каждого токена входной последовательности, прибавляет к ним эмбеддинги позиционного кодирования, после чего выдаёт такое же количество -мерных выходных эмбеддингов, но которые в результате некоторых преобразований уже учитывают контекст всей входной последовательности (всего переводимого предложения). В статье берётся .
Поскольку в трансформере предлагается наслаивать такие блоки раз, каждый из блоков будет всё больше уточнять эмбеддинги по контексту, пока последний блок не выдаст итоговые эмбеддинги, с которыми впоследствии будет работать декодировщик модели, состоящий из отдельных блоков декодировщика.
Считается, что сети достаточно передать информацию о позициях токенов в последовательности только один раз, поэтому позиционные эмбеддинги прибавляются к эмбеддингам входных токенах только для первого блока декодировщика. Последующие блоки работают только с выходными эмбеддингам предшествующих блоков в неизменном виде.
Каждый блок кодировщика имеет свои параметры, но функционально устроен единообразно:
-
каждый входной эмбеддинг преобразуется через блок самовнимания (self-attention) в выходной эмбеддинг;
-
входной и выходной эмбеддинги суммируются, как показано пунктиром на схеме;
-
суммарный эмбеддинг пропускается через послойную нормализацию (LayerNorm);
-
результирующий эмбеддинг преобразуется двухслойным персептроном (Feed Forward) в выходной;
-
затем опять входной и выходной эмбеддинги (в контексте предыдущего шага) сумми руются, как показано пунктиром на схеме;
-
суммарный эмбеддинг снова пропускается через послойную нормализацию (LayerNorm).
Для каждого из блоков декодировщика этапы 1-6 применяются к каждому эмбеддингу входной последовательности независимо с одинаковыми весами. При этом веса внутри каждого блока свои. Рассмотрим подробнее каждый этап.
Сумма входного и выходного эмбеддингов
Суммирование входа с выходом, как и в модели ResNet, мотивировано тем, что
-
градиент при обучении проще доходит от функции потерь к более ранним слоям, что упрощ ает настройку более ранних слоёв и ускоряет обучение всей сети;
-
в глубоких сетях целесообразно наслаивать слои, оставляя возможность модели оставить всё "как есть" (что обеспечивает тождественная связь), а при необходимости вносить лишь небольшие уточнения (нелинейным блоком, веса которого инициализируются малыми числами).
Feed Forward
Feed Forward преобразование представляет двухслойный персептрон с активацией ReLU:
где
-
- входной эмбеддинг токена,
-
- его нелинейно преобразованная версия той же размерности,
-
- обучаемые матрицы,
-
- обучаемые вектора смещений.
Этот блок позволяет модели настраивать сложные зависимости и строить более богатые признаковые представления.
Self-attention
Блок самовнимания (self-attention) призван об огатить эмбеддинг каждого токена информацией о других токенах последовательности. Поскольку каждый токен при этом использует информацию со всех других токенов, то схематично это можно представить в виде:
Рассмотрим детально, как работает блок самовнимания. Для для большей ясности изложения будем писать сбоку внизу размерности векторов, матриц и производимых над ними операций.
Пусть мы обрабатываем последовательность длины и каждый токен этой последовательности представляется -мерным эмбеддингом. Тогда входную последовательность можно представить в виде матрицы .
Используется механизм внимания (attention mechanism), в котором по эмбеддингу каждого токена генерируется
-
-мерный запрос
-
-мерный ключ
-
-мерное значение
В статье [1] , поскольку самовнимание впоследствии повторялось 8 раз.
Объединяя для каждого токена последовательности вектора-строки их запросов, ключей и значений, получим
-
матрицу запросов ,
-
матрицу ключей ,
-
матрицу значений .
Тогда выходной эмбеддинг вычисляется агрегацией значений (values) для всех токенов последовательности:
Агрегация производится суммированием значений с весами, пропорциональным похожести ключа соответствующему запросу, которое вычисляется методом scaled dot-product attention.
Матричная запись
Для ускорения вычислений, они производятся для всех токенов одновременно, используя матричную запись:
Генерируются матрицы
– запросов (queries):
– ключей (keys):
– значений (values):
что графически показано ниже [2]:
Результат самовнимания для всех токенов запишется как
что графически можно изобразить как [2]:
Объединяя все операции, одна головка самовнимания (self-attention head) работает следующим образом:
Сравнение с рекуррентной сетью
Сравним блок самовнимания трансформера с рекуррентной сетью. Пуст ь lkz для простоты обрабатываются -мерные эмбеддинги, размерность скрытого состояния рекуррентной сети совпадает с размерностями эмбеддингов самовнимания и тоже равна .
Для суммаризации информации о -мерных эмбеддингах последовательности длины рекуррентной сети требуется операций, в то время как модулю самовнимания - (обоснуйте!). Таким образом, сложность вычислений трансформера существеннее зависит от длины обрабатываемой последовательности.
Для сбора информации для эмбеддинга с другого эмбеддинга рекуррентной сети требуется порядка проходов по последовательности (а с каждой итерацией часть информации теряется!), в то время как самовнимание считывает эту информацию напрямую за .
Рекуррентная сеть хранит историю в -мерном внутреннем состоянии, а трансформеру требуется хранить все эмбеддингов (-мерных), в связи с чем у него увеличенное потребление памяти.
Multi-head self-attention
В трансформере [1] используется не одна, а 8 головок самовнимания, каждая - со своими весами . Каждая головка призвана агрегировать информацию исходя их собственных принципов, что и наблюдается на практике. После применения головок самовнимания их результаты конкатенируются, после чего пропускаются через линейное преобразование c матрицей , чтобы вернуть размерность эмбеддинга к исходному -мерному вектору:
Графически это выглядит следующим образом [2]:
Весь процесс совместно можно визуализировать как [2]:
Итог
Подытоживая, блок кодировщика уточняет начальные эмбеддинги каждого токена входной последовательности, пропуская их через нелинейные преобразования Feed Forward и агрегируя информацию с эмбеддингов других токенов последовательности, используя self-attention. Для удобства настройки, блоки самовнимания и Feed Forward реализуются как остаточные блоки.
Всего в трансформере [1] использовано 6 таких блоков, работающих последовательно.
Первый блок кодировщика обрабатывает эмбеддинги входных токенов в сумме с эмбеддингами, кодирующими расположение этих токенов в последовательности (позиционное кодирование). Последующие блоки кодировщика обрабатывают выходные эмбеддинги предыдущего блока в неизменном виде.
В следующей главе мы рассмотрим, как устроено позиционное кодирование.