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):
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:
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:
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:
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=True
para 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:
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=True
es lo que "nos permite" detener la secuencia, manipular lo que queremos y continuar desde donde nos detuvimos.
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).
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=True
casos anteriores, puede leer más en esta respuesta: Dudas con respecto a `Comprender los LSTM de Keras`