Решение проблем с последовательностью с помощью LSTM в Keras

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

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

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

Было доказано, что рекуррентные нейронные сети (RNN) эффективно решают проблемы с последовательностью. В частности, сеть долгосрочной краткосрочной памяти (LSTM), которая является разновидностью RNN, в настоящее время используется в различных областях для решения проблем последовательности.

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

Проблемы последовательности можно в общих чертах разделить на следующие категории:

  1. Один к одному: где есть один вход и один выход. Типичный пример проблем с однозначной последовательностью - это случай, когда у вас есть изображение, и вы хотите предсказать одну метку для изображения.
  2. Многие-к-одному: в задачах последовательности многие-к-одному у нас есть последовательность данных в качестве входных данных, и мы должны предсказать единственный выход. Классификация текста - это яркий пример проблем с последовательностью «многие к одному», когда у нас есть входная последовательность слов и мы хотим предсказать единственный выходной тег.
  3. Один ко многим: в задачах последовательности один ко многим у нас есть один вход и последовательность выходов. Типичный пример - изображение и соответствующее ему описание.
  4. Многие-ко- многим. Задачи последовательности «многие-ко-многим» включают в себя ввод последовательности и вывод последовательности. Например, цены акций за 7 дней в качестве входных данных и цены акций следующих 7 дней в качестве выходных данных. Чат-боты также являются примером проблем с последовательностью «многие ко многим», когда текстовая последовательность является входом, а другая текстовая последовательность - выходом.

Эта статья является частью первой из серии. В этой статье мы увидим, как LSTM и его различные варианты могут использоваться для решения задач однозначной и многозначной последовательности. В следующей части этой серии мы увидим, как решать задачи последовательности «один ко многим» и «многие ко многим». Мы будем работать с библиотекой Python Keras.

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

Задачи однозначной последовательности

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

Проблемы последовательности один-к-одному с одним признаком

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

Давайте сначала импортируем необходимые библиотеки, которые мы собираемся использовать в этой статье:

 from numpy import array 
 from keras.preprocessing.text import one_hot 
 from keras.preprocessing.sequence import pad_sequences 
 from keras.models import Sequential 
 from keras.layers.core import Activation, Dropout, Dense 
 from keras.layers import Flatten, LSTM 
 from keras.layers import GlobalMaxPooling1D 
 from keras.models import Model 
 from keras.layers.embeddings import Embedding 
 from sklearn.model_selection import train_test_split 
 from keras.preprocessing.text import Tokenizer 
 from keras.layers import Input 
 from keras.layers.merge import Concatenate 
 from keras.layers import Bidirectional 
 
 import pandas as pd 
 import numpy as np 
 import re 
 
 import matplotlib.pyplot as plt 
Создание набора данных

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

 X = list() 
 Y = list() 
 X = [x+1 for x in range(20)] 
 Y = [y * 15 for y in X] 
 
 print(X) 
 print(Y) 

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

 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] 
 [15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240, 255, 270, 285, 300] 

Вход в слой LSTM должен иметь трехмерную форму, т.е. (образцы, временные шаги, функции). Выборки - это количество выборок во входных данных. У нас на входе 20 сэмплов. Временные шаги - это количество временных шагов на выборку. У нас есть 1 временной шаг. Наконец, функции соответствуют количеству функций на временном шаге. У нас есть одна функция на каждый временной шаг.

Мы можем изменить форму наших данных с помощью следующей команды:

 X = array(X).reshape(20, 1, 1) 
Решение через Simple LSTM

Теперь мы можем создать нашу простую модель LSTM с одним слоем LSTM.

 model = Sequential() 
 model.add(LSTM(50, activation='relu', input_shape=(1, 1))) 
 model.add(Dense(1)) 
 model.compile(optimizer='adam', loss='mse') 
 print(model.summary()) 

В приведенном выше скрипте мы создаем модель LSTM с одним слоем LSTM из 50 нейронов и relu активации. Вы можете видеть, что форма ввода - (1,1), поскольку наши данные имеют один временной шаг с одной функцией. При выполнении вышеуказанного сценария печатается следующая сводка:

 Layer (type) Output Shape Param # 
 ================================================================= 
 lstm_16 (LSTM) (None, 50) 10400 
 _________________________________________________________________ 
 dense_15 (Dense) (None, 1) 51 
 ================================================================= 
 Total params: 10,451 
 Trainable params: 10,451 
 Non-trainable params: 0 

Теперь обучим нашу модель:

 model.fit(X, Y, epochs=2000, validation_split=0.2, batch_size=5) 

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

Допустим, мы хотим спрогнозировать выход для входа 30. Фактический выход должен быть 30 x 15 = 450. Давайте посмотрим, какое значение мы получим. Во-первых, нам нужно преобразовать наши тестовые данные в правильную форму, то есть в 3D-форму, как и ожидалось LSTM. Следующий скрипт предсказывает вывод числа 30:

 test_input = array([30]) 
 test_input = test_input.reshape((1, 1, 1)) 
 test_output = model.predict(test_input, verbose=0) 
 print(test_output) 

Я получил выходное значение 437.86 что немного меньше 450.

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

Решение через Stacked LSTM

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

 model = Sequential() 
 model.add(LSTM(50, activation='relu', return_sequences=True, input_shape=(1, 1))) 
 model.add(LSTM(50, activation='relu')) 
 model.add(Dense(1)) 
 model.compile(optimizer='adam', loss='mse') 
 print(model.summary()) 

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

 _________________________________________________________________ 
 Layer (type) Output Shape Param # 
 ================================================================= 
 lstm_33 (LSTM) (None, 1, 50) 10400 
 _________________________________________________________________ 
 lstm_34 (LSTM) (None, 50) 20200 
 _________________________________________________________________ 
 dense_24 (Dense) (None, 1) 51 
 ================================================================= 
 Total params: 30,651 
 Trainable params: 30,651 
 Non-trainable params: 0 
 ________________________ 

Затем нам нужно обучить нашу модель, как показано в следующем скрипте:

 history = model.fit(X, Y, epochs=2000, validation_split=0.2, verbose=1, batch_size=5) 

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

 test_input = array([30]) 
 test_input = test_input.reshape((1, 1, 1)) 
 test_output = model.predict(test_input, verbose=0) 
 print(test_output) 

Я получил результат 459,85, что лучше, чем 437, число, которое мы достигли с помощью одного уровня LSTM.

Проблемы последовательности один-к-одному с несколькими функциями

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

Создание набора данных

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

 nums = 25 
 
 X1 = list() 
 X2 = list() 
 X = list() 
 Y = list() 
 
 X1 = [(x+1)*2 for x in range(25)] 
 X2 = [(x+1)*3 for x in range(25)] 
 Y = [x1*x2 for x1,x2 in zip(X1,X2)] 
 
 print(X1) 
 print(X2) 
 print(Y) 

В приведенном выше скрипте мы создаем три списка: X1 , X2 и Y Каждый список состоит из 25 элементов, что означает, что общий размер выборки равен 25. Наконец, Y содержит результат. X1 , X2 и Y напечатаны ниже:

 [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50] 
 [3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75] 
 [6, 24, 54, 96, 150, 216, 294, 384, 486, 600, 726, 864, 1014, 1176, 1350, 1536, 1734, 1944, 2166, 2400, 2646, 2904, 3174, 3456, 3750] 

Каждый элемент в выходном списке, по сути, является продуктом соответствующих элементов в списках X1 и X2 Например, вторым элементом в выходном списке является 24, который является произведением второго элемента в списке X1 т. Е. 4, и второго элемента в списке X2 т. Е. 6.

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

 X = np.column_stack((X1, X2)) 
 print(X) 

