Сети с долговременной кратковременной памятью (LSTM): концепция, архитектура и реализация!

Прежде чем мы перейдем к LSTM, давайте сначала разберемся с рекуррентными нейронными сетями или, лучше сказать, с Vanilla RNN, учитывая все последние достижения. Я предполагаю, что вы уже знакомы с основами моделей нейронных сетей и понимаете, как работает простая сеть прямого распространения. RNN — это основное оружие, которое нужно иметь в своем арсенале при решении проблем с последовательными данными. Итак, что такое последовательные проблемы с данными! Ну, с точки зрения непрофессионала — Задачи, в которых мы можем взять один или несколько наборов входных данных и должны сгенерировать один или несколько выходных данных, известны как задачи моделирования последовательности. Немногие из популярных связаны с прогнозированием одномерных и многомерных временных рядов, машинным переводом, генерацией текста, распознаванием голоса, созданием подписей к изображениям и т. д.

Чем RNN отличается от сети прямого распространения?

RNN имеют преимущество перед другими методологиями Feed Neural Network, когда данные содержат зависимые наблюдения (строки), что означает, что каждое наблюдение в подготовленном наборе данных имеет больше смысла или, лучше сказать, выделяет контекст при просмотре вместе с предыдущей или следующей доступной информацией. в последовательности; Сильный контекст — это то, чего пытается достичь каждая из последовательных моделей.

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

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

Чем LSTM отличается от RNN?

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

Это можно решить разными способами, например, изменением функции активации, отсечением градиентов, тщательной инициализацией веса, усеченным обратным распространением во времени и т. д. Gated Recurrent Units (GRU) и LSTM считаются, по крайней мере, невосприимчивыми к проблеме исчезающего градиента. У них есть шлюзы памяти, которые помогают поддерживать контекст в длинных предложениях, но все же, если веса ненормально высоки и стремительно растут, то даже эти шлюзы памяти ничего не могут сделать, чтобы решить эту проблему. Проще говоря, просто для лучшего понимания семантического контекста.

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

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

  1. Сложность проблемы и последовательности
  2. Характер бизнес-проблемы (доступные входы и ожидаемый результат)

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

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

  1. Простая модель LSTM
  2. Сложенная модель LSTM
  3. Двунаправленный LSTM (BiLSTM)

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

Ниже приведен базовый пример построения простой сети LSTM:

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

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

Ниже приведена базовая многоуровневая сеть 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')
plot_model(model, show_shapes=True)

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

model = Sequential()
model.add(Bidirectional(LSTM(50, activation='relu'), input_shape=(3, 2)))
model.add(Dense(2))
model.compile(optimizer='adam', loss='mse')
plot_model(model, show_shapes=True)

Классификация на основе характера бизнес-проблемы (доступные входные данные и ожидаемая длина выходных данных)

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

  1. Проблемы последовательности «один к одному»
  2. Проблемы последовательности «многие к одному»
  3. Проблемы последовательности «один ко многим»
  4. Проблемы последовательностей «многие ко многим»

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

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

Выборка, размер партии и функции имеют то же определение, что и в сетях с прямой связью. Образец – это отдельный обучающий пример; Размер пакета – это набор образцов, предоставляемых сети за один раз. Функции обозначают входные данные (параметры), доступные для одного наблюдения. Временной шаг представляет одну полную последовательность; обычно в случае Таймсерий это наименьший цикл, а в НЛП это может быть символ, слово или предложение.

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

1. Задача последовательности «один к одному»

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

1.a — Проблемы с одной функцией

Создайте набор данных

X — набор поездов содержит 20 выборок значений от 1 до 20.
Y — X * 15

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

print(f'X: {X}')
print(f'Y: {Y}')
# since it's one-to-one model, so timesteps = features = 1, So
# (samples, timesteps, features)
X = array(X).reshape(20, 1, 1)
# Output
# X: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, # 19, 20]
# Y: [15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240, 255, 270, 285, 300]

1.a.i — простое решение LSTM:

model = Sequential()
# LSTM input shape should be (time_steps, features)
model.add(LSTM(50, activation='relu', input_shape=(1, 1)))
# output
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')

