web-dev-qa-db-fra.com

Comprendre les LSTM Keras

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

  1. Le remodelage de la série de données en [samples, time steps, features] et,
  2. Les LSTM à états

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.

Qu'est-ce que les pas de temps et les fonctionnalités?

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)? enter image description here

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?

LSTM avec état

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/

Modifier 1:

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.): enter image description hereenter image description here

Edit 2:

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 =

Mise à jour:

Il s’avère que model.add(TimeDistributed(Dense(vocab_len))) était ce que je recherchais. Voici un exemple: https://github.com/sachinruk/ShakespeareBot

Update2:

J'ai résumé l'essentiel de ma compréhension des LSTM ici: https://www.youtube.com/watch?v=ywinX5wgdE

239
sachinruk

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.

132
Van

En complément de la réponse acceptée, cette réponse montre les comportements de keras et comment atteindre chaque image.

Comportement général keras

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

ManyToMany

Dans cette image, j'ai augmenté le nombre d'étapes à 5, pour éviter toute confusion avec les autres dimensions.

Pour cet exemple:

  • Nous avons N réservoirs d'huile
  • Nous avons passé 5 heures à prendre des mesures heure par heure
  • Nous avons mesuré deux caractéristiques:
    • Pression P
    • Température t

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

Entrées pour fenêtres coulissantes

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 avoir des lots avec de nombreuses séquences individuelles
  • ce qui fait que les séquences sont des séquences, c'est qu'elles évoluent par étapes (généralement des pas de temps)

Réaliser chaque cas avec des "couches simples"

Atteindre la norme plusieurs à plusieurs:

StandardManyToMany

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)

Atteindre plusieurs à un:

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.

ManyToOne

outputs = LSTM(units)(inputs)

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

Atteindre un à plusieurs

À 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:

  • Créez une entrée constante en plusieurs étapes en répétant un tenseur
  • Utilisez un 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)

Un à plusieurs avec le vecteur de répétition

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:

OneToManyRepeat

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

#output_shape -> (batch_size, steps, units)

Compréhension avec état = Vrai

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:

  • Dans stateful=False, le deuxième lot contient de toutes nouvelles séquences, indépendantes du premier lot.
  • Dans 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:

  • ces fenêtres ne se superposent pas !!
  • stateful=True verra ces fenêtres connectées en une seule séquence longue

Dans stateful=True, chaque nouveau lot sera interprété comme une continuation du lot précédent (jusqu'à ce que vous appeliez model.reset_states()).

  • La séquence 1 du lot 2 continuera la séquence 1 du lot 1.
  • La séquence 2 du lot 2 continuera la séquence 2 du lot 1.
  • La séquence n du lot 2 continuera la séquence n du lot 1.

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

Un à plusieurs avec stateful = True

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.

OneToManyStateful

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

Beaucoup à beaucoup avec stateful = True

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:

  • nous allons utiliser la séquence elle-même pour être la donnée cible, un pas en avant
  • nous connaissons une partie de la séquence (nous écartons donc cette partie des résultats).

ManyToManyStateful

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:

Réaliser des configurations complexes

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)

Explications supplémentaires

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`

117
Daniel Möller

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))
0
Sanjay Krishna