Вот результат:

 [[ 2 3] 
 [ 4 6] 
 [ 6 9] 
 [ 8 12] 
 [10 15] 
 [12 18] 
 [14 21] 
 [16 24] 
 [18 27] 
 [20 30] 
 [22 33] 
 [24 36] 
 [26 39] 
 [28 42] 
 [30 45] 
 [32 48] 
 [34 51] 
 [36 54] 
 [38 57] 
 [40 60] 
 [42 63] 
 [44 66] 
 [46 69] 
 [48 72] 
 [50 75]] 

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

 X = array(X).reshape(25, 1, 2) 
Решение через Simple LSTM

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

 model = Sequential() 
 model.add(LSTM(80, activation='relu', input_shape=(1, 2))) 
 model.add(Dense(10, activation='relu')) 
 model.add(Dense(1)) 
 model.compile(optimizer='adam', loss='mse') 
 print(model.summary()) 

Здесь наш слой LSTM содержит 80 нейронов. У нас есть два плотных слоя, где первый слой содержит 10 нейронов, а второй плотный слой, который также действует как выходной слой, содержит 1 нейрон. Краткое изложение модели выглядит следующим образом:

 Layer (type) Output Shape Param # 
 ================================================================= 
 lstm_38 (LSTM) (None, 80) 26560 
 _________________________________________________________________ 
 dense_29 (Dense) (None, 10) 810 
 _________________________________________________________________ 
 dense_30 (Dense) (None, 1) 11 
 ================================================================= 
 Total params: 27,381 
 Trainable params: 27,381 
 Non-trainable params: 0 
 _________________________________________________________________ 
 None 

Следующий скрипт обучает модель:

 model.fit(X, Y, epochs=2000, validation_split=0.2, batch_size=5) 

Давайте протестируем нашу обученную модель на новой точке данных. Наша точка данных будет иметь две особенности, то есть (55,80) фактический результат должен быть 55 x 80 = 4400. Давайте посмотрим, что предсказывает наш алгоритм. Выполните следующий скрипт:

 test_input = array([55,80]) 
 test_input = test_input.reshape((1, 1, 2)) 
 test_output = model.predict(test_input, verbose=0) 
 print(test_output) 

Я получил 3263,44 на выходе, что далеко от фактического результата.

Решение через Stacked LSTM

Давайте теперь создадим более сложный LSTM с несколькими LSTM и плотными слоями и посмотрим, сможем ли мы улучшить наш ответ:

 model = Sequential() 
 model.add(LSTM(200, activation='relu', return_sequences=True, input_shape=(1, 2))) 
 model.add(LSTM(100, activation='relu', return_sequences=True)) 
 model.add(LSTM(50, activation='relu', return_sequences=True)) 
 model.add(LSTM(25, activation='relu')) 
 model.add(Dense(20, activation='relu')) 
 model.add(Dense(10, activation='relu')) 
 model.add(Dense(1)) 
 model.compile(optimizer='adam', loss='mse') 
 print(model.summary()) 

Краткое описание модели выглядит следующим образом:

 _________________________________________________________________ 
 Layer (type) Output Shape Param # 
 ================================================================= 
 lstm_53 (LSTM) (None, 1, 200) 162400 
 _________________________________________________________________ 
 lstm_54 (LSTM) (None, 1, 100) 120400 
 _________________________________________________________________ 
 lstm_55 (LSTM) (None, 1, 50) 30200 
 _________________________________________________________________ 
 lstm_56 (LSTM) (None, 25) 7600 
 _________________________________________________________________ 
 dense_43 (Dense) (None, 20) 520 
 _________________________________________________________________ 
 dense_44 (Dense) (None, 10) 210 
 _________________________________________________________________ 
 dense_45 (Dense) (None, 1) 11 
 ================================================================= 
 Total params: 321,341 
 Trainable params: 321,341 
 Non-trainable params: 0 

Следующий шаг - обучить нашу модель и протестировать ее на контрольной точке данных, то есть (55,80).

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

 history = model.fit(X, Y, epochs=1000, validation_split=0.1, verbose=1, batch_size=3) 
 
 test_output = model.predict(test_input, verbose=0) 
 print(test_output) 

