Entendiendo Keras LSTMs

311

Estoy tratando de conciliar mi comprensión de los LSTM y lo señalé aquí en esta publicación de Christopher Olah implementado en Keras. Estoy siguiendo el blog escrito por Jason Brownlee para el tutorial de Keras. Lo que más me confunde es,

  1. La remodelación de la serie de datos en [samples, time steps, features]y,
  2. Los LSTM con estado

Vamos a concentrarnos en las dos preguntas anteriores con referencia al código pegado a continuación:

# reshape into X=t and Y=t+1
look_back = 3
trainX, trainY = create_dataset(train, look_back)
testX, testY = create_dataset(test, look_back)

# reshape input to be [samples, time steps, features]
trainX = numpy.reshape(trainX, (trainX.shape[0], look_back, 1))
testX = numpy.reshape(testX, (testX.shape[0], look_back, 1))
########################
# The IMPORTANT BIT
##########################
# create and fit the LSTM network
batch_size = 1
model = Sequential()
model.add(LSTM(4, batch_input_shape=(batch_size, look_back, 1), stateful=True))
model.add(Dense(1))
model.compile(loss='mean_squared_error', optimizer='adam')
for i in range(100):
    model.fit(trainX, trainY, nb_epoch=1, batch_size=batch_size, verbose=2, shuffle=False)
    model.reset_states()

Nota: create_dataset toma una secuencia de longitud N y devuelve una N-look_backmatriz de la cual cada elemento es una look_backsecuencia de longitud.

¿Qué son los pasos de tiempo y las características?

Como se puede ver, TrainX es una matriz tridimensional con Time_steps y Feature siendo las dos últimas dimensiones respectivamente (3 y 1 en este código en particular). Con respecto a la imagen a continuación, ¿significa esto que estamos considerando el many to onecaso, donde el número de cajas rosadas es 3? ¿O significa literalmente que la longitud de la cadena es 3 (es decir, solo se consideran 3 casillas verdes)?ingrese la descripción de la imagen aquí

¿El argumento de las características se vuelve relevante cuando consideramos series multivariadas? por ejemplo, modelar dos acciones financieras simultáneamente?

LSTM con estado

¿Los LSTM con estado significan que guardamos los valores de memoria de la celda entre ejecuciones de lotes? Si este es el caso, batch_sizees uno, y la memoria se restablece entre las sesiones de entrenamiento, entonces, ¿cuál era el punto de decir que tenía estado? Supongo que esto está relacionado con el hecho de que los datos de entrenamiento no se mezclan, pero no estoy seguro de cómo hacerlo.

¿Alguna idea? Referencia de la imagen: http://karpathy.github.io/2015/05/21/rnn-effectiveness/

Editar 1:

Un poco confundido sobre el comentario de @ van sobre las cajas rojas y verdes que son iguales. Entonces, para confirmar, ¿las siguientes llamadas a la API corresponden a los diagramas desenrollados? Especialmente observando el segundo diagrama ( batch_sizefue elegido arbitrariamente): ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

Edición 2:

Para las personas que han realizado el curso de aprendizaje profundo de Udacity y aún están confundidos sobre el argumento time_step, miren la siguiente discusión: https://discussions.udacity.com/t/rnn-lstm-use-implementation/163169

Actualizar:

Resulta que model.add(TimeDistributed(Dense(vocab_len)))era lo que estaba buscando. Aquí hay un ejemplo: https://github.com/sachinruk/ShakespeareBot

Actualización2:

He resumido la mayor parte de mi comprensión de los LSTM aquí: https://www.youtube.com/watch?v=ywinX5wgdEU

sachinruk
fuente
77
La primera foto debe ser (batch_size, 5, 1); la segunda foto debe ser (batch_size, 4, 3) (si no hay secuencias siguientes). ¿Y por qué la salida sigue siendo "X"? ¿Debería ser "Y"?
Van
1
Aquí supongo que X_1, X_2 ... X_6 es un solo número. Y tres números (X_1, X_2, X_3) forman un vector de forma (3,). Un número (X_1) forma un vector de forma (1,).
Van
2
@ Van, su suposición es correcta. Eso es interesante, por lo que básicamente el modelo no aprende patrones más allá del número de pasos de tiempo. Entonces, si tengo una serie temporal de 1000, y puedo ver visualmente un patrón cada 100 días, debería hacer que el parámetro time_steps tenga al menos 100. ¿Es esta una observación correcta?
sachinruk
3
Si. Y si puede recopilar 3 funciones relevantes por día, puede establecer el tamaño de la función en 3 como lo hizo en la segunda foto. Bajo esa circunstancia, la forma de entrada será (batch_size, 100, 3).
Van
1
y para responder a su primera pregunta fue porque estaba tomando una sola serie de tiempo. Por ejemplo, precios de acciones, entonces X e Y son de la misma serie.
sachinruk

