Resolución de problemas de secuencia con LSTM en Keras

En este artículo, aprenderá a realizar pronósticos de series de tiempo que se utilizan para resolver problemas de secuencias. El pronóstico de series de tiempo se refiere al tipo de p...

En este artículo, aprenderá a realizar pronósticos de series de tiempo que se utilizan para resolver problemas de secuencias.

El pronóstico de series de tiempo se refiere al tipo de problemas en los que tenemos que predecir un resultado basado en entradas dependientes del tiempo. Un ejemplo típico de datos de series temporales son los datos del mercado de valores, donde los precios de las acciones cambian con el tiempo. De manera similar, la temperatura por hora de un lugar en particular también cambia y también puede considerarse como datos de series temporales. Los datos de series de tiempo son básicamente una secuencia de datos, por lo que los problemas de series de tiempo a menudo se denominan problemas de secuencia.

Redes neuronales recurrentes (RNN) han demostrado ser eficientes en la resolución de problemas de secuencia. En particular, Red de memoria a largo y corto plazo (LSTM), que es una variación de RNN, se usa actualmente en una variedad de dominios para resolver problemas de secuencias.

Tipos de problemas de secuencias

Los problemas de secuencia se pueden clasificar en términos generales en las siguientes categorías:

  1. Uno a uno: Donde hay una entrada y una salida. Un ejemplo típico de un problema de secuencia uno a uno es el caso en el que tiene una imagen y desea predecir una sola etiqueta para la imagen.
  2. Muchos a uno: En los problemas de secuencias de muchos a uno, tenemos una secuencia de datos como entrada y tenemos que predecir una única salida. La clasificación de texto es un excelente ejemplo de problemas de secuencia de muchos a uno en los que tenemos una secuencia de entrada de palabras y queremos predecir una única etiqueta de salida.
  3. Uno a muchos: En los problemas de secuencia de uno a muchos, tenemos una sola entrada y una secuencia de salidas. Un ejemplo típico es una imagen y su correspondiente descripción.
  4. Muchos a muchos: Los problemas de secuencias de muchos a muchos involucran una entrada de secuencia y una salida de secuencia. Por ejemplo, los precios de las acciones de 7 días como entrada y los precios de las acciones de los próximos 7 días como salidas. Los chatbots también son un ejemplo de problemas de secuencia de muchos a muchos donde una secuencia de texto es una entrada y otra secuencia de texto es la salida.

Este artículo es la parte 1 de la serie. En este artículo, veremos cómo se puede usar LSTM y sus diferentes variantes para resolver problemas de secuencia uno a uno y muchos a uno. En la siguiente parte de esta serie, veremos cómo resolver uno a muchos y muchos a muchos problemas de secuencia Estaremos trabajando con la biblioteca Keras de Python.

Después de leer este artículo, podrás resolver problemas como la predicción del precio de las acciones, predicción del tiempo, etc., basados ​​en datos históricos. Dado que el texto también es una secuencia de palabras, el conocimiento adquirido en este artículo también se puede utilizar para resolver procesamiento natural del lenguaje tareas como text clasificación, generación de lenguaje, etc.

Problemas de secuencia uno a uno

Como dije antes, en los problemas de secuencia uno a uno, hay una sola entrada y una sola salida. En esta sección veremos dos tipos de problemas de sucesión. Primero veremos cómo resolver problemas de secuencia uno a uno con una sola característica y luego veremos cómo resolver problemas de secuencia uno a uno con múltiples características.

Problemas de secuencia uno a uno con una sola función

En esta sección, veremos cómo resolver un problema de secuencia uno a uno donde cada paso de tiempo tiene una sola característica.

Primero importemos las bibliotecas requeridas que vamos a usar en este artículo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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
Crear el conjunto de datos

En este próximo paso, prepararemos el conjunto de datos que vamos a utilizar para esta sección.

1
2
3
4
5
6
7
X = list()
Y = list()
X = [x+1 for x in range(20)]
Y = [y * 15 for y in X]

print(X)
print(Y)

En el script anterior, creamos 20 entradas y 20 salidas. Cada entrada consta de un paso de tiempo, que a su vez contiene una característica única. Cada valor de salida es 15 veces el valor de entrada correspondiente. Si ejecuta el script anterior, debería ver los valores de entrada y salida como se muestra a continuación:

1
2
[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]

