J'essaie de réconcilier ma compréhension des LSTM et cela est souligné ici dans cet article de Christopher Olah implémenté à Keras. Je suis le blog écrit par Jason Brownlee pour le tutoriel sur Keras. Ce qui me rend le plus confus, c'est
[samples, time steps, features]
et,Permet de se concentrer sur les deux questions ci-dessus en se référant au code collé ci-dessous:
# 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()
Remarque: create_dataset prend une séquence de longueur N et retourne un tableau N-look_back
dont chaque élément est une séquence look_back
longueur.
Comme on peut le voir, TrainX est un tableau 3-D avec Time_steps et Feature étant les deux dernières dimensions, respectivement (3 et 1 dans ce code particulier). En ce qui concerne l'image ci-dessous, cela signifie-t-il que nous considérons le cas many to one
où le nombre de cases roses est égal à 3? Ou signifie-t-il littéralement que la longueur de la chaîne est de 3 (c’est-à-dire que seulement 3 cases vertes sont considérées)?
L'argument des caractéristiques devient-il pertinent lorsque l'on considère des séries multivariées? par exemple. modéliser deux valeurs financières simultanément?
Les LSTM avec état signifient-ils que nous sauvegardons les valeurs de mémoire de cellule entre les exécutions de lots? Si tel est le cas, batch_size
en est un et la mémoire est réinitialisée entre les séances d’entraînement, alors quel était l’intérêt de dire qu’elle était dynamique. J'imagine que cela est lié au fait que les données d'entraînement ne sont pas brassées, mais je ne sais pas comment.
Des pensées? Référence de l'image: http://karpathy.github.io/2015/05/21/rnn-effectiveness/
Un peu confus à propos du commentaire de @ van à propos de l'égalité des cases rouge et verte. Donc, juste pour confirmer, les appels d'API suivants correspondent-ils aux diagrammes déroulés? Notant particulièrement le deuxième diagramme (batch_size
a été choisi arbitrairement.):
Pour les personnes qui ont suivi le cours d'apprentissage approfondi d'Udacity et qui ne comprennent toujours pas l'argument time_step, consultez la discussion suivante: https://discussions.udacity.com/t/rnn-lstm-use-implementation/163169 =
Il s’avère que model.add(TimeDistributed(Dense(vocab_len)))
était ce que je recherchais. Voici un exemple: https://github.com/sachinruk/ShakespeareBot
J'ai résumé l'essentiel de ma compréhension des LSTM ici: https://www.youtube.com/watch?v=ywinX5wgdE
Tout d’abord, vous choisissez d’excellents tutoriels ( 1 , 2 ) pour commencer.
Signification du pas de temps : Time-steps==3
in X.shape (description de la forme des données) signifie qu'il existe trois zones roses. Étant donné qu’en Keras chaque étape nécessite une entrée, le nombre de cases vertes doit donc normalement être égal au nombre de cases rouges. Sauf si vous piratez la structure.
plusieurs à plusieurs contre plusieurs à un : Dans keras, il existe un paramètre return_sequences
lors de l'initialisation de LSTM
ou GRU
ou SimpleRNN
. Lorsque return_sequences
est False
(par défaut), il s'agit alors de plusieurs à un , comme indiqué dans l'image. Sa forme de retour est (batch_size, hidden_unit_length)
, qui représente le dernier état. Lorsque return_sequences
est True
, il s'agit de nombre en nombre . Sa forme de retour est (batch_size, time_step, hidden_unit_length)
Est-ce que l'argument features devient pertinent : l'argument Feature signifie "Quelle est la taille de votre boîte rouge" ou quelle est la dimension en entrée à chaque étape. Si vous voulez prédire à partir de, par exemple, 8 types d'informations sur le marché, vous pouvez générer vos données avec feature==8
.
Stateful : Vous pouvez rechercher le code source . Lors de l'initialisation de l'état, si stateful==True
, l'état de la dernière formation sera utilisé comme état initial, sinon il générera un nouvel état. Je n'ai pas encore allumé stateful
. Cependant, je ne suis pas d'accord avec le fait que le batch_size
ne peut être égal à 1 que lorsque stateful==True
.
Actuellement, vous générez vos données avec les données collectées. Image Si vos informations boursières arrivent sous forme de flux, plutôt que d'attendre une journée pour collecter toutes les données de manière séquentielle, vous souhaitez générer des données d'entrée en ligne pendant la formation/la prévision. avec réseau. Si vous avez 400 actions partageant le même réseau, vous pouvez définir batch_size==400
.
En complément de la réponse acceptée, cette réponse montre les comportements de keras et comment atteindre chaque image.
Le traitement interne standard des keras est toujours multiple, comme dans l’image suivante (où j’utilisais features=2
, pression et température, à titre d’exemple):
Dans cette image, j'ai augmenté le nombre d'étapes à 5, pour éviter toute confusion avec les autres dimensions.
Pour cet exemple:
Notre tableau d’entrée devrait alors être de la forme (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]],
]
Souvent, les couches LSTM sont supposées traiter toutes les séquences. Diviser les fenêtres peut ne pas être la meilleure idée. La couche a des états internes sur l'évolution d'une séquence à mesure qu'elle avance. Les fenêtres éliminent la possibilité d'apprendre de longues séquences, en limitant toutes les séquences à la taille de la fenêtre.
Dans les fenêtres, chaque fenêtre fait partie d’une longue séquence originale, mais, par Keras, elles seront vues comme une séquence indépendante:
[ 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]],
....
]
Notez que dans ce cas, vous n'avez initialement qu'une séquence, mais vous la divisez en plusieurs séquences pour créer des fenêtres.
Le concept de "qu'est-ce qu'une séquence" est abstrait. Les parties importantes sont:
Vous pouvez obtenir plusieurs résultats avec un simple calque LSTM, en utilisant return_sequences=True
:
outputs = LSTM(units, return_sequences=True)(inputs)
#output_shape -> (batch_size, steps, units)
En utilisant exactement le même calque, keras effectuera exactement le même traitement interne, mais si vous utilisez return_sequences=False
(ou ignorez simplement cet argument), keras ignorera automatiquement les étapes précédentes.
outputs = LSTM(units)(inputs)
#output_shape -> (batch_size, units) --> steps were discarded, only the last was returned
À présent, ceci n'est pas pris en charge par les couches keras LSTM uniquement. Vous devrez créer votre propre stratégie pour multiplier les étapes. Il y a deux bonnes approches:
stateful=True
pour prendre de manière récurrente la sortie d'une étape et la servir comme entrée de l'étape suivante (nécessite output_features == input_features
)Afin de s’adapter au comportement standard de keras, nous avons besoin d’entrées par étapes. Nous répétons donc simplement les entrées pour la longueur souhaitée:
outputs = RepeatVector(steps)(inputs) #where inputs is (batch,features)
outputs = LSTM(units,return_sequences=True)(outputs)
#output_shape -> (batch_size, steps, units)
Vient maintenant l'une des utilisations possibles de stateful=True
(en plus d'éviter de charger des données qui ne peuvent pas contenir la mémoire de votre ordinateur à la fois)
Stateful nous permet de saisir des "parties" des séquences par étapes. La différence est:
stateful=False
, le deuxième lot contient de toutes nouvelles séquences, indépendantes du premier lot.stateful=True
, le deuxième lot continue le premier, étendant les mêmes séquences.C'est comme si on divisait les séquences dans Windows aussi, avec ces deux différences principales:
stateful=True
verra ces fenêtres connectées en une seule séquence longueDans stateful=True
, chaque nouveau lot sera interprété comme une continuation du lot précédent (jusqu'à ce que vous appeliez model.reset_states()
).
Exemple d’entrées, le lot 1 contient les étapes 1 et 2, le lot 2 contient les étapes 3 à 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]],
] ]
Notez l'alignement des réservoirs des lots 1 et 2! C'est pourquoi nous avons besoin de shuffle=False
(à moins que nous n'utilisions qu'une seule séquence, bien sûr).
Vous pouvez avoir un nombre illimité de lots, indéfiniment. (Pour avoir des longueurs variables dans chaque lot, utilisez input_shape=(None,features)
.
Dans notre cas, nous n'utiliserons qu'une étape par lot, car nous souhaitons obtenir une étape de sortie et en faire une entrée.
Veuillez noter que le comportement dans l'image n'est pas "causé par" stateful=True
. Nous allons forcer ce comportement dans une boucle manuelle ci-dessous. Dans cet exemple, stateful=True
est ce qui "nous permet" d'arrêter la séquence, de manipuler ce que nous voulons et de continuer à partir de l'endroit où nous nous sommes arrêtés.
Honnêtement, la répétition est probablement un meilleur choix pour ce cas. Mais puisque nous examinons stateful=True
, c'est un bon exemple. La meilleure façon de l'utiliser est le prochain cas "plusieurs à plusieurs".
Couche:
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)
Maintenant, nous allons avoir besoin d’une boucle manuelle pour les prédictions:
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()
Maintenant, ici, nous obtenons une application très agréable: à partir d’une séquence d’entrée, essayez de prédire ses futures étapes inconnues.
Nous utilisons la même méthode que dans le "un à plusieurs" ci-dessus, à la différence que:
Couche (comme ci-dessus):
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)
Formation:
Nous allons former notre modèle pour prédire la prochaine étape des séquences:
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)
Prédiction:
La première étape de notre prédiction consiste à "ajuster les états". C'est pourquoi nous allons prédire la séquence complète à nouveau, même si nous connaissons déjà cette partie:
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
Nous passons maintenant à la boucle comme dans le cas un à plusieurs. Mais ne réinitialise pas les états ici! . Nous voulons que le modèle sache à quelle étape de la séquence il se trouve (et il sait que c'est la première étape nouvelle en raison de la prédiction que nous venons de faire ci-dessus)
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()
Cette approche a été utilisée dans ces réponses et fichier:
Dans tous les exemples ci-dessus, j'ai montré le comportement de "une couche".
Vous pouvez bien entendu empiler plusieurs couches les unes sur les autres, sans nécessairement suivre le même modèle, et créer vos propres modèles.
Un exemple intéressant qui est apparu est le "autoencoder" qui a un "encodeur plusieurs à un" suivi d'un décodeur "un à plusieurs":
Encodeur:
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)
Décodeur:
Utiliser la méthode "répéter";
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)
Entraînez-vous avec fit(X,X)
Si vous souhaitez des détails sur la manière dont les étapes sont calculées dans les LSTM ou sur les cas stateful=True
ci-dessus, vous pouvez en savoir plus dans cette réponse: Doutes concernant la compréhension de Keras LSTM`
Lorsque vous avez return_sequences dans votre dernière couche de RNN, vous ne pouvez pas utiliser une simple couche dense à la place, utilisez plutôt TimeDistributed.
Voici un exemple de code susceptible d’aider les autres.
mots = 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))