• 4.2.4.1 Построение тестовой рекуррентной модели Python

4.Построение тестовой рекуррентной модели Python

Для построения тестовых рекуррентных моделей на языке Python воспользуемся разработанным ранее шаблоном. Более того, мы возьмем файл скрипта convolution.py, который мы использовали при тестровании сверточных моделей. Создадим его копию с именем файла lstm.py. В созданной копии мы оставим модель перцептрона и лучшую сверточную модель, удалив остальные. Такой подход позволит нам сравнить работу новых моделей с рассмотренными ранее архитектурными решениями.

# Создание модели перцептрона с тремя скрытыми слоями и регуляризацией
model1 = keras.Sequential([keras.Input(shape=inputs),
                           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) 
                         ])

# Модель с 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-блока. Вначале мы возьмем модель сверточной нейронной сети и заменим в ней сверточный и подвыборочный слои на один рекуррентный слой с 40 нейронами на выходе. Здесь надо сказать, что на вход рекуррентного LSTM-блока должен подаваться трехмерный тензор формата [batch, timesteps, feature]. Как и в случае сверточного слоя, при задании размерности слоя в модели мы не указываем измерение batch, так как значение берется из размерности пакета исходных данных.

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

В данной модели мы указали параметр return_sequences=False, который указывает рекуррентному слою выдавать результат только после обработки полного пакета. В таком варианте наш LSTM-слой возвращает двухмерный тензор в формате [batch, feature]. При этом размерность измерения feature будет равна количеству нейронов, которое мы указали во время создания рекуррентного слоя. Тензор такой же размерности требуется на вход полносвязного нейронного слоя. Следовательно, от нас не требуется дополнительное переформатирование данных, и мы можем использовать полносвязный нейронный слой.

                           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) 
                         ])

Структура рекуррентной модели с четырьмя полносвязными слоями

Структура рекуррентной модели с четырьмя полносвязными слоями

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

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

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

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

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

history2 = model2.fit(train_data, train_target,
                      epochs=500, batch_size=1000,
                      callbacks=[callback],
                      verbose=2,
                      validation_split=0.01,
                      shuffle=False)

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

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

# Модель 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) 
                         ])

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

Структура рекуррентной нейронной сети без использования полносвязных слоёв

Структура рекуррентной нейронной сети без использования полносвязных слоёв

Компилирование и обучение модели мы осуществляем с теми же параметрами, как и все предыдущие модели.

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

history4 = model4.fit(train_data, train_target,
                      epochs=500, batch_size=1000,
                      callbacks=[callback],
                      verbose=2,
                      validation_split=0.01,
                      shuffle=False)

Во второй рекуррентной модели для создания тензора исходных данных второму LSTM-слою мы переформатировали тензор результатов предыдущего слоя. Библиотека Keras дает нам возможность и другого варианта. Мы можем в первом LSTM-слое указать параметр return_sequences=True, который переводит рекуррентный слой в режим работы с выводом результатов на каждой итерации. В результате такого действия, на выходе рекуррентного слоя мы сразу получаем трехмерный тензор формата [batch, timesteps, feature]. Это позволит нам отказаться от переформатирования данных перед вторым рекуррентным слоем.

# Модель LSTM блок без полносвязных слоев
model5 = 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=True),
# 2-й выдает результат вместо полносвязного слоя
                           keras.layers.LSTM(targerts) 
                         ])

Структура рекуррентной нейронной сети без использования полносвязных слоёв

Структура рекуррентной нейронной сети без использования полносвязных слоёв

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

Дополним блок построения графиков новыми моделями.

# отрисовка результатов обучения моделей
plt.figure()
plt.plot(history1.history['loss'], label='Perceptron train')
plt.plot(history1.history['val_loss'], label='Perceptron validation')
plt.plot(history3.history['loss'], label='Conv2D train')
plt.plot(history3.history['val_loss'], label='Conv2D validation')
plt.plot(history2.history['loss'], label='LSTM train')
plt.plot(history2.history['val_loss'], label='LSTM 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='LSTM sequences train')
plt.plot(history5.history['val_loss'], label='LSTM sequences validation')
plt.ylabel('$MSE$ $loss$')
plt.xlabel('$Epochs$')
plt.title('Model training dynamics')
plt.legend(loc='upper right', ncol=2)

plt.figure()
plt.plot(history1.history['accuracy'], label='Perceptron train')
plt.plot(history1.history['val_accuracy'], label='Perceptron validation')
plt.plot(history3.history['accuracy'], label='Conv2D train')
plt.plot(history3.history['val_accuracy'], label='Conv2D validation')
plt.plot(history2.history['accuracy'], label='LSTM train')
plt.plot(history2.history['val_accuracy'], label='LSTM 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='LSTM sequences train')
plt.plot(history5.history['val_accuracy'], label='LSTM sequences validation')
plt.ylabel('$Accuracy$')
plt.xlabel('$Epochs$')
plt.title('Model training dynamics')
plt.legend(loc='lower right', ncol=2)

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

# Проверка результатов моделей на тестовой выборке
test_loss1, test_acc1 = model1.evaluate(test_data, test_target, verbose=2
test_loss2, test_acc2 = model2.evaluate(test_data, test_target, verbose=2
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

print('LSTM model')
print('Test accuracy:', test_acc2)
print('Test loss:', test_loss2)

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

print('LSTM sequences model')
print('Test accuracy:', test_acc5)
print('Test loss:', test_loss5)

В данном разделе мы подготовили скрипт на языке Python, в котором создается в общей сложности 5 моделей нейронных сетей:

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

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