5.Создание скрипта для тестирования технологии Multi-Head Self-Attention

Для тестирования работы нашего нового класса нейронного слоя Multi-Head Self-Attention мы создадим скрипт с реализацией модели нейронной сети, в которой и будем использовать новый тип нейронного слоя. Создавать свой скрипт мы будем на базе скрипта lstm.py, который использовали при тестировании рекуррентных моделей ранее. Перед началом работы создадим копию указанного скрипта с названием файла attention.py. В новой копии скрипта мы удалим ранее созданные модели. Оставим только сверточную модель и лучшую рекуррентную модель — они нам послужат базой для сравнения новых моделей.

# Модель с 2-мерным сверточным слоем
model3 = keras.Sequential([keras.Input(shape=inputs),
                           # Переформатируем тензор в 4-мерный.
        # Указываем 3 измерения, т.к. 4-е измерение определяется размером пакета
                           keras.layers.Reshape((-1,4,1)), 
                           # Сверточный слой с 8 фильтрами
                           keras.layers.Conv2D(8,(3,1),1,activation=tf.nn.swish,
        kernel_regularizer=keras.regularizers.l1_l2(l1=1e-7, l2=1e-5)),
                           # Подвыборочный слой
                           keras.layers.MaxPooling2D((2,1),strides=1),                         
        # Переформатируем тензор в 2-мерный для полносвязных слоев
                           keras.layers.Flatten(),
                           keras.layers.Dense(40, activation=tf.nn.swish,
        kernel_regularizer=keras.regularizers.l1_l2(l1=1e-7, l2=1e-5)), 
                           keras.layers.Dense(40, activation=tf.nn.swish,
        kernel_regularizer=keras.regularizers.l1_l2(l1=1e-7, l2=1e-5)), 
                           keras.layers.Dense(40, activation=tf.nn.swish,
        kernel_regularizer=keras.regularizers.l1_l2(l1=1e-7, l2=1e-5)), 
                           keras.layers.Dense(targerts, activation=tf.nn.tanh) 
                         ])

# Модель LSTM блок без полносвязных слоев
model4 = keras.Sequential([keras.Input(shape=inputs),
        # Переформатируем тензор в 3-мерный.
        # Указываем 2 измерения, т.к. 3-е измерение определяется размером пакета
                           keras.layers.Reshape((-1,4)), 
        # 2 последовательных LSTM блока
        # 1-й содержит 40 элементами  
                           keras.layers.LSTM(40,
        kernel_regularizer=keras.regularizers.l1_l2(l1=1e-7, l2=1e-5),
                           return_sequences=False),
        # 2-й выдает результат вместо полносвязного слоя
                           keras.layers.Reshape((-1,2)), 
                           keras.layers.LSTM(targerts) 
                         ])

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

heads=8
key_dimension=4
 
model5 = keras.Sequential([keras.layers.InputLayer(input_shape=inputs),
        # Переформатируем тензор в 3-мерный. Указываем 2 измерения,
        # т.к. 3-е измерение определяется размером пакета
        # первое измерение - элементы последовательности
        # второе измерение - вектор описания одного элемента
                           keras.layers.Reshape((-1,4)), 
                           MHAttention(key_dimension,heads),

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

        # Переформатируем тензор в 2-мерный для полносвязных слоев
                           keras.layers.Flatten(),
                           keras.layers.Dense(40, activation=tf.nn.swish,
        kernel_regularizer=keras.regularizers.l1_l2(l1=1e-7, l2=1e-5)), 
                           keras.layers.Dense(40, activation=tf.nn.swish,
        kernel_regularizer=keras.regularizers.l1_l2(l1=1e-7, l2=1e-5)), 
                           keras.layers.Dense(40, activation=tf.nn.swish, 
        kernel_regularizer=keras.regularizers.l1_l2(l1=1e-7, l2=1e-5)), 
                           keras.layers.Dense(targerts, activation=tf.nn.tanh) 
                         ])

Надо сказать, что не смотря на внешнюю схожесть моделей, модель с использованием слоя механизма внимания использует в 5 раз меньше параметров.

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

Модель с использованием слоя Multi-Heads Self-Attention

Модель с использованием слоя Multi-Heads Self-Attention

model6 = keras.Sequential([keras.layers.InputLayer(input_shape=inputs),
        # Переформатируем тензор в 3-мерный. Указываем 2 измерения,
        # т.к. 3-е измерение определяется размером пакета
        # первое измерение - элементы последовательности
        # второе измерение - вектор описание одного элемента
                           keras.layers.Reshape((-1,4)), 
                           MHAttention(key_dimension,heads), 
                           MHAttention(key_dimension,heads), 
                           MHAttention(key_dimension,heads), 
                           MHAttention(key_dimension,heads), 
        # Переформатируем тензор в 2-мерный для полносвязных слоев
                           keras.layers.Flatten(),
                           keras.layers.Dense(40, activation=tf.nn.swish, 
        kernel_regularizer=keras.regularizers.l1_l2(l1=1e-7, l2=1e-5)), 
                           keras.layers.Dense(40, activation=tf.nn.swish, 
        kernel_regularizer=keras.regularizers.l1_l2(l1=1e-7, l2=1e-5)), 
                           keras.layers.Dense(40, activation=tf.nn.swish,
        kernel_regularizer=keras.regularizers.l1_l2(l1=1e-7, l2=1e-5)), 
                           keras.layers.Dense(targerts, activation=tf.nn.tanh) 
                         ])