La entrada a la capa LSTM debe tener forma 3D, es decir, (muestras, pasos de tiempo, características). Las muestras son el número de muestras en los datos de entrada. Tenemos 20 muestras en la entrada. Los pasos de tiempo es el número de pasos de tiempo por muestra. Tenemos 1 paso de tiempo. Finalmente, las características corresponden al número de características por paso de tiempo. Tenemos una característica por paso de tiempo.

Podemos remodelar nuestros datos mediante el siguiente comando:

1
X = array(X).reshape(20, 1, 1)
Solución mediante LSTM simple

Ahora podemos crear nuestro modelo LSTM simple con una capa LSTM.

1
2
3
4
5
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())

En el script anterior, creamos un modelo LSTM con una capa LSTM de 50 neuronas y funciones de activación “relu”. Puede ver que la forma de entrada es (1,1) ya que nuestros datos tienen un paso de tiempo con una característica. Ejecutar el script anterior imprime el siguiente resumen:

1
2
3
4
5
6
7
8
9
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

Ahora entrenemos nuestro modelo:

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

Entrenamos nuestro modelo para 2000 épocas con un tamaño de lote de 5. Puede elegir cualquier número. Una vez que se entrena el modelo, podemos hacer predicciones en una nueva instancia.

Digamos que queremos predecir la salida para una entrada de 30. La salida real debería ser 30 x 15 = 450. Veamos qué valor obtenemos. Primero, necesitamos convertir nuestros datos de prueba a la forma correcta, es decir, forma 3D, como lo espera LSTM. El siguiente script predice la salida para el número 30:

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

Obtuve un valor de salida de 437.86 que es un poco menos de 450.

Nota: Es importante mencionar que los resultados que obtenga al ejecutar los scripts serán diferentes a los míos. Esto se debe a que la red neuronal LSTM inicializa pesos con valores aleatorios y sus valores. Pero en general, los resultados no deberían diferir mucho.

Solución mediante LSTM apilado

Ahora vamos a crear un LSTM apilado y ver si podemos obtener mejores resultados. El conjunto de datos seguirá siendo el mismo, el modelo cambiará. Mira el siguiente guión:

1
2
3
4
5
6
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())

En el modelo anterior, tenemos dos capas LSTM. Tenga en cuenta que la primera capa LSTM tiene el parámetro return_sequences, que se establece en True. Cuando la secuencia de retorno se establece en “Verdadero”, la salida del estado oculto de cada neurona se utiliza como entrada para la siguiente capa LSTM. El resumen del modelo anterior es el siguiente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
_________________________________________________________________
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
________________________

A continuación, debemos entrenar nuestro modelo como se muestra en el siguiente script:

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

Una vez que el modelo esté entrenado, volveremos a hacer predicciones sobre el punto de datos de prueba, es decir, 30.

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

Obtuve una salida de 459.85 que es mejor que 437, el número que logramos a través de una sola capa LSTM.

Problemas de secuencia uno a uno con múltiples funciones

En la última sección, cada muestra de entrada tenía un paso de tiempo, donde cada paso de tiempo tenía una característica. En esta sección, veremos cómo resolver un problema de secuencia uno a uno donde los pasos de tiempo de entrada tienen múltiples características.

Crear el conjunto de datos

Primero vamos a crear nuestro conjunto de datos. Mira el siguiente guión:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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)

En el script anterior, creamos tres listas: X1, X2 e Y. Cada lista tiene 25 elementos, lo que significa que el tamaño total de la muestra es 25. Finalmente, Y contiene la salida. Las listas X1, X2 e Y se han impreso a continuación:

1
2
3
[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]

Cada elemento en la lista de salida es básicamente el producto de los elementos correspondientes en las listas X1 y X2. Por ejemplo, el segundo elemento de la lista de salida es 24, que es el producto del segundo elemento de la lista X1, es decir, 4, y el segundo elemento de la lista X2, es decir, 6.

La entrada consistirá en la combinación de las listas X1 y X2, donde cada lista se representará como una columna. El siguiente script crea la entrada final:

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

Aquí está la salida:

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

Aquí la variable X contiene nuestro conjunto de características final. Puede ver que contiene dos columnas, es decir, dos características por entrada. Como discutimos anteriormente, necesitamos convertir la entrada en una forma tridimensional. Nuestra entrada tiene 25 muestras, donde cada muestra consta de 1 paso de tiempo y cada paso de tiempo consta de 2 características. El siguiente script remodela la entrada.

