403 votes

Comprendre les LSTM de Keras

J'essaye de réconcilier ma compréhension des LSTMs et ce qui est indiqué ici dans ce billet par Christopher Olah mis en œuvre dans Keras. Je suis les blog écrit par Jason Brownlee pour le tutoriel Keras. Ce qui m'embrouille le plus est le suivant,

  1. Le remodelage des séries de données en [samples, time steps, features] et,
  2. Les LSTMs à états

Concentrons-nous sur les deux questions ci-dessus en nous 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 renvoie une séquence de type N-look_back dont chaque élément est un look_back séquence de longueur.

Qu'est-ce que les Time Steps et les Features ?

Comme on peut le voir, TrainX est un tableau 3-D dont les deux dernières dimensions sont respectivement Time_steps et Feature (3 et 1 dans ce code particulier). En ce qui concerne l'image ci-dessous, cela signifie-t-il que nous considérons le tableau many to one où le nombre de boîtes roses est de 3 ? Ou cela signifie-t-il littéralement que la longueur de la chaîne est de 3 (c'est-à-dire que seules 3 cases vertes sont prises en compte). enter image description here

L'argument des caractéristiques devient-il pertinent lorsque l'on considère des séries multivariées ? Par exemple, en modélisant deux valeurs financières simultanément ?

LSTMs avec état

Les LSTMs stateful signifient-ils que nous sauvegardons les valeurs de la mémoire cellulaire entre les passages de lots ? Si c'est le cas, batch_size est un, et la mémoire est réinitialisée entre les cycles d'entraînement, alors quel était l'intérêt de dire qu'il était stateful. Je suppose que cela est lié au fait que les données d'entraînement ne sont pas mélangées, mais je ne sais pas comment.

Des idées ? Référence de l'image : http://karpathy.github.io/2015/05/21/rnn-effectiveness/

Edit 1 :

Je suis un peu confus quant au commentaire de @van sur l'égalité des cases rouge et verte. Donc, juste pour confirmer, est-ce que les appels API suivants correspondent aux diagrammes déroulés ? Je remarque en particulier le deuxième diagramme ( batch_size a été choisi arbitrairement) : enter image description here enter image description here

Edit 2 :

Pour les personnes qui ont suivi le cours d'apprentissage profond 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

Mise à jour2 :

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

10 votes

La première photo devrait être (batch_size, 5, 1) ; la deuxième photo devrait être (batch_size, 4, 3) (s'il n'y a pas de séquences suivantes). Et pourquoi la sortie est toujours "X" ? Devrait-il être "Y" ?

1 votes

Ici, je suppose que X_1, X_2 ... X_6 est un seul nombre. Et trois nombres (X_1, X_2, X_3) forment un vecteur de forme (3,). Un seul nombre (X_1) forme un vecteur de forme (1,).

2 votes

@Van, votre hypothèse est correcte. C'est intéressant, donc en fait le modèle n'apprend pas de modèle au-delà du nombre de pas de temps. Donc si j'ai une série temporelle d'une longueur de 1000, et que je peux visuellement voir un modèle tous les 100 jours, je devrais faire en sorte que le paramètre time_steps soit au moins de 100. Cette observation est-elle correcte ?

268voto

Daniel Points 2149

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

Comportement général de Keras

Le traitement interne standard de keras est toujours un many to many comme dans l'image suivante (où j'ai utilisé features=2 la pression et la 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 des réservoirs d'huile N
  • Nous avons passé 5 heures à prendre des mesures à l'heure (pas de temps)
  • Nous avons mesuré deux caractéristiques :
    • Pression P
    • Température T

Notre tableau d'entrée devrait alors avoir la forme suivante (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 les fenêtres coulissantes

Souvent, les couches LSTM sont censées traiter les séquences entières. Diviser les fenêtres n'est peut-être pas la meilleure idée. La couche a des états internes sur la façon dont une séquence évolue au fur et à 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 Windows, chaque fenêtre fait partie d'une longue séquence originale, mais avec Keras, elles seront considérées chacune 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]],
  ....
        ]

Remarquez que dans ce cas, vous n'avez initialement qu'une seule séquence, mais vous la divisez en plusieurs séquences pour créer Windows.

Le concept de "ce qu'est 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 étapes de temps).

Réaliser chaque cas avec des "couches uniques".

Atteindre la norme beaucoup à beaucoup :

StandardManyToMany

