Différence entre ces implémentations de LSTM Autoencoder?
Plus précisément, ce qui a suscité cette question est l' return_sequence
argument de la version de TensorFlow d'une couche LSTM.
Les documents disent:
Booléen. Indique s'il faut renvoyer la dernière sortie. dans la séquence de sortie ou la séquence complète. Valeur par défaut: False.
J'ai vu quelques implémentations, en particulier les auto-encodeurs qui utilisent cet argument pour tout supprimer sauf le dernier élément de la séquence de sortie comme sortie de la moitié «encoder» de l'autoencoder.
Voici trois implémentations différentes. J'aimerais comprendre les raisons derrière les différences, car elles semblent être de très grandes différences, mais toutes s'appellent la même chose.
Exemple 1 (TensorFlow):
Cette implémentation supprime toutes les sorties du LSTM à l'exception du dernier élément de la séquence, puis répète cet élément un certain nombre de fois pour reconstruire la séquence:
model = Sequential()
model.add(LSTM(100, activation='relu', input_shape=(n_in,1)))
# Decoder below
model.add(RepeatVector(n_out))
model.add(LSTM(100, activation='relu', return_sequences=True))
model.add(TimeDistributed(Dense(1)))
Lorsque je regarde les implémentations d'autoencodeurs dans PyTorch, je ne vois pas d'auteurs faire cela. Au lieu de cela, ils utilisent toute la sortie du LSTM pour l'encodeur (parfois suivi d'une couche dense et parfois non).
Exemple 1 (PyTorch):
Cette implémentation entraîne une incorporation AVANT qu'une couche LSTM soit appliquée ... Elle semble presque vaincre l'idée d'un auto-encodeur basé sur LSTM ... La séquence est déjà encodée au moment où elle atteint la couche LSTM.
class EncoderLSTM(nn.Module):
def __init__(self, input_size, hidden_size, n_layers=1, drop_prob=0):
super(EncoderLSTM, self).__init__()
self.hidden_size = hidden_size
self.n_layers = n_layers
self.embedding = nn.Embedding(input_size, hidden_size)
self.lstm = nn.LSTM(hidden_size, hidden_size, n_layers, dropout=drop_prob, batch_first=True)
def forward(self, inputs, hidden):
# Embed input words
embedded = self.embedding(inputs)
# Pass the embedded word vectors into LSTM and return all outputs
output, hidden = self.lstm(embedded, hidden)
return output, hidden
Exemple 2 (PyTorch):
Cet exemple d'encodeur étend d' abord l'entrée avec une couche LSTM, puis effectue sa compression via une seconde couche LSTM avec un plus petit nombre de nœuds cachés. Outre l'expansion, cela semble conforme à cet article que j'ai trouvé:https://arxiv.org/pdf/1607.00148.pdf
Cependant, dans le décodeur de cette implémentation, il n'y a pas de couche dense finale. Le décodage s'effectue via une seconde couche lstm qui étend le codage à la même dimension que l'entrée d'origine. Voyez-le ici . Ce n'est pas conforme à l'article (bien que je ne sache pas si l'article fait autorité ou non).
class Encoder(nn.Module):
def __init__(self, seq_len, n_features, embedding_dim=64):
super(Encoder, self).__init__()
self.seq_len, self.n_features = seq_len, n_features
self.embedding_dim, self.hidden_dim = embedding_dim, 2 * embedding_dim
self.rnn1 = nn.LSTM(
input_size=n_features,
hidden_size=self.hidden_dim,
num_layers=1,
batch_first=True
)
self.rnn2 = nn.LSTM(
input_size=self.hidden_dim,
hidden_size=embedding_dim,
num_layers=1,
batch_first=True
)
def forward(self, x):
x = x.reshape((1, self.seq_len, self.n_features))
x, (_, _) = self.rnn1(x)
x, (hidden_n, _) = self.rnn2(x)
return hidden_n.reshape((self.n_features, self.embedding_dim))
Question:
Je m'interroge sur cette divergence dans les implémentations. La différence semble assez grande. Est-ce que tous ces moyens valables d'accomplir la même chose? Ou est-ce que certaines de ces tentatives mal guidées d'un «vrai» auto-encodeur LSTM?
Réponses
Il n'y a pas de manière officielle ou correcte de concevoir l'architecture d'un auto-encodeur basé sur LSTM ... Les seules spécificités du nom sont que le modèle doit être un Autoencoder et qu'il doit utiliser une couche LSTM quelque part.
Les implémentations que vous avez trouvées sont toutes différentes et uniques, même si elles peuvent être utilisées pour la même tâche.
Décrivons-les:
Implémentation TF :
- Il suppose que l'entrée n'a qu'un seul canal , ce qui signifie que chaque élément de la séquence n'est qu'un nombre et que celui-ci est déjà prétraité .
- Le comportement par défaut du
LSTM layer
dans Keras / TF est de ne sortir que la dernière sortie du LSTM, vous pouvez le définir pour sortir toutes les étapes de sortie avec lereturn_sequences
paramètre. - Dans ce cas, les données d'entrée ont été réduites à
(batch_size, LSTM_units)
- Considérez que la dernière sortie d'un LSTM est bien sûr une fonction des sorties précédentes (en particulier s'il s'agit d'un LSTM avec état)
- Il applique un
Dense(1)
dans le dernier calque afin d'obtenir la même forme que l'entrée.
PyTorch 1 :
- Ils appliquent une incorporation à l'entrée avant qu'elle ne soit envoyée au LSTM.
- C'est une pratique courante et cela permet par exemple de transformer chaque élément d'entrée en une forme vectorielle (voir word2vec par exemple où dans une séquence de texte, chaque mot qui n'est pas un vecteur est mappé dans un espace vectoriel). Il ne s'agit que d'une étape de prétraitement pour que les données aient une forme plus significative.
- Cela ne va pas à l'encontre de l'idée de l'autoencodeur LSTM, car l'incorporation est appliquée indépendamment à chaque élément de la séquence d'entrée, de sorte qu'il n'est pas codé lorsqu'il entre dans la couche LSTM.
PyTorch 2 :
- Dans ce cas, la forme d'entrée n'est pas
(seq_len, 1)
comme dans le premier exemple TF, le décodeur n'a donc pas besoin d'un après dense. L'auteur a utilisé un nombre d'unités dans le calque LSTM égal à la forme d'entrée.
- Dans ce cas, la forme d'entrée n'est pas
Au final, vous choisissez l'architecture de votre modèle en fonction des données sur lesquelles vous souhaitez vous entraîner, en particulier: la nature (texte, audio, images), la forme d'entrée, la quantité de données dont vous disposez, etc.