Como complemento à resposta aceita, esta resposta mostra os comportamentos de keras e como obter cada imagem.
Comportamento geral de Keras
O processamento interno do keras padrão é sempre de muitos para muitos, como na figura a seguir (onde usei features=2
pressão e temperatura, apenas como exemplo):
Nesta imagem, aumentei o número de etapas para 5, para evitar confusão com as outras dimensões.
Para este exemplo:
- Temos N tanques de óleo
- Passamos 5 horas tomando medidas por hora (intervalos de tempo)
- Medimos dois recursos:
Nossa matriz de entrada deve ter o formato de (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 janelas deslizantes
Freqüentemente, as camadas LSTM devem processar as seqüências inteiras. Dividir janelas pode não ser a melhor ideia. A camada possui estados internos sobre como uma sequência está evoluindo à medida que avança. O Windows elimina a possibilidade de aprender seqüências longas, limitando todas as sequências ao tamanho da janela.
Nas janelas, cada janela faz parte de uma longa sequência original, mas por Keras elas serão vistas como uma sequência independente:
[ 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]],
....
]
Observe que, nesse caso, você possui inicialmente apenas uma sequência, mas a divide em várias seqüências para criar janelas.
O conceito de "o que é uma sequência" é abstrato. As partes importantes são:
- você pode ter lotes com muitas sequências individuais
- o que faz as sequências serem sequências é que elas evoluem em etapas (geralmente etapas de tempo)
Atingir cada caso com "camadas únicas"
Atingir o padrão de muitos para muitos:
Você pode conseguir muitos para muitos com uma camada LSTM simples, usando return_sequences=True
:
outputs = LSTM(units, return_sequences=True)(inputs)
#output_shape -> (batch_size, steps, units)
Conseguindo muitos para um:
Usando exatamente a mesma camada, o keras executará exatamente o mesmo pré-processamento interno, mas quando você usar return_sequences=False
(ou simplesmente ignorar esse argumento), o keras descartará automaticamente as etapas anteriores à anterior:
outputs = LSTM(units)(inputs)
#output_shape -> (batch_size, units) --> steps were discarded, only the last was returned
Conseguir um para muitos
Agora, isso não é suportado apenas pelas camadas keras LSTM. Você precisará criar sua própria estratégia para multiplicar as etapas. Existem duas boas abordagens:
- Crie uma entrada constante de várias etapas repetindo um tensor
- Use a
stateful=True
para obter recorrentemente a saída de uma etapa e servi-la como a entrada da próxima etapa (necessidades output_features == input_features
)
Um para muitos com vetor de repetição
Para ajustar ao comportamento padrão do keras, precisamos de entradas em etapas; portanto, simplesmente repetimos as entradas pelo comprimento que queremos:
outputs = RepeatVector(steps)(inputs) #where inputs is (batch,features)
outputs = LSTM(units,return_sequences=True)(outputs)
#output_shape -> (batch_size, steps, units)
Compreendendo stateful = True
Agora vem um dos possíveis usos de stateful=True
(além de evitar o carregamento de dados que não cabem na memória do computador de uma só vez)
Stateful nos permite inserir "partes" das seqüências em etapas. A diferença é:
- Em
stateful=False
, o segundo lote contém novas seqüências inteiras, independentes do primeiro lote
- Em
stateful=True
, o segundo lote continua o primeiro lote, estendendo as mesmas seqüências.
É como dividir as seqüências nas janelas também, com essas duas principais diferenças:
- essas janelas não se sobrepõem !!
stateful=True
verá essas janelas conectadas como uma única sequência longa
Em stateful=True
, cada novo lote será interpretado como continuando o lote anterior (até você ligar model.reset_states()
).
- A sequência 1 no lote 2 continuará a sequência 1 no lote 1.
- A sequência 2 no lote 2 continuará a sequência 2 no lote 1.
- A sequência n no lote 2 continuará a sequência n no lote 1.
Exemplo de entradas, o lote 1 contém as etapas 1 e 2, o lote 2 contém as etapas 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 o alinhamento dos tanques no lote 1 e no lote 2! É por isso que precisamos shuffle=False
(a menos que estejamos usando apenas uma sequência, é claro).
Você pode ter qualquer número de lotes, indefinidamente. (Para ter comprimentos variáveis em cada lote, use input_shape=(None,features)
.
Um para muitos com stateful = True
Para o nosso caso aqui, usaremos apenas 1 etapa por lote, porque queremos obter uma etapa de saída e torná-la uma entrada.
Observe que o comportamento na imagem não é "causado por" stateful=True
. Forçaremos esse comportamento em um loop manual abaixo. Neste exemplo, stateful=True
é o que "nos permite" parar a sequência, manipular o que queremos e continuar de onde paramos.
Honestamente, a abordagem de repetição é provavelmente a melhor escolha para este caso. Mas, como estamos analisando stateful=True
, este é um bom exemplo. A melhor maneira de usar isso é o próximo caso "muitos para muitos".
Camada:
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)
Agora, vamos precisar de um loop manual para previsões:
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()
Muitos para muitos com stateful = True
Agora, aqui, temos uma aplicação muito boa: dada uma sequência de entrada, tente prever suas futuras etapas desconhecidas.
Estamos usando o mesmo método do "um para muitos" acima, com a diferença de que:
- usaremos a própria sequência como dados de destino, um passo à frente
- conhecemos parte da sequência (então descartamos essa parte dos resultados).
Camada (o mesmo que acima):
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)
Treinamento:
Vamos treinar nosso modelo para prever o próximo passo das seqüências:
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)
Prever:
O primeiro estágio de nossa previsão envolve "ajustar os estados". É por isso que vamos prever a sequência inteira novamente, mesmo que já conheçamos essa parte:
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
Agora vamos ao loop, como no caso de um para muitos. Mas não redefina os estados aqui! . Queremos que o modelo saiba em qual etapa da sequência ele está (e sabe que está no primeiro novo passo por causa da previsão que acabamos de fazer acima)
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()
Esta abordagem foi usada nestas respostas e arquivo:
Atingindo configurações complexas
Em todos os exemplos acima, mostrei o comportamento de "uma camada".
Obviamente, é possível empilhar muitas camadas umas sobre as outras, nem todas seguindo o mesmo padrão, e criar seus próprios modelos.
Um exemplo interessante que vem aparecendo é o "autoencoder" que possui um "muitos para um codificador" seguido por um "um para muitos" decodificador:
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)
Decodificador:
Usando o método "repeat";
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)
Treinar com fit(X,X)
Explicações adicionais
Se você quiser detalhes sobre como as etapas são calculadas nos LSTMs ou detalhes sobre os stateful=True
casos acima, poderá ler mais nesta resposta: Dúvidas sobre `Noções básicas sobre LSTMs do Keras`