Построение Multi-Head Self-Attention в Python

Мы уже реализовали алгоритм Multi-Head Self-Attention средствами MQL5 и даже добавили возможность осуществления вычислений в многопоточном режиме с использование технологии OpenCL. Теперь давайте посмотрим на вариант реализации подобного алгоритма на языке Python с использованием библиотеки Keras для TensorFlow. Мы познакомились с этой библиотекой при создании предыдущих моделей. Правда, до этого мы использовали только готовые нейронные слои, предлагаемые библиотекой, и с их помощью строили линейные модели.

Модель Multi-Head Self-Attention нельзя назвать линейной. Параллельная работа нескольких голов внимания — это уже отказ от линейности модели. Да и в самом алгоритме Self-Attention исходные данные одновременно идут в четырех направлениях.

Multi-Head Self-Attention

Поэтому для построения модели Multi-Head Self-Attention мы рассмотрим еще один функционал, предлагаемый указанной библиотекой, — создание пользовательских нейронных слоев.

Слой — это вызываемый объект, который принимает на вход один или несколько тензоров и выводит один или несколько тензоров. Он включает в себя вычисление и состояние.

Все нейронные слои в библиотеке Keras представляют собой классы, унаследованные от базового класса tf.keras.layers.Layer. Следовательно, создавая свой новый нейронный слой, мы также будем наследоваться от указанного базового класса.

Базовый класс предусматривает следующие параметры:

  • trainable — флаг, указывающий на необходимость обучения параметров нейронного слоя;
  • name — название слоя;
  • dtype — тип результатов и весовых коэффициентов слоя;
  • dynamic — флаг, указывающий на невозможность использования слоя для создания графа статических вычислений.

tf.keras.layers.Layer(
    trainable=True, name=None, dtype=None, dynamic=False, **kwargs
)

Также для каждого слоя архитектурой библиотеки определен минимальный набор методов:

  • __init__ — метод инициализации слоя,
  • call — метод вычислений (прямого прохода).

В методе инициализации мы определяем пользовательские атрибуты слоя и создаем матрицы весовых коэффициентов, структура которых не зависят от формата и структуры исходных данных. Но при решении практических задач чаще всего мы не знаем структуру исходных данных и, как следствие, не можем создать матрицы весовых коэффициентов без понимания размерности исходных данных. В таких случаях инициализация матриц весовых коэффициентов и других объектов переносится в метод build(self, input_shape). Данный метод вызывается один раз при первом вызове метода call.

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

Каждый нейронный слой имеет следующие атрибуты (приведен список наиболее употребляемых атрибутов):

  • name — имя слоя;
  • dtype — тип весовых коэффициентов;
  • trainable_weights — список обучаемых переменных;
  • non_trainable_weights — список не обучаемых переменных;
  • weights — объединяет списки обучаемых и не обучаемых переменных;
  • trainable — логический флаг, указывающий на необходимость обучения параметров слоя;
  • activity_regularizer — дополнительная функция регуляризации для выхода нейронного слоя.

Преимущества такой реализации очевидны: мы не создаем методы обратного прохода. Весь функционал реализуется библиотекой. Нам достаточно лишь правильно описать логику прямого прохода в методе call.

Данный подход позволяет создавать довольно сложные архитектурные решения. При этом создаваемый слой может содержать другие вложенные нейронные слои. При этом параметры внутренних нейронных слоев включаются в список параметров внешнего нейронного слоя.