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次?在一些图像描述方法中,它们仅将编码版本作为第一个时间步传递,然后它们使用循环功能来生成向量的其余部分,直到它生成某种<stop>
标记。也许试试这个?
您是否正在尝试学习 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_encoder
和 init_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 sequence或seq2seq(常用于语言翻译等任务)。好吧,也许是这种方法的一种变体,但无论如何我都会把它归类。
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 自动编码器总是返回输入序列的平均值的主要内容,如果未能解决你的问题,请参考以下文章