Рубрики
Без рубрики

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

Автор оригинала: Usman Malik.

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

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

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

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

Типы задач последовательности

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

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

Эта статья является частью 1 серии. В этой статье мы увидим, как 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 должны быть в 3D-форме, т. е. (образцы, временные шаги, объекты). Выборки – это количество выборок во входных данных. У нас есть 20 образцов на входе. Временные шаги-это количество временных шагов на выборку. У нас есть 1 временной шаг. Наконец, функции соответствуют количеству функций на каждом временном шаге. У нас есть одна функция на каждый временной шаг.

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

X = array(X).reshape(20, 1, 1)
Решение с помощью простого 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. Давайте посмотрим, какое значение мы получим. Во-первых, нам нужно преобразовать наши тестовые данные в правильную форму, то есть 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 , выход скрытого состояния каждого нейрона используется в качестве входа в следующий слой 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 содержит наш окончательный набор функций. Вы можете видеть, что он содержит два столбца, то есть два объекта на вход. Как мы уже обсуждали ранее, нам нужно преобразовать входные данные в 3-мерную форму. Наш входной сигнал имеет 25 выборок, где каждая выборка состоит из 1 временного шага и каждый временной шаг состоит из 2 признаков. Следующий сценарий изменяет входные данные.

X = array(X).reshape(25, 1, 2)
Решение с помощью простого 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. Давайте посмотрим, что предсказывает наш алгоритм. Выполните следующий сценарий:

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 +.

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

Давайте сначала создадим список целых чисел от 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 в 3-мерную форму с 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]
Решение с помощью простого 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 +. Следующий сценарий преобразует наши тестовые точки в 3-мерную форму, а затем предсказывает результат:

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. Аналогично, два значения признаков на третьем временном шаге 2-го образца равны 18 и 30; соответствующий выход будет равен 48 и так далее.

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

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

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

Решение с помощью простого 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 +. Наша модель с одним слоем 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 могут быть использованы для решения задач последовательности один-к-одному и много-к-одному. Это первая часть статьи. Во второй части мы увидим , как решать задачи последовательности “один ко многим” и “многие ко многим”. Мы также изучим механизм кодирования-декодирования, который чаще всего используется для создания чат-ботов. До тех пор счастливого кодирования:)