Построение Multi-Head Self-Attention в Python
Мы уже реализовали алгоритм Multi-Head Self-Attention средствами MQL5 и даже добавили возможность осуществления вычислений в многопоточном режиме с использование технологии OpenCL. Теперь давайте посмотрим на вариант реализации подобного алгоритма на языке Python с использованием библиотеки Keras для TensorFlow. Мы познакомились с этой библиотекой при создании предыдущих моделей. Правда, до этого мы использовали только готовые нейронные слои, предлагаемые библиотекой, и с их помощью строили линейные модели.
Модель Multi-Head Self-Attention нельзя назвать линейной. Параллельная работа нескольких голов внимания — это уже отказ от линейности модели. Да и в самом алгоритме Self-Attention исходные данные одновременно идут в четырех направлениях.
Поэтому для построения модели Multi-Head Self-Attention мы рассмотрим еще один функционал, предлагаемый указанной библиотекой, — создание пользовательских нейронных слоев.
Слой — это вызываемый объект, который принимает на вход один или несколько тензоров и выводит один или несколько тензоров. Он включает в себя вычисление и состояние.
Все нейронные слои в библиотеке Keras представляют собой классы, унаследованные от базового класса tf.keras.layers.Layer. Следовательно, создавая свой новый нейронный слой, мы также будем наследоваться от указанного базового класса.
Базовый класс предусматривает следующие параметры:
- trainable — флаг, указывающий на необходимость обучения параметров нейронного слоя;
- name — название слоя;
- dtype — тип результатов и весовых коэффициентов слоя;
- dynamic — флаг, указывающий на невозможность использования слоя для создания графа статических вычислений.
tf.keras.layers.Layer(
|
Также для каждого слоя архитектурой библиотеки определен минимальный набор методов:
- __init__ — метод инициализации слоя,
- call — метод вычислений (прямого прохода).
В методе инициализации мы определяем пользовательские атрибуты слоя и создаем матрицы весовых коэффициентов, структура которых не зависят от формата и структуры исходных данных. Но при решении практических задач чаще всего мы не знаем структуру исходных данных и, как следствие, не можем создать матрицы весовых коэффициентов без понимания размерности исходных данных. В таких случаях инициализация матриц весовых коэффициентов и других объектов переносится в метод build(self, input_shape). Данный метод вызывается один раз при первом вызове метода call.
В методе call описываются операции прямого прохода, которые необходимо совершить с исходными данными. Результат операций возвращается в виде одного или нескольких тензоров. Для слоев, используемых в линейных моделях, существует ограничение на результат в виде одного тензора.
Каждый нейронный слой имеет следующие атрибуты (приведен список наиболее употребляемых атрибутов):
- name — имя слоя;
- dtype — тип весовых коэффициентов;
- trainable_weights — список обучаемых переменных;
- non_trainable_weights — список не обучаемых переменных;
- weights — объединяет списки обучаемых и не обучаемых переменных;
- trainable — логический флаг, указывающий на необходимость обучения параметров слоя;
- activity_regularizer — дополнительная функция регуляризации для выхода нейронного слоя.
Преимущества такой реализации очевидны: мы не создаем методы обратного прохода. Весь функционал реализуется библиотекой. Нам достаточно лишь правильно описать логику прямого прохода в методе call.
Данный подход позволяет создавать довольно сложные архитектурные решения. При этом создаваемый слой может содержать другие вложенные нейронные слои. При этом параметры внутренних нейронных слоев включаются в список параметров внешнего нейронного слоя.