1
X = array(X).reshape(25, 1, 2)
Solución mediante LSTM simple

Ahora estamos listos para entrenar nuestros modelos LSTM. Primero desarrollemos un modelo de una sola capa LSTM como lo hicimos en la sección anterior:

1
2
3
4
5
6
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())

Aquí nuestra capa LSTM contiene 80 neuronas. Tenemos dos capas densas donde la primera capa contiene 10 neuronas y la segunda capa densa, que también actúa como capa de salida, contiene 1 neurona. El resumen del modelo es el siguiente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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

El siguiente script entrena el modelo:

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

Probemos nuestro modelo entrenado en un nuevo punto de datos. Nuestro punto de datos tendrá dos características, es decir, (55,80) la salida real debería ser 55 x 80 = 4400. Veamos qué predice nuestro algoritmo. Ejecute el siguiente script:

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

Obtuve 3263.44 en la salida, que está lejos de la salida real.

Solución mediante LSTM apilado

Ahora vamos a crear un LSTM más complejo con múltiples LSTM y capas densas y veamos si podemos mejorar nuestra respuesta:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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())

El resumen del modelo es el siguiente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
_________________________________________________________________
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

El siguiente paso es entrenar nuestro modelo y probarlo en el punto de datos de prueba, es decir, (55,80).

Para mejorar la precisión, reduciremos el tamaño del lote y, dado que nuestro modelo es más complejo, ahora también podemos reducir el número de épocas. El siguiente script entrena el modelo LSTM y hace una predicción en el punto de datos de prueba.

1
2
3
4
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)

En la salida, obtuve un valor de 3705.33 que aún es menor que 4400, pero es mucho mejor que el valor obtenido anteriormente de 3263.44 usando una sola capa LSTM. Puede jugar con diferentes combinaciones de capas LSTM, capas densas, tamaño de lote y la cantidad de épocas para ver si obtiene mejores resultados.

Problemas de secuencias de muchos a uno {#problemas de secuencias de muchos a uno}

En las secciones anteriores vimos cómo resolver problemas de secuencia uno a uno con LSTM. En un problema de secuencia uno a uno, cada muestra consta de un solo paso de tiempo de una o varias características. Los datos con un solo paso de tiempo no pueden considerarse datos de secuencia en un sentido real. Se ha demostrado que las redes neuronales densamente conectadas funcionan mejor con datos de un solo paso de tiempo.

Los datos de secuencia real consisten en múltiples pasos de tiempo, como los precios del mercado de valores de los últimos 7 días, una oración que contiene varias palabras, etc.

En esta sección, veremos cómo resolver problemas de secuencias de muchos a uno. En los problemas de secuencia de muchos a uno, cada muestra de entrada tiene más de un paso de tiempo, sin embargo, la salida consta de un solo elemento. Cada paso de tiempo en la entrada puede tener una o más características. Comenzaremos con problemas de secuencias de muchos a uno que tienen una característica, y luego veremos cómo resolver problemas de muchos a uno donde los pasos de tiempo de entrada tienen múltiples características.

Problemas de secuencias de muchos a uno con una sola característica

Primero vamos a crear el conjunto de datos. Nuestro conjunto de datos constará de 15 muestras. Cada muestra tendrá 3 pasos de tiempo donde cada paso de tiempo consistirá en una sola característica, es decir, un número. La salida para cada muestra será la suma de los números en cada uno de los tres pasos de tiempo. Por ejemplo, si nuestra muestra contiene una secuencia 4,5,6, la salida será 4 + 5 + 6 = 10.

Crear el conjunto de datos

Primero, creemos una lista de números enteros del 1 al 45. Dado que queremos 15 muestras en nuestro conjunto de datos, remodelaremos la lista de números enteros que contiene los primeros 45 números enteros.

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

En la salida, debería ver los primeros 45 enteros:

1
2
[ 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]

Podemos remodelarlo en número de muestras, pasos de tiempo y características usando la siguiente función:

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

El script anterior convierte la lista X en una forma tridimensional con 15 muestras, 3 pasos de tiempo y 1 función. El script anterior también imprime los datos reformados.

 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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
[[[ 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]]]

Hemos convertido nuestros datos de entrada al formato correcto, ahora creemos nuestro vector de salida. Como dije antes, cada elemento en la salida será igual a la suma de los valores en los pasos de tiempo en la muestra de entrada correspondiente. El siguiente script crea el vector de salida:

1
2
3
4
5
6
Y = list()
for x in X:
    Y.append(x.sum())

Y = np.array(Y)
print(Y)

La matriz de salida Y se ve así:

1
[  6  15  24  33  42  51  60  69  78  87  96 105 114 123 132]
Solución mediante LSTM simple

Ahora vamos a crear nuestro modelo con una capa LSTM.

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

El siguiente script entrena nuestro modelo:

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

Una vez que se entrena el modelo, podemos usarlo para hacer predicciones sobre los puntos de datos de prueba. Predigamos la salida para la secuencia numérica 50,51,52. La salida real debe ser 50 + 51 + 52 = 153. El siguiente script convierte nuestros puntos de prueba en una forma tridimensional y luego predice la salida:

1
2
3
4
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)