Respuestas:

173

En primer lugar, elige excelentes tutoriales ( 1 , 2 ) para comenzar.

Qué significa Time-step : Time-steps==3en X.shape (Describir la forma de los datos) significa que hay tres cuadros rosados. Dado que en Keras cada paso requiere una entrada, por lo tanto, el número de cuadros verdes generalmente debería ser igual al número de cuadros rojos. A menos que hackees la estructura.

muchos a muchos versus muchos a uno : en keras, hay un return_sequencesparámetro cuando se inicializa LSTMo GRUo SimpleRNN. Cuando return_sequenceses False(por defecto), entonces es muchos a uno como se muestra en la imagen. Su forma de retorno es (batch_size, hidden_unit_length), que representa el último estado. Cuando return_sequenceses True, entonces es de muchos a muchos . Su forma de retorno es(batch_size, time_step, hidden_unit_length)

¿El argumento de las características se vuelve relevante ? El argumento de las características significa "Qué tan grande es su cuadro rojo" o cuál es la dimensión de entrada en cada paso. Si desea predecir, por ejemplo, 8 tipos de información de mercado, puede generar sus datos con feature==8.

Con estado : puede buscar el código fuente . Al inicializar el estado, si stateful==True, entonces el estado de la última capacitación se usará como el estado inicial, de lo contrario generará un nuevo estado. No he encendido statefultodavía. Sin embargo, no estoy de acuerdo con que batch_sizesolo puede ser 1 cuando stateful==True.

Actualmente, genera sus datos con los datos recopilados. La imagen de su información de stock se presentará como flujo, en lugar de esperar un día para recopilar todos los datos secuenciales, le gustaría generar datos de entrada en línea mientras entrena / predice con la red. Si tiene 400 acciones compartiendo una misma red, puede configurar batch_size==400.

camioneta
fuente
Ligeramente confundido acerca de por qué los cuadros rojo y verde tienen que ser iguales. ¿Podrías mirar la edición que hice (principalmente las nuevas imágenes) y comentar?
sachinruk
1
En efecto. Consulte el documento:stateful: Boolean (default False). If True, the last state for each sample at index i in a batch will be used as initial state for the sample of index i in the following batch.
Van
1
@Van Si tengo una serie de tiempo multivariante, ¿debo usarla lookback = 1?
innm
1
¿Por qué la dimensionalidad LSTM del espacio de salida (32) difiere del número de neuronas (células LSTM)?
Sticky
1
Además de stateful=True: El tamaño del lote puede ser lo que desee, pero debe atenerse a él. Si usted construye su modelo con un tamaño de lote de 5, entonces todo fit(), predict()y métodos relacionados a requerir un lote de 5. Nota sin embargo que este estado no se guardará con model.save(), lo que podría parecer indeseable. Sin embargo, puede agregar manualmente el estado al archivo hdf5, si lo necesita. Pero efectivamente, esto le permite cambiar el tamaño del lote simplemente guardando y recargando un modelo.
jlh
192

Como complemento de la respuesta aceptada, esta respuesta muestra comportamientos keras y cómo lograr cada imagen.

Comportamiento general de Keras

El procesamiento interno estándar de Keras es siempre de muchos a muchos como en la siguiente imagen (donde usé features=2, presión y temperatura, solo como ejemplo):

Muchos a muchos

En esta imagen, aumenté el número de pasos a 5, para evitar confusión con las otras dimensiones.

Para este ejemplo:

  • Tenemos N tanques de aceite
  • Pasamos 5 horas tomando medidas por hora (pasos de tiempo)
  • Medimos dos características:
    • Presión P
    • Temperatura T

Nuestra matriz de entrada debería ser algo así como (N,5,2):

        [     Step1      Step2      Step3      Step4      Step5
Tank A:    [[Pa1,Ta1], [Pa2,Ta2], [Pa3,Ta3], [Pa4,Ta4], [Pa5,Ta5]],
Tank B:    [[Pb1,Tb1], [Pb2,Tb2], [Pb3,Tb3], [Pb4,Tb4], [Pb5,Tb5]],
  ....
Tank N:    [[Pn1,Tn1], [Pn2,Tn2], [Pn3,Tn3], [Pn4,Tn4], [Pn5,Tn5]],
        ]

Entradas para ventanas correderas

