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

Posted

技术标签:

【中文标题】LSTM 自动编码器总是返回输入序列的平均值【英文标题】:LSTM autoencoder always returns the average of the input sequence 【发布时间】:2019-06-22 00:38:04 【问题描述】:

我正在尝试使用 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)

【问题讨论】:

我想知道你为什么repeat编码状态5次?在一些图像描述方法中,它们仅将编码版本作为第一个时间步传递,然后它们使用循环功能来生成向量的其余部分,直到它生成某种&lt;stop&gt; 标记。也许试试这个? 您是否正在尝试学习 5 项序列?我想知道您的 input_dim 是否应该 >1 以允许生成序列而不是单个 1-dim 浮点预测? machinelearningmastery.com/lstm-autoencoders 使用model.add(RepeatVector(n_in)) 并设法生成预期的序列。 是的,我正在尝试精简一个 n 项序列(在这种特殊情况下,n=5)。我不确定为什么 input_dim 不应该是 1,因为我的输入序列只有 1 个特征。 Pytorch 的 LSTM 期望其所有输入为 3D tensors 【参考方案1】:

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 代码所示,我们想要做的(实际上您做得正确)是从编码器获取 last hiddden state(它编码我们的整个序列)并从这个状态解码序列以获得原始序列。

顺便说一句。这种方法简称为sequence to sequenceseq2seq(常用于语言翻译等任务)。好吧,也许是这种方法的一种变体,但无论如何我都会把它归类。

PyTorch 为我们提供了最后一个隐藏状态,作为来自 RNN 家族的单独返回变量。 我会反对你的encoded[-1]。其原因将是双向和多层次的方法。比如说,你想对双向输出求和,这意味着沿着这些线编写代码

# batch_size and hidden_size should be inferred cluttering 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",尽管它确实是临时的并且会意外工作。 首先,网络会尝试让所有输出都等于 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。我重复输入序列的整个形状,因为它是更通用的方法,并且应该适用于开箱即用的批次和更多维度。

【讨论】:

+1 这很棒。我可能错了,但.repeat(input.shape) 似乎在LSTM 是多层的或批量大小> 1 的情况下具有误导性。我认为.repeat((len(input), 1, 1) 解决了.shape 可能导致的任何问题? 我也面临同样的问题,但在解码器中使用了 LSTMCell 的自动编码器。它不是“重复”编码,而是从编码器中获取最后一个隐藏状态,并重新生成序列中的每个连续元素。我希望你的回答能帮助我解决我的问题。

以上是关于LSTM 自动编码器总是返回输入序列的平均值的主要内容,如果未能解决你的问题,请参考以下文章

LSTM 自动编码器

为啥scikit learn的平均精度分数返回nan?

Keras LSTM 自动编码器时间序列重建

用于可变长度序列的 LSTM 变分自动编码器

如何计算 PyTorch 中注意力分数和编码器输出的加权平均值?

用于时间序列异常检测的 Keras LSTM-VAE(变分自动编码器)