Obtuve 145,96 en la salida, que es alrededor de 7 puntos menos que el valor de salida real de 153.

Solución mediante LSTM apilado

Ahora vamos a crear un modelo LSTM complejo con varias capas y ver si podemos obtener mejores resultados. Ejecute el siguiente script para crear y entrenar un modelo complejo con múltiples LSTM y capas densas:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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)

Probemos ahora nuestro modelo en la secuencia de prueba, es decir, 50, 51, 52:

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

La respuesta que obtuve aquí es 155,37, que es mejor que el resultado de 145,96 que obtuvimos antes. En este caso, tenemos una diferencia de solo 2 puntos con respecto a 153, que es la respuesta real.

Solución a través de LSTM bidireccional

LSTM bidireccional es un tipo de LSTM que aprende de la secuencia de entrada tanto hacia adelante como hacia atrás. La interpretación final de la secuencia es la concatenación de los pases de aprendizaje hacia adelante y hacia atrás. Veamos si podemos obtener mejores resultados con LSTM bidireccionales.

El siguiente script crea un modelo LSTM bidireccional con una capa bidireccional y una capa densa que actúa como salida del modelo.

1
2
3
4
5
6
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')

El siguiente script entrena el modelo y hace predicciones sobre la secuencia de prueba, que es 50, 51 y 52.

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

El resultado que obtuve es 152,26, que es solo una fracción del resultado real. Por lo tanto, podemos concluir que para nuestro conjunto de datos, el LSTM bidireccional con una sola capa supera tanto a los LSTM unidireccionales apilados como a los de una sola capa.

Problemas de secuencias de muchos a uno con múltiples funciones

En un problema de secuencia de muchos a uno, tenemos una entrada donde cada paso de tiempo consta de múltiples características. La salida puede ser un valor único o varios valores, uno por función en el paso de tiempo de entrada. Cubriremos ambos casos en esta sección.

Crear el conjunto de datos

Nuestro conjunto de datos contendrá 15 muestras. Cada muestra constará de 3 pasos de tiempo. Cada paso de tiempo tendrá dos características.

Vamos a crear dos listas. Uno contendrá múltiplos de 3 hasta 135, es decir, 45 elementos en total. La segunda lista contendrá múltiplos de 5, del 1 al 225. La segunda lista también contendrá 45 elementos en total. El siguiente script crea estas dos listas:

1
2
3
4
5
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)

Puede ver el contenido de la lista en el siguiente resultado:

1
2
3
4
5
6
[  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]

Cada una de las listas anteriores representa una característica en la muestra de tiempo. El conjunto de datos agregado se puede crear uniendo las dos listas como se muestra a continuación:

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

El resultado muestra el conjunto de datos agregado:

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

Necesitamos remodelar nuestros datos en tres dimensiones para que LSTM pueda utilizarlos. Tenemos 45 filas en total y dos columnas en nuestro conjunto de datos. Reformaremos nuestro conjunto de datos en 15 muestras, 3 pasos de tiempo y dos características.

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

Puede ver las 15 muestras en el siguiente resultado:

 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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
[[[  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]]]

