Diferença entre essas implementações do LSTM Autoencoder?

Dec 08 2020

Especificamente, o que gerou essa pergunta foi o return_sequenceargumento 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

2 LucaAngioloni Dec 08 2020 at 22:38

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 layerem Keras / TF é produzir apenas a última saída do LSTM, você pode configurá-lo para produzir todas as etapas de saída com o return_sequencesparâ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.

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 ...