A menudo, se supone que las capas LSTM procesan las secuencias completas. Dividir ventanas puede no ser la mejor idea. La capa tiene estados internos sobre cómo evoluciona una secuencia a medida que avanza. Windows elimina la posibilidad de aprender secuencias largas, limitando todas las secuencias al tamaño de la ventana.

En Windows, cada ventana es parte de una secuencia original larga, pero Keras las verá como una secuencia independiente:

        [     Step1    Step2    Step3    Step4    Step5
Window  A:  [[P1,T1], [P2,T2], [P3,T3], [P4,T4], [P5,T5]],
Window  B:  [[P2,T2], [P3,T3], [P4,T4], [P5,T5], [P6,T6]],
Window  C:  [[P3,T3], [P4,T4], [P5,T5], [P6,T6], [P7,T7]],
  ....
        ]

Tenga en cuenta que en este caso, inicialmente tiene solo una secuencia, pero la está dividiendo en muchas secuencias para crear ventanas.

El concepto de "qué es una secuencia" es abstracto. Las partes importantes son:

  • puedes tener lotes con muchas secuencias individuales
  • lo que hace que las secuencias sean secuencias es que evolucionan en pasos (generalmente pasos de tiempo)

Logrando cada caso con "capas individuales"

Alcanzar el estándar de muchos a muchos:

StandardManyToMany

Puede lograr muchos a muchos con una simple capa LSTM, utilizando return_sequences=True:

outputs = LSTM(units, return_sequences=True)(inputs)

#output_shape -> (batch_size, steps, units)

Logrando muchos a uno:

Usando exactamente la misma capa, los keras harán exactamente el mismo preprocesamiento interno, pero cuando use return_sequences=False(o simplemente ignore este argumento), los keras descartarán automáticamente los pasos anteriores al último:

ManyToOne

outputs = LSTM(units)(inputs)

#output_shape -> (batch_size, units) --> steps were discarded, only the last was returned

Logrando uno a muchos

Ahora, esto no es compatible solo con las capas de Keras LSTM. Tendrás que crear tu propia estrategia para multiplicar los pasos. Hay dos buenos enfoques:

  • Cree una entrada constante de varios pasos repitiendo un tensor
  • Use a stateful=Truepara tomar el resultado de un paso y servirlo como entrada del siguiente paso (necesidades output_features == input_features)

Uno a muchos con vector de repetición

Para adaptarnos al comportamiento estándar de Keras, necesitamos entradas en pasos, por lo tanto, simplemente repetimos las entradas para la longitud que queremos:

OneToManyRepeat

outputs = RepeatVector(steps)(inputs) #where inputs is (batch,features)
outputs = LSTM(units,return_sequences=True)(outputs)

#output_shape -> (batch_size, steps, units)

Comprensión stateful = True

Ahora viene uno de los posibles usos de stateful=True(además de evitar cargar datos que no pueden caber en la memoria de su computadora a la vez)

Stateful nos permite ingresar "partes" de las secuencias en etapas. La diferencia es:

  • En stateful=False, el segundo lote contiene secuencias completamente nuevas, independientes del primer lote
  • En stateful=True, el segundo lote continúa el primer lote, extendiendo las mismas secuencias.

Es como dividir las secuencias en ventanas también, con estas dos diferencias principales:

  • ¡Estas ventanas no se superponen!
  • stateful=True verá estas ventanas conectadas como una sola secuencia larga

En stateful=True, cada lote nuevo se interpretará como la continuación del lote anterior (hasta que llame model.reset_states()).

  • La secuencia 1 en el lote 2 continuará la secuencia 1 en el lote 1.
  • La secuencia 2 en el lote 2 continuará la secuencia 2 en el lote 1.
  • La secuencia n en el lote 2 continuará la secuencia n en el lote 1.

Ejemplo de entradas, el lote 1 contiene los pasos 1 y 2, el lote 2 contiene los pasos 3 a 5:

                   BATCH 1                           BATCH 2
        [     Step1      Step2        |    [    Step3      Step4      Step5
Tank A:    [[Pa1,Ta1], [Pa2,Ta2],     |       [Pa3,Ta3], [Pa4,Ta4], [Pa5,Ta5]],
Tank B:    [[Pb1,Tb1], [Pb2,Tb2],     |       [Pb3,Tb3], [Pb4,Tb4], [Pb5,Tb5]],
  ....                                |
Tank N:    [[Pn1,Tn1], [Pn2,Tn2],     |       [Pn3,Tn3], [Pn4,Tn4], [Pn5,Tn5]],
        ]                                  ]

