【发布时间】:2021-03-20 03:54:48
【问题描述】:
TLDR:
自动编码器不适合时间序列重建,仅预测平均值。
问题设置:
这是我对序列到序列自动编码器的尝试的总结。这张图片取自这篇论文:https://arxiv.org/pdf/1607.00148.pdf
编码器:标准 LSTM 层。输入序列在最终隐藏状态下编码。
解码器: LSTM 单元(我认为!)。从最后一个元素 x[N] 开始,一次重建一个元素。
对于长度为N的序列的解码算法如下:
- 获取解码器初始隐藏状态
hs[N]:使用编码器最终隐藏状态即可。 - 重构序列中的最后一个元素:
x[N]= w.dot(hs[N]) + b。 - 其他元素的模式相同:
x[i]= w.dot(hs[i]) + b - 使用
x[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
解码器类:
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
将两者结合起来:
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)
这是我的训练函数:
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
数据:
从新闻中抓取的大型事件数据集 (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)
这是自定义的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.")
问题:
模型只学习平均值,无论我制作模型多么复杂或现在我训练它多久。
实际:
我的研究:
此问题与本问题中讨论的问题相同:LSTM autoencoder always returns the average of the input sequence
这种情况下的问题最终是目标函数在计算损失之前对目标时间序列进行平均。这是由于一些广播错误,因为作者没有为目标函数提供大小合适的输入。
就我而言,我认为这不是问题所在。我已经检查并仔细检查了我所有的尺寸/尺寸是否对齐。我很茫然。
我尝试过的其他事情
- 我已经尝试过从 7 个时间步长到 100 个时间步长的不同序列长度。
- 我尝试过在时间序列中使用不同数量的变量。我一直尝试使用单变量数据包含的所有 274 个变量。
- 我在
nn.MSELoss模块上尝试了各种reduction参数。该论文要求sum,但我已经尝试了sum和mean。没有区别。 - 论文要求以相反的顺序重建序列(见上图)。我已经在原始输入上使用
flipud尝试过这种方法(在训练之后但在计算损失之前)。这没什么区别。 - 我尝试通过在编码器中添加一个额外的 LSTM 层来使模型更复杂。
- 我尝试过使用潜在空间。我尝试了从 50% 的输入特征数到 150%。
- 我尝试过拟合单个序列(在上面的数据部分提供)。
问题:
是什么导致我的模型预测平均值以及如何修复它?
【问题讨论】:
-
评论不用于扩展讨论;这个对话是moved to chat。
标签: python neural-network pytorch lstm autoencoder