Трансформер — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
(Написал статью про трансформеры)
 
м (rollbackEdits.php mass rollback)
 
(не показано 18 промежуточных версий 2 участников)
Строка 2: Строка 2:
  
 
==Архитектура трансформера==
 
==Архитектура трансформера==
[[Файл:TransformerSimpleArchitecture.png|350px|thumb|right|Архитектура трансформера]]
+
[[Файл:TransformerSimpleArchitecture.png|400px|thumb|right|Архитектура трансформера<ref>https://jalammar.github.io/illustrated-transformer/</ref>]]
Устройство трансформера состоит из кодирующего и декодирующего компонентов. На вход принимается некая последовательность, создается ее [[:Векторное_представление_слов|векторное представление]] (англ. ''embedding''), векторизованная последовательность поступает в кодирующий компонент, а затем декодирующий компонент получает на вход часть этой последовательности и выход кодирующего. В результате получается новая выходная последовательность.
+
Устройство трансформера состоит из кодирующего и декодирующего компонентов. На вход принимается некая последовательность, создается ее [[:Векторное_представление_слов|векторное представление]] (англ. ''embedding''), прибавляется вектор позиционного кодирования, после чего набор элементов без учета порядка в последовательности поступает в кодирующий компонент (параллельная обработка), а затем декодирующий компонент получает на вход часть этой последовательности и выход кодирующего. В результате получается новая выходная последовательность.
 
 
Кодирующий компонент – это стек кодировщиков (англ. ''encoders''), а декодирующий компонент – это стек декодировщиков (англ. ''decoders''). Каждый кодировщики последовательно передает результат своей работы следующему кодировщику на вход. Декодировщики последовательно передают друг другу на вход результат работы вместе с результатом кодирующего компонента.  
 
  
 +
Внутри кодирующего и декодирующего компонента нет рекуррентности. Кодирующий компонент состоит из кодировщиков, которые повторяются несколько раз, аналогично устроен декодирующий компонент. Трансформер {{---}} это поставленные друг за другом модели внимания, которые позволяют исходную последовательность векторов перевести в новую последовательность векторов, которые кодируют информацию о контексте каждого элемента. Трансформер-кодировщик переводит исходные векторы в скрытые, которые правильно сохраняют в себе информацию о контексте каждого элемента. Далее трансформер-декодировщик декодирует результат кодировщика в новую последовательность, которая состоит из эмбедингов элементов выходного языка. После по эмбедингам генерируются сами итоговые элементы с помощью вероятностной языковой модели.
 +
 
 
Ниже рассмотрим архитектуру кодировщика и декодировщика подробнее.
 
Ниже рассмотрим архитектуру кодировщика и декодировщика подробнее.
  
 
==Архитектура трансформера-кодировщика==
 
==Архитектура трансформера-кодировщика==
[[Файл:TransformerEncoderArchitecture.png|150px|thumb|left|Архитектура трансформера-кодировщика]]
+
[[Файл:TransformerEncoderArchitecture.png|150px|thumb|left|Архитектура трансформера-кодировщика<ref>https://arxiv.org/abs/1706.03762</ref>]]
Рассмотрим последовательно шаг за шагом этапы кодировщика:
+
Рассмотрим последовательно шаг за шагом этапы работы кодировщика:
  
 
1. На вход поступает последовательность элементов <math>w_i</math>, по ней создается последовательность эмбедингов, где каждый <math>x_i</math> это векторное представление элемента <math>w_i</math>.
 
1. На вход поступает последовательность элементов <math>w_i</math>, по ней создается последовательность эмбедингов, где каждый <math>x_i</math> это векторное представление элемента <math>w_i</math>.
Строка 19: Строка 19:
  
 
3. Полученный вектор <math>h_i</math> подается на вход в блок многомерного самовнимания (англ. ''multi-headed self-attention'').  
 
3. Полученный вектор <math>h_i</math> подается на вход в блок многомерного самовнимания (англ. ''multi-headed self-attention'').  
<math>h^j_i = Attn(W^j_q h_i, W^j_k H, W^j_v H)</math>, где <math>W</math> - обучаемые матрицы, <math>W_q</math> для запроса, <math>W_k</math> для ключа, <math>W_v</math> для значения. Подробное объяснения работы механизма self-attention будет разобрано ниже.  
+
<math>h^j_i = \mathrm{Attn}(Q^j h_i, K^j H, V^j H)</math>, где обучаемые матрицы: <math>Q</math> для запроса, <math>K</math> для ключа, <math>V</math> для значения. Подробное объяснения работы механизма self-attention будет разобрано ниже.  
  
 
4. Затем необходима конкатенация, чтобы вернуться в исходную размерность: <math> h'_i = M H_j (h^j_i) = [h^1_i...h^J_i] </math>
 
4. Затем необходима конкатенация, чтобы вернуться в исходную размерность: <math> h'_i = M H_j (h^j_i) = [h^1_i...h^J_i] </math>
  
5. Добавим сквозные связи (англ. ''skip connection'') - по факту просто добавление из входного вектора к выходному (<math>h'_i + h_i</math>). После делаем нормировку уровня (англ. ''layer normalization''): <math>h''_i = LN(h'_i + h_i; \mu_1, \sigma_1)</math>. У нее два обучаемых параметра, для каждой размерности вектора вычисляется среднее и дисперсия.  
+
5. Добавим сквозные связи (англ. ''skip connection'') {{---}} по факту просто добавление из входного вектора к выходному (<math>h'_i + h_i</math>). После делаем нормализацию слоя (англ. ''layer normalization''): <math>h''_i = \mathrm{LN}(h'_i + h_i; \mu_1, \sigma_1)</math>. У нее два обучаемых параметра, для каждой размерности вектора вычисляется среднее и дисперсия.  
  
6. Теперь добавим преобразование, которое будет обучаемым - полносвязную 2х-слойную нейронную сеть:  
+
6. Теперь добавим преобразование, которое будет обучаемым {{---}} полносвязную двухслойную нейронную сеть:  
<math> h'''_i = W_2 ReLU (W_1 h''_i + b_1) + b_2 </math>   
+
<math> h'''_i = W_2 \mathrm{ReLU} (W_1 h''_i + b_1) + b_2 </math>   
  
7. Повторим пункт 5 еще раз: добавим сквозную связь и нормировку уровня: <math>z_i = LN(h'''_i + h''_i; \mu_2, \sigma_2)</math>
+
7. Повторим пункт 5 еще раз: добавим сквозную связь и нормализацию слоя: <math>z_i = \mathrm{LN}(h'''_i + h''_i; \mu_2, \sigma_2)</math>
  
После, в кодирующем компоненте пункты кодировщика 3-7 повторяются еще несколько раз, преобразовывая друг за другом из контекста контекст. Тем самым мы обогащаем модель и увеличиваем в ней количество параметров.
+
После, в кодирующем компоненте пункты кодировщика 3--7 повторяются еще несколько раз, преобразовывая друг за другом из контекста контекст. Тем самым мы обогащаем модель и увеличиваем в ней количество параметров.
  
  
Строка 36: Строка 36:
  
 
===Позиционное кодирование===
 
===Позиционное кодирование===
[[Файл:PositionalEncoding.png|450px|thumb|right|Визуализация работы позиционного кодирования]]
+
[[Файл:PositionalEncodingNew.png|400px|thumb|right|Визуализация работы позиционного кодирования<ref>https://kazemnejad.com/blog/transformer_architecture_positional_encoding/</ref>]]
Позиционное кодирование (англ. ''positional encoding'') - позволяет модели получить информацию о порядке элементов в последовательности путем прибавления специальных меток к вектору входных элементов. Позиции элементов <math>i</math> кодируются векторами <math>p_i</math>, <math>i = 1, 2, ..., n</math>, так, что чем больше <math>|i - j|</math>, тем больше <math>||p_i - p_j||</math>, и <math>n</math> не ограничено:
+
Так как в архитектуре трансформер обработка последовательности заменяется на обработку множества мы теряем информацию о порядке элементов последовательности. Чтобы отобразить информацию о позиции элемента в исходной последовательности мы используем позиционное кодирование.
<math>p_{(i, s)} = \sin(i \cdot 10000^{\frac{-2s}{d_{model}}})</math>,
+
<math>p_{(i, s + \frac{d}{2})} = \cos(i \cdot 10000^{\frac{-2s}{d_{model}}})</math>  
+
Позиционное кодирование (англ. ''positional encoding'') {{---}} позволяет модели получить информацию о порядке элементов в последовательности путем прибавления специальных меток к вектору входных элементов. Позиции элементов <math>i</math> кодируются векторами <math>p_i</math>, <math>i = 1, 2, ..., n</math>, так, что чем больше <math>|i - j|</math>, тем больше <math>||p_i - p_j||</math>, и <math>n</math> не ограничено. Пример такого кодирования:
 +
 
 +
<math>
 +
p_{(i, s)} =
 +
  \begin{cases}
 +
    \sin \left(i \cdot 10000^{\frac{-2k}{d_{model}}}\right)       & \quad \text{если } s=2k\\
 +
    \cos \left(i \cdot 10000^{\frac{-2k}{d_{model}}}\right)       & \quad \text{если } s=2k+1
 +
  \end{cases}
 +
</math>
  
 
===Self-attention===
 
===Self-attention===
'''Self-Attention''' {{---}} разновидность [[:Механизм_внимания|механизма внимания]], задачей которой является выявление закономерности между входными данными.  
+
'''Self-Attention''' {{---}} разновидность [[:Механизм_внимания|механизма внимания]], задачей которой является выявление закономерности между входными данными.
  
Будем для каждого элемента <math>x_i</math> обучать три вектора:
+
Будем для каждого элемента <math>x_i</math> получать обучаемым преобразованием три вектора:
  
* Запрос (''query'') <math>q_i = W_Q x_i</math>
+
* Запрос (''query'') <math>q_i = Q x_i</math>
* Ключ (''key'') <math>k_i = W_K x_i</math>
+
* Ключ (''key'') <math>k_i = K x_i</math>
* Значение (''value'') <math>v_i = W_V x_i</math>
+
* Значение (''value'') <math>v_i = V x_i</math>
  
Векторы <math>q_i</math> и <math>k_i</math> будем использовать, что посчитать важность элемента <math>x_j</math> для элемента <math>x_i</math>. Чтобы понять, насколько для пересчета вектора элемента <math>x_i</math> важен элемент <math>x_j</math> мы берем <math>k_j</math> (вектор ключа элемента <math>x_j</math>) и умножаем на <math>q_i</math> (вектор запроса элемента <math>x_i</math>). Так мы скалярно перемножаем вектор запроса на все векторы ключей, тем самым понимаем, насколько каждый входной элемент нам нужен, чтобы пересчитать вектор элемента <math>x_i</math>.
+
Векторы <math>q_i</math> и <math>k_i</math> будем использовать, чтобы посчитать важность элемента <math>x_j</math> для элемента <math>x_i</math>. Чтобы понять, насколько для пересчета вектора элемента <math>x_i</math> важен элемент <math>x_j</math> мы берем <math>k_j</math> (вектор ключа элемента <math>x_j</math>) и умножаем на <math>q_i</math> (вектор запроса элемента <math>x_i</math>). Так мы скалярно перемножаем вектор запроса на все векторы ключей, тем самым понимаем, насколько каждый входной элемент нам нужен, чтобы пересчитать вектор элемента <math>x_i</math>.
  
Далее считаем важность влияния элемента <math>x_j</math> для кодирования элемента <math>x_i</math>:
+
Далее считаем важность элемента <math>x_j</math> для кодирования элемента <math>x_i</math>:
 
<math>w_{ji}=\frac{
 
<math>w_{ji}=\frac{
\exp(\frac{\langle q_i, k_j \rangle}{\sqrt{d}})
+
\exp \left(\frac{\langle q_i, k_j \rangle}{\sqrt{d}} \right)
 
}{
 
}{
\sum_{p=1}^n \exp(\frac{\langle q_i, k_p \rangle}{\sqrt{d}})
+
\sum_{p=1}^n \exp \left(\frac{\langle q_i, k_p \rangle}{\sqrt{d}} \right)
 
}</math>,  
 
}</math>,  
где <math>d</math> - размерность векторов <math>q_i</math> и <math>k_j</math>, а <math>n</math> - число элементов во входной последовательности.  
+
где <math>d</math> {{---}} размерность векторов <math>q_i</math> и <math>k_j</math>, а <math>n</math> {{---}} число элементов во входной последовательности.  
  
Таким образом, новое представление элемента <math>x_i</math> считаем как взвешенную сумму векторов значения: <math>z_i = Attn(W_q x_i, W_k X, W_v X) = \sum_{p=1}^n w_{p i} v_p</math>, где <math>X = (x_1, x_2, ..., x_n)</math> - входные векторы. По итогу мы перемешиваем все входные векторы, чтобы получить новые векторы всех элементов, где каждый элемент зависит от всех входных элементов.  
+
Таким образом, новое представление элемента <math>x_i</math> считаем как взвешенную сумму векторов значения: <math>z_i = \mathrm{Attn}(Q x_i, K X, V X) = \sum_{p=1}^n w_{p i} v_p</math>, где <math>X = (x_1, x_2, ..., x_n)</math> {{---}} входные векторы.  
 +
По факту ''self-attention'' {{---}} это ''soft-arg-max'' с температурой <math>\sqrt{d}</math>. Мы перемешиваем все входные векторы, чтобы получить новые векторы всех элементов, где каждый элемент зависит от всех входных элементов.  
  
 
===Multi-headed self-attention===
 
===Multi-headed self-attention===
Строка 69: Строка 78:
 
То есть параллельно независимо несколько раз делаем attention. Потом результат каждого attention по элементам конкатенируем, затем сжимаем получившуюся матрицу и получаем для каждого элемента свой вектор той же размерности.
 
То есть параллельно независимо несколько раз делаем attention. Потом результат каждого attention по элементам конкатенируем, затем сжимаем получившуюся матрицу и получаем для каждого элемента свой вектор той же размерности.
  
<math>с^j = Attn(W^j_q q, W^j_k X, W^j_v X)</math>, где <math>j = 1...J</math>, <math>J</math> - количество разных моделей внимания, <math>X = (x_1, x_2, ..., x_n)</math> - входные векторы, а <math>W</math> - обучаемые матрицы.
+
<math>с^j = \mathrm{Attn}(Q^j q, K^j X, V^j X)</math>, где <math>j = 1...J</math>, <math>J</math> {{---}} число разных моделей внимания, <math>X = (x_1, x_2, ..., x_n)</math> {{---}} входные векторы, а <math>W</math> {{---}} обучаемые матрицы.
  
  
  
 
==Архитектура трансформера-декодировщика==
 
==Архитектура трансформера-декодировщика==
[[Файл:TransformerDecoderArchitecture.png|150px|thumb|left|Архитектура трансформера-декодировщика]]
+
[[Файл:TransformerDecoderArchitecture.png|150px|thumb|left|Архитектура трансформера-декодировщика<ref>https://arxiv.org/abs/1706.03762</ref>]]
 
На вход декодировщику подается выход кодировщика. Главное отличие архитектуры декодировщика заключается в том, что дополнительно имеется attention к вектору, который получен из последнего блока кодирующего компонента. Компонент декодировщика тоже многослойный и каждому блоку компонента на вход подается вектор именно с последнего блока кодирующего компонента. Разберем по порядку этапы работы декодировщика:
 
На вход декодировщику подается выход кодировщика. Главное отличие архитектуры декодировщика заключается в том, что дополнительно имеется attention к вектору, который получен из последнего блока кодирующего компонента. Компонент декодировщика тоже многослойный и каждому блоку компонента на вход подается вектор именно с последнего блока кодирующего компонента. Разберем по порядку этапы работы декодировщика:
  
Строка 80: Строка 89:
  
 
2. Далее идет этап многомерного самовнимания: линейная нормализация и multi-headed self-attention. Особенность в том, что в attention ключи и значения применяются не ко всем векторам, а только к тем, значения которых уже синтезировали (<math>H_t</math>):  
 
2. Далее идет этап многомерного самовнимания: линейная нормализация и multi-headed self-attention. Особенность в том, что в attention ключи и значения применяются не ко всем векторам, а только к тем, значения которых уже синтезировали (<math>H_t</math>):  
<math> h'_t = LN \circ M H_j \circ Attn(W^j_q h_t, W^j_k H_t, W^j_v H_t) </math>
+
<math> h'_t = \mathrm{LN} \circ M H_j \circ \mathrm{Attn}(Q^j h_t, K^j H_t, V^j H_t) </math>, где <math>\circ</math> {{---}} композиция.
  
 
3. На следующем этапе мы делаем многомерное внимание на кодировку <math>Z</math>, результат работы компонента кодировщика:
 
3. На следующем этапе мы делаем многомерное внимание на кодировку <math>Z</math>, результат работы компонента кодировщика:
<math> h''_t = LN \circ M H_j \circ Attn(W^j_q h_t, W^j_k Z, W^j_v Z) </math>
+
<math> h''_t = \mathrm{LN} \circ M H_j \circ \mathrm{Attn}(Q^j h_t, K^j Z, V^j Z) </math>
  
4. Линейная полносвязная сеть (по аналогии, как и в кодировщике):
+
4. Линейная полносвязная сеть (по аналогии с кодировщиком):
<math> y_t = LN \circ FNN(h''_t) </math>
+
<math> y_t = \mathrm{LN} \circ FNN(h''_t) </math>
  
5. Линейный предсказывающий слой:
+
5. В самом конце мы хотим получить вероятностную порождающую модель для элементов. Результат (индекс слова с наибольшей вероятностью): <math>\mathrm{SoftArgMax}(W_y y_t + b_y) </math>, где <math> W_y </math>, <math> b_y </math> {{---}} обучаемые параметры линейного преобразования. Для каждой позиции <math>t</math> выходной последовательности мы строим вероятностную модель языка, то есть все элементы из выходного словаря получают значение вероятности. Эти значения как раз получаются из векторов <math>y_t</math> из предыдущего пункта, которые мы берем с последнего блока трансформера-декодировщика.
<math> p(\tilde w|t) = SoftMax_{\tilde w}(W_y y_t + b_y) </math>, <math> W_y </math> - обучаемая матрица весовых коэффицентов, <math> b_y </math> - обучаемый вектор смещения
 
  
Последний этап выполняется только после того, когда повторились пункты 1-4 для всех декодировщиков. На выходе получаем вероятности классов, по факту решаем для каждого класса задачу многоклассовой классификации, для того, чтобы понять какие элементы лучше поставить на каждые позиции. В самом конце происходит генерация самих элементов: <math> {\tilde w}_t = arg max_{\tilde w} p(\tilde w|t) </math>, пока не сгенерируется слово обозначающее конец последовательности.
+
Последний этап выполняется только после того, когда повторились пункты 1--4 для всех декодировщиков. На выходе получаем вероятности классов, по факту для каждой позиции решаем задачу многоклассовой классификации, для того, чтобы понять какие элементы лучше поставить на каждые позиции.
  
 
==Источники информации==
 
==Источники информации==

Текущая версия на 19:11, 4 сентября 2022

Трансформер (англ. transformer) — архитектура глубоких нейронных сетей, основанная на механизме внимания без использования рекуррентных нейронных сетей (сокр. RNN). Самое большое преимущество трансформеров по сравнению с RNN заключается в их высокой эффективности в условиях параллелизации. Впервые модель трансформера была предложена в статье Attention is All You Need[1] от разработчиков Google в 2017 году.

Архитектура трансформера

Архитектура трансформера[2]

Устройство трансформера состоит из кодирующего и декодирующего компонентов. На вход принимается некая последовательность, создается ее векторное представление (англ. embedding), прибавляется вектор позиционного кодирования, после чего набор элементов без учета порядка в последовательности поступает в кодирующий компонент (параллельная обработка), а затем декодирующий компонент получает на вход часть этой последовательности и выход кодирующего. В результате получается новая выходная последовательность.

Внутри кодирующего и декодирующего компонента нет рекуррентности. Кодирующий компонент состоит из кодировщиков, которые повторяются несколько раз, аналогично устроен декодирующий компонент. Трансформер — это поставленные друг за другом модели внимания, которые позволяют исходную последовательность векторов перевести в новую последовательность векторов, которые кодируют информацию о контексте каждого элемента. Трансформер-кодировщик переводит исходные векторы в скрытые, которые правильно сохраняют в себе информацию о контексте каждого элемента. Далее трансформер-декодировщик декодирует результат кодировщика в новую последовательность, которая состоит из эмбедингов элементов выходного языка. После по эмбедингам генерируются сами итоговые элементы с помощью вероятностной языковой модели.

Ниже рассмотрим архитектуру кодировщика и декодировщика подробнее.

Архитектура трансформера-кодировщика

Архитектура трансформера-кодировщика[3]

Рассмотрим последовательно шаг за шагом этапы работы кодировщика:

1. На вход поступает последовательность элементов [math]w_i[/math], по ней создается последовательность эмбедингов, где каждый [math]x_i[/math] это векторное представление элемента [math]w_i[/math].

2. Добавляются позиционные векторы [math]p_i[/math]: [math]h_i = x_i + p_i[/math], [math]H = (h_1,...,h_n)[/math]. Это необходимо для того, чтобы отобразить информацию о позиции элемента в исходной последовательности. Основное свойство позиционного кодирования — чем дальше два вектора будут стоять друг от друга в последовательности, тем больше между ними будет расстояние. Более подробное устройство позиционного кодирования будет рассмотрено ниже.

3. Полученный вектор [math]h_i[/math] подается на вход в блок многомерного самовнимания (англ. multi-headed self-attention). [math]h^j_i = \mathrm{Attn}(Q^j h_i, K^j H, V^j H)[/math], где обучаемые матрицы: [math]Q[/math] для запроса, [math]K[/math] для ключа, [math]V[/math] для значения. Подробное объяснения работы механизма self-attention будет разобрано ниже.

4. Затем необходима конкатенация, чтобы вернуться в исходную размерность: [math] h'_i = M H_j (h^j_i) = [h^1_i...h^J_i] [/math]

5. Добавим сквозные связи (англ. skip connection) — по факту просто добавление из входного вектора к выходному ([math]h'_i + h_i[/math]). После делаем нормализацию слоя (англ. layer normalization): [math]h''_i = \mathrm{LN}(h'_i + h_i; \mu_1, \sigma_1)[/math]. У нее два обучаемых параметра, для каждой размерности вектора вычисляется среднее и дисперсия.

6. Теперь добавим преобразование, которое будет обучаемым — полносвязную двухслойную нейронную сеть: [math] h'''_i = W_2 \mathrm{ReLU} (W_1 h''_i + b_1) + b_2 [/math]

7. Повторим пункт 5 еще раз: добавим сквозную связь и нормализацию слоя: [math]z_i = \mathrm{LN}(h'''_i + h''_i; \mu_2, \sigma_2)[/math]

После, в кодирующем компоненте пункты кодировщика 3--7 повторяются еще несколько раз, преобразовывая друг за другом из контекста контекст. Тем самым мы обогащаем модель и увеличиваем в ней количество параметров.



Позиционное кодирование

Визуализация работы позиционного кодирования[4]

Так как в архитектуре трансформер обработка последовательности заменяется на обработку множества мы теряем информацию о порядке элементов последовательности. Чтобы отобразить информацию о позиции элемента в исходной последовательности мы используем позиционное кодирование.

Позиционное кодирование (англ. positional encoding) — позволяет модели получить информацию о порядке элементов в последовательности путем прибавления специальных меток к вектору входных элементов. Позиции элементов [math]i[/math] кодируются векторами [math]p_i[/math], [math]i = 1, 2, ..., n[/math], так, что чем больше [math]|i - j|[/math], тем больше [math]||p_i - p_j||[/math], и [math]n[/math] не ограничено. Пример такого кодирования:

[math] p_{(i, s)} = \begin{cases} \sin \left(i \cdot 10000^{\frac{-2k}{d_{model}}}\right) & \quad \text{если } s=2k\\ \cos \left(i \cdot 10000^{\frac{-2k}{d_{model}}}\right) & \quad \text{если } s=2k+1 \end{cases} [/math]

Self-attention

Self-Attention — разновидность механизма внимания, задачей которой является выявление закономерности между входными данными.

Будем для каждого элемента [math]x_i[/math] получать обучаемым преобразованием три вектора:

  • Запрос (query) [math]q_i = Q x_i[/math]
  • Ключ (key) [math]k_i = K x_i[/math]
  • Значение (value) [math]v_i = V x_i[/math]

Векторы [math]q_i[/math] и [math]k_i[/math] будем использовать, чтобы посчитать важность элемента [math]x_j[/math] для элемента [math]x_i[/math]. Чтобы понять, насколько для пересчета вектора элемента [math]x_i[/math] важен элемент [math]x_j[/math] мы берем [math]k_j[/math] (вектор ключа элемента [math]x_j[/math]) и умножаем на [math]q_i[/math] (вектор запроса элемента [math]x_i[/math]). Так мы скалярно перемножаем вектор запроса на все векторы ключей, тем самым понимаем, насколько каждый входной элемент нам нужен, чтобы пересчитать вектор элемента [math]x_i[/math].

Далее считаем важность элемента [math]x_j[/math] для кодирования элемента [math]x_i[/math]: [math]w_{ji}=\frac{ \exp \left(\frac{\langle q_i, k_j \rangle}{\sqrt{d}} \right) }{ \sum_{p=1}^n \exp \left(\frac{\langle q_i, k_p \rangle}{\sqrt{d}} \right) }[/math], где [math]d[/math] — размерность векторов [math]q_i[/math] и [math]k_j[/math], а [math]n[/math] — число элементов во входной последовательности.

Таким образом, новое представление элемента [math]x_i[/math] считаем как взвешенную сумму векторов значения: [math]z_i = \mathrm{Attn}(Q x_i, K X, V X) = \sum_{p=1}^n w_{p i} v_p[/math], где [math]X = (x_1, x_2, ..., x_n)[/math] — входные векторы. По факту self-attention — это soft-arg-max с температурой [math]\sqrt{d}[/math]. Мы перемешиваем все входные векторы, чтобы получить новые векторы всех элементов, где каждый элемент зависит от всех входных элементов.

Multi-headed self-attention

Multi-headed self-attention — улучшенная модификация self-attention.

Слой внимания снабжается множеством «подпространств представлений» (англ. representation subspaces). Теперь у нас есть не один, а множество наборов матриц запроса/ключа/значения. Каждый из этих наборов создается случайным образом. Далее после обучения каждый набор используется для отображения входящих векторов в разных подпространствах представлений. Также появляется способность модели фокусироваться на разных аспектах входной информации.

То есть параллельно независимо несколько раз делаем attention. Потом результат каждого attention по элементам конкатенируем, затем сжимаем получившуюся матрицу и получаем для каждого элемента свой вектор той же размерности.

[math]с^j = \mathrm{Attn}(Q^j q, K^j X, V^j X)[/math], где [math]j = 1...J[/math], [math]J[/math] — число разных моделей внимания, [math]X = (x_1, x_2, ..., x_n)[/math] — входные векторы, а [math]W[/math] — обучаемые матрицы.


Архитектура трансформера-декодировщика

Архитектура трансформера-декодировщика[5]

На вход декодировщику подается выход кодировщика. Главное отличие архитектуры декодировщика заключается в том, что дополнительно имеется attention к вектору, который получен из последнего блока кодирующего компонента. Компонент декодировщика тоже многослойный и каждому блоку компонента на вход подается вектор именно с последнего блока кодирующего компонента. Разберем по порядку этапы работы декодировщика:

1. Для того, чтобы распараллелить декодировщик и уйти от рекуррентности, но тем не менее генерировать элементы друг за другом, используется прием маскирования данных из будущего. Идея в том, что мы запрещаем себе подглядывать в те элементы, которые еще не сгенерированы с учетом порядка. Когда генерируем элемент под номером [math]t[/math], имеем право смотреть только первые [math]t-1[/math] элементов: [math]h_t = y_{t-1} + p_t[/math]; [math]H_t=(h_1, ...,h_t)[/math]

2. Далее идет этап многомерного самовнимания: линейная нормализация и multi-headed self-attention. Особенность в том, что в attention ключи и значения применяются не ко всем векторам, а только к тем, значения которых уже синтезировали ([math]H_t[/math]): [math] h'_t = \mathrm{LN} \circ M H_j \circ \mathrm{Attn}(Q^j h_t, K^j H_t, V^j H_t) [/math], где [math]\circ[/math] — композиция.

3. На следующем этапе мы делаем многомерное внимание на кодировку [math]Z[/math], результат работы компонента кодировщика: [math] h''_t = \mathrm{LN} \circ M H_j \circ \mathrm{Attn}(Q^j h_t, K^j Z, V^j Z) [/math]

4. Линейная полносвязная сеть (по аналогии с кодировщиком): [math] y_t = \mathrm{LN} \circ FNN(h''_t) [/math]

5. В самом конце мы хотим получить вероятностную порождающую модель для элементов. Результат (индекс слова с наибольшей вероятностью): [math]\mathrm{SoftArgMax}(W_y y_t + b_y) [/math], где [math] W_y [/math], [math] b_y [/math] — обучаемые параметры линейного преобразования. Для каждой позиции [math]t[/math] выходной последовательности мы строим вероятностную модель языка, то есть все элементы из выходного словаря получают значение вероятности. Эти значения как раз получаются из векторов [math]y_t[/math] из предыдущего пункта, которые мы берем с последнего блока трансформера-декодировщика.

Последний этап выполняется только после того, когда повторились пункты 1--4 для всех декодировщиков. На выходе получаем вероятности классов, по факту для каждой позиции решаем задачу многоклассовой классификации, для того, чтобы понять какие элементы лучше поставить на каждые позиции.

Источники информации