Автокодер LSTM всегда возвращает среднее значение входной последовательности - PullRequest
0 голосов
/ 29 января 2019

Я пытаюсь создать очень простой автокодер LSTM с PyTorch.Я всегда тренирую его с одними и теми же данными:

x = torch.Tensor([[0.0], [0.1], [0.2], [0.3], [0.4]])

Я построил свою модель по этой ссылке:

inputs = Input(shape=(timesteps, input_dim))
encoded = LSTM(latent_dim)(inputs)

decoded = RepeatVector(timesteps)(encoded)
decoded = LSTM(input_dim, return_sequences=True)(decoded)

sequence_autoencoder = Model(inputs, decoded)
encoder = Model(inputs, encoded)

Мой код работает без ошибок, ноy_pred сходится к:

tensor([[[0.2]],
        [[0.2]],
        [[0.2]],
        [[0.2]],
        [[0.2]]], grad_fn=<StackBackward>)

Вот мой код:

import torch
import torch.nn as nn
import torch.optim as optim


class LSTM(nn.Module):

    def __init__(self, input_dim, latent_dim, batch_size, num_layers):
        super(LSTM, self).__init__()
        self.input_dim = input_dim
        self.latent_dim = latent_dim
        self.batch_size = batch_size
        self.num_layers = num_layers

        self.encoder = nn.LSTM(self.input_dim, self.latent_dim, self.num_layers)

        self.decoder = nn.LSTM(self.latent_dim, self.input_dim, self.num_layers)

    def init_hidden_encoder(self):
        return (torch.zeros(self.num_layers, self.batch_size, self.latent_dim),
                torch.zeros(self.num_layers, self.batch_size, self.latent_dim))

    def init_hidden_decoder(self):
        return (torch.zeros(self.num_layers, self.batch_size, self.input_dim),
                torch.zeros(self.num_layers, self.batch_size, self.input_dim))

    def forward(self, input):
        # Reset hidden layer
        self.hidden_encoder = self.init_hidden_encoder()
        self.hidden_decoder = self.init_hidden_decoder()

        # Reshape input
        input = input.view(len(input), self.batch_size, -1)

        # Encode
        encoded, self.hidden = self.encoder(input, self.hidden_encoder)
        encoded = encoded[-1].repeat(5, 1, 1)

        # Decode
        y, self.hidden = self.decoder(encoded, self.hidden_decoder)
        return y


model = LSTM(input_dim=1, latent_dim=20, batch_size=1, num_layers=1)
loss_function = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.0001)

x = torch.Tensor([[0.0], [0.1], [0.2], [0.3], [0.4]])

while True:
    y_pred = model(x)
    optimizer.zero_grad()
    loss = loss_function(y_pred, x)
    loss.backward()
    optimizer.step()
    print(y_pred)

1 Ответ

0 голосов
/ 01 февраля 2019

1.Инициализация скрытых состояний

В вашем исходном коде вы используете функции init_hidden_encoder и init_hidden_decoder для обнуления скрытых состояний обоих повторяющихся блоков при каждом прямом проходе.

В PyTorch вы используете't должен сделать это, если начальное скрытое состояние не передается в RNN-ячейку (будь то LSTM, GRU или RNN из тех, которые в настоящее время доступны по умолчанию в PyTorch), оно неявно заполняется нулями.

Итак, чтобы получить тот же код, что и в вашем исходном решении (что упрощает последующие части), я удалю ненужные части, что оставляет нам модель, показанную ниже:

class LSTM(nn.Module):
    def __init__(self, input_dim, latent_dim, num_layers):
        super(LSTM, self).__init__()
        self.input_dim = input_dim
        self.latent_dim = latent_dim
        self.num_layers = num_layers

        self.encoder = nn.LSTM(self.input_dim, self.latent_dim, self.num_layers)

        self.decoder = nn.LSTM(self.latent_dim, self.input_dim, self.num_layers)

    def forward(self, input):
        # Encode
        _, (last_hidden, _) = self.encoder(input)
        encoded = last_hidden.repeat(5, 1, 1)

        # Decode
        y, _ = self.decoder(encoded)
        return torch.squeeze(y)

Добавление torch.squeeze

Нам не нужны лишние размеры (например, 1 в [5,1,1]).На самом деле, это ключ к вашим результатам, равный 0,2

Кроме того, я оставил изменение формы входа вне сети (по моему мнению, сеть должна быть снабжена входом, готовым к обработке), чтобы строго разделить обе задачи (подготовка ввода и сама модель).

Этот подход дает нам следующий код установки и цикл обучения:

model = LSTM(input_dim=1, latent_dim=20, num_layers=1)
loss_function = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.0001)

y = torch.Tensor([[0.0], [0.1], [0.2], [0.3], [0.4]])
# Sequence x batch x dimension
x = y.view(len(y), 1, -1)

while True:
    y_pred = model(x)
    optimizer.zero_grad()
    loss = loss_function(y_pred, y)
    loss.backward()
    optimizer.step()
    print(y_pred)

Вся сеть идентична вашей (на данный момент), за исключением того, что она более краткая ичитаемый.

2.То, что мы хотим, описывая изменения в сети

Как указывает предоставленный вами код Keras , мы хотим (и на самом деле вы делаете это правильно) получить последнее скрытое состояние от кодировщика (он кодирует всю нашу последовательность) и декодирует последовательность из этого состояния, чтобы получить исходную.

Кстати.этот подход называется последовательность к последовательности или seq2seq для краткости (часто используется в таких задачах, как языковой перевод).Что ж, возможно, это вариант такого подхода, но я бы все равно его классифицировал.

PyTorch предоставляет нам последнее скрытое состояние в виде отдельной возвращаемой переменной из семейства RNN.Я бы посоветовал против твоего encoded[-1].Причиной этого будет двунаправленный и многослойный подход.Скажем, вы хотите суммировать двунаправленный вывод, это будет означать код вдоль этих строк

# batch_size and hidden_size should be inferred which clusters the code further    
encoded[-1].view(batch_size, 2, hidden_size).sum(dim=1)

И именно поэтому была использована строка _, (last_hidden, _) = self.encoder(input).

3.Почему выходные данные сходятся к 0,2?

На самом деле, это была ошибка на вашей стороне и только в последней части.

Выведите формы ваших прогнозов и целей:

# Your output
torch.Size([5, 1, 1])
# Your target
torch.Size([5, 1])

Если предоставлены эти фигуры, MSELoss по умолчанию использует аргумент size_average=True.И да, он усредняет ваши цели и ваш результат, который, по сути, рассчитывает потери для среднего значения вашего тензора (около 2,5 в начале) и среднего значения вашей цели , равного 0,2 .

Таким образом, сеть сходится правильно, но ваши цели неверны.

3.1 Первое и неправильное решение

Предоставьте MSELoss с аргументомсокращение = "сумма", хотя это действительно временно и работает случайно.Сначала сеть попытается получить все выходы равными сумме (0 + 0,1 + 0,2 + 0,3 + 0,4 = 1,0), сначала с полуслучайными выходами, через некоторое время будет сходитесь к тому, что вы хотите, , но не по тем причинам, которые вы хотите! .

Функция идентификации здесь является самым простым выбором, даже для суммирования (поскольку ваши входные данные действительно просты).

3.2 Второе и правильное решение.

Просто передайте соответствующие фигурыФункция потерь, например, batch x outputs, в вашем случае конечная часть будет выглядеть следующим образом:

model = LSTM(input_dim=1, latent_dim=20, num_layers=1)
loss_function = nn.MSELoss()
optimizer = optim.Adam(model.parameters())

y = torch.Tensor([0.0, 0.1, 0.2, 0.3, 0.4])
x = y.view(len(y), 1, -1)

while True:
    y_pred = model(x)
    optimizer.zero_grad()
    loss = loss_function(y_pred, y)
    loss.backward()
    optimizer.step()
    print(y_pred)

Ваша цель является одномерной (так как пакет имеет размер 1), как и ваш вывод (после сжатия)ненужные размеры).

Я изменил параметры Адама на значения по умолчанию, так как он быстрее сходится таким образом.

4.Окончательный рабочий код

Для краткости, вот код и результаты:

import torch
import torch.nn as nn
import torch.optim as optim


class LSTM(nn.Module):
    def __init__(self, input_dim, latent_dim, num_layers):
        super(LSTM, self).__init__()
        self.input_dim = input_dim
        self.latent_dim = latent_dim
        self.num_layers = num_layers

        self.encoder = nn.LSTM(self.input_dim, self.latent_dim, self.num_layers)

        self.decoder = nn.LSTM(self.latent_dim, self.input_dim, self.num_layers)

    def forward(self, input):
        # Encode
        _, (last_hidden, _) = self.encoder(input)
        # It is way more general that way
        encoded = last_hidden.repeat(input.shape)

        # Decode
        y, _ = self.decoder(encoded)
        return torch.squeeze(y)


model = LSTM(input_dim=1, latent_dim=20, num_layers=1)
loss_function = nn.MSELoss()
optimizer = optim.Adam(model.parameters())

y = torch.Tensor([0.0, 0.1, 0.2, 0.3, 0.4])
x = y.view(len(y), 1, -1)

while True:
    y_pred = model(x)
    optimizer.zero_grad()
    loss = loss_function(y_pred, y)
    loss.backward()
    optimizer.step()
    print(y_pred)

А вот результаты после ~ 60k шагов (на самом деле они застряли после ~ 20k шагов, вы можете захотетьулучшить оптимизацию и поиграть со скрытым размером для лучших результатов):

step=59682                       
tensor([0.0260, 0.0886, 0.1976, 0.3079, 0.3962], grad_fn=<SqueezeBackward0>)

Кроме того, L1Loss (он же Mean Absolute Error ) может получить лучшие результаты в этом случае:

step=10645                        
tensor([0.0405, 0.1049, 0.1986, 0.3098, 0.4027], grad_fn=<SqueezeBackward0>)

Осталась настройка и правильное пакетирование этой сетидля вас, надеюсь, вы повеселитесь сейчас, и вы поняли идею.:)

PS.Я повторяю всю форму входной последовательности, так как это более общий подход и должен работать с партиями и большим количеством измерений из коробки.

...