plot_model(model, show_shapes=True)

# Train
model.fit(X, np.asarray(Y), epochs=EPOCHS, validation_split=0.2, batch_size=5)
# Test set prediction
test_input = array([30])
test_input = test_input.reshape((1, 1, 1))
test_output = model.predict(test_input, verbose=0)
print(f'Actual:{30*15}, Prediction:{test_output}')
# Output:
# Actual:450, Prediction:[[381.64233]]

1.a.ii — решение Stacked LSTM:

model = Sequential()
# We specify an additional layer of LSTM which returns the sequence of outputs for next layer
model.add(LSTM(50, activation='relu', return_sequences=True, input_shape=(1, 1)))
# LSTM input shape should be (time_steps, features)
model.add(LSTM(50, activation='relu'))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')
plot_model(model, show_shapes=True)

# Train
model.fit(X, np.asarray(Y), epochs=EPOCHS, validation_split=0.2, verbose=1, batch_size=5)
# Predict
test_output = model.predict(test_input, verbose=0)
print(f'Actual:{30*15}, Prediction:{test_output}')
# Output
Actual:450, Prediction:[[479.5048]]

1.b — Проблема с несколькими функциями

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

Создайте набор данных

Образцы — 25
X1 — кратные 2 до 50
X2 — кратные 3 до 75
Y — X1 * X2

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(f'X1: {X1}')
print(f'X2: {X2}')
print(f'Y: {Y}')
# Output
# X1: [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]
# 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]
# Y: [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]
X = np.column_stack((X1, X2))
print(f'X: {X}')
# Output
# [[ 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]]
# (sample, timesteps, features=2)
X = array(X).reshape(25, 1, 2)

1.b.i — простое решение LSTM

model = Sequential()
# LSTM input shape should be (time_steps, features)
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')
plot_model(model, show_shapes=True)

# Train
model.fit(X, np.asanyarray(Y), epochs=EPOCHS, validation_split=0.2, batch_size=5)
# construct test
test_input = array([55,80])
test_input = test_input.reshape((1, 1, 2))
# Test
test_output = model.predict(test_input, verbose=0)
print(f'Actual:{55*80}, Prediction:{test_output}')
# Output
# Actual:4400, Prediction:[[3038.4048]]

1.b.ii — решение 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')
plot_model(model, show_shapes=True)

# Train
model.fit(X, np.asanyarray(Y), epochs=EPOCHS, validation_split=0.2, verbose=1, batch_size=3)
# Predict
test_output = model.predict(test_input, verbose=0)
print(f'Actual:{55*80}, Prediction:{test_output}')
# Output
# Actual:4400, Prediction:[[3759.111]]

2. Задача последовательности «многие к одному»

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

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

2.a — Проблема с одной функцией

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

выборки - 15
X - непрерывное число от 1 до 45, где каждая выборка содержит 3 непрерывных значения в своих временных шагах.
Y - сумма всех временных шагов/выборки

X = np.array([x+1 for x in range(45)])
print(f'X:{X}')
# Output
# [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]
# Reshape
X = X.reshape(15,3,1)
print(f'X:{X}')
# Output
# [[[ 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]]]
# Preparing target values
Y = list()
for x in X:
    Y.append(x.sum())

Y = np.array(Y)
print(f'Y:{Y}')
# Output
# Y: [  6  15  24  33  42  51  60  69  78  87  96 105 114 123 132]

2.a.i — простое решение LSTM

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

model.fit(X, Y, epochs=EPOCHS, validation_split=0.2, verbose=1)
test_input = array([50,51,52])
test_input = test_input.reshape((1, 3, 1))
test_output = model.predict(test_input, verbose=0)
print(f'Actual:{50+51+52}, Prediction:{test_output}')
# Output
# Actual:153, Prediction:[[152.71086]]

2.a.ii — многоуровневое решение 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')
plot_model(model, show_shapes=True)

model.fit(X, Y, epochs=EPOCHS, validation_split=0.2, verbose=1)
test_output = model.predict(test_input, verbose=0)
print(f'Actual:{50+51+52}, Prediction:{test_output}')
# Output
# Actual:153, Prediction:[[153.18105]]

