我目前正在使用PyTorch构建一个LSTM模型来预测时间序列数据。我使用滞后特征将之前的n个步骤作为输入来训练网络。我将数据分成了三个集合,即训练-验证-测试分割,并使用前两个集合来训练模型。我的验证函数从验证数据集中获取数据,并使用DataLoaders和TensorDataset类将其传递给LSTM模型以计算预测值。最初,我得到了相当不错的结果,R2值在0.85到0.95之间。
然而,我对这个验证函数是否也适合测试模型性能感到不安。因为该函数现在使用DataLoader中的实际X值,即时间滞后特征,来预测y^值,即预测目标值,而不是使用预测的y^值作为下一个预测的特征。这种情况与现实相去甚远,模型对之前时间步长的真实值一无所知,尤其是当你预测较长时间段的时间序列数据时,比如3到6个月。
我目前对如何解决这个问题以及定义一个依赖于模型值而不是测试集中的实际值来预测未来值的函数感到困惑。我有以下predict
函数,它可以进行一步预测,但我还没有真正弄清楚如何使用DataLoader来预测整个测试数据集。
def predict(self, x): # 将行转换为数据 x = x.to(device) # 进行预测 yhat = self.model(x) # 检索numpy数组 yhat = yhat.to(device).detach().numpy() return yhat
您可以在下方找到我如何分割和加载数据集、LSTM模型的构造函数以及验证函数。如果您需要更多信息,请随时与我联系。
分割和加载数据集
def create_tensor_datasets(X_train_arr, X_val_arr, X_test_arr, y_train_arr, y_val_arr, y_test_arr): train_features = torch.Tensor(X_train_arr) train_targets = torch.Tensor(y_train_arr) val_features = torch.Tensor(X_val_arr) val_targets = torch.Tensor(y_val_arr) test_features = torch.Tensor(X_test_arr) test_targets = torch.Tensor(y_test_arr) train = TensorDataset(train_features, train_targets) val = TensorDataset(val_features, val_targets) test = TensorDataset(test_features, test_targets) return train, val, testdef load_tensor_datasets(train, val, test, batch_size=64, shuffle=False, drop_last=True): train_loader = DataLoader(train, batch_size=batch_size, shuffle=shuffle, drop_last=drop_last) val_loader = DataLoader(val, batch_size=batch_size, shuffle=shuffle, drop_last=drop_last) test_loader = DataLoader(test, batch_size=batch_size, shuffle=shuffle, drop_last=drop_last) return train_loader, val_loader, test_loader
类LSTM
class LSTMModel(nn.Module): def __init__(self, input_dim, hidden_dim, layer_dim, output_dim, dropout_prob): super(LSTMModel, self).__init__() self.hidden_dim = hidden_dim self.layer_dim = layer_dim self.lstm = nn.LSTM( input_dim, hidden_dim, layer_dim, batch_first=True, dropout=dropout_prob ) self.fc = nn.Linear(hidden_dim, output_dim) def forward(self, x, future=False): h0 = torch.zeros(self.layer_dim, x.size(0), self.hidden_dim).requires_grad_() c0 = torch.zeros(self.layer_dim, x.size(0), self.hidden_dim).requires_grad_() out, (hn, cn) = self.lstm(x, (h0.detach(), c0.detach())) out = out[:, -1, :] out = self.fc(out) return out
验证(在训练器类中定义)
def validation(self, val_loader, batch_size, n_features): with torch.no_grad(): predictions = [] values = [] for x_val, y_val in val_loader: x_val = x_val.view([batch_size, -1, n_features]).to(device) y_val = y_val.to(device) self.model.eval() yhat = self.model(x_val) predictions.append(yhat.cpu().detach().numpy()) values.append(y_val.cpu().detach().numpy()) return predictions, values
回答:
我终于找到了基于早期观察的预测值来预测值的方法。正如预期的那样,短期内的预测相当准确,随着时间的推移,预测效果略有下降。未来预测随着时间的推移而偏离并不令人惊讶,因为它们不再依赖于实际值。反思我的结果和关于这个话题的讨论,这里是我的一些收获:
-
在现实生活中,可以在每次预测时(无论是每周、每天还是每小时)检索并将实际值输入模型,以便使用前一步的实际值来预测下一步。因此,基于测试集中的实际值来测试性能可能在某种程度上反映了定期维护的模型的真实性能。
-
然而,对于长期预测未来值,即预测,如果你愿意的话,你需要进行多个一步预测或跨越你希望预测的时间段的多步预测。
-
基于模型预测的值进行多个一步预测在短期内产生合理的结果。随着预测期的增加,预测变得不那么准确,因此不太适合预测的目的。
-
要进行多个一步预测并在每次预测后更新输入,我们必须逐一处理数据集,就像我们在测试集上进行for循环一样。毫不奇怪,这让我们失去了矩阵运算和小批量训练提供的所有计算优势。
-
另一种方法可以是预测一系列值,而不是仅预测下一个值,比如使用具有多维输出的RNN,采用多对多或序列到序列结构。它们可能更难训练,并且不太灵活,无法为不同时间段进行预测。尽管我自己还没有实现,但编码器-解码器结构可能有助于解决这个问题。
您可以找到我基于数据集X
(时间滞后特征)和y
(目标值)的最后一行的函数代码,该函数预测接下来的n_steps
。为了遍历数据集中的每一行,我将batch_size
设置为1,将n_features
设置为滞后观察的数量。
def forecast(self, X, y, batch_size=1, n_features=1, n_steps=100): predictions = [] X = torch.roll(X, shifts=1, dims=2) X[..., -1, 0] = y.item(0) with torch.no_grad(): self.model.eval() for _ in range(n_steps): X = X.view([batch_size, -1, n_features]).to(device) yhat = self.model(X) yhat = yhat.to(device).detach().numpy() X = torch.roll(X, shifts=1, dims=2) X[..., -1, 0] = yhat.item(0) predictions.append(yhat) return predictions
以下行将张量的第二个维度上的值向后移动一个位置,使得张量[[[x1, x2, x3, ... , xn ]]]
变为[[[xn, x1, x2, ... , x(n-1)]]]
。
X = torch.roll(X, shifts=1, dims=2)
而下面的行从3D张量的最后一个维度中选择第一个元素,并将该项设置为存储在NumPy数组(yhat)中的预测值,[[xn+1]]
。然后,新的输入张量变为[[[x(n+1), x1, x2, ... , x(n-1)]]]
X[..., -1, 0] = yhat.item(0)
最近,我决定整理我学到的东西以及我希望早点知道的东西。如果您想看一看,可以在下方找到链接。我希望您会发现它有用。如果您同意或不同意我上面提到的任何评论,请随时评论或联系我。