На выходе я получил значение 3705,33, которое все еще меньше 4400, но намного лучше, чем ранее полученное значение 3263,44 с использованием одного слоя LSTM. Вы можете поиграть с различными комбинациями слоев LSTM, плотных слоев, размера пакета и количества эпох, чтобы увидеть, получите ли вы лучшие результаты.

Проблемы последовательности "многие к одному"

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

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

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

Проблемы последовательности "многие к одному" с одним признаком

Давайте сначала создадим набор данных. Наш набор данных будет состоять из 15 образцов. Каждый образец будет иметь 3 временных шага, где каждый временной шаг будет состоять из одной функции, то есть числа. Результатом для каждой выборки будет сумма чисел на каждом из трех временных шагов. Например, если наш образец содержит последовательность 4,5,6, на выходе будет 4 + 5 + 6 = 10.

Создание набора данных

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

 X = np.array([x+1 for x in range(45)]) 
 print(X) 

На выходе вы должны увидеть первые 45 целых чисел:

 [ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 
 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45] 

Мы можем преобразовать его в количество сэмплов, временных шагов и функций, используя следующую функцию:

 X = X.reshape(15,3,1) 
 print(X) 

Приведенный выше сценарий преобразует список X в трехмерную форму с 15 выборками, 3 временными шагами и 1 функцией. Приведенный выше сценарий также распечатывает измененные данные.

 [[[ 1] 
 [ 2] 
 [ 3]] 
 
 [[ 4] 
 [ 5] 
 [ 6]] 
 
 [[ 7] 
 [ 8] 
 [ 9]] 
 
 [[10] 
 [11] 
 [12]] 
 
 [[13] 
 [14] 
 [15]] 
 
 [[16] 
 [17] 
 [18]] 
 
 [[19] 
 [20] 
 [21]] 
 
 [[22] 
 [23] 
 [24]] 
 
 [[25] 
 [26] 
 [27]] 
 
 [[28] 
 [29] 
 [30]] 
 
 [[31] 
 [32] 
 [33]] 
 
 [[34] 
 [35] 
 [36]] 
 
 [[37] 
 [38] 
 [39]] 
 
 [[40] 
 [41] 
 [42]] 
 
 [[43] 
 [44] 
 [45]]] 

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

 Y = list() 
 for x in X: 
 Y.append(x.sum()) 
 
 Y = np.array(Y) 
 print(Y) 

Выходной массив Y выглядит так:

 [ 6 15 24 33 42 51 60 69 78 87 96 105 114 123 132] 
Решение через Simple LSTM

Давайте теперь создадим нашу модель с одним слоем LSTM.

 model = Sequential() 
 model.add(LSTM(50, activation='relu', input_shape=(3, 1))) 
 model.add(Dense(1)) 
 model.compile(optimizer='adam', loss='mse') 

Следующий скрипт обучает нашу модель:

 history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1) 

После обучения модели мы можем использовать ее для прогнозирования контрольных точек данных. Давайте спрогнозируем результат для числовой последовательности 50,51,52. Фактический результат должен быть 50 + 51 + 52 = 153. Следующий скрипт преобразует наши тестовые точки в трехмерную форму, а затем прогнозирует результат:

 test_input = array([50,51,52]) 
 test_input = test_input.reshape((1, 3, 1)) 
 test_output = model.predict(test_input, verbose=0) 
 print(test_output) 

Я получил 145,96 на выходе, что примерно на 7 пунктов меньше фактического выходного значения 153.

Решение через Stacked LSTM

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

 model = Sequential() 
 model.add(LSTM(200, activation='relu', return_sequences=True, input_shape=(3, 1))) 
 model.add(LSTM(100, activation='relu', return_sequences=True)) 
 model.add(LSTM(50, activation='relu', return_sequences=True)) 
 model.add(LSTM(25, activation='relu')) 
 model.add(Dense(20, activation='relu')) 
 model.add(Dense(10, activation='relu')) 
 model.add(Dense(1)) 
 model.compile(optimizer='adam', loss='mse') 
 
 history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1) 