Vous pouvez obtenir un grand nombre d'entre eux avec une simple couche LSTM, en utilisant les éléments suivants return_sequences=True :

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

#output_shape -> (batch_size, steps, units)

Réaliser le "many to one" :

En utilisant exactement la même couche, keras effectuera exactement le même prétraitement interne, mais lorsque vous utilisez return_sequences=False (ou ignorez simplement cet argument), keras ignorera automatiquement les étapes antérieures à la dernière :

ManyToOne

outputs = LSTM(units)(inputs)

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

Réaliser le one to many

Or, ceci n'est pas pris en charge par les seules couches LSTM de Keras. Vous devrez créer votre propre stratégie pour multiplier les étapes. Il existe deux bonnes approches :

  • Créer une entrée constante à 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 l'utiliser comme entrée de l'étape suivante (besoin de output_features == input_features )

Un à plusieurs avec vecteur de répétition

Afin de s'adapter au comportement standard de Keras, nous avons besoin d'entrées par étapes, donc, nous répétons simplement les entrées pour la longueur que nous voulons :

OneToManyRepeat

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

#output_shape -> (batch_size, steps, units)

Comprendre stateful = True

Voici maintenant l'une des utilisations possibles de stateful=True (en plus d'éviter de charger des données qui ne peuvent pas occuper la mémoire de votre ordinateur en une seule fois)

Stateful nous permet de saisir des "parties" des séquences par étapes. La différence est la suivante :

  • Sur stateful=False le deuxième lot contient des séquences entièrement nouvelles, indépendantes du premier lot.
  • Sur stateful=True le deuxième lot poursuit le premier lot, en prolongeant les mêmes séquences.

C'est comme diviser 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 comme une seule longue séquence.

Sur stateful=True chaque nouveau lot sera interprété comme la 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 poursuivra 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]],
        ]                                  ]

Remarquez l'alignement des réservoirs dans le lot 1 et le lot 2 ! C'est pourquoi nous avons besoin shuffle=False (à moins que l'on n'utilise qu'une seule séquence, bien sûr).

Vous pouvez avoir un nombre quelconque de lots, indéfiniment. (Pour avoir des longueurs variables dans chaque lot, utilisez input_shape=(None,features) .

Un à plusieurs avec stateful=True

Pour notre cas ici, nous allons utiliser seulement 1 étape par lot, parce que nous voulons obtenir une étape de sortie et la faire devenir 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 reprendre là où nous nous sommes arrêtés.

OneToManyStateful

Honnêtement, l'approche de la répétition est probablement un meilleur choix pour ce cas. Mais puisque nous cherchons à stateful=True Il s'agit d'un bon exemple. La meilleure façon de l'utiliser est le cas suivant "many to many".

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

Plusieurs à plusieurs avec stateful=True

Maintenant, ici, nous obtenons une très belle application : étant donné 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, avec la différence que :

  • nous utiliserons la séquence elle-même comme donnée cible, avec une longueur d'avance.
  • nous connaissons une partie de la séquence (nous rejetons 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 entraîner notre modèle à prédire l'étape suivante 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édire :

La première étape de notre prédiction consiste à "ajuster les états". C'est pourquoi nous allons à nouveau prédire la séquence entière, même si nous connaissons déjà cette partie de la séquence :

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

Maintenant, nous passons à la boucle comme dans le cas d'un à plusieurs. Mais ne réinitialisez pas les états ici ! . Nous voulons que le modèle sache à quelle étape de la séquence il se trouve (et il sait qu'il se trouve à la première nouvelle étape grâce à 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 ce dossier :

Réaliser des configurations complexes

Dans tous les exemples ci-dessus, j'ai montré le comportement d'une "seule couche".

Vous pouvez, bien sûr, empiler de nombreuses 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 a fait son apparition est celui de l'"autoencodeur" qui comporte un "codeur de plusieurs à un" suivi d'un décodeur de "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 :

En utilisant la méthode de "répétition" ;

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)

Auto-codeur :

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

autoencoder = Model(inputs,outputs)

S'entraîner avec fit(X,X)

Explications supplémentaires

Si vous souhaitez obtenir des détails sur la façon dont les étapes sont calculées dans les LSTM, ou des détails sur la méthode de calcul de l'indice d'efficacité de l'énergie. stateful=True ci-dessus, vous pouvez en savoir plus dans cette réponse : Doutes concernant "Comprendre les LSTM de Keras".

0 votes

C'est une excellente réponse. Mais il me manque encore une chose. Disons que le LSTM est censé apprendre 2 séquences I :[1,2,3]->O :[4,5,6] et I :[1,2,0]->O :[0,0,0]. Il est censé prédire 3 étapes, mais pour prédire l'étape 3, les étapes 1 et 2 doivent être prédites en premier. Le problème est que les étapes 1 et 2 ont des résultats ambigus par elles-mêmes. Alors que se passe-t-il lorsque l'étape 3 est introduite dans le réseau ? Les sorties des étapes 1 et 2 sont corrigées ?

0 votes

Ce filet n'apprendra probablement pas bien. Il n'y a pas de correction du passé. Mais vous pouvez toujours essayer d'ajouter des couches supplémentaires (y compris non récurrentes) pour analyser les étapes et compenser les erreurs, puis des couches récurrentes supplémentaires. Vous pouvez également créer un réseau en utilisant go_backwards=True ou utiliser Bidirectional(LSTM(...)) et empiler plus de couches pour analyser et compenser les erreurs. Mais honnêtement, je n'ai aucune idée de la façon dont on peut faire une solution "pas à pas" pour les réseaux bidirectionnels de longueur indéfinie.

0 votes

Hmm ça fait bizarre. Cela signifie-t-il que les séquences ne peuvent pas avoir le même début pour être correctement reconnues ? Je crois que cela arrive tout le temps. Surtout dans les séquences binaires.

212voto

Van Points 1812

Tout d'abord, vous choisissez de superbes tutoriels( 1 , 2 ) pour commencer.

Ce que signifie Time-step : Time-steps==3 dans X.shape (Description de la forme des données) signifie qu'il y a trois boîtes roses. Puisque dans Keras, chaque étape nécessite une entrée, le nombre de cases vertes devrait normalement être égal au nombre de cases rouges. A moins que vous ne piratez la structure.

plusieurs à plusieurs vs. plusieurs à un : Dans keras, il existe un return_sequences lors de votre initialisation LSTM o GRU o SimpleRNN . Quand return_sequences es False (par défaut), alors c'est de plusieurs à un comme indiqué sur la photo. Sa forme de retour est (batch_size, hidden_unit_length) qui représentent le dernier état. Lorsque return_sequences es True alors c'est beaucoup à beaucoup . Sa forme de retour est (batch_size, time_step, hidden_unit_length)

L'argument des caractéristiques devient-il pertinent ? : L'argument caractéristique signifie "Quelle est la taille de votre boîte rouge" ou quelle est la dimension de l'entrée à chaque étape. Si vous voulez prédire à partir de, disons, 8 types d'informations sur le marché, alors vous pouvez générer vos données avec feature==8 .

Stateful : Vous pouvez consulter le code source . Lors de l'initialisation de l'état, si stateful==True alors l'état de la dernière formation sera utilisé comme état initial, sinon il générera un nouvel état. Je n'ai pas activé stateful encore. Cependant, je ne suis pas d'accord avec le fait que le batch_size ne peut être égal à 1 que si stateful==True .

Actuellement, vous générez vos données à partir des données collectées. L'image de vos informations de stock arrive en flux continu, plutôt que d'attendre une journée pour collecter toutes les données séquentielles, vous voudriez générer des données d'entrée en ligne tout en formant/prédisant avec le réseau. Si vous avez 400 actions qui partagent un même réseau, vous pouvez définir les paramètres suivants batch_size==400 .

0 votes

Je ne comprends pas très bien pourquoi les cases rouges et vertes doivent être identiques. Pourriez-vous regarder la modification que j'ai faite (les nouvelles images principalement) et commenter ?

0 votes

Je pense que si vous définissez le pas de temps à 1, il ne s'agit pas d'un réseau neuronal récurrent. Il se réduira au cas "un à un" de la figure la plus à gauche. Est-ce exact ?

0 votes

C'est quand même un réseau récurrent. Parce que l'état initial (h_0) peut être hérité de la mise à jour précédente. En appliquant time-step=1 N fois (en gardant le dernier état caché comme état initial suivant), vous pouvez en fait obtenir le même effet que time-step=N. Ceci est très utile lorsque vos données sont acquises via un flux sans tampon.

11voto

Sanjay Krishna Points 16

Lorsque vous avez des séquences de retour dans votre dernière couche de RNN, vous ne pouvez pas utiliser une simple couche dense, mais plutôt une couche distribuée dans le temps.

Voici un exemple de code qui pourrait aider d'autres personnes.

words = 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))

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X