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

Как и было обещано, все анимации и коды собраны в блокнот jupyter, доступный здесь.

Давай поржать!!!

Уровень преобразователя использует блок AddandNorm, следующий за каждым из двух подуровней (многоголовое внутреннее внимание, точечная упреждающая связь N/w) внутри. Нормализация уровня и остаточное соединение создают слой AddandNorm. Давайте посмотрим на них один за другим.

Нормализация слоя

Нормализация слоев — не новая концепция, представленная в Transformers. Он был популярен в задачах обработки естественного языка. Он нормализует каждую точку данных по всем ее функциям. Дан набор данных
X = {x1,x2,…..xm}, где каждый xᵢ представляет собой последовательность, представленную K-мерным вектором таким образом, что xᵢ = {xᵢ,ₒ……xᵢ,ₖ}.

И, наконец, вывод слоя Layernorm 𝑦ᵢ

Превратим это в код.

Остаточное соединение

В документе используется остаточное соединение, за которым следует layerNorm в блоке Add&Norm.

AddandNorm(Sublayer(x)) = LayerNorm(x+Dropout(Sublayer(x)))

Блок энкодера

С многоголовым вниманием, нормой слоев и остаточными соединениями теперь у нас есть все необходимое для реализации слоя кодировщика. Входными данными для EncoderLayer являются либо встраивание (в случае первого слоя кодировщика)
, либо представления из предыдущего уровня (для слоев кодировщика, следующих за первым слоем). Мы используем key=query=value = input (x) для подачи в многоголовый блок внимания на уровне кодировщика.

Следуя рисунку выше, реализовать это не так уж сложно. Посмотрим, как

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

Рекуррентная нейронная сеть по своей конструкции включает упорядочение последовательности при изучении ее параметров. Даже сверточная нейронная сеть может собирать некоторую информацию об упорядочении в своем ядре фиксированного размера. Само-внимание, с другой стороны, оперирует множествами, что делает его инвариантным к перестановкам. Он работает с наборами {ключ, запрос, значение}. В своем текущем состоянии наш преобразователь не может отличить случайный набор токенов в последовательности от значимого порядка токенов. С этой целью Vaswani et al. передавать такую ​​информацию о положении токенам, обогащая их вложения дополнительным вектором позиционного встраивания. Эти позиционные вложения не изучаются как часть обучения сети. Скорее, они являются фиксированными функциями положения токенов в последовательности.

Эти позиционные вложения являются функцией синусов и косинусов положения элементов в векторе вложения. Четные (2j) и нечетные (2j+1) позиции в векторе встраивания токена iᵗʰ задаются как

Блок декодера

Подробно ознакомившись с блоком энкодера, мы готовы рассмотреть блок декодера. Архитектурно они очень похожи, за исключением дополнительного многоголового внимания «кодировщик-декодер» и блока «Добавить и нормализовать» .

Уровень декодера выглядит лишь небольшой модификацией уровня кодера. Ведь у нас уже есть реализация многоголового внимания и Add&Norm. Хотя этот многоголовый блок внимания функционально подобен любому другому многоголовому блоку внимания, используемому где-либо в сети преобразователя, его особенность заключается в том, что он принимает запрос от предыдущего уровня декодера и «ключ, значение» от кодировщика. С этой целью мы назвали это «многоголовое внимание кодировщик-декодер». Обратите внимание, что все другие многоголовые блоки внимания в сети трансформатора потребляют key=query=value

Почему разумно использовать отдельный запрос, ‹ключ-значение›?

Представьте, что вы пытаетесь узнать о трансформерах, просматривая видео на YouTube. Теперь вы посмотрели видео о механизме внутреннего внимания и узнали об этом. Этому обучению соответствует первоначальный блок многоголового внимания. Он обрабатывает всю информацию в видео «самовнимание» и генерирует сигнал запроса, указывающий, что смотреть и изучать дальше. Это поможет вам посмотреть «Multi-headed Attention».

Youtube сам выступает в качестве кодировщика в этой настройке. Выход кодировщика представляет собой изученное богатое кодирование сложных функций, объединяющих похожие видео в базе данных, контент, который просматривали другие люди с такими же интересами (трансформеры) и т. д. Используя эти функции в качестве ключа и значения и используя «Многоголовое внимание» в качестве запрос, блок внимания кодера-декодера способен искать наиболее похожий и релевантный ключ (может быть, видео под названием «Трансформеры с многоголовым вниманием» и с большим количеством голосов) и возвращать значение, соответствующее к нему (само видео).

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

Продолжая наше обсуждение уровня декодера….

Поговорив об архитектурных различиях кодировщика и декодера, сосредоточим внимание на реализации. Различие в реализации, которое отличает декодер от кодировщика, заключается в применении авторегрессии в декодере. Авторегрессия — это парадигма моделирования, в которой входными данными для модели являются токены до текущих временных шагов. Например, при декодировании токена j входными данными для модели должны быть токены 0……j-1, а не что-то большее, чем j. Это имеет смысл, поскольку декодер не будет иметь доступа к будущим событиям при выводе.

Эта авторегрессия может быть реализована путем маскирования (обнуления всех будущих входных данных). При обработке j=1декодеру предоставляется только j=0.

Все, что дальше, замаскировано. Точно так же в j=2открываются элементы в j=0,1 и т. д.

Кроме того, уровень декодера во время обучения ведет себя иначе, чем во время проверки.

Во время обучения декодер имеет доступ ко всей выходной (наземной) последовательности. Это может быть предложение на целевом языке в приложении для машинного перевода или любая другая последовательность токенов, с которой мы собираемся сопоставить нашу исходную последовательность. С другой стороны, делая вывод (предсказывая целевую последовательность из исходной последовательности), декодер предсказывает токены по одному шагу за раз. Ради краткости я опускаю подробное описание этого. Я планирую написать отдельный пост только на «Последовательный вывод с использованием декодера-трансформера». Тем временем следующий рисунок должен помочь объяснить, что происходит на каждом временном шаге декодера во время логического вывода.

Вооружившись этими знаниями, попробуем реализовать слой декодера.

Трансформаторный кодер и декодер

Теперь мы можем сложить блоки кодировщика и декодера, чтобы получить кодировщик и декодер преобразователя соответственно. Оригинальная бумага использует 6 слоев как в кодере, так и в декодере.

Кодер

Декодер

Объединяя все это

Трансформаторная сеть обрела форму. Нам просто нужно собрать кодировщик и декодер вместе.

Вывод

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