Resolver problemas de secuencia con LSTM en Keras: Parte 2

    Esta es la segunda y 煤ltima parte de la serie de art铆culos de dos partes sobre la resoluci贸n de problemas de secuencia con LSTM. En la parte 1 de la serie, expliqu茅 c贸mo resolver problemas de secuencia uno a uno y muchos a uno usando LSTM. En esta parte, ver谩 c贸mo resolver problemas de secuencia de uno a muchos y de muchos a muchos mediante LSTM en Keras.

    Los subt铆tulos de im谩genes son un ejemplo cl谩sico de problemas de secuencia de uno a muchos en los que tiene una sola imagen como entrada y debe predecir la descripci贸n de la imagen en forma de secuencia de palabras. De manera similar, la predicci贸n del mercado de valores para los pr贸ximos X d铆as, donde la entrada es el precio de las acciones de los Y d铆as anteriores, es un ejemplo cl谩sico de problemas de secuencia de muchos a muchos.

    En este art铆culo, ver谩 ejemplos muy b谩sicos de problemas de uno a muchos y de muchos a muchos. Sin embargo, los conceptos aprendidos en este art铆culo sentar谩n las bases para resolver problemas de secuencia avanzada, como la predicci贸n del precio de las acciones y los subt铆tulos de im谩genes automatizados que veremos en los pr贸ximos art铆culos.

    Problemas de secuencia de uno a muchos

    Los problemas de secuencia uno a muchos son el tipo de problemas de secuencia donde los datos de entrada tienen un paso de tiempo y la salida contiene un vector de valores m煤ltiples o pasos de tiempo m煤ltiples. En esta secci贸n, veremos c贸mo resolver problemas de secuencia de uno a muchos donde la entrada tiene una sola caracter铆stica. Luego pasaremos a ver c贸mo trabajar con la entrada de m煤ltiples caracter铆sticas para resolver problemas de secuencia de uno a muchos.

    Problemas de secuencia de uno a varios con una sola funci贸n

    Primero creemos un conjunto de datos y entendamos el problema que vamos a resolver en esta secci贸n.

    Crear el conjunto de datos

    El siguiente script importa las bibliotecas necesarias:

    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
    

    Y el siguiente script crea el conjunto de datos:

    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(X)
    print(Y)
    

    Aqu铆 est谩 el resultado:

    [1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34, 37, 40, 43]
    [[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]]
    

    Nuestra entrada contiene 15 muestras con un paso de tiempo y un valor de caracter铆stica. Para cada valor en la muestra de entrada, el vector de salida correspondiente contiene los siguientes dos enteros. Por ejemplo, si la entrada es 4, el vector de salida contendr谩 los valores 5 y 6. Por lo tanto, el problema es un problema de secuencia simple de uno a muchos.

    El siguiente script cambia la forma de nuestros datos seg煤n lo requiera el LSTM:

    X = np.array(X).reshape(15, 1, 1)
    Y = np.array(Y)
    

    Ahora podemos entrenar nuestros modelos. Entrenaremos LSTM simples y apilados.

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

    Una vez que el modelo est谩 entrenado, podemos hacer predicciones sobre los datos de prueba:

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

    Los datos de prueba contienen un valor 10. En la salida, deber铆amos obtener un vector que contiene 11 y 12. La salida que recib铆 es [10.982891 12.109697] que en realidad est谩 muy cerca de la salida esperada.

    Soluci贸n a trav茅s de Stacked LSTM

    El siguiente script entrena LSTM apilados en nuestros datos y hace predicciones en los puntos de prueba:

    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")
    history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1, batch_size=3)
    
    test_output = model.predict(test_input, verbose=0)
    print(test_output)
    

    La respuesta es [11.00432 11.99205] que est谩 muy cerca de la salida real.

    Soluci贸n v铆a LSTM bidireccional

    El siguiente script entrena un LSTM bidireccional en nuestros datos y luego hace una predicci贸n en el conjunto de prueba.

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

    La salida que recib铆 es [11.035181 12.082813]

    Problemas de secuencia de uno a varios con varias funciones

    En esta secci贸n veremos problemas de secuencia de uno a muchos donde las muestras de entrada tendr谩n un paso de tiempo, pero dos caracter铆sticas. La salida ser谩 un vector de dos elementos.

    Crear el conjunto de datos

    Como siempre, el primer paso es crear el conjunto de datos:

    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)]
    
    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(X)
    

    Nuestro conjunto de datos de entrada se ve as铆:

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

    Puede ver que cada paso de tiempo de entrada consta de dos caracter铆sticas. La salida ser谩 un vector que contiene los siguientes dos elementos que corresponden a las dos caracter铆sticas en el paso de tiempo de la muestra de entrada. Por ejemplo, para la muestra de entrada [2, 3], la salida ser谩 [3, 4], y as铆.

    Reformemos nuestros datos:

    X = np.array(X).reshape(25, 1, 2)
    Y = np.array(Y)
    
    Soluci贸n a trav茅s de Simple LSTM
    model = Sequential()
    model.add(LSTM(50, activation='relu', input_shape=(1, 2)))
    model.add(Dense(2))
    model.compile(optimizer="adam", loss="mse")
    model.fit(X, Y, epochs=1000, validation_split=0.2, batch_size=3)
    

    Ahora creemos nuestro punto de prueba y veamos qu茅 tan bien funciona nuestro algoritmo:

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

    La entrada es [40, 60], la salida debe ser [41, 61]. La salida predicha por nuestro simple LSTM es [40.946873 60.941723] que est谩 muy cerca de la salida esperada.

    Soluci贸n a trav茅s de Stacked 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")
    history = model.fit(X, Y, epochs=1000, 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(test_output)
    

    La salida en este caso es: [40.978477 60.994644]

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

    El resultado obtenido es: [41.0975 61.159065]

    Problemas de secuencia de muchos a muchos

    En los problemas de secuencia de uno a muchos y de muchos a uno, vimos que el vector de salida puede contener varios valores. Dependiendo del problema, se puede considerar que un vector de salida que contiene m煤ltiples valores tiene salidas simples (ya que la salida contiene datos de un intervalo de tiempo en t茅rminos estrictos) o m煤ltiples (ya que un vector contiene m煤ltiples valores).

    Sin embargo, en algunos problemas de secuencia, queremos m煤ltiples salidas divididas en pasos de tiempo. En otras palabras, para cada paso de tiempo en la entrada, queremos un paso de tiempo correspondiente en la salida. Estos modelos se pueden utilizar para resolver problemas de secuencia de muchos a muchos con longitudes variables.

    Modelo codificador-decodificador

    Para resolver estos problemas de secuencia, se ha dise帽ado el modelo codificador-decodificador. El modelo codificador-decodificador es b谩sicamente un nombre elegante para la arquitectura de red neuronal con dos capas LSTM.

    La primera capa funciona como una capa de codificador y codifica la secuencia de entrada. El decodificador tambi茅n es una capa LSTM, que acepta tres entradas: la secuencia codificada del codificador LSTM, el estado oculto anterior y la entrada actual. Durante el entrenamiento, la salida real en cada paso de tiempo se usa para entrenar el modelo codificador-decodificador. Al hacer predicciones, la salida del codificador, el estado oculto actual y la salida anterior se utilizan como entrada para hacer predicciones en cada paso de tiempo. Estos conceptos ser谩n m谩s comprensibles cuando los vea en acci贸n en una pr贸xima secci贸n.

    Problemas de secuencia de varios a varios con una sola funci贸n

    En esta secci贸n resolveremos problemas de secuencia de muchos a muchos a trav茅s del modelo codificador-decodificador, donde cada paso de tiempo en la muestra de entrada contendr谩 una caracter铆stica.

    Primero creemos nuestro conjunto de datos.

    Crear el conjunto de datos
    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)
    

    La entrada X contiene 20 muestras donde cada muestra contiene 3 pasos de tiempo con una caracter铆stica. Una muestra de entrada se ve as铆:

    [[[  5]
      [ 10]
      [ 15]]
    

    Puede ver que la muestra de entrada contiene 3 valores que son b谩sicamente 3 m煤ltiplos consecutivos de 5. La secuencia de salida correspondiente para la muestra de entrada anterior es la siguiente:

    [[[ 20]
      [ 25]
      [ 30]]
    

    La salida contiene los siguientes tres m煤ltiplos consecutivos de 5. Puede ver que la salida en este caso es diferente a la que hemos visto en las secciones anteriores. Para el modelo de codificador-decodificador, la salida tambi茅n debe convertirse a un formato 3D que contenga el n煤mero de muestras, pasos de tiempo y caracter铆sticas. Esto se debe a que el decodificador genera una salida por paso de tiempo.

    Hemos creado nuestro conjunto de datos; el siguiente paso es entrenar nuestros modelos. Entrenaremos modelos LSTM apilados y LSTM bidireccionales en las siguientes secciones.

    Soluci贸n a trav茅s de Stacked LSTM

    La siguiente secuencia de comandos crea el modelo de codificador-decodificador utilizando LSTM apilados:

    from keras.layers import RepeatVector
    from keras.layers import TimeDistributed
    
    model = Sequential()
    
    # encoder layer
    model.add(LSTM(100, activation='relu', input_shape=(3, 1)))
    
    # repeat vector
    model.add(RepeatVector(3))
    
    # decoder layer
    model.add(LSTM(100, activation='relu', return_sequences=True))
    
    model.add(TimeDistributed(Dense(1)))
    model.compile(optimizer="adam", loss="mse")
    
    print(model.summary())
    

    En el script anterior, la primera capa LSTM es la capa del codificador.

    A continuaci贸n, hemos agregado el vector de repetici贸n a nuestro modelo. El vector de repetici贸n toma la salida del codificador y la alimenta repetidamente como entrada en cada paso de tiempo al decodificador. Por ejemplo, en la salida tenemos tres pasos de tiempo. Para predecir cada paso de tiempo de salida, el decodificador utilizar谩 el valor del vector de repetici贸n, el estado oculto de la salida anterior y la entrada actual.

    A continuaci贸n, tenemos una capa de decodificador. Dado que la salida tiene la forma de un intervalo de tiempo, que es un formato 3D, return_sequences para el modelo de decodificador se ha configurado True. los TimeDistributed La capa se utiliza para predecir individualmente la salida para cada paso de tiempo.

    El resumen del modelo para el modelo de codificador-decodificador creado en el script anterior es el siguiente:

    Layer (type)                 Output Shape              Param #
    =================================================================
    lstm_40 (LSTM)               (None, 100)               40800
    _________________________________________________________________
    repeat_vector_7 (RepeatVecto (None, 3, 100)            0
    _________________________________________________________________
    lstm_41 (LSTM)               (None, 3, 100)            80400
    _________________________________________________________________
    time_distributed_7 (TimeDist (None, 3, 1)              101
    =================================================================
    Total params: 121,301
    Trainable params: 121,301
    Non-trainable params: 0
    

    Puede ver que el vector de repetici贸n solo repite la salida del codificador y no tiene par谩metros para entrenar.

    El siguiente script entrena el modelo de codificador-decodificador anterior.

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

    Creemos un punto de prueba y veamos si nuestro modelo de codificador-decodificador es capaz de predecir la salida de varios pasos. Ejecute el siguiente script:

    test_input = array([300, 305, 310])
    test_input = test_input.reshape((1, 3, 1))
    test_output = model.predict(test_input, verbose=0)
    print(test_output)
    

    Nuestra secuencia de entrada contiene tres valores de paso de tiempo 300, 305 y 310. La salida debe ser los siguientes tres m煤ltiplos de 5, es decir, 315, 320 y 325. Recib铆 la siguiente salida:

    [[[316.02878]
      [322.27145]
      [328.5536 ]]]
    

    Puede ver que la salida est谩 en formato 3D.

    Soluci贸n v铆a LSTM bidireccional

    Creemos ahora un modelo de codificador-decodificador con LSTM bidireccionales y veamos si podemos obtener mejores resultados:

    from keras.layers import RepeatVector
    from keras.layers import TimeDistributed
    
    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")
    
    history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1, batch_size=3)
    

    El script anterior entrena el modelo codificador-decodificador a trav茅s de LSTM bidireccional. Hagamos ahora predicciones en el punto de prueba, es decir [300, 305, 310].

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

    Aqu铆 est谩 el resultado:

    [[[315.7526 ]
      [321.47153]
      [327.94025]]]
    

    La salida que obtuve a trav茅s de LSTM bidireccionales es mejor que la que obtuve a trav茅s del modelo de codificador-decodificador basado en LSTM apilado simple.

    Problemas de secuencia de varios a varios con varias funciones

    Como ya lo habr谩 adivinado, en los problemas de secuencia de muchos a muchos, cada paso de tiempo en la muestra de entrada contiene m煤ltiples caracter铆sticas.

    Crear el conjunto de datos

    Creemos un conjunto de datos simple para nuestro problema:

    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))
    

    En el script anterior creamos dos listas X1 y X2. La lista X1 contiene todos los m煤ltiplos de 5 de 5 a 300 (inclusive) y la lista X2 contiene todos los m煤ltiplos de 5 desde 20 hasta 315 (inclusive). Finalmente, la lista Y, que resulta ser que la salida contiene todos los m煤ltiplos de 5 entre 35 y 330 (inclusive). La lista de entrada final X es una fusi贸n de columnas de X1 y X2.

    Como siempre, necesitamos remodelar nuestra entrada X y salida Y antes de que puedan utilizarse para entrenar a LSTM.

    X = np.array(X).reshape(20, 3, 2)
    Y = np.array(Y).reshape(20, 3, 1)
    

    Puedes ver la entrada X se ha remodelado en 20 muestras de tres pasos de tiempo con 2 caracter铆sticas donde la salida se ha remodelado en dimensiones similares pero con 1 caracter铆stica.

    La primera muestra de la entrada se ve as铆:

    [[ 5  20]
    [ 10  25]
    [ 15  30]]
    

    La entrada contiene 6 m煤ltiplos consecutivos de un n煤mero entero 5, tres cada uno en las dos columnas. Aqu铆 est谩 la salida correspondiente para la muestra de entrada anterior:

    [[ 35]
    [ 40]
    [ 45]]
    

    Como puede ver, la salida contiene los siguientes tres m煤ltiplos consecutivos de 5.

    Entrenemos ahora nuestro modelo de codificador-decodificador para aprender la secuencia anterior. Primero entrenaremos un codificador-decodificador basado en LSTM apilado simple.

    Soluci贸n a trav茅s de Stacked LSTM

    La siguiente secuencia de comandos entrena el modelo LSTM apilado. Puede ver que la forma de entrada ahora es (3, 2) correspondiente a tres pasos de tiempo y dos caracter铆sticas en la entrada.

    from keras.layers import RepeatVector
    from keras.layers import TimeDistributed
    
    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")
    
    history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1, batch_size=3)
    

    Creemos ahora un punto de prueba que se utilizar谩 para hacer una predicci贸n.

    X1 = [300, 305, 310]
    X2 = [315, 320, 325]
    
    test_input = np.column_stack((X1, X2))
    
    test_input = test_input.reshape((1, 3, 2))
    print(test_input)
    

    El punto de prueba se ve as铆:

    [[[300 315]
      [305 320]
      [310 325]]]
    

    La salida real del punto de prueba anterior es [330, 335, 340]. Veamos qu茅 son las predicciones del modelo:

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

    La salida prevista es:

    [[[324.5786 ]
      [328.89658]
      [335.67603]]]
    

    El resultado est谩 lejos de ser correcto.

    Soluci贸n v铆a LSTM bidireccional

    Entrenemos ahora el modelo de codificador-decodificador basado en LSTM bidireccionales y veamos si podemos obtener mejores resultados. El siguiente script entrena el modelo.

    from keras.layers import RepeatVector
    from keras.layers import TimeDistributed
    
    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")
    
    history = model.fit(X, Y, epochs=1000, validation_split=0.2, verbose=1, batch_size=3)
    

    El siguiente script hace predicciones sobre el conjunto de prueba:

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

    Aqu铆 est谩 el resultado:

    [[[330.49133]
      [335.35327]
      [339.64398]]]
    

    La salida lograda est谩 bastante cerca de la salida real, es decir [330, 335, 340]. Por lo tanto, nuestro LSTM bidireccional super贸 al LSTM simple.

    Conclusi贸n

    Esta es la segunda parte de mi art铆culo sobre “Soluci贸n de problemas de secuencia con LSTM en Keras” (parte 1 aqu铆). En este art铆culo, vio c贸mo resolver problemas de secuencia de uno a muchos y de muchos a muchos en LSTM. Tambi茅n vio c贸mo se puede utilizar el modelo codificador-decodificador para predecir salidas de varios pasos. El modelo codificador-decodificador se utiliza en una variedad de aplicaciones de procesamiento de lenguaje natural, como la traducci贸n autom谩tica neuronal y el desarrollo de chatbot.

    En el pr贸ximo art铆culo, veremos la aplicaci贸n del modelo codificador-decodificador en NLP.

    Etiquetas:

    Deja una respuesta

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