Давайте теперь протестируем нашу модель на тестовой последовательности, то есть 50, 51, 52:

 test_output = model.predict(test_input, verbose=0) 
 print(test_output) 

Ответ, который я получил, - 155,37, что лучше, чем результат 145,96, который мы получили ранее. В этом случае у нас есть разница всего в 2 балла от 153, что является фактическим ответом.

Решение через двунаправленный LSTM

Двунаправленный LSTM

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

Следующий скрипт создает двунаправленную модель LSTM с одним двунаправленным слоем и одним плотным слоем, который действует как выходные данные модели.

 from keras.layers import Bidirectional 
 
 model = Sequential() 
 model.add(Bidirectional(LSTM(50, activation='relu'), input_shape=(3, 1))) 
 model.add(Dense(1)) 
 model.compile(optimizer='adam', loss='mse') 

Следующий скрипт обучает модель и делает прогнозы для тестовой последовательности, равной 50, 51 и 52.

 history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1) 
 test_output = model.predict(test_input, verbose=0) 
 print(test_output) 

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

Проблемы последовательности "многие к одному" с несколькими функциями

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

Создание набора данных

Наш набор данных будет содержать 15 образцов. Каждый сэмпл будет состоять из 3-х временных шагов. Каждый временной шаг будет иметь две особенности.

Создадим два списка. Один будет содержать кратные от 3 до 135, т.е. всего 45 элементов. Второй список будет содержать числа, кратные 5, от 1 до 225. Второй список также будет содержать всего 45 элементов. Следующий скрипт создает эти два списка:

 X1 = np.array([x+3 for x in range(0, 135, 3)]) 
 print(X1) 
 
 X2 = np.array([x+5 for x in range(0, 225, 5)]) 
 print(X2) 

Вы можете увидеть содержимое списка в следующем выводе:

 [ 3 6 9 12 15 18 21 24 27 30 33 36 39 42 45 48 51 54 
 57 60 63 66 69 72 75 78 81 84 87 90 93 96 99 102 105 108 
 111 114 117 120 123 126 129 132 135] 
 [ 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 
 95 100 105 110 115 120 125 130 135 140 145 150 155 160 165 170 175 180 
 185 190 195 200 205 210 215 220 225] 

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

 X = np.column_stack((X1, X2)) 
 print(X) 

Выходные данные показывают агрегированный набор данных:

 [ 6 10] 
 [ 9 15] 
 [ 12 20] 
 [ 15 25] 
 [ 18 30] 
 [ 21 35] 
 [ 24 40] 
 [ 27 45] 
 [ 30 50] 
 [ 33 55] 
 [ 36 60] 
 [ 39 65] 
 [ 42 70] 
 [ 45 75] 
 [ 48 80] 
 [ 51 85] 
 [ 54 90] 
 [ 57 95] 
 [ 60 100] 
 [ 63 105] 
 [ 66 110] 
 [ 69 115] 
 [ 72 120] 
 [ 75 125] 
 [ 78 130] 
 [ 81 135] 
 [ 84 140] 
 [ 87 145] 
 [ 90 150] 
 [ 93 155] 
 [ 96 160] 
 [ 99 165] 
 [102 170] 
 [105 175] 
 [108 180] 
 [111 185] 
 [114 190] 
 [117 195] 
 [120 200] 
 [123 205] 
 [126 210] 
 [129 215] 
 [132 220] 
 [135 225]] 

Нам нужно преобразовать наши данные в три измерения, чтобы их мог использовать LSTM. Всего у нас 45 строк и два столбца в нашем наборе данных. Мы изменим наш набор данных на 15 образцов, 3 временных шага и две функции.

 X = array(X).reshape(15, 3, 2) 
 print(X) 

Вы можете увидеть 15 образцов в следующем выводе:

 [[[ 3 5] 
 [ 6 10] 
 [ 9 15]] 
 
 [[ 12 20] 
 [ 15 25] 
 [ 18 30]] 
 
 [[ 21 35] 
 [ 24 40] 
 [ 27 45]] 
 
 [[ 30 50] 
 [ 33 55] 
 [ 36 60]] 
 
 [[ 39 65] 
 [ 42 70] 
 [ 45 75]] 
 
 [[ 48 80] 
 [ 51 85] 
 [ 54 90]] 
 
 [[ 57 95] 
 [ 60 100] 
 [ 63 105]] 
 
 [[ 66 110] 
 [ 69 115] 
 [ 72 120]] 
 
 [[ 75 125] 
 [ 78 130] 
 [ 81 135]] 
 
 [[ 84 140] 
 [ 87 145] 
 [ 90 150]] 
 
 [[ 93 155] 
 [ 96 160] 
 [ 99 165]] 
 
 [[102 170] 
 [105 175] 
 [108 180]] 
 
 [[111 185] 
 [114 190] 
 [117 195]] 
 
 [[120 200] 
 [123 205] 
 [126 210]] 
 
 [[129 215] 
 [132 220] 
 [135 225]]] 

На выходе также будет 15 значений, соответствующих 15 входным отсчетам. Каждое значение в выходных данных будет суммой двух значений характеристик на третьем временном шаге каждой входной выборки. Например, третий временной шаг первой выборки имеет признаки 9 и 15, следовательно, на выходе будет 24. Точно так же два значения признаков на третьем временном шаге второй выборки - 18 и 30; соответствующий выход будет 48 и так далее.

Следующий скрипт создает и отображает выходной вектор:

 [ 24 48 72 96 120 144 168 192 216 240 264 288 312 336 360] 

Давайте теперь решим эту проблему последовательности "многие к одному" с помощью простых, составных и двунаправленных LSTM.

Решение через Simple LSTM
 model = Sequential() 
 model.add(LSTM(50, activation='relu', input_shape=(3, 2))) 
 model.add(Dense(1)) 
 model.compile(optimizer='adam', loss='mse') 
 history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1) 

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

 test_input = array([[8, 51], 
 [11,56], 
 [14,61]]) 
 
 test_input = test_input.reshape((1, 3, 2)) 
 test_output = model.predict(test_input, verbose=0) 
 print(test_output) 

Сумма двух характеристик третьего временного шага входных данных составляет 14 + 61 = 75. Наша модель с одним слоем LSTM предсказала 73,41, что довольно близко.

Решение через Stacked LSTM

Следующий скрипт обучает составной LSTM и делает прогнозы в контрольной точке:

 model = Sequential() 
 model.add(LSTM(200, activation='relu', return_sequences=True, input_shape=(3, 2))) 
 model.add(LSTM(100, activation='relu', return_sequences=True)) 
 model.add(LSTM(50, activation='relu', return_sequences=True)) 
 model.add(LSTM(25, activation='relu')) 
 model.add(Dense(20, activation='relu')) 
 model.add(Dense(10, activation='relu')) 
 model.add(Dense(1)) 
 model.compile(optimizer='adam', loss='mse') 
 
 history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1) 
 
 test_output = model.predict(test_input, verbose=0) 
 print(test_output) 

Я получил результат 71,56, что хуже, чем у простого LSTM. Похоже, наш сложенный LSTM переоснащается.

Решение через двунаправленный LSTM

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

 from keras.layers import Bidirectional 
 
 model = Sequential() 
 model.add(Bidirectional(LSTM(50, activation='relu'), input_shape=(3, 2))) 
 model.add(Dense(1)) 
 model.compile(optimizer='adam', loss='mse') 
 
 history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1) 
 test_output = model.predict(test_input, verbose=0) 
 print(test_output) 

Результат - 76,82, что довольно близко к 75. И снова двунаправленный LSTM, похоже, превосходит остальные алгоритмы.