2.a.iii — двунаправленное решение LSTM

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

model.fit(X, Y, epochs=EPOCHS, validation_split=0.2, verbose=1)
test_output = model.predict(test_input, verbose=0)
print(f'Actual:{50+51+52}, Prediction:{test_output}')
# Output
# Actual:153, Prediction:[[153.41699]]

2.b — Проблема с несколькими функциями

Создайте набор данных

Образцы - 15
X1 - кратные 3 до 135 (153)
X2 - кратные 5 до 225 (15
5)
Y - сумма признаков при 3-й временной шаг каждого образца

X1 = np.array([x+3 for x in range(0, 135, 3)])
print(f'X1:{X1}')

X2 = np.array([x+5 for x in range(0, 225, 5)])
print(f'X2:{X2}')
# Output
# X1:[  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]
# X2:[  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(f'X:{X}')
# Output - X:[[  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]]

Изменить форму входных данных

X = array(X).reshape(15, 3, 2)
print(f'X:{X}')
# Output - X: 
[[[  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]]]

Целевая конструкция

Y = list()
for x in X:
    Y.append(x[2].sum())

Y = np.array(Y)
print(f'Y:{Y}')
# Output - Y:[ 24  48  72  96 120 144 168 192 216 240 264 288 312 336 360]

2.b.i — простое решение LSTM

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

model.fit(X, Y, epochs=EPOCHS, 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(f'Actual:{14+61}, Prediction:{test_output}')
# Output
# Actual:75, Prediction:[[59.815548]]

2.b.ii — решение 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')
plot_model(model, show_shapes=True)

model.fit(X, Y, epochs=EPOCHS, validation_split=0.2, verbose=1)
test_output = model.predict(test_input, verbose=0)
print(f'Actual:{14+61}, Prediction:{test_output}')
# Output
# Actual:75, Prediction:[[76.85707]]

2.b.iii — двунаправленное решение LSTM

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

model.fit(X, Y, epochs=EPOCHS, validation_split=0.2, verbose=1)
test_output = model.predict(test_input, verbose=0)
print(f'Actual:{14+61}, Prediction:{test_output}')
# Output
# Actual:75, Prediction:[[67.3588]]

Stacked LSTM дает наилучший результат для этого. Простой LSTM и двунаправленный оба находятся в стадии прогнозирования.

3. Задача последовательности «один ко многим»

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

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

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

Образцы - 15
X - элементы, начиная с 1 с промежутком 3
Y - вектор [X+1, X+2]

X = list()
Y = list()
X = [x+3 for x in range(-2, 43, 3)]

for i in X:
    output_vector = list()
    output_vector.append(i+1)
    output_vector.append(i+2)
    Y.append(output_vector)

print(f'X:{X}')
print(f'Y:{Y}')
# Output
# X: [1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34, 37, 40, 43]
# Y: [[2, 3], [5, 6], [8, 9], [11, 12], [14, 15], [17, 18], [20, 21], [23, 24], [26, 27], [29, 30], [32, 33], [35, 36], [38, 39], [41, 42], [44, 45]]
X = np.array(X).reshape(15, 1, 1)
Y = np.array(Y)

3.a — Проблема с одной функцией

3.a.i — простое решение LSTM

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

# Train
model.fit(X, Y, epochs=EPOCHS, validation_split=0.2, verbose=1, batch_size=3)
test_input = array([10])
test_input = test_input.reshape((1, 1, 1))
test_output = model.predict(test_input, verbose=0)
print(f'Actual:{[10+1, 10+2]}, Prediction:{test_output}')
# Output
# Actual:[11, 12], Prediction:[[9.478097 9.592108]]

3.a.ii – решение Stacked 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(2))
model.compile(optimizer='adam', loss='mse')
plot_model(model, show_shapes=True)

# Train
model.fit(X, Y, epochs=EPOCHS, validation_split=0.2, verbose=1, batch_size=3)
test_output = model.predict(test_input, verbose=0)
print(f'Actual:{[10+1, 10+2]}, Prediction:{test_output}')
# Output
# Actual:[11, 12], Prediction:[[11.031942 12.171929]]