Компилировать все нейронные модели мы будем с одинаковыми параметрами. Используем метод оптимизации Adam, в качестве ошибки сети используем среднеквадратичное отклонение, добавляем дополнительную метрику accuracy.

model3.compile(optimizer='Adam'
               loss='mean_squared_error'
               metrics=['accuracy'])

С такими же параметрами мы компилировали модели нейронных сетей и ранее.

Напомню, что рекуррентные модели чувствительны к последовательности подаваемого на вход сигнала. Поэтому при обучении рекуррентной нейронной сети, в отличие от остальных моделей, нельзя перемешивать исходные данные. Именно с этой целью при запуске рекуррентной модели мы указываем значение False для параметра shuffle. При этом у сверточной модели и моделей с использованием слоя внимания указанный параметр установлен True. Остальные параметры обучения моделей остаются без изменений, в том числе и ранний выход из процесса обучения при достижении минимума ошибки на обучающей выборке.

callback = tf.keras.callbacks.EarlyStopping(monitor='loss', patience=20)
 
history3 = model3.fit(train_data, train_target,
                      epochs=500, batch_size=1000,
                      callbacks=[callback],
                      verbose=2,
                      validation_split=0.01,
                      shuffle=True)

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

# Отрисовка результатов обучения моделей
plt.figure()
plt.plot(history3.history['loss'], label='Conv2D train')
plt.plot(history3.history['val_loss'], label='Conv2D validation')
plt.plot(history4.history['loss'], label='LSTM only train')
plt.plot(history4.history['val_loss'], label='LSTM only validation')
plt.plot(history5.history['loss'], label='MH Attention train')
plt.plot(history5.history['val_loss'], label='MH Attention validation')
plt.plot(history6.history['loss'], label='MH Attention 4 layers train')
plt.plot(history6.history['val_loss'], label='MH Attention 4 layers validation')
plt.ylabel('$MSE$ $loss$')
plt.xlabel('$Epochs$')
plt.title('Model training dynamics')
plt.legend(loc='upper right', ncol=2)

На втором графике выведем аналогичные результаты по показателю Accuracy.

plt.figure()
plt.plot(history3.history['accuracy'], label='Conv2D train')
plt.plot(history3.history['val_accuracy'], label='Conv2D validation')
plt.plot(history4.history['accuracy'], label='LSTM only train')
plt.plot(history4.history['val_accuracy'], label='LSTM only validation')
plt.plot(history5.history['accuracy'], label='MH Attention train')
plt.plot(history5.history['val_accuracy'], label='MH Attention validation')
plt.plot(history6.history['accuracy'], label='MH Attention 4 layers train')
plt.plot(history6.history['val_accuracy'], label='MH Attention 4 layers validation')
plt.ylabel('$Accuracy$')
plt.xlabel('$Epochs$')
plt.title('Model training dynamics')
plt.legend(loc='lower right', ncol=2)

Затем загрузим тестовую выборку и проверим на ней работу предварительно обученных моделей.

# Загрузка тестовой выборки
test_filename = os.path.join(path,'test_data.csv')
test = np.asarray( pd.read_table(test_filename,
                   sep=',',
                   header=None,
                   skipinitialspace=True,
                   encoding='utf-8',
                   float_precision='high',
                   dtype=np.float64,
                   low_memory=False))

# Разделение тестовой выборки на исходные данные и цели
test_data=test[:,0:inputs]
test_target=test[:,inputs:]

# Проверка результатов моделей на тестовой выборке
test_loss3, test_acc3 = model3.evaluate(test_data, test_target, verbose=2
test_loss4, test_acc4 = model4.evaluate(test_data, test_target, verbose=2
test_loss5, test_acc5 = model5.evaluate(test_data, test_target, verbose=2
test_loss6, test_acc6 = model6.evaluate(test_data, test_target, verbose=2

Результаты работы модели на тестовой выборке в числовом виде выведем в журнал и визуализируем на графике.

# Вывод результатов тестирования в журнал
print('Conv2D model')
print('Test accuracy:', test_acc3)
print('Test loss:', test_loss3)

print('LSTM only model')
print('Test accuracy:', test_acc4)
print('Test loss:', test_loss4)

print('MH Attention model')
print('Test accuracy:', test_acc5)
print('Test loss:', test_loss5)

print('MH Attention 4l Model')
print('Test accuracy:', test_acc5)
print('Test loss:', test_loss5)

plt.figure()
plt.bar(['Conv2D','LSTM''MH Attention','MH Attention\n4 layers'],
        [test_loss3,test_loss4,test_loss5,test_loss6])
plt.ylabel('$MSE$ $loss$')
plt.title('Test results')

plt.figure()
plt.bar(['Conv2D','LSTM''MH Attention','MH Attention\n4 layers'],
        [test_acc3,test_acc4,test_acc5,test_acc6])
plt.ylabel('$Accuracy$')
plt.title('Test results')
plt.show()

Завершаем нашу работу над механизмом Multi-Head Self-Attention. Мы воссоздали данный механизм средствами MQL5 и на языке Python. В данном разделе мы подготовили скрипт на языке Python, в котором создается в общей сложности четыре модели нейронных сетей:

  • сверточная модель,
  • рекуррентная нейронная сеть,
  • две модели с использованием технологии Multi-Head Self-Attention.

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