Differenza tra queste implementazioni di LSTM Autoencoder?

Dec 08 2020

Nello specifico, ciò che ha stimolato questa domanda è l' return_sequenceargomento della versione di TensorFlow di un livello LSTM.

I documenti dicono:

Booleano. Se restituire l'ultimo output. nella sequenza di output o nella sequenza completa. Predefinito: Falso.

Ho visto alcune implementazioni, in particolare gli autoencoder che usano questo argomento per rimuovere tutto tranne l'ultimo elemento nella sequenza di output come output della metà 'encoder' dell'autoencoder.

Di seguito sono riportate tre diverse implementazioni. Mi piacerebbe capire le ragioni dietro le differenze, poiché sembrano differenze molto grandi ma si definiscono tutte la stessa cosa.

Esempio 1 (TensorFlow):

Questa implementazione elimina tutti gli output dell'LSTM tranne l'ultimo elemento della sequenza, quindi ripete quell'elemento un certo numero di volte per ricostruire la sequenza:

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

Quando guardo le implementazioni degli autoencoder in PyTorch, non vedo autori che lo fanno. Invece usano l'intero output di LSTM per il codificatore (a volte seguito da uno strato denso e talvolta no).

Esempio 1 (PyTorch):

Questa implementazione addestra un embedding PRIMA che venga applicato un layer LSTM ... Sembra quasi sconfiggere l'idea di un auto-encoder basato su LSTM ... La sequenza è già codificata nel momento in cui raggiunge il layer 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

Esempio 2 (PyTorch):

Questo codificatore di esempio espande prima l'input con un livello LSTM, quindi esegue la compressione tramite un secondo livello LSTM con un numero inferiore di nodi nascosti. Oltre all'espansione, questo sembra in linea con questo documento che ho trovato:https://arxiv.org/pdf/1607.00148.pdf

Tuttavia, nel decodificatore di questa implementazione, non è presente uno strato denso finale. La decodifica avviene attraverso un secondo strato lstm che espande la codifica alla stessa dimensione dell'input originale. Guardalo qui . Questo non è in linea con il documento (anche se non so se il documento sia autorevole o meno).

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

Domanda:

Mi chiedo su questa discrepanza nelle implementazioni. La differenza sembra abbastanza grande. Tutti questi modi validi per ottenere la stessa cosa? O sono alcuni di questi tentativi mal guidati di un autoencoder LSTM "reale"?

Risposte

2 LucaAngioloni Dec 08 2020 at 22:38

Non esiste un modo ufficiale o corretto di progettare l'architettura di un autoencoder basato su LSTM ... Le uniche specifiche fornite dal nome è che il modello dovrebbe essere un Autoencoder e che dovrebbe utilizzare un layer LSTM da qualche parte.

Le implementazioni trovate sono ciascuna diversa e unica anche se potrebbero essere utilizzate per la stessa attività.

Descriviamoli:

  • Implementazione TF :

    • Si presume che l'ingresso abbia un solo canale , il che significa che ogni elemento nella sequenza è solo un numero e che questo è già preelaborato .
    • Il comportamento predefinito di LSTM layerin Keras / TF è di emettere solo l'ultimo output di LSTM, puoi impostarlo per emettere tutti i passaggi di output con il return_sequencesparametro.
    • In questo caso i dati di input sono stati ridotti (batch_size, LSTM_units)
    • Considera che l'ultimo output di un LSTM è ovviamente una funzione degli output precedenti (in particolare se è un LSTM con stato)
    • Applica un Dense(1)nell'ultimo livello per ottenere la stessa forma dell'input.
  • PyTorch 1 :

    • Applicano un incorporamento all'input prima che venga inviato a LSTM.
    • Questa è una pratica standard e aiuta ad esempio a trasformare ogni elemento di input in una forma vettoriale (vedi word2vec per esempio dove in una sequenza di testo, ogni parola che non è un vettore è mappata in uno spazio vettoriale). È solo una fase di pre-elaborazione in modo che i dati abbiano una forma più significativa.
    • Ciò non vanifica l'idea dell'autoencoder LSTM, perché l'incorporamento viene applicato indipendentemente a ciascun elemento della sequenza di input, quindi non viene codificato quando entra nel livello LSTM.
  • PyTorch 2 :

    • In questo caso la forma dell'ingresso non è (seq_len, 1)come nel primo esempio TF, quindi il decoder non ha bisogno di un denso dopo. L'autore ha utilizzato un numero di unità nel livello LSTM uguale alla forma di input.

Alla fine scegli l'architettura del tuo modello a seconda dei dati su cui vuoi allenarti, nello specifico: la natura (testo, audio, immagini), la forma dell'input, la quantità di dati che hai e così via ...