Resolución de problemas de secuencias con LSTM en Keras

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

    El pronóstico de series de tiempo se refiere al tipo de problemas en los que tenemos que predecir un resultado en función de las entradas que dependen del tiempo. Un ejemplo típico de datos de series de tiempo son los datos del mercado de valores donde los precios de las acciones cambian con el tiempo. De manera similar, la temperatura horaria de un lugar en particular también cambia y también se puede considerar como datos de series de tiempo. 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.

    Se ha demostrado que las redes neuronales recurrentes (RNN) resuelven eficientemente los problemas de secuencia. Particularmente, Long Short Term Memory Network (LSTM), que es una variación de RNN, se utiliza actualmente en una variedad de dominios para resolver problemas de secuencia.

    Tipos de problemas de secuencia

    Los problemas de secuencia se pueden clasificar ampliamente en las siguientes categorías:

    • Uno a uno: donde hay una entrada y una salida. Un ejemplo típico de problemas de secuencia uno a uno es el caso en el que tiene una imagen y desea predecir una sola etiqueta para la imagen.
    • Muchos a uno: En los problemas de secuencia 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 donde tenemos una secuencia de entrada de palabras y queremos predecir una sola etiqueta de salida.
    • 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 descripción correspondiente.
    • Muchos a muchos : los problemas de secuencia de muchos a muchos implican una entrada de secuencia y una salida de secuencia. Por ejemplo, los precios de las acciones de 7 días como insumo 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 LSTM y sus diferentes variantes se pueden utilizar para resolver problemas de secuencia uno a uno y muchos a uno. En la siguiente parte de esta serie, veremos cómo resolver problemas de secuencia de uno a muchos y de muchos a muchos. Trabajaremos con la biblioteca Keras de Python.

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

    Problemas de secuencia uno a uno

    Como dije anteriormente, 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 secuencia. 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 característica única.

    Primero importemos las bibliotecas necesarias que vamos a utilizar en este artículo:

    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 usaremos para esta sección.

    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 única característica. 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, 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 estar en 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 función por paso de tiempo.

    Podemos remodelar nuestros datos mediante el siguiente comando:

    X = array(X).reshape(20, 1, 1)
    
    Solución a través de Simple LSTM

    Ahora podemos crear nuestro modelo LSTM simple con una capa de 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())
    

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

    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
    

    Entrenemos ahora nuestro modelo:

    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, a la forma 3D, como esperaba LSTM. El siguiente script predice la salida del número 30:

    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 un 437.86poco menos de 450.

    Nota: Es importante mencionar que los resultados que obtiene 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 a través de Stacked LSTM

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

    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 de LSTM. Observe que la primera capa de LSTM tiene un parámetro return_sequences, que está configurado en True. Cuando la secuencia de retorno se establece en True, la salida del estado oculto de cada neurona se usa como entrada para la siguiente capa LSTM. El resumen del modelo anterior es el siguiente:

    _________________________________________________________________
    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, necesitamos entrenar nuestro modelo como se muestra en el siguiente script:

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

    Una vez entrenado el modelo, haremos nuevamente predicciones en el punto de datos de prueba, es decir, 30.

    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 creemos nuestro conjunto de datos. Mira el siguiente guión:

    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 guión anterior, creamos tres listas: X1, X2, y Y. Cada lista tiene 25 elementos, lo que significa que el tamaño total de la muestra es 25. Finalmente, Ycontiene la salida. X1, X2Y Ylas listas se han impreso a continuación:

    [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 de la lista de salida es básicamente el producto de los elementos correspondientes en las listas X1y X2. Por ejemplo, el segundo elemento de la lista de salida es 24, que es el producto del segundo elemento de la lista, X1es decir, 4, y el segundo elemento de la lista, X2es decir, 6.

    La entrada consistirá en la combinación de X1y X2listas, donde cada lista se representará como una columna. El siguiente script crea la entrada final:

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

    Aquí está el resultado:

    [[ 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 Xvariable contiene nuestro conjunto de características final. Puede ver que contiene dos columnas, es decir, dos características por entrada. Como comentamos 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 cambia la forma de la entrada.

    X = array(X).reshape(25, 1, 2)
    
    Solución a través de Simple LSTM

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

    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:

    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:

    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:

    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 a través de Stacked LSTM

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

    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:

    _________________________________________________________________
    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 predicciones en el punto de datos de prueba.

    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 previamente obtenido 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 secuencia de varios 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 constan de varios 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 secuencia 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 secuencia 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 secuencia de varios a uno con una sola función

    Primero creemos 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. El resultado de 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 enteros del 1 al 45. Como queremos 15 muestras en nuestro conjunto de datos, cambiaremos la forma de la lista de enteros que contiene los primeros 45 enteros.

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

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

    [ 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 una cantidad de muestras, pasos de tiempo y características usando la siguiente función:

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

    El script anterior convierte la lista Xen una forma tridimensional con 15 muestras, 3 pasos de tiempo y 1 característica. El script anterior también imprime los datos remodelados.

    [[[ 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 anteriormente, 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:

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

    La matriz de salida se Yve así:

    [  6  15  24  33  42  51  60  69  78  87  96 105 114 123 132]
    
    Solución a través de Simple LSTM

    Creemos ahora nuestro modelo con una capa LSTM.

    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:

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

    Una vez entrenado el modelo, podemos usarlo para hacer predicciones en los puntos de datos de prueba. Vamos a predecir la salida de la secuencia numérica 50,51,52. La salida real debería ser 50 + 51 + 52 = 153. La siguiente secuencia de comandos convierte nuestros puntos de prueba en una forma tridimensional y luego predice la salida:

    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 a través de Stacked LSTM

    Creemos ahora un modelo LSTM complejo con múltiples capas y veamos si podemos obtener mejores resultados. Ejecute el siguiente script para crear y entrenar un modelo complejo con múltiples LSTM y capas densas:

    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:

    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 de 153, que es la respuesta real.

    Solución vía 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 de la secuencia final es la concatenación de pases de aprendizaje hacia adelante y hacia atrás. Veamos si podemos obtener mejores resultados con LSTM bidireccionales.

    La siguiente secuencia de comandos crea un modelo LSTM bidireccional con una capa bidireccional y una capa densa que actúa como la salida del modelo.

    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.

    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, LSTM bidireccional con capa única supera tanto a los LSTM unidireccionales apilados como a los de capa única.

    Problemas de secuencia de varios a uno con varias 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 característica 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.

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

    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:

    [  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 la lista anterior 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:

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

    El resultado muestra el conjunto de datos agregado:

     [  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 puedan ser utilizados por LSTM. Tenemos 45 filas en total y dos columnas en nuestro conjunto de datos. Reestructuraremos nuestro conjunto de datos en 15 muestras, 3 pasos de tiempo y dos características.

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

    Puede ver las 15 muestras en el siguiente resultado:

    [[[  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 de características 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:

    [ 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 mediante LSTM simples, apilados y bidireccionales.

    Solución a través de Simple LSTM
    model = Sequential()
    model.add(LSTM(50, activation='relu', input_shape=(3, 2)))
    model.add(Dense(1))
    model.compile(optimizer="adam", loss="mse")
    history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1)
    

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

    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 intervalo de tiempo de la entrada es 14 + 61 = 75. Nuestro modelo con una capa LSTM predijo 73,41, que es bastante parecido.

    Solución a través de Stacked LSTM

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

    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)
    

    La salida que recibí es 71.56, que es peor que la simple LSTM. Parece que nuestro LSTM apilado está sobreajustado.

    Solución vía 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:

    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. Una vez más, LSTM bidireccional parece estar superando al resto de los algoritmos.

    Hasta ahora hemos predicho valores únicos basados ​​en valores de características múltiples de diferentes pasos de tiempo. Existe otro caso de secuencias de muchos a uno en el que desea predecir un valor para cada característica en el intervalo 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 deseemos predecir el valor individual para cada serie de características. El siguiente ejemplo lo aclara, supongamos que tenemos la siguiente entrada:

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

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

    [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 estos problemas simplemente cambiando el número de neuronas en la capa densa de salida al número 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:

    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í:

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

    Entrenemos ahora nuestras redes LSTM simples, apiladas y bidireccionales en nuestro conjunto de datos. El siguiente guión entrena un LSTM simple:

    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:

    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 predicemos la salida del punto de datos de prueba:

    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 de nuevo está muy cerca de la salida real.

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

    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 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 debemos 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 de uno a muchos y de 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 🙂

    Etiquetas:

    Deja una respuesta

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *