web-dev-qa-db-fra.com

Stateful LSTM et prévisions de flux

J'ai formé un modèle LSTM (construit avec Keras et TF) sur plusieurs lots de 7 échantillons avec 3 fonctions chacun, avec une forme similaire à celle de l'échantillon ci-dessous (les chiffres ci-dessous ne sont que des espaces réservés aux fins de l'explication), chaque lot est étiqueté 0 ou 1:

Les données:

[
   [[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3]]
   [[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3]]
   [[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3]]
   ...
]

c'est-à-dire: des lots de m séquences, chacune de longueur 7, dont les éléments sont des vecteurs tridimensionnels (le lot a donc une forme (m * 7 * 3))

Cible:

[
   [1]
   [0]
   [1]
   ...
]

Dans mon environnement de production, les données sont un flux d’échantillons avec 3 fonctionnalités ([1,2,3],[1,2,3]...). Je voudrais diffuser chaque échantillon à mesure qu'il arrive à mon modèle et obtenir la probabilité intermédiaire sans attendre le lot entier (7) - voir l'animation ci-dessous.

 enter image description here

Une de mes pensées a été de remplir le lot avec 0 pour les échantillons manquants, [[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[1,2,3]] mais cela semble être inefficace.

J'apprécierai toute aide qui me guidera dans la bonne direction, à la fois pour sauvegarder l'état intermédiaire LSTM de manière persistante, en attendant l'échantillon suivant et pour prédire sur un modèle formé sur une taille de lot spécifique avec des données partielles.


Update,, y compris le code du modèle:

opt = optimizers.Adam(lr=0.001, beta_1=0.9, beta_2=0.999, epsilon=10e-8, decay=0.001)
model = Sequential()

num_features = data.shape[2]
num_samples = data.shape[1]

first_lstm = LSTM(32, batch_input_shape=(None, num_samples, num_features), return_sequences=True, activation='tanh')
model.add(
    first_lstm)
model.add(LeakyReLU())
model.add(Dropout(0.2))
model.add(LSTM(16, return_sequences=True, activation='tanh'))
model.add(Dropout(0.2))
model.add(LeakyReLU())
model.add(Flatten())
model.add(Dense(1, activation='sigmoid'))

model.compile(loss='binary_crossentropy', optimizer=opt,
              metrics=['accuracy', keras_metrics.precision(), keras_metrics.recall(), f1])

Résumé du modèle:

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
lstm_1 (LSTM)                (None, 100, 32)           6272      
_________________________________________________________________
leaky_re_lu_1 (LeakyReLU)    (None, 100, 32)           0         
_________________________________________________________________
dropout_1 (Dropout)          (None, 100, 32)           0         
_________________________________________________________________
lstm_2 (LSTM)                (None, 100, 16)           3136      
_________________________________________________________________
dropout_2 (Dropout)          (None, 100, 16)           0         
_________________________________________________________________
leaky_re_lu_2 (LeakyReLU)    (None, 100, 16)           0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 1600)              0         
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 1601      
=================================================================
Total params: 11,009
Trainable params: 11,009
Non-trainable params: 0
_________________________________________________________________
12
Shlomi Schwartz

Je pense qu'il pourrait y avoir une solution plus facile. 

Si votre modèle ne comporte pas de couches convolutives ou d'autres couches agissant sur la dimension longueur/pas, vous pouvez simplement le marquer comme stateful=True.

Attention: votre modèle comporte des calques qui agissent sur la dimension en longueur !!

La couche Flatten transforme la dimension de longueur en dimension d’entité. Cela vous empêchera complètement d'atteindre votre objectif. Si la couche Flatten attend 7 étapes, vous en aurez toujours besoin. 

Donc, avant d'appliquer ma réponse ci-dessous, corrigez votre modèle pour qu'il n'utilise pas la couche Flatten. Au lieu de cela, il peut simplement supprimer le return_sequences=True pour la couche last LSTM. 

Le code suivant corrige cela et prépare également quelques choses à utiliser avec la réponse ci-dessous: 

def createModel(forTraining):

    #model for training, stateful=False, any batch size   
    if forTraining == True:
        batchSize = None
        stateful = False

    #model for predicting, stateful=True, fixed batch size
    else:
        batchSize = 1
        stateful = True

    model = Sequential()

    first_lstm = LSTM(32, 
        batch_input_shape=(batchSize, num_samples, num_features), 
        return_sequences=True, activation='tanh', 
        stateful=stateful)   

    model.add(first_lstm)
    model.add(LeakyReLU())
    model.add(Dropout(0.2))

    #this is the last LSTM layer, use return_sequences=False
    model.add(LSTM(16, return_sequences=False, stateful=stateful,  activation='tanh'))

    model.add(Dropout(0.2))
    model.add(LeakyReLU())

    #don't add a Flatten!!!
    #model.add(Flatten())

    model.add(Dense(1, activation='sigmoid'))

    if forTraining == True:
        compileThisModel(model)

Avec cela, vous pourrez vous entraîner avec 7 étapes et prévoir avec une étape. Sinon, ce ne sera pas possible. 

L'utilisation d'un modèle avec état comme solution à votre question

Tout d’abord, entraînez à nouveau ce nouveau modèle, car il n’a pas de couche Aplatir:

trainingModel = createModel(forTraining=True)
trainThisModel(trainingModel)

Maintenant, avec ce modèle formé, vous pouvez simplement créer un nouveau modèle exactement de la même manière que vous avez créé le modèle formé, mais en marquant stateful=True dans toutes ses couches LSTM. Et nous devrions copier les poids du modèle formé.

Étant donné que ces nouvelles couches auront besoin d'une taille de lot fixe (règles de Keras), j'ai supposé que ce serait 1 (un seul flux arrive, pas m flux) et l'a ajouté à la création du modèle ci-dessus.

predictingModel = createModel(forTraining=False)
predictingModel.set_weights(trainingModel.get_weights())

Et voilà. Prévoyez simplement les résultats du modèle en une seule étape:

pseudo for loop as samples arrive to your model:
    prob = predictingModel.predict_on_batch(sample)

    #where sample.shape == (1, 1, 3)

Lorsque vous décidez que vous avez atteint la fin de ce que vous considérez comme une séquence continue, appelez predictingModel.reset_states() pour pouvoir commencer en toute sécurité une nouvelle séquence sans que le modèle ne pense qu'il devrait être réparé à la fin de la précédente.


Sauvegarde et chargement des états

Il suffit de les récupérer et de les configurer en enregistrant avec h5py

def saveStates(model, saveName):

    f = h5py.File(saveName,'w')

    for l, lay in enumerate(model.layers):
        #if you have nested models, 
            #consider making this recurrent testing for layers in layers
        if isinstance(lay,RNN):
            for s, stat in enumerate(lay.states):
                f.create_dataset('states_' + str(l) + '_' + str(s),
                                 data=K.eval(stat), 
                                 dtype=K.dtype(stat))

    f.close()


def loadStates(model, saveName):

    f = h5py.File(saveName, 'r')
    allStates = list(f.keys())

    for stateKey in allStates:
        name, layer, state = stateKey.split('_')
        layer = int(layer)
        state = int(state)

        K.set_value(model.layers[layer].states[state], f.get(stateKey))

    f.close()

Test de fonctionnement pour les états de sauvegarde/chargement

import h5py, numpy as np
from keras.layers import RNN, LSTM, Dense, Input
from keras.models import Model
import keras.backend as K




def createModel():
    inp = Input(batch_shape=(1,None,3))
    out = LSTM(5,return_sequences=True, stateful=True)(inp)
    out = LSTM(2, stateful=True)(out)
    out = Dense(1)(out)
    model = Model(inp,out)
    return model


def saveStates(model, saveName):

    f = h5py.File(saveName,'w')

    for l, lay in enumerate(model.layers):
        #if you have nested models, consider making this recurrent testing for layers in layers
        if isinstance(lay,RNN):
            for s, stat in enumerate(lay.states):
                f.create_dataset('states_' + str(l) + '_' + str(s), data=K.eval(stat), dtype=K.dtype(stat))

    f.close()


def loadStates(model, saveName):

    f = h5py.File(saveName, 'r')
    allStates = list(f.keys())

    for stateKey in allStates:
        name, layer, state = stateKey.split('_')
        layer = int(layer)
        state = int(state)

        K.set_value(model.layers[layer].states[state], f.get(stateKey))

    f.close()

def printStates(model):

    for l in model.layers:
        #if you have nested models, consider making this recurrent testing for layers in layers
        if isinstance(l,RNN):
            for s in l.states:
                print(K.eval(s))   

model1 = createModel()
model2 = createModel()
model1.predict_on_batch(np.ones((1,5,3))) #changes model 1 states

print('model1')
printStates(model1)
print('model2')
printStates(model2)

saveStates(model1,'testStates5')
loadStates(model2,'testStates5')

print('model1')
printStates(model1)
print('model2')
printStates(model2)

Considérations sur les aspects des données

Dans votre premier modèle (s'il s'agit de stateful=False), il considère que chaque séquence de m est individuelle et non connectée aux autres. Il considère également que chaque lot contient des séquences uniques. 

Si ce n'est pas le cas, vous voudrez peut-être former le modèle avec état (en considérant que chaque séquence est réellement connectée à la séquence précédente). Et vous auriez alors besoin de m lots de 1 séquence. -> m x (1, 7 or None, 3).

4
Daniel Möller

Si j'ai bien compris, vous avez des lots de m séquences, chacune de longueur 7, dont les éléments sont des vecteurs tridimensionnels (donc batch a la forme (m*7*3)) . Dans tout Keras RNN vous pouvez définir l'indicateur return_sequences sur True pour devenir les états intermédiaires, c'est-à-dire que pour chaque lot, au lieu de la prédiction définitive, vous obtiendrez les 7 sorties correspondantes, où la sortie i représente la prédiction à l'étape i étant donné toutes les entrées de 0 à i.

Mais vous auriez tout à la fois à la fin. Autant que je sache, Keras ne fournit pas d'interface directe pour récupérer le débit pendant le traitement du lot}. Cela peut être encore plus contraignant si vous utilisez l'une des variantes optimisées CUDNN-. Ce que vous pouvez faire est essentiellement de considérez votre lot comme 7 lots successifs de forme (m*1*3) et transmettez-les progressivement à votre LSTM} en enregistrant l’état caché et la prédiction à chaque étape. Pour cela, vous pouvez définir return_state sur True et le faire manuellement, ou vous pouvez simplement définir stateful sur True et laisser l'objet le suivre.


L'exemple suivant Python2 + Keras devrait représenter exactement ce que vous voulez. Plus précisément:

  • permettant de sauvegarder de manière persistante tout l'état intermédiaire LSTM
  • en attendant le prochain échantillon
  • et prévoir sur un modèle formé sur une taille de lot spécifique qui peut être arbitraire et inconnue.

Pour cela, il inclut un exemple de stateful=True pour une formation plus simple et de return_state=True pour une inférence plus précise, pour vous donner une idée des deux approches. Cela suppose également que vous obteniez un modèle sérialisé dont vous ne connaissez pas grand-chose. La structure est étroitement liée à celle du cours d'Andrew Ng, qui fait certainement plus autorité que moi dans le sujet. Puisque vous ne spécifiez pas comment le modèle a été formé, j'ai supposé une configuration de formation plusieurs à un, mais cela pourrait être facilement adapté.

from __future__ import print_function
from keras.layers import Input, LSTM, Dense
from keras.models import Model, load_model
from keras.optimizers import Adam
import numpy as np

# globals
SEQ_LEN = 7
HID_DIMS = 32
OUTPUT_DIMS = 3 # outputs are assumed to be scalars


##############################################################################
# define the model to be trained on a fixed batch size:
# assume many-to-one training setup (otherwise set return_sequences=True)
TRAIN_BATCH_SIZE = 20

x_in = Input(batch_shape=[TRAIN_BATCH_SIZE, SEQ_LEN, 3])
lstm = LSTM(HID_DIMS, activation="tanh", return_sequences=False, stateful=True)
dense = Dense(OUTPUT_DIMS, activation='linear')
m_train = Model(inputs=x_in, outputs=dense(lstm(x_in)))
m_train.summary()

# a dummy batch of training data of shape (TRAIN_BATCH_SIZE, SEQ_LEN, 3), with targets of shape (TRAIN_BATCH_SIZE, 3):
batch123 = np.repeat([[1, 2, 3]], SEQ_LEN, axis=0).reshape(1, SEQ_LEN, 3).repeat(TRAIN_BATCH_SIZE, axis=0)
targets = np.repeat([[123,234,345]], TRAIN_BATCH_SIZE, axis=0) # dummy [[1,2,3],,,]-> [123,234,345] mapping to be learned


# train the model on a fixed batch size and save it
print(">> INFERECE BEFORE TRAINING MODEL:", m_train.predict(batch123, batch_size=TRAIN_BATCH_SIZE, verbose=0))
m_train.compile(optimizer=Adam(lr=0.5), loss='mean_squared_error', metrics=['mae'])
m_train.fit(batch123, targets, epochs=100, batch_size=TRAIN_BATCH_SIZE)
m_train.save("trained_lstm.h5")
print(">> INFERECE AFTER TRAINING MODEL:", m_train.predict(batch123, batch_size=TRAIN_BATCH_SIZE, verbose=0))


##############################################################################
# Now, although we aren't training anymore, we want to do step-wise predictions
# that do alter the inner state of the model, and keep track of that.


m_trained = load_model("trained_lstm.h5")
print(">> INFERECE AFTER RELOADING TRAINED MODEL:", m_trained.predict(batch123, batch_size=TRAIN_BATCH_SIZE, verbose=0))

# now define an analogous model that allows a flexible batch size for inference:
x_in = Input(shape=[SEQ_LEN, 3])
h_in = Input(shape=[HID_DIMS])
c_in = Input(shape=[HID_DIMS])
pred_lstm = LSTM(HID_DIMS, activation="tanh", return_sequences=False, return_state=True, name="lstm_infer")
h, cc, c = pred_lstm(x_in, initial_state=[h_in, c_in])
prediction = Dense(OUTPUT_DIMS, activation='linear', name="dense_infer")(h)
m_inference = Model(inputs=[x_in, h_in, c_in], outputs=[prediction, h,cc,c])

#  Let's confirm that this model is able to load the trained parameters:
# first, check that the performance from scratch is not good:
print(">> INFERENCE BEFORE SWAPPING MODEL:")
predictions, hs, zs, cs = m_inference.predict([batch123,
                                               np.zeros((TRAIN_BATCH_SIZE, HID_DIMS)),
                                               np.zeros((TRAIN_BATCH_SIZE, HID_DIMS))],
                                              batch_size=1)
print(predictions)


# import state from the trained model state and check that it works:
print(">> INFERENCE AFTER SWAPPING MODEL:")
for layer in m_trained.layers:
    if "lstm" in layer.name:
        m_inference.get_layer("lstm_infer").set_weights(layer.get_weights())
    Elif "dense" in layer.name:
        m_inference.get_layer("dense_infer").set_weights(layer.get_weights())

predictions, _, _, _ = m_inference.predict([batch123,
                                            np.zeros((TRAIN_BATCH_SIZE, HID_DIMS)),
                                            np.zeros((TRAIN_BATCH_SIZE, HID_DIMS))],
                                           batch_size=1)
print(predictions)


# finally perform granular predictions while keeping the recurrent activations. Starting the sequence with zeros is a common practice, but depending on how you trained, you might have an <END_OF_SEQUENCE> character that you might want to propagate instead:
h, c = np.zeros((TRAIN_BATCH_SIZE, HID_DIMS)), np.zeros((TRAIN_BATCH_SIZE, HID_DIMS))
for i in range(len(batch123)):
    # about output shape: https://keras.io/layers/recurrent/#rnn
    # h,z,c hold the network's throughput: h is the proper LSTM output, c is the accumulator and cc is (probably) the candidate
    current_input = batch123[i:i+1] # the length of this feed is arbitrary, doesn't have to be 1
    pred, h, cc, c = m_inference.predict([current_input, h, c])
    print("input:", current_input)
    print("output:", pred)
    print(h.shape, cc.shape, c.shape)
    raw_input("do something with your prediction and hidden state and press any key to continue")

Information additionnelle:

Puisque nous avons deux formes de persistance d'état:
1. Les paramètres enregistrés/formés du modèle qui sont les mêmes pour chaque séquence
2. Les états a, c qui évoluent au fil des séquences et peuvent être "redémarrés"

Il est intéressant de jeter un coup d’œil sur les entrailles de l’objet LSTM. Dans l'exemple Python que je fournis, les poids a et c sont explicitement gérés, mais les paramètres formés ne le sont pas et il n'est peut-être pas évident de savoir comment ils sont implémentés en interne ou ce qu'ils signifient. Ils peuvent être inspectés comme suit:

for w in lstm.weights:
    print(w.name, w.shape)

Dans notre cas (32 états cachés) renvoie ce qui suit:

lstm_1/kernel:0 (3, 128)
lstm_1/recurrent_kernel:0 (32, 128)
lstm_1/bias:0 (128,)

Nous observons une dimensionnalité de 128. Pourquoi est-ce? ce lien décrit la mise en œuvre de Keras LSTM comme suit:

 enter image description here

Le g est l'activation récurrente, p est l'activation, Ws sont les noyaux, Nous sont les noyaux récurrents, h est la variable cachée qui est aussi la sortie et la notation * est une multiplication élément par élément.

Ce qui explique que 128=32*4 soit les paramètres de la transformation affine se produisant à l'intérieur de chacune des 4 portes concaténées:

  • La matrice de forme (3, 128) (nommée kernel) gère l’entrée pour un élément de séquence donné. 
  • La matrice de forme (32, 128) (nommée recurrent_kernel) gère l'entrée pour le dernier état récurrent h.
  • Le vecteur de forme (128,) (nommé bias), comme d'habitude dans toute autre configuration NN.
4
fr_andres

Remarque: cette réponse suppose que votre modèle en phase de formation n’est pas avec état. Vous devez comprendre ce qu'est une couche RNN avec état et vous assurer que les données d'apprentissage ont les propriétés correspondantes d'état. En bref, cela signifie qu’il existe une dépendance entre les séquences, c’est-à-dire qu’une séquence est le suivi d’une autre séquence, que vous souhaitez prendre en compte dans votre modèle. Si votre modèle et vos données d'apprentissage sont dynamiques, d'autres réponses impliquant le paramétrage de stateful=True pour les couches RNN depuis le début sont plus simples.

Mise à jour: que le modèle de formation ait ou non un état, vous pouvez toujours copier ses poids dans le modèle d'inférence et activer l'étatfulness. Je pense donc que les solutions basées sur le paramètre stateful=True sont plus courtes et meilleures que la mienne. Leur seul inconvénient est que la taille du lot dans ces solutions doit être corrigée.


Notez que la sortie d'une couche LSTM sur une seule séquence est déterminée par ses matrices de pondération, qui sont fixes, et par ses états internes, qui dépendent du timestep précédemment traité. Maintenant, pour obtenir la sortie de la couche LSTM pour une seule séquence de longueur m, une méthode évidente consiste à transmettre la séquence entière à la couche LSTM en une seule fois. Cependant, comme je l’ai dit plus tôt, étant donné que ses états internes dépendent du pas de temps précédent, nous pouvons exploiter ce fait et alimenter ce bloc de séquence unique par bloc en obtenant l’état de la couche LSTM à la fin du traitement d’un bloc et le transmettre au LSTM. couche pour traiter le morceau suivant. Pour être plus clair, supposons que la longueur de la séquence soit de 7 (c’est-à-dire qu’elle a 7 pas de temps de vecteurs de caractéristiques de longueur fixe). A titre d'exemple, il est possible de traiter cette séquence comme ceci:

  1. Introduisez les pas de temps 1 et 2 dans la couche LSTM; obtenir l'état final (appelez-le C1).
  2. Envoyez les timesteps 3, 4 et 5 et l'état C1 comme état initial à la couche LSTM; obtenir l'état final (appelez-le C2).
  3. Envoyez les horodatages 6 et 7 et l'état C2 comme état initial à la couche LSTM; obtenir le résultat final.

Cette sortie finale est équivalente à la sortie produite par la couche LSTM si nous lui avions donné les 7 timesteps complets à la fois.

Pour réaliser cela dans Keras, vous pouvez définir l’argument return_state de la couche LSTM sur True afin d’obtenir l’état intermédiaire. En outre, ne spécifiez pas de durée fixe lors de la définition du calque d'entrée. Utilisez plutôt None pour pouvoir alimenter le modèle avec des séquences de longueur arbitraire, ce qui nous permet de traiter chaque séquence progressivement (c'est bien si vos données d'entrée dans le temps d'apprentissage sont des séquences de longueur fixe). 

Comme vous avez besoin de cette capacité de traitement du mandrin en temps d'inférence, nous devons définir un nouveau modèle qui partage la couche LSTM utilisée dans le modèle d'apprentissage et peut obtenir les états initiaux en entrée et donne également les états résultants en sortie. Vous trouverez ci-dessous un aperçu général de cette opération (notez que l'état renvoyé de la couche LSTM n'est pas utilisé lors de la formation du modèle, nous en avons uniquement besoin pendant la période de test):

# define training model
train_input = Input(shape=(None, n_feats))   # note that the number of timesteps is None
lstm_layer = LSTM(n_units, return_state=True)
lstm_output, _, _ =  lstm_layer(train_input) # note that we ignore the returned states
classifier = Dense(1, activation='sigmoid')
train_output = classifier(lstm_output)

train_model = Model(train_input, train_output)

# compile and fit the model on training data ...

# ==================================================

# define inference model
inf_input = Input(shape=(None, n_feats))
state_h_input = Input(shape=(n_units,))
state_c_input = Input(shape=(n_units,))

# we use the layers of previous model
lstm_output, state_h, state_c = lstm_layer(inf_input,
                                           initial_state=[state_h_input, state_c_input])
output = classifier(lstm_output)

inf_model = Model([inf_input, state_h_input, state_c_input],
                  [output, state_h, state_c])  # note that we return the states as output

Vous pouvez maintenant alimenter le inf_model autant que les pas de temps d'une séquence sont disponibles maintenant. Cependant, notez qu'au départ, vous devez alimenter les états avec des vecteurs de tous les zéros (valeur par défaut des états). Par exemple, si la longueur de la séquence est 7, voici un aperçu de ce qui se produit lorsqu'un nouveau flux de données est disponible:

state_h = np.zeros((1, n_units,))
state_c = np.zeros((1, n_units))

# three new timesteps are available
outputs = inf_model.predict([timesteps, state_h, state_c])

out = output[0,0]  # you may ignore this output since the entire sequence has not been processed yet
state_h = outputs[0,1]
state_c = outputs[0,2]

# after some time another four new timesteps are available
outputs = inf_model.predict([timesteps, state_h, state_c])

# we have processed 7 timesteps, so the output is valid
out = output[0,0]  # store it, pass it to another thread or do whatever you want to do with it

# reinitialize the state to make them ready for the next sequence chunk
state_h = np.zeros((1, n_units))
state_c = np.zeros((1, n_units))

# to be continued...

Bien sûr, vous devez le faire en boucle ou mettre en place une structure de flux de contrôle pour traiter le flux de données, mais je pense que vous obtenez ce à quoi ressemble l'idée générale.

Enfin, bien que votre exemple spécifique ne soit pas un modèle séquence à séquence, je vous recommande toutefois de lire le tutoriel officiel de Keras seq2seq }, qui, je pense, permet d’en tirer beaucoup d’idées.

2
today

Autant que je sache, en raison du graphique statique dans Tensorflow, il n’existe aucun moyen efficace d’alimenter des entrées d’une longueur différente de celle de la formation. 

Le rembourrage est le moyen officiel de contourner ce problème, mais il est moins efficace et moins gourmand en mémoire. Je vous suggère de regarder dans Pytorch, qui sera trivial pour résoudre votre problème. 

Il y a beaucoup de bons messages pour construire LSTM avec Pytorch, et vous comprendrez les avantages du graphe dynamique une fois que vous les verrez. 

0
Shawn