3.a.iii - Двунаправленное решение LSTM

model = Sequential()
model.add(Bidirectional(LSTM(50, activation='relu'), input_shape=(1, 1)))
model.add(Dense(2))
model.compile(optimizer='adam', loss='mse')
plot_model(model, show_shapes=True)

model.fit(X, Y, epochs=EPOCHS, validation_split=0.2, verbose=1, batch_size=3)
test_output = model.predict(test_input, verbose=0)
print(f'Actual:{[10+1, 10+2]}, Prediction:{test_output}')
# Output
# Actual:[11, 12], Prediction:[[10.954651 11.856668]]

Простой LSTM хорошо справляется с этой задачей.

3.b — Проблема с несколькими функциями

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

Создать набор данных

Образцы - 25
X1 - кратно 2
X2 - кратно 3
Y - [X1+1, X2+1]

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)]
# combine 
for x1, x2 in zip(X1, X2):
    output_vector = list()
    output_vector.append(x1+1)
    output_vector.append(x2+1)
    Y.append(output_vector)

X = np.column_stack((X1, X2))
print(f'X:{X}')
# Output
[[ 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 = np.array(X).reshape(25, 1, 2)
Y = np.array(Y)
Y.shape
# (25, 2)

3.b.i — простое решение LSTM

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

model.fit(X, Y, epochs=EPOCHS, validation_split=0.2, verbose=1, batch_size=3)
test_input = array([40, 60])
test_input = test_input.reshape((1, 1, 2))
test_output = model.predict(test_input, verbose=0)
print(f'Actual:{40+1, 60+1}, Prediction:{test_output}')
# Output
# Actual:(41, 61), Prediction:[[41.07751  61.055786]]

3.b.ii — многоуровневое решение LSTM

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

model.fit(X, Y, epochs=EPOCHS, validation_split=0.2, verbose=1, batch_size=3)
test_input = array([40, 60])
test_input = test_input.reshape((1, 1, 2))
test_output = model.predict(test_input, verbose=0)
print(f'Actual:{40+1, 60+1}, Prediction:{test_output}')
# Output
# Actual:(41, 61), Prediction:[[40.975334 60.911556]]

3.b.iii — двунаправленное решение LSTM

model = Sequential()
model.add(Bidirectional(LSTM(50, activation='relu'), input_shape=(1, 2)))
model.add(Dense(2))
model.compile(optimizer='adam', loss='mse')
plot_model(model, show_shapes=True)

model.fit(X, Y, epochs=EPOCHS, validation_split=0.2, verbose=1, batch_size=3)
test_output = model.predict(test_input, verbose=0)
print(f'Actual:{40+1, 60+1}, Prediction:{test_output}')
# Output
# Actual:(41, 61), Prediction:[[41.05581  61.000996]]

Все три модели дают значения, близкие к фактическим.

4. Задача последовательности «многие ко многим»

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

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

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

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

Создать набор данных

Образцы - 20

X — каждая выборка содержит 3 временных шага, что не что иное, как кратное 5.

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

X = list()
Y = list()
X = [x for x in range(5, 301, 5)]
Y = [y for y in range(20, 316, 5)]

X = np.array(X).reshape(20, 3, 1)
Y = np.array(Y).reshape(20, 3, 1)
print(f'X: {X[:3]}')
print(f'Y: {Y[:3]}')
# Output
# X: 
 [[[ 5]
  [10]
  [15]]
 [[20]
  [25]
  [30]]
 [[35]
  [40]
  [45]]]
# Y: 
 [[[20]
  [25]
  [30]]
 [[35]
  [40]
  [45]]
 [[50]
  [55]
  [60]]]

4.a.i — комплексное решение LSTM

model = Sequential()
model.add(LSTM(100, activation='relu', input_shape=(3, 1)))
model.add(RepeatVector(3))
model.add(LSTM(100, activation='relu', return_sequences=True))
model.add(TimeDistributed(Dense(1)))
model.compile(optimizer='adam', loss='mse')
plot_model(model, show_shapes=True)

model.fit(X, Y, epochs=EPOCHS, validation_split=0.2, verbose=1, batch_size=3)
test_input = array([300, 305, 310])
test_input = test_input.reshape((1, 3, 1))
test_output = model.predict(test_input, verbose=0)
print(f'Actual:{[315, 320, 325]}, Prediction:{test_output}')
# Output
# Actual:[315, 320, 325], Prediction:[[[316.4855 ][322.0853 ]
 [327.92822]]]