До сих пор мы прогнозировали единичные значения на основе значений нескольких характеристик для разных временных шагов. Есть еще один случай последовательностей «многие к одному», когда вы хотите предсказать одно значение для каждой функции на временном шаге. Например, набор данных, который мы использовали в этом разделе, имеет три временных шага, и каждый временной шаг имеет две особенности. Мы можем захотеть предсказать индивидуальную ценность для каждой серии функций. Следующий пример проясняет это, предположим, что у нас есть следующие входные данные:

 [[[ 3 5] 
 [ 6 10] 
 [ 9 15]] 

На выходе нам нужен один временной шаг с двумя функциями, как показано ниже:

 [12, 20] 

Вы можете видеть, что первое значение в выходных данных является продолжением первой серии, а второе значение - продолжением второй серии. Мы можем решить такие проблемы, просто изменив количество нейронов в выходном плотном слое на количество значений функций, которые мы хотим в выходных данных. Однако сначала нам нужно обновить наш выходной вектор Y Входной вектор останется прежним:

 Y = list() 
 for x in X: 
 new_item = list() 
 new_item.append(x[2][0]+3) 
 new_item.append(x[2][1]+5) 
 Y.append(new_item) 
 
 Y = np.array(Y) 
 print(Y) 

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

 [[ 12 20] 
 [ 21 35] 
 [ 30 50] 
 [ 39 65] 
 [ 48 80] 
 [ 57 95] 
 [ 66 110] 
 [ 75 125] 
 [ 84 140] 
 [ 93 155] 
 [102 170] 
 [111 185] 
 [120 200] 
 [129 215] 
 [138 230]] 

Давайте теперь обучим наши простые, составные и двунаправленные сети LSTM на нашем наборе данных. Следующий скрипт обучает простой LSTM:

 model = Sequential() 
 model.add(LSTM(50, activation='relu', input_shape=(3, 2))) 
 model.add(Dense(2)) 
 model.compile(optimizer='adam', loss='mse') 
 
 history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1) 

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

 test_input = array([[20,34], 
 [23,39], 
 [26,44]]) 
 
 test_input = test_input.reshape((1, 3, 2)) 
 test_output = model.predict(test_input, verbose=0) 
 print(test_output) 

Фактический результат - [29, 45]. Наша модель предсказывает [29.089157, 48.469097], что довольно близко.

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

 model = Sequential() 
 model.add(LSTM(100, activation='relu', return_sequences=True, input_shape=(3, 2))) 
 model.add(LSTM(50, activation='relu', return_sequences=True)) 
 model.add(LSTM(25, activation='relu')) 
 model.add(Dense(10, activation='relu')) 
 model.add(Dense(2)) 
 model.compile(optimizer='adam', loss='mse') 
 
 history = model.fit(X, Y, epochs=500, validation_split=0.2, verbose=1) 
 
 test_output = model.predict(test_input, verbose=0) 
 print(test_output) 

Результатом будет [29.170143, 48.688267], что опять же очень близко к фактическому результату.

Наконец, мы можем обучить наш двунаправленный LSTM и сделать прогноз на тестовой точке:

 from keras.layers import Bidirectional 
 
 model = Sequential() 
 model.add(Bidirectional(LSTM(50, activation='relu'), input_shape=(3, 2))) 
 model.add(Dense(2)) 
 model.compile(optimizer='adam', loss='mse') 
 
 history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1) 
 test_output = model.predict(test_input, verbose=0) 
 print(test_output) 

Результатом будет [29.2071, 48.737988].

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

Заключение

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

В этой статье мы увидели, как можно использовать разные варианты алгоритма LSTM для решения задач однозначной и многозначной последовательности. Это первая часть статьи. Во второй части мы увидим, как решать задачи последовательности «один ко многим» и «многие ко многим». Мы также изучим механизм кодировщика-декодера, который чаще всего используется для создания чат-ботов. А пока удачного кодирования :)

comments powered by Disqus