Тестирование механизма внимания

В отличие от ранее рассмотренного рекуррентного блока LSTM, блок внимания работает только с текущими данными. Поэтому для создания более репрезентативной выборки между обновлением весовых коэффициентов в процессе обучения нейронной сети мы будем использовать случайные паттерны из генеральной обучающей выборки. Именно такой подход мы использовали при тестировании полносвязного перцептрона и сверточной модели. В такой ситуации будет вполне логично взять скрипт тестирования сверточной модели convolution_test.mq5, пересохранить его с новым именем attention_test.mq5 и внести изменения в части описания создаваемой модели.

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

В поле type мы передадим константу defNeuronAttention, которая соответствует создаваемому блоку внимания.

В поле count мы должны указать количество анализируемых элементов последовательности. Его мы запрашиваем у пользователя при запуске скрипта и сохраняем в переменную BarsToLine. Следовательно, в описание нейронного слоя мы можем передать значение переменной.

Следующий далее параметр window при описании сверточного слоя мы использовали для указания размера окна исходных данных. Здесь же мы его будем использовать для указания размера вектора описания одного элемента последовательности исходных данных. Согласитесь, за немного разной формулировкой кроется схожий функционал. Но в отличие от сверточного слоя, мы не будем указывать шаг окна, так как в данном случае оно будет равно самому окну. Количество нейронов для описания одной свечи мы так же запрашиваем у пользователя в параметрах скрипта. Это значение сохраняется в переменной NeuronsToBar. Как и в случае с предыдущим полем, просто передаем в указанное поле значение из переменной.

Алгоритм Self-Attention не предусматривает изменение размера данных. На выходе из блока мы получаем тензор того же размера, что и исходных данных. Получается, поле window_out в описании нейронного слоя останется невостребованным. Но мы воспользуемся им, чтобы указать размер вектора ключа одного элемента в тензоре Key. На практике размер ключа не всегда отличается от размера вектора описания одного элемента. Понижение размерности используется при большом размере вектора описания одного элемента для экономии вычислительных ресурсов при расчете матрицы коэффициентов зависимости. В нашем случае, когда вектор описания одной свечи составляет всего четыре элемента, мы не будем понижать размерность и передадим в поле window_out значение переменной NeuronsToBar.

Дополнительно укажем метод оптимизации и его параметры. В тестовом примере я использовал метод Adam, как и во всех предыдущих тестах.

bool CreateLayersDesc(CArrayObj &layers)
  {
   CLayerDescription *descr;
//--- создаем слой исходных данных
   .....
//--- слой внимания
   if(!(descr = new CLayerDescription()))
     {
      PrintFormat("Error creating CLayerDescription: %d"GetLastError());
      return false;
     }
   descr.type = defNeuronAttention;
   descr.count = BarsToLine;
   descr.window = NeuronsToBar;
   descr.window_out = NeuronsToBar;
   descr.optimization = Adam;
   descr.activation_params[0] = 1;
   if(!layers.Add(descr))
     {
      PrintFormat("Error adding layer: %d"GetLastError());
      delete descr;
      return false;
     }
//--- скрытый слой
   .....
//---  Слой результатов
   .....
//---
   return true;
  }

После указания всех параметров мы добавляем объект в динамический массив описаний нейронных слоев. И, конечно, проверяем результат проведения операций. Далее код скрипта остался без изменения.

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

Тестирование новой модели с использованием блока внимания мы проводили с сохранением всех прочих условий проверки предыдущих моделей. Такой подход позволяет максимально точно оценить влияние изменения архитектуры модели на результат обучения.

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

Тестирование модели с использованием блока внимания

Тестирование модели с использованием блока внимания

 

Тестирование модели с использованием блока внимания

Тестирование модели с использованием блока внимания

 

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

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