4.a.ii — двунаправленное решение LSTM

model = Sequential()
model.add(Bidirectional(LSTM(100, activation='relu'), input_shape=(3, 1)))
model.add(RepeatVector(3))
model.add(Bidirectional(LSTM(100, activation='relu', return_sequences=True)))
model.add(TimeDistributed(Dense(1)))
model.compile(optimizer='adam', loss='mse')
plot_model(model, show_shapes=True)

model.fit(X, Y, epochs=EPOCHS, validation_split=0.2, verbose=1, batch_size=3)
test_output = model.predict(test_input, verbose=0)
print(f'Actual:{[315, 320, 325]}, Prediction:{test_output}')
# Output
# Actual:[315, 320, 325], Prediction:[[[315.95044]
  [321.83328]
  [327.24933]]]

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

4.b — пример с одной функцией

Создать набор данных

Образцы - 20

X1 - кратно 5, начиная с 5

X2 - кратно 5, начиная с 20

Y — следующие 3 временных шага после второго значения функции в каждом образце.

X = list()
Y = list()
X1 = [x1 for x1 in range(5, 301, 5)]
X2 = [x2 for x2 in range(20, 316, 5)]
Y = [y for y in range(35, 331, 5)]

X = np.column_stack((X1, X2))
X = np.array(X).reshape(20, 3, 2)
Y = np.array(Y).reshape(20, 3, 1)
print(f'X: {X[:3]}')
print(f'Y: {Y[:3]}')
# Output
# X: [[[ 5 20]
  [10 25]
  [15 30]]

 [[20 35]
  [25 40]
  [30 45]]

 [[35 50]
  [40 55]
  [45 60]]]
# Y: [[[35]
  [40]
  [45]]

 [[50]
  [55]
  [60]]

 [[65]
  [70]
  [75]]]

4.b.i – комплексное решение LSTM

model = Sequential()
model.add(LSTM(100, activation='relu', input_shape=(3, 2)))
model.add(RepeatVector(3))
model.add(LSTM(100, activation='relu', return_sequences=True))
model.add(TimeDistributed(Dense(1)))
model.compile(optimizer='adam', loss='mse')
plot_model(model, show_shapes=True)

model.fit(X, Y, epochs=EPOCHS, validation_split=0.2, verbose=1, batch_size=3)
X1 = [300, 305, 310]
X2 = [315, 320, 325]

test_input = np.column_stack((X1, X2))

test_input = test_input.reshape((1, 3, 2))
print(f'test_input: {test_input}')
test_output = model.predict(test_input, verbose=0)
print(f'Actual:{330, 335, 340}, Prediction:{test_output}')
# Output
# test_input: [[[300 315]
  [305 320]
  [310 325]]]
# Actual:(330, 335, 340), Prediction:[[[334.5704 ]
  [342.8413 ]
  [350.75357]]]

4.b.ii — двунаправленное решение LSTM

model = Sequential()
model.add(Bidirectional(LSTM(100, activation='relu'), input_shape=(3, 2)))
model.add(RepeatVector(3))
model.add(Bidirectional(LSTM(100, activation='relu', return_sequences=True)))
model.add(TimeDistributed(Dense(1)))
model.compile(optimizer='adam', loss='mse')
plot_model(model, show_shapes=True)

model.fit(X, Y, epochs=EPOCHS, validation_split=0.2, verbose=1, batch_size=3)
test_output = model.predict(test_input, verbose=0)
print(f'Actual:{330, 335, 340}, Prediction:{test_output}')
# Output
# Actual:(330, 335, 340), Prediction:[[[331.3079 ]
  [337.31476]
  [342.70563]]]

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

Ссылки:



https://machinelearningmastery.com/models-sequence-prediction-recurrent-neural-networks/
https://stackabuse.com/solving-sequence-problems-with-lstm-in-keras/