roc*_*ves 11 python neural-network autoencoder lstm pytorch
自编码器欠拟合时间序列重建,仅预测平均值。
这是我对序列到序列自动编码器的尝试的总结。该图片取自本文:https : //arxiv.org/pdf/1607.00148.pdf

编码器:标准 LSTM 层。输入序列在最终隐藏状态中编码。
解码器: LSTM Cell(我想!)。从最后一个元素开始,一次一个元素地重建序列x[N]。
对于长度为 的序列,解码器算法如下N:
hs[N]:只需使用编码器最终隐藏状态。x[N]= w.dot(hs[N]) + b。x[i]= w.dot(hs[i]) + bx[i]和hs[i]作为输入LSTMCell来获取x[i-1]和hs[i-1]这是我的实现,从编码器开始:
class SeqEncoderLSTM(nn.Module):
def __init__(self, n_features, latent_size):
super(SeqEncoderLSTM, self).__init__()
self.lstm = nn.LSTM(
n_features,
latent_size,
batch_first=True)
def forward(self, x):
_, hs = self.lstm(x)
return hs
Run Code Online (Sandbox Code Playgroud)
解码器类:
class SeqDecoderLSTM(nn.Module):
def __init__(self, emb_size, n_features):
super(SeqDecoderLSTM, self).__init__()
self.cell = nn.LSTMCell(n_features, emb_size)
self.dense = nn.Linear(emb_size, n_features)
def forward(self, hs_0, seq_len):
x = torch.tensor([])
# Final hidden and cell state from encoder
hs_i, cs_i = hs_0
# reconstruct first element with encoder output
x_i = self.dense(hs_i)
x = torch.cat([x, x_i])
# reconstruct remaining elements
for i in range(1, seq_len):
hs_i, cs_i = self.cell(x_i, (hs_i, cs_i))
x_i = self.dense(hs_i)
x = torch.cat([x, x_i])
return x
Run Code Online (Sandbox Code Playgroud)
将两者结合起来:
class LSTMEncoderDecoder(nn.Module):
def __init__(self, n_features, emb_size):
super(LSTMEncoderDecoder, self).__init__()
self.n_features = n_features
self.hidden_size = emb_size
self.encoder = SeqEncoderLSTM(n_features, emb_size)
self.decoder = SeqDecoderLSTM(emb_size, n_features)
def forward(self, x):
seq_len = x.shape[1]
hs = self.encoder(x)
hs = tuple([h.squeeze(0) for h in hs])
out = self.decoder(hs, seq_len)
return out.unsqueeze(0)
Run Code Online (Sandbox Code Playgroud)
这是我的训练功能:
def train_encoder(model, epochs, trainload, testload=None, criterion=nn.MSELoss(), optimizer=optim.Adam, lr=1e-6, reverse=False):
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print(f'Training model on {device}')
model = model.to(device)
opt = optimizer(model.parameters(), lr)
train_loss = []
valid_loss = []
for e in tqdm(range(epochs)):
running_tl = 0
running_vl = 0
for x in trainload:
x = x.to(device).float()
opt.zero_grad()
x_hat = model(x)
if reverse:
x = torch.flip(x, [1])
loss = criterion(x_hat, x)
loss.backward()
opt.step()
running_tl += loss.item()
if testload is not None:
model.eval()
with torch.no_grad():
for x in testload:
x = x.to(device).float()
loss = criterion(model(x), x)
running_vl += loss.item()
valid_loss.append(running_vl / len(testload))
model.train()
train_loss.append(running_tl / len(trainload))
return train_loss, valid_loss
Run Code Online (Sandbox Code Playgroud)
从新闻中抓取的大型事件数据集 (ICEWS)。存在描述每个事件的各种类别。我最初对这些变量进行单热编码,将数据扩展到 274 维。但是,为了调试模型,我将其缩减为一个 14 个时间步长且仅包含 5 个变量的序列。这是我试图过度拟合的序列:
tensor([[0.5122, 0.0360, 0.7027, 0.0721, 0.1892],
[0.5177, 0.0833, 0.6574, 0.1204, 0.1389],
[0.4643, 0.0364, 0.6242, 0.1576, 0.1818],
[0.4375, 0.0133, 0.5733, 0.1867, 0.2267],
[0.4838, 0.0625, 0.6042, 0.1771, 0.1562],
[0.4804, 0.0175, 0.6798, 0.1053, 0.1974],
[0.5030, 0.0445, 0.6712, 0.1438, 0.1404],
[0.4987, 0.0490, 0.6699, 0.1536, 0.1275],
[0.4898, 0.0388, 0.6704, 0.1330, 0.1579],
[0.4711, 0.0390, 0.5877, 0.1532, 0.2201],
[0.4627, 0.0484, 0.5269, 0.1882, 0.2366],
[0.5043, 0.0807, 0.6646, 0.1429, 0.1118],
[0.4852, 0.0606, 0.6364, 0.1515, 0.1515],
[0.5279, 0.0629, 0.6886, 0.1514, 0.0971]], dtype=torch.float64)
Run Code Online (Sandbox Code Playgroud)
这是自定义Dataset类:
class TimeseriesDataSet(Dataset):
def __init__(self, data, window, n_features, overlap=0):
super().__init__()
if isinstance(data, (np.ndarray)):
data = torch.tensor(data)
elif isinstance(data, (pd.Series, pd.DataFrame)):
data = torch.tensor(data.copy().to_numpy())
else:
raise TypeError(f"Data should be ndarray, series or dataframe. Found {type(data)}.")
self.n_features = n_features
self.seqs = torch.split(data, window)
def __len__(self):
return len(self.seqs)
def __getitem__(self, idx):
try:
return self.seqs[idx].view(-1, self.n_features)
except TypeError:
raise TypeError("Dataset only accepts integer index/slices, not lists/arrays.")
Run Code Online (Sandbox Code Playgroud)
模型只学习平均值,无论我制作模型有多复杂,或者现在训练它多长时间。
实际的:
此问题与此问题中讨论的问题相同:LSTM 自编码器始终返回输入序列的平均值
这种情况下的问题最终是目标函数在计算损失之前对目标时间序列进行平均。这是由于一些广播错误造成的,因为作者没有正确大小的目标函数输入。
就我而言,我不认为这是问题所在。我已经检查并仔细检查了我所有的尺寸/尺寸是否对齐。我很茫然。
reduction在nn.MSELoss模块上使用各种参数。该论文要求sum,但我已经尝试了sum和mean。没有不同。flipud在原始输入上使用 尝试了这种方法(在训练之后但在计算损失之前)。这没什么区别。是什么导致我的模型预测平均值,我该如何解决?
好的,经过一些调试我想我知道原因了。
hidden_features数量太少,导致模型无法拟合单个样本先上代码(模型是一样的):
import seaborn as sns
import matplotlib.pyplot as plt
def get_data(subtract: bool = False):
# (1, 14, 5)
input_tensor = torch.tensor(
[
[0.5122, 0.0360, 0.7027, 0.0721, 0.1892],
[0.5177, 0.0833, 0.6574, 0.1204, 0.1389],
[0.4643, 0.0364, 0.6242, 0.1576, 0.1818],
[0.4375, 0.0133, 0.5733, 0.1867, 0.2267],
[0.4838, 0.0625, 0.6042, 0.1771, 0.1562],
[0.4804, 0.0175, 0.6798, 0.1053, 0.1974],
[0.5030, 0.0445, 0.6712, 0.1438, 0.1404],
[0.4987, 0.0490, 0.6699, 0.1536, 0.1275],
[0.4898, 0.0388, 0.6704, 0.1330, 0.1579],
[0.4711, 0.0390, 0.5877, 0.1532, 0.2201],
[0.4627, 0.0484, 0.5269, 0.1882, 0.2366],
[0.5043, 0.0807, 0.6646, 0.1429, 0.1118],
[0.4852, 0.0606, 0.6364, 0.1515, 0.1515],
[0.5279, 0.0629, 0.6886, 0.1514, 0.0971],
]
).unsqueeze(0)
if subtract:
initial_values = input_tensor[:, 0, :]
input_tensor -= torch.roll(input_tensor, 1, 1)
input_tensor[:, 0, :] = initial_values
return input_tensor
if __name__ == "__main__":
torch.manual_seed(0)
HIDDEN_SIZE = 10
SUBTRACT = False
input_tensor = get_data(SUBTRACT)
model = LSTMEncoderDecoder(input_tensor.shape[-1], HIDDEN_SIZE)
optimizer = torch.optim.Adam(model.parameters())
criterion = torch.nn.MSELoss()
for i in range(1000):
outputs = model(input_tensor)
loss = criterion(outputs, input_tensor)
loss.backward()
optimizer.step()
optimizer.zero_grad()
print(f"{i}: {loss}")
if loss < 1e-4:
break
# Plotting
sns.lineplot(data=outputs.detach().numpy().squeeze())
sns.lineplot(data=input_tensor.detach().numpy().squeeze())
plt.show()
Run Code Online (Sandbox Code Playgroud)
它能做什么:
get_data要么处理您提供的数据,如果subtract=False或(如果subtract=True)它从当前时间步长中减去前一个时间步长的值1e-4达到损失(因此我们可以比较模型的容量和增加如何帮助以及当我们使用时间步长而不是时间步长时会发生什么)我们只会改变HIDDEN_SIZE和SUBTRACT参数!
HIDDEN_SIZE=5SUBTRACT=False在这种情况下,我们得到一条直线。模型无法拟合和掌握数据中呈现的现象(因此您提到了扁平线)。
达到 1000 次迭代限制
HIDDEN_SIZE=5SUBTRACT=True目标现在远非平坦线,但由于容量太小,模型无法适应。
达到 1000 次迭代限制
HIDDEN_SIZE=100SUBTRACT=False它变得更好了,我们的目标在942几步之后就被击中了。没有更多的扁平线,模型容量似乎很好(对于这个单一的例子!)
HIDDEN_SIZE=100SUBTRACT=True虽然图看起来不那么漂亮,但我们只经过215迭代就得到了期望的损失。
300我认为的那样),但您可以简单地调整该模型。flipud. 使用双向 LSTM,这样您就可以从 LSTM 的前向和后向传递中获取信息(不要与反向传播混淆!)。这也应该提高你的分数好的,问题 1:您是说对于时间序列中的变量 x,我应该训练模型学习 x[i] - x[i-1] 而不是 x[i] 的值?我的解释正确吗?
对,就是这样。差异消除了神经网络过多地基于过去的时间步长进行预测的冲动(通过简单地获取最后一个值并可能稍微改变它)
问题 2:您说我对零瓶颈的计算不正确。但是,例如,假设我使用一个简单的密集网络作为自动编码器。获得正确的瓶颈确实取决于数据。但是如果你让瓶颈与输入的大小相同,你就会得到恒等函数。
是的,假设不涉及非线性,这会使事情变得更难(请参阅此处了解类似情况)。在 LSTM 存在非线性的情况下,这是一点。
另一个是我们正在累积timesteps到单个编码器状态。所以本质上,我们必须将timesteps身份积累到单个隐藏和细胞状态中,这是极不可能的。
最后一点,根据序列的长度,LSTM 很容易忘记一些最不相关的信息(这是它们的设计目的,不仅是记住所有内容),因此更不可能。
num_features * num_timesteps 不是与输入大小相同的瓶颈,因此它不应该促进模型学习身份吗?
确实如此,但它假设您num_timesteps对每个数据点都有,这种情况很少见,可能在这里。关于身份以及为什么很难对上面回答的网络进行非线性处理。
最后一点,关于恒等函数;如果它们真的很容易学习,ResNets 架构就不太可能成功。网络可以收敛到身份并在没有身份的情况下对输出进行“小修正”,但事实并非如此。
我对以下声明很好奇:“始终使用时间步长的差异而不是时间步长”通过将所有功能更紧密地结合在一起似乎有一些规范化效果,但我不明白为什么这是关键?拥有一个更大的模型似乎是解决方案,而减法只是有帮助。
事实上,这里的关键是增加模型容量。减法技巧实际上取决于数据。让我们想象一个极端的情况:
100时间步长,单一功能100001最多变化神经网络会做什么(这里最简单的是什么)?它可能会将这个1或更小的变化作为噪音丢弃,并只预测1000所有这些变化(特别是如果一些正则化到位),因为偏离1/1000并不多。
如果我们减去呢?整个神经网络损失在[0, 1]每个时间步的边缘而不是[0, 1001],因此错误更严重。
是的,从某种意义上说,它与规范化有关。
| 归档时间: |
|
| 查看次数: |
928 次 |
| 最近记录: |