我正在尝试使用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)
答案 0 :(得分:3)
在您的源代码中,您使用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)
我们不需要任何多余的尺寸(例如[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)
整个网络(到目前为止)与您的网络相同,只不过它更简洁易读。
正如您提供的 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)
的原因。
实际上,这只是您的错误,只是在最后一部分。
输出预测和目标的形状
# Your output
torch.Size([5, 1, 1])
# Your target
torch.Size([5, 1])
如果提供了这些形状,则默认情况下 MSELoss 使用参数size_average=True
。是的,它可以平均目标和输出,实际上可以计算出张量的平均值(开始时约为2.5)和目标的平均值 0.2
因此网络可以正确收敛,但是您的目标是错误的。
为 MSELoss 提供参数reduction =“ sum”,尽管它确实是临时的,并且偶然地起作用。 首先,网络会尝试使所有输出等于和(0 + 0.1 + 0.2 + 0.3 + 0.4 = 1.0),首先是半随机输出,过一会儿它将收敛到您想要的内容,但并非出于您想要的原因!。
在这里,即使是求和(由于您的输入数据非常简单),身份功能也是最简单的选择。
只需将适当的形状传递给损失函数,例如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的参数更改为默认值,因为它收敛得更快。
为简便起见,下面是代码和结果:
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。我会重复输入序列的整个形状,因为这是更通用的方法,应该可以批量使用和处理更大的尺寸。