¿Diferencia entre estas implementaciones de LSTM Autoencoder?
Específicamente, lo que impulsó esta pregunta es el return_sequence
argumento de la versión de TensorFlow de una capa LSTM.
Los doctores dicen:
Booleano. Ya sea para devolver la última salida. en la secuencia de salida o en la secuencia completa. Predeterminado: falso.
He visto algunas implementaciones, especialmente codificadores automáticos que usan este argumento para quitar todo menos el último elemento de la secuencia de salida como la salida de la mitad 'codificador' del codificador automático.
A continuación se muestran tres implementaciones diferentes. Me gustaría entender las razones detrás de las diferencias, ya que parecen diferencias muy grandes, pero todas se llaman a sí mismas lo mismo.
Ejemplo 1 (TensorFlow):
Esta implementación elimina todas las salidas del LSTM excepto el último elemento de la secuencia, y luego repite ese elemento varias veces para reconstruir la secuencia:
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)))
Al observar las implementaciones de codificadores automáticos en PyTorch, no veo que los autores hagan esto. En su lugar, utilizan toda la salida del LSTM para el codificador (a veces seguida de una capa densa y otras no).
Ejemplo 1 (PyTorch):
Esta implementación entrena una incrustación ANTES de que se aplique una capa LSTM ... Parece que casi anula la idea de un codificador automático basado en LSTM ... La secuencia ya está codificada cuando llega a la capa 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
Ejemplo 2 (PyTorch):
Este codificador de ejemplo primero expande la entrada con una capa LSTM, luego realiza su compresión a través de una segunda capa LSTM con un número menor de nodos ocultos. Además de la expansión, esto parece estar en línea con este documento que encontré:https://arxiv.org/pdf/1607.00148.pdf
Sin embargo, en el decodificador de esta implementación, no hay una capa densa final. La decodificación ocurre a través de una segunda capa lstm que expande la codificación a la misma dimensión que la entrada original. Véalo aquí . Esto no está en línea con el documento (aunque no sé si el documento es autorizado o no).
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))
Pregunta:
Me pregunto acerca de esta discrepancia en las implementaciones. La diferencia parece bastante grande. ¿Son todas estas formas válidas de lograr lo mismo? ¿O son algunos de estos intentos mal guiados en un autoencoder LSTM "real"?
Respuestas
No existe una forma oficial o correcta de diseñar la arquitectura de un codificador automático basado en LSTM ... Los únicos detalles que proporciona el nombre es que el modelo debe ser un codificador automático y que debe usar una capa LSTM en alguna parte.
Las implementaciones que encontró son diferentes y únicas por sí mismas, aunque podrían usarse para la misma tarea.
Vamos a describirlos:
Implementación de TF :
- Asume que la entrada tiene un solo canal , lo que significa que cada elemento de la secuencia es solo un número y que ya está preprocesado .
- El comportamiento predeterminado de
LSTM layer
en Keras / TF es generar solo la última salida del LSTM, puede configurarlo para que muestre todos los pasos de salida con elreturn_sequences
parámetro. - En este caso, los datos de entrada se han reducido a
(batch_size, LSTM_units)
- Tenga en cuenta que la última salida de un LSTM es, por supuesto, una función de las salidas anteriores (específicamente si es un LSTM con estado)
- Aplica un
Dense(1)
en la última capa para obtener la misma forma que la entrada.
PyTorch 1 :
- Aplican una incrustación a la entrada antes de que se alimente al LSTM.
- Esta es una práctica estándar y ayuda, por ejemplo, a transformar cada elemento de entrada en una forma vectorial (consulte word2vec, por ejemplo, donde en una secuencia de texto, cada palabra que no es un vector se asigna a un espacio vectorial). Es solo un paso de procesamiento previo para que los datos tengan una forma más significativa.
- Esto no anula la idea del codificador automático LSTM, porque la incrustación se aplica de forma independiente a cada elemento de la secuencia de entrada, por lo que no se codifica cuando entra en la capa LSTM.
PyTorch 2 :
- En este caso, la forma de entrada no es
(seq_len, 1)
como en el primer ejemplo de TF, por lo que el decodificador no necesita un después denso. El autor usó una cantidad de unidades en la capa LSTM igual a la forma de entrada.
- En este caso, la forma de entrada no es
Al final, eliges la arquitectura de tu modelo en función de los datos sobre los que quieras entrenar, específicamente: la naturaleza (texto, audio, imágenes), la forma de entrada, la cantidad de datos que tienes, etc.