Różnica między tymi implementacjami LSTM Autoencoder?

Dec 08 2020

W szczególności to, co skłoniło to pytanie, to return_sequenceargument dotyczący wersji warstwy LSTM opracowanej przez TensorFlow.

Doktorzy mówią:

Boolean. Czy zwrócić ostatnie wyjście. w sekwencji wyjściowej lub w pełnej sekwencji. Domyślnie: False.

Widziałem kilka implementacji, zwłaszcza autoenkodery, które używają tego argumentu, aby usunąć wszystko oprócz ostatniego elementu w sekwencji wyjściowej jako wyjście połowy „kodera” autoenkodera.

Poniżej znajdują się trzy różne implementacje. Chciałbym zrozumieć przyczyny tych różnic, ponieważ wydają się one bardzo dużymi różnicami, ale wszyscy nazywają siebie tym samym.

Przykład 1 (TensorFlow):

Ta implementacja usuwa wszystkie dane wyjściowe LSTM z wyjątkiem ostatniego elementu sekwencji, a następnie powtarza ten element kilka razy, aby zrekonstruować sekwencję:

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

Patrząc na implementacje autoenkoderów w PyTorch, nie widzę, aby autorzy to robili. Zamiast tego używają całego wyjścia LSTM dla kodera (czasami następuje gęsta warstwa, a czasami nie).

Przykład 1 (PyTorch):

Ta implementacja uczy osadzania PRZED nałożeniem warstwy LSTM ... Wydaje się, że prawie obala ideę automatycznego kodera opartego na LSTM ... Sekwencja jest już zakodowana zanim trafi na warstwę 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

Przykład 2 (PyTorch):

Ten przykładowy koder najpierw rozszerza dane wejściowe o jedną warstwę LSTM, a następnie dokonuje kompresji poprzez drugą warstwę LSTM z mniejszą liczbą ukrytych węzłów. Oprócz rozszerzenia wydaje się to zgodne z tym artykułem, który znalazłem:https://arxiv.org/pdf/1607.00148.pdf

Jednak w dekoderze tej implementacji nie ma końcowej gęstej warstwy. Dekodowanie odbywa się za pośrednictwem drugiej warstwy lstm, która rozszerza kodowanie z powrotem do tego samego wymiaru, co oryginalne dane wejściowe. Zobacz to tutaj . Nie jest to zgodne z artykułem (chociaż nie wiem, czy artykuł jest autorytatywny, czy nie).

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

Pytanie:

Zastanawiam się nad tą rozbieżnością we wdrożeniach. Różnica wydaje się dość duża. Czy wszystkie te ważne sposoby osiągnięcia tego samego? A może niektóre z tych błędnych prób stworzenia „prawdziwego” autoenkodera LSTM?

Odpowiedzi

2 LucaAngioloni Dec 08 2020 at 22:38

Nie ma oficjalnego ani poprawnego sposobu zaprojektowania architektury autoenkodera opartego na LSTM ... Jedyną szczegółową cechą, jaką zapewnia nazwa, jest to, że model powinien być Autoenkoderem i powinien gdzieś używać warstwy LSTM.

Wszystkie znalezione implementacje są różne i niepowtarzalne, mimo że można ich użyć do tego samego zadania.

Opiszmy je:

  • Wdrożenie TF :

    • Zakłada się, że wejście ma tylko jeden kanał , co oznacza, że ​​każdy element w sekwencji jest tylko liczbą i jest już wstępnie przetworzona .
    • Domyślnym zachowaniem LSTM layerw Keras / TF jest wyprowadzanie tylko ostatniego wyjścia LSTM, możesz ustawić wyprowadzanie wszystkich kroków wyjściowych z return_sequencesparametrem.
    • W tym przypadku dane wejściowe zostały ograniczone do (batch_size, LSTM_units)
    • Weź pod uwagę, że ostatnie wyjście LSTM jest oczywiście funkcją poprzednich wyjść (szczególnie jeśli jest to stanowy LSTM)
    • Stosuje a Dense(1)na ostatniej warstwie, aby uzyskać ten sam kształt, co dane wejściowe.
  • PyTorch 1 :

    • Stosują osadzanie na wejściu, zanim zostanie ono przekazane do LSTM.
    • Jest to standardowa praktyka i pomaga na przykład przekształcić każdy element wejściowy w postać wektorową (zobacz na przykład word2vec, gdzie w sekwencji tekstowej każde słowo, które nie jest wektorem, jest mapowane na przestrzeń wektorową). Jest to tylko etap wstępnego przetwarzania, dzięki któremu dane mają bardziej zrozumiałą formę.
    • Nie jest to sprzeczne z ideą autoenkodera LSTM, ponieważ osadzanie jest stosowane niezależnie do każdego elementu sekwencji wejściowej, więc nie jest kodowany, gdy wchodzi do warstwy LSTM.
  • PyTorch 2 :

    • W tym przypadku kształt wejściowy nie jest taki, (seq_len, 1)jak w pierwszym przykładzie TF, więc dekoder nie potrzebuje gęstego after. Autor zastosował w warstwie LSTM liczbę jednostek równą kształtowi wejściowemu.

Ostatecznie wybierasz architekturę swojego modelu w zależności od danych, na których chcesz trenować, a konkretnie: natury (tekst, dźwięk, obrazy), kształtu wejściowego, ilości posiadanych danych i tak dalej ...