La salida también tendrá 15 valores correspondientes a 15 muestras de entrada. Cada valor en la salida será la suma de los dos valores característicos en el tercer paso de tiempo de cada muestra de entrada. Por ejemplo, el tercer paso de tiempo de la primera muestra tiene las características 9 y 15, por lo que la salida será 24. De manera similar, los dos valores de características en el tercer paso de tiempo de la segunda muestra son 18 y 30; la salida correspondiente será 48, y así sucesivamente.

El siguiente script crea y muestra el vector de salida:

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

Resolvamos ahora este problema de secuencia de muchos a uno a través de LSTM simples, apilados y bidireccionales.

Solución mediante LSTM simple
1
2
3
4
5
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)

El modelo está entrenado. Crearemos un punto de datos de prueba y luego usaremos nuestro modelo para hacer predicciones sobre el punto de prueba.

1
2
3
4
5
6
7
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)

La suma de dos características del tercer paso de tiempo de la entrada es 14 + 61 = 75. Nuestro modelo con una capa LSTM predijo 73,41, que está bastante cerca.

Solución mediante LSTM apilado

El siguiente script entrena un LSTM apilado y hace predicciones en el punto de prueba:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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)

El resultado que recibí es 71.56, que es peor que el LSTM simple. Parece que nuestro LSTM apilado se está sobreajustando.

Solución a través de LSTM bidireccional

Aquí está el script de entrenamiento para LSTM bidireccional simple junto con el código que se usa para hacer predicciones en el punto de datos de prueba:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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)

La salida es 76,82, que está bastante cerca de 75. Nuevamente, LSTM bidireccional parece estar superando al resto de los algoritmos.

Hasta ahora, hemos pronosticado valores únicos basados ​​en múltiples valores de características de diferentes pasos de tiempo. Hay otro caso de secuencias de muchos a uno en el que desea predecir un valor para cada característica en el paso de tiempo. Por ejemplo, el conjunto de datos que usamos en esta sección tiene tres pasos de tiempo y cada paso de tiempo tiene dos características. Es posible que queramos predecir el valor individual de cada serie de características. El siguiente ejemplo lo deja claro, supongamos que tenemos la siguiente entrada:

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

En el resultado, queremos un paso de tiempo con dos características, como se muestra a continuación:

1
[12, 20]

Puede ver que el primer valor en la salida es una continuación de la primera serie y el segundo valor es la continuación de la segunda serie. Podemos resolver tales problemas simplemente cambiando la cantidad de neuronas en la capa densa de salida a la cantidad de valores de características que queremos en la salida. Sin embargo, primero necesitamos actualizar nuestro vector de salida Y. El vector de entrada seguirá siendo el mismo:

1
2
3
4
5
6
7
8
9
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)

El script anterior crea un vector de salida actualizado y lo imprime en la consola, la salida se ve así:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
[[ 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]]

Ahora entrenemos nuestras redes LSTM simples, apiladas y bidireccionales en nuestro conjunto de datos. El siguiente script entrena un LSTM simple:

1
2
3
4
5
6
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)

El siguiente paso es probar nuestro modelo en el punto de datos de prueba. El siguiente script crea un punto de datos de prueba:

1
2
3
4
5
6
7
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)

La salida real es [29, 45]. Nuestro modelo predice [29.089157, 48.469097], que está bastante cerca.

Ahora entrenemos un LSTM apilado y predigamos la salida para el punto de datos de prueba:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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)

La salida es [29.170143, 48.688267], que nuevamente está muy cerca de la salida real.

Finalmente, podemos entrenar nuestro LSTM bidireccional y hacer predicciones en el punto de prueba:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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)

La salida es [29.2071, 48.737988].

Puede ver una vez más que el LSTM bidireccional hace la predicción más precisa.

Conclusión

Las redes neuronales simples no son adecuadas para resolver problemas de secuencia, ya que en los problemas de secuencia, además de la entrada actual, también necesitamos realizar un seguimiento de las entradas anteriores. Las Redes Neuronales con algún tipo de memoria son más adecuadas para resolver problemas de secuencia. LSTM es una de esas redes.

En este artículo, vimos cómo se pueden usar diferentes variantes del algoritmo LSTM para resolver problemas de secuencia uno a uno y muchos a uno. Esta es la primera parte del artículo. En la segunda parte, veremos cómo resolver problemas de secuencia uno a muchos y muchos a muchos. También estudiaremos el mecanismo codificador-decodificador que se usa más comúnmente para crear chatbots. Hasta entonces, feliz codificación :) ción :)