¡Observe la alineación de los tanques en el lote 1 y el lote 2! Es por eso que necesitamos shuffle=False(a menos que estemos usando solo una secuencia, por supuesto).

Puede tener cualquier cantidad de lotes, indefinidamente. (Para tener longitudes variables en cada lote, use input_shape=(None,features).

Uno a muchos con stateful = True

Para nuestro caso aquí, vamos a usar solo 1 paso por lote, porque queremos obtener un paso de salida y hacer que sea una entrada.

Tenga en cuenta que el comportamiento en la imagen no es "causado por" stateful=True. Forzaremos ese comportamiento en un bucle manual a continuación. En este ejemplo, stateful=Truees lo que "nos permite" detener la secuencia, manipular lo que queremos y continuar desde donde nos detuvimos.

OneToManyStateful

Honestamente, el enfoque repetido es probablemente una mejor opción para este caso. Pero ya que estamos investigando stateful=True, este es un buen ejemplo. La mejor manera de usar esto es el próximo caso "muchos a muchos".

Capa:

outputs = LSTM(units=features, 
               stateful=True, 
               return_sequences=True, #just to keep a nice output shape even with length 1
               input_shape=(None,features))(inputs) 
    #units = features because we want to use the outputs as inputs
    #None because we want variable length

#output_shape -> (batch_size, steps, units) 

Ahora, vamos a necesitar un bucle manual para las predicciones:

input_data = someDataWithShape((batch, 1, features))

#important, we're starting new sequences, not continuing old ones:
model.reset_states()

output_sequence = []
last_step = input_data
for i in steps_to_predict:

    new_step = model.predict(last_step)
    output_sequence.append(new_step)
    last_step = new_step

 #end of the sequences
 model.reset_states()

Muchos a muchos con stateful = True

Ahora, aquí, tenemos una aplicación muy agradable: dada una secuencia de entrada, intente predecir sus futuros pasos desconocidos.

Estamos utilizando el mismo método que en el "uno a muchos" anterior, con la diferencia de que:

  • utilizaremos la secuencia misma para ser los datos objetivo, un paso adelante
  • conocemos parte de la secuencia (por lo que descartamos esta parte de los resultados).

ManyToManyStateful

Capa (igual que la anterior):

outputs = LSTM(units=features, 
               stateful=True, 
               return_sequences=True, 
               input_shape=(None,features))(inputs) 
    #units = features because we want to use the outputs as inputs
    #None because we want variable length

#output_shape -> (batch_size, steps, units) 

Formación:

Vamos a entrenar nuestro modelo para predecir el siguiente paso de las secuencias:

totalSequences = someSequencesShaped((batch, steps, features))
    #batch size is usually 1 in these cases (often you have only one Tank in the example)

X = totalSequences[:,:-1] #the entire known sequence, except the last step
Y = totalSequences[:,1:] #one step ahead of X

#loop for resetting states at the start/end of the sequences:
for epoch in range(epochs):
    model.reset_states()
    model.train_on_batch(X,Y)

Prediciendo:

La primera etapa de nuestra predicción implica "ajustar los estados". Es por eso que vamos a predecir la secuencia completa nuevamente, incluso si ya conocemos esta parte de ella:

model.reset_states() #starting a new sequence
predicted = model.predict(totalSequences)
firstNewStep = predicted[:,-1:] #the last step of the predictions is the first future step

Ahora vamos al bucle como en el caso uno a muchos. ¡Pero no restablezca los estados aquí! . Queremos que el modelo sepa en qué paso de la secuencia se encuentra (y sabe que está en el primer paso nuevo debido a la predicción que acabamos de hacer)

output_sequence = [firstNewStep]
last_step = firstNewStep
for i in steps_to_predict:

    new_step = model.predict(last_step)
    output_sequence.append(new_step)
    last_step = new_step

 #end of the sequences
 model.reset_states()

Este enfoque se utilizó en estas respuestas y archivo:

Logrando configuraciones complejas

En todos los ejemplos anteriores, mostré el comportamiento de "una capa".

Por supuesto, puede apilar muchas capas una encima de la otra, no necesariamente todas siguiendo el mismo patrón, y crear sus propios modelos.

Un ejemplo interesante que ha estado apareciendo es el "autoencoder" que tiene un "codificador muchos a uno" seguido de un decodificador "uno a muchos":

Codificador:

inputs = Input((steps,features))

#a few many to many layers:
outputs = LSTM(hidden1,return_sequences=True)(inputs)
outputs = LSTM(hidden2,return_sequences=True)(outputs)    

#many to one layer:
outputs = LSTM(hidden3)(outputs)

encoder = Model(inputs,outputs)

Descifrador:

Usando el método de "repetición";

inputs = Input((hidden3,))

#repeat to make one to many:
outputs = RepeatVector(steps)(inputs)

#a few many to many layers:
outputs = LSTM(hidden4,return_sequences=True)(outputs)

#last layer
outputs = LSTM(features,return_sequences=True)(outputs)

decoder = Model(inputs,outputs)

Autoencoder:

inputs = Input((steps,features))
outputs = encoder(inputs)
outputs = decoder(outputs)

autoencoder = Model(inputs,outputs)

Entrenar con fit(X,X)

Explicaciones adicionales

Si desea detalles sobre cómo se calculan los pasos en los LSTM, o detalles sobre los stateful=Truecasos anteriores, puede leer más en esta respuesta: Dudas con respecto a `Comprender los LSTM de Keras`

Daniel Möller
fuente
1
Uso muy interesante de stateful con el uso de salidas como entradas. Solo como una nota adicional, otra forma de hacer esto sería usar la API funcional de Keras (como ha hecho aquí, aunque creo que podría haber usado la secuencial), y simplemente reutilizar la misma celda LSTM para cada paso de tiempo , mientras pasa tanto el estado resultante como la salida de la celda a sí mismo. Es decir my_cell = LSTM(num_output_features_per_timestep, return_state=True), seguido de un bucle dea, _, c = my_cell(output_of_previous_time_step, initial_states=[a, c])
Jacob R
1
Las celdas y la longitud son valores completamente independientes. Ninguna de las imágenes representa el número de "celdas". Todos son para "longitud".
Daniel Möller
1
@ DanielMöller Sé que es un poco tarde, pero su respuesta realmente me llamó la atención. Una vez, su punto destrozó todo sobre mi comprensión de lo que es el lote para LSTM. Proporciona ejemplo con N tanques, cinco pasos y dos características. Creí que, si el lote es, por ejemplo, dos, eso significa que dos muestras (tanques con características de 5 pasos 2) se alimentarán a la red y luego se adaptarán los pesos. Pero si lo entiendo correctamente, declara que el lote 2 significa que los pasos de tiempo de las muestras se dividirán en 2 y la primera mitad de todas las muestras se alimentarán a LSTM-> actualización de peso y luego a la segunda.
viceriel
1
Si. En un estado = verdadero, lote 1 = grupo de muestras, actualizar. Luego lote 2 = más pasos para el mismo grupo de muestras, actualizar.
Daniel Möller
2
Desearía poder votar esto 100 veces. Respuesta super útil.
adamconkey
4

Cuando tiene return_sequences en su última capa de RNN, no puede usar una simple capa densa en lugar de usar TimeDistributed.

Aquí hay un ejemplo de código que podría ayudar a otros.

palabras = keras.layers.Input (batch_shape = (None, self.maxSequenceLength), name = "input")

    # Build a matrix of size vocabularySize x EmbeddingDimension 
    # where each row corresponds to a "word embedding" vector.
    # This layer will convert replace each word-id with a word-vector of size Embedding Dimension.
    embeddings = keras.layers.embeddings.Embedding(self.vocabularySize, self.EmbeddingDimension,
        name = "embeddings")(words)
    # Pass the word-vectors to the LSTM layer.
    # We are setting the hidden-state size to 512.
    # The output will be batchSize x maxSequenceLength x hiddenStateSize
    hiddenStates = keras.layers.GRU(512, return_sequences = True, 
                                        input_shape=(self.maxSequenceLength,
                                        self.EmbeddingDimension),
                                        name = "rnn")(embeddings)
    hiddenStates2 = keras.layers.GRU(128, return_sequences = True, 
                                        input_shape=(self.maxSequenceLength, self.EmbeddingDimension),
                                        name = "rnn2")(hiddenStates)

    denseOutput = TimeDistributed(keras.layers.Dense(self.vocabularySize), 
        name = "linear")(hiddenStates2)
    predictions = TimeDistributed(keras.layers.Activation("softmax"), 
        name = "softmax")(denseOutput)  

    # Build the computational graph by specifying the input, and output of the network.
    model = keras.models.Model(input = words, output = predictions)
    # model.compile(loss='kullback_leibler_divergence', \
    model.compile(loss='sparse_categorical_crossentropy', \
        optimizer = keras.optimizers.Adam(lr=0.009, \
            beta_1=0.9,\
            beta_2=0.999, \
            epsilon=None, \
            decay=0.01, \
            amsgrad=False))
Sanjay Krishna
fuente