LSTM自动编码器始终返回输入序列的平均值

时间:2019-01-28 23:15:05

标签: python machine-learning lstm pytorch

我正在尝试使用PyTorch构建一个非常简单的LSTM自动编码器。我总是用相同的数据训练它:

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

我已通过this链接建立了模型:

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 :(得分:3)

1。初始化隐藏状态

在您的源代码中,您使用init_hidden_encoderinit_hidden_decoder函数在每次正向传递中将两个循环单元的隐藏状态归零。

在PyTorch中,如果没有初始隐藏状态传递到RNN单元(不是PyTorch中当前默认可用的状态,则是LSTM,GRU或RNN),您不必这样做。 ,它隐式地填充了零。

因此,为了获得与初始解决方案相同的代码(简化了下一部分),我将废弃不需要的部分,这使我们得到了如下所示的模型:

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的添加

我们不需要任何多余的尺寸(例如[5,1,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 提供参数reduction =“ sum”,尽管它确实是临时的,并且偶然地起作用。 首先,网络会尝试使所有输出等于和(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),输出也是(压缩不必要的尺寸之后)。

我将Adam的参数更改为默认值,因为它收敛得更快。

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(又称平均绝对错误)可能会得到更好的结果:

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

此网络的调整和正确批处理工作留给您,希望您现在会有所乐趣,并了解到这一点。 :)

PS。我会重复输入序列的整个形状,因为这是更通用的方法,应该可以批量使用和处理更大的尺寸。