42 votes

LSTM Autoencodeur

Je suis en train de construire un autoencodeur LSTM dans le but d'obtenir un vecteur de taille fixe à partir d'une séquence, qui représente la séquence aussi bien que possible. Cet autoencodeur se compose de deux parties :

  • Encodeur LSTM : Prend une séquence et renvoie un vecteur de sortie (return_sequences = False)
  • Décodeur LSTM : Prend un vecteur de sortie et renvoie une séquence (return_sequences = True)

En fin de compte, l'encodeur est un LSTM type plusieurs à un et le décodeur est un LSTM type un à plusieurs.

description de l'image ici Source de l'image : Andrej Karpathy

À un niveau élevé, le code ressemble à ceci (similaire à ce qui est décrit ici) :

encodeur = Model(...)
decodeur = Model(...)

autoencodeur = Model(encodeur.inputs, decodeur(encodeur(encodeur.inputs)))

autoencodeur.compile(loss='binary_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])

autoencodeur.fit(data, data,
          batch_size=100,
          epochs=1500)

La forme (nombre d'exemples d'entraînement, longueur de séquence, dimension d'entrée) du tableau data est (1200, 10, 5) et ressemble à cela :

array([[[1, 0, 0, 0, 0],
        [0, 1, 0, 0, 0],
        [0, 0, 1, 0, 0],
        ..., 
        [0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0]],
        ... ]

Problème : Je ne suis pas sûr de la marche à suivre, en particulier de l'intégration de LSTM à Model et de la façon de faire en sorte que le décodeur génère une séquence à partir d'un vecteur.

J'utilise keras avec backend tensorflow.

ÉDIT : Si quelqu'un veut essayer, voici ma procédure pour générer des séquences aléatoires avec des déplacements de valeurs (y compris le padding) :

import random
import math

def listePasSiAléatoire(x):
    rlen = 8
    rlist = [0 for x in range(rlen)]
    if x <= 7:
        rlist[x] = 1
    return rlist

séquence = [[listePasSiAléatoire(x) for x in range(round(random.uniform(0, 10)))] for y in range(5000)]

### Padding ensuite

from keras.preprocessing import sequence as seq

data = seq.pad_sequences(
    sequences = séquence,
    padding='post',
    maxlen=None,
    truncating='post',
    value=0.
)

1 votes

Pourquoi générez-vous des séquences aléatoires ?

1 votes

@lelloman : Juste à des fins de test. J'espère que pour tester ceci est suffisant. Je pense que cela devrait fonctionner de toute façon puisque cette tâche consiste à reconstruire et non vraiment à trouver des motifs.

1 votes

Je pose cette question juste par curiosité, je ne suis vraiment pas expert. Mais les autoencodeurs ne devraient-ils pas avoir besoin de modèles pour fonctionner ?

31voto

Daniel Points 2149

Les modèles peuvent être créés de n'importe quelle manière que vous souhaitez. Si j'ai bien compris, vous voulez juste savoir comment créer des modèles avec LSTM?

Utilisation des LSTMs

Eh bien, tout d'abord, vous devez définir à quoi ressemble votre vecteur encodé. Supposons que vous voulez qu'il soit un tableau de 20 éléments, un vecteur unidimensionnel. Donc, la forme serait (None, 20). Sa taille vous appartient, et il n'y a pas de règle claire pour savoir quelle est la taille idéale.

Et votre entrée doit être tridimensionnelle, par exemple (1200, 10, 5). Dans les résumés et les messages d'erreur de Keras, cela apparaîtra comme (None, 10, 5), "None" représentant la taille du lot, qui peut varier à chaque entraînement/prédiction.

Il existe de nombreuses façons de le faire, mais supposons que vous ne voulez qu'une seule couche LSTM:

from keras.layers import *
from keras.models import Model

inpE = Input((10, 5)) #ici, vous ne définissez pas la taille du lot
outE = LSTM(units=20, return_sequences=False, ...paramètres facultatifs...)(inpE)

Cela suffit pour un encodeur très simple résultant en un tableau de 20 éléments (mais vous pouvez empiler plus de couches si vous le souhaitez). Créons le modèle :

encodeur = Model(inpE, outE)   

Maintenant, pour le décodeur, ça devient obscur. Vous n'avez plus une véritable séquence, mais un vecteur statique et significatif. Vous voudrez peut-être toujours utiliser des LSTMs, ils supposeront que le vecteur est une séquence.

Mais ici, étant donné que l'entrée a une forme de (None, 20), vous devez d'abord la remodeler en un tableau tridimensionnel quelconque pour attacher ensuite une couche LSTM.

La façon dont vous la remodelerez vous appartient entièrement. 20 étapes d'1 élément? 1 étape de 20 éléments? 10 étapes de 2 éléments? Qui sait?

inpD = Input((20,))   
outD = Reshape((10, 2))(inpD) #en supposant 10 étapes de 2 éléments    

Il est important de remarquer que si vous n'avez plus 10 étapes, vous ne pourrez pas simplement activer "return_sequences" et obtenir la sortie souhaitée. Vous devrez un peu travailler. En réalité, il n'est pas nécessaire d'utiliser "return_sequences" ou même des LSTMs, mais vous pouvez le faire.

Comme dans mon remaniement j'ai 10 pas (intentionnellement), il sera bon d'utiliser "return_sequences", car le résultat aura 10 pas (comme l'entrée initiale)

outD1 = LSTM(5, return_sequences=True, ...paramètres facultatifs...)(outD)    
#5 cellules car nous voulons un vecteur (None, 10, 5)  

Vous pourriez travailler de nombreuses autres façons, comme simplement créer un LSTM à 50 cellules sans renvoyer de séquences, puis remodeler le résultat :

alternativeOut = LSTM(50, return_sequences=False, ...)(outD)    
alternativeOut = Reshape((10, 5))(alternativeOut)

Et notre modèle avance :

decodeur = Model(inpD, outD1)  
decodeurAlternatif = Model(inpD, alternativeOut)   

Après cela, vous fusionnez les modèles avec votre code et entraînez l'autoencodeur. Les trois modèles auront les mêmes poids, vous pouvez donc faire en sorte que l'encodeur produise des résultats en utilisant simplement sa méthode predict.

prédictionsEncodeur = encodeur.predict(data) 

Ce que je vois souvent avec les LSTMs pour la génération de séquences, c'est quelque chose comme prédire le prochain élément.

Vous prenez juste quelques éléments de la séquence et essayez de trouver le prochain élément. Et vous prenez un autre segment d'un pas en avant, et ainsi de suite. Cela peut être utile pour générer des séquences.

0 votes

Merci :) C'est très utile. Tu as défini les variables inp et out deux fois ce qui est confus (et entraînera une erreur si je copie et colle ton code). Mais j'ai fini par comprendre. Il semble aussi que Reshape((10,2)) s'attende à out en tant que paramètre. De toute façon, j'ai testé ton idée sur une séquence moins aléatoire (un 1 en mouvement, comme 1 0 0 -> 0 1 0 -> 0 0 1). Les séquences générées ressemblent à ceci [ 7.61515856e-01, 0.00000000e+00, 0.00000000e+00, -7.51162320e-02, -8.43070745e-02, ...] et ont une perte d'environ 0,08 à 0,13. Si tu as des idées pour améliorer cela davantage, j'aimerais beaucoup les connaître :)

1 votes

Désolé pour la petite erreur. La fonction Reshape((10,2)) s'attend à inpD, comme je l'ai corrigé. -- Si vous vous entraînez pendant de nombreux epochs et que vos résultats ne correspondent pas à ce que vous attendez, peut-être avez-vous besoin de plus de couches. Il est possible que ce modèle ne soit pas assez "intelligent" pour la tâche. Gardez à l'esprit que la "fonction d'activation" standard pour les LSTM est tanh, ce qui signifie que les résultats seront compris entre -1 et 1. Si vous avez besoin de sorties entre 0 et 1, vous devriez changer l'activation en sigmoid, par exemple.

7voto

user6903745 Points 1296

Vous pouvez trouver un exemple simple de sequence to sequence autoencoder ici: https://blog.keras.io/building-autoencoders-in-keras.html

0 votes

Ce que j'ai réalisé, c'est que les vrais autoencodeurs seq2seq n'utilisent pas RepeatVector ou Reshape, mais redirigent la sortie d'une unité à une autre. Jetez un œil à cette image : suriyadeepan.github.io/img/seq2seq/seq2seq1.png

1voto

user702846 Points 1084

Voici un exemple

Créons des données synthétiques composées de quelques séquences. L'idée est d'examiner ces séquences à travers le prisme d'un autoencodeur. En d'autres termes, réduire la dimension ou les résumer en une longueur fixe.

# définir la séquence d'entrée
séquence = np.array([[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9], 
                     [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8],
                     [0.2, 0.4, 0.6, 0.8],
                     [0.3, 0.6, 0.9, 1.2]])

# préparer pour la normalisation
x = pd.DataFrame(séquence.tolist()).T.values
scaler = preprocessing.StandardScaler()
x_scaled = scaler.fit_transform(x)
séquence_normalisée = [col[~np.isnan(col)] for col in  x_scaled.T]

# assurez-vous d'utiliser dtype='float32' dans le padding sinon avec des nombres à virgule flottante
séquence = pad_sequences(séquence, padding='post', dtype='float32')

# remodeler l'entrée en [échantillons, pas de temps, fonctionnalités]
n_obs = len(séquence)
n_in = 9
séquence = séquence.reshape((n_obs, n_in, 1))

Imaginons un LSTM simple

#définir l'encodeur
visible = Input(shape=(n_in, 1))
encodeur = LSTM(2, activation='relu')(visible)

# définir le décodeur de reconstruction
décodeur1 = RepeatVector(n_in)(encodeur)
décodeur1 = LSTM(100, activation='relu', return_sequences=True)(décodeur1)
décodeur1 = TimeDistributed(Dense(1))(décodeur1)

# lier le tout
monModèle = Model(inputs=visible, outputs=décodeur1)

# résumé des couches
print(monModèle.summary())

#séquence = tmp
monModèle.compile(optimizer='adam', loss='mse')

historique = monModèle.fit(séquence, séquence, 
                      epochs=400, 
                      verbose=0, 
                      validation_split=0.1, 
                      shuffle=True)

plot_model(monModèle, show_shapes=True, to_file='reconstruct_lstm_autoencoder.png')
# démontrer la recréation
yhat = monModèle.predict(séquence, verbose=0)
# yhat

import matplotlib.pyplot as plt

#tracer notre perte
plt.plot(historique.history['loss'])
plt.plot(historique.history['val_loss'])
plt.title('perte d\'entraînement du modèle vs validation')
plt.ylabel('perte')
plt.xlabel('époque')
plt.legend(['train', 'validation'], loc='upper right')
plt.show()

                                                              entrer la description de l'image ici

Construisons l'autoencodeur

# utiliser notre couche encodée pour encoder l'entrée d'entraînement
couche_décodeur = monModèle.layers[1]

entrée_encodée = Input(shape=(9, 1))
décodeur = Model(entrée_encodée, couche_décodeur(entrée_encodée))

# nous souhaitons voir à quoi ressemblent les séquences encodées d'une longueur de 2 (identique à la dimension de l'encodeur)
out = décodeur.predict(séquence)

f = plt.figure()
monx = out[:,0]
mony = out[:,1]
s = plt.scatter(monx, mony)

for i, txt in enumerate(out[:,0]):
    plt.annotate(i+1, (monx[i], mony[i]))

Et voici la représentation des séquences

                                        &

0 votes

Votre solution ne tente pas de masquer le rembourrage que vous avez correctement effectué, n'est-ce pas? Le rembourrage ne va-t-il pas interférer avec l'apprentissage de l'AE? Je pose cette question car j'ai des données de séries temporelles où certaines séquences ont une longueur de 20, d'autres une longueur de 1, donc je les rembourre pour que toutes aient une longueur de 20 et je les passe à travers une couche de masquage pour ignorer le rembourrage. Dans le cas des AE, je ne sais pas comment procéder pour masquer le rembourrage.

1 votes

@LFisher - tu as raison ! Est-ce que cela pourrait être utile stackoverflow.com/questions/49670832/… Je dois regarder et mettre à jour ma réponse peut-être

0 votes

Votre lien semble avoir une bonne solution pour le problème! J'ai contourné le problème d'une manière plus bricolée en rognant manuellement la sortie dans les zones rembourrées, mais apparemment il y a une manière plus facile, je vais certainement jeter un œil, merci!

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