Diferença entre essas implementações do LSTM Autoencoder?
Especificamente, o que gerou essa pergunta foi o return_sequence
argumento da versão do TensorFlow de uma camada LSTM.
Os documentos dizem:
Boleano. Se deve retornar a última saída. na sequência de saída ou na sequência completa. Padrão: falso.
Já vi algumas implementações, especialmente os codificadores automáticos que usam esse argumento para retirar tudo, exceto o último elemento na sequência de saída como a saída da metade do 'codificador' do codificador automático.
Abaixo estão três implementações diferentes. Eu gostaria de entender as razões por trás das diferenças, já que parecem diferenças muito grandes, mas todos se chamam a mesma coisa.
Exemplo 1 (TensorFlow):
Esta implementação remove todas as saídas do LSTM, exceto o último elemento da sequência e, em seguida, repete esse elemento algumas vezes para reconstruir a sequência:
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)))
Ao examinar as implementações de codificadores automáticos no PyTorch, não vejo autores fazendo isso. Em vez disso, eles usam toda a saída do LSTM para o codificador (às vezes seguido por uma camada densa e às vezes não).
Exemplo 1 (PyTorch):
Esta implementação treina uma incorporação ANTES de uma camada LSTM ser aplicada ... Parece quase derrotar a ideia de um codificador automático baseado em LSTM ... A sequência já está codificada no momento em que atinge a camada 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
Exemplo 2 (PyTorch):
Este codificador de exemplo primeiro expande a entrada com uma camada LSTM e, em seguida, faz sua compactação por meio de uma segunda camada LSTM com um número menor de nós ocultos. Além da expansão, isso parece estar de acordo com este artigo que encontrei:https://arxiv.org/pdf/1607.00148.pdf
No entanto, no decodificador desta implementação, não há camada densa final. A decodificação ocorre por meio de uma segunda camada lstm que expande a codificação de volta para a mesma dimensão da entrada original. Veja aqui . Isso não está de acordo com o artigo (embora eu não saiba se o artigo é oficial ou não).
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))
Pergunta:
Estou me perguntando sobre essa discrepância nas implementações. A diferença parece muito grande. Todas essas maneiras válidas de realizar a mesma coisa? Ou algumas dessas tentativas equivocadas de um autencoder LSTM "real"?
Respostas
Não há uma maneira oficial ou correta de projetar a arquitetura de um autoencoder baseado em LSTM ... As únicas especificações que o nome fornece é que o modelo deve ser um Autoencoder e que deve usar uma camada LSTM em algum lugar.
As implementações que você encontrou são diferentes e exclusivas por conta própria, embora possam ser usadas para a mesma tarefa.
Vamos descrevê-los:
Implementação TF :
- Ele assume que a entrada tem apenas um canal , o que significa que cada elemento na sequência é apenas um número e já está pré-processado .
- O comportamento padrão do
LSTM layer
em Keras / TF é produzir apenas a última saída do LSTM, você pode configurá-lo para produzir todas as etapas de saída com oreturn_sequences
parâmetro. - Neste caso, os dados de entrada foram reduzidos para
(batch_size, LSTM_units)
- Considere que a última saída de um LSTM é, obviamente, uma função das saídas anteriores (especificamente se for um LSTM com estado)
- Ele aplica um
Dense(1)
na última camada para obter a mesma forma da entrada.
PyTorch 1 :
- Eles aplicam uma incorporação à entrada antes de alimentá-la ao LSTM.
- Esta é uma prática padrão e ajuda, por exemplo, a transformar cada elemento de entrada em uma forma vetorial (consulte word2vec por exemplo, onde em uma sequência de texto, cada palavra que não é um vetor é mapeada em um espaço vetorial). É apenas uma etapa de pré-processamento para que os dados tenham uma forma mais significativa.
- Isso não anula a ideia do autoencoder LSTM, porque a incorporação é aplicada independentemente a cada elemento da sequência de entrada, portanto, não é codificado quando entra na camada LSTM.
PyTorch 2 :
- Neste caso, a forma de entrada não é
(seq_len, 1)
como no primeiro exemplo TF, então o decodificador não precisa de um depois denso. O autor usou um número de unidades na camada LSTM igual à forma de entrada.
- Neste caso, a forma de entrada não é
No final, você escolhe a arquitetura do seu modelo dependendo dos dados que deseja treinar, especificamente: a natureza (texto, áudio, imagens), a forma de entrada, a quantidade de dados que você tem e assim por diante ...