PyTorch搭建GNN-LSTM和LSTM-GNN模型实现多变量输入多变量输出时间序列预测

27 篇文章 49 订阅
20 篇文章 35 订阅

I. 前言

前面已经写过不少时间序列预测的文章:

  1. 深入理解PyTorch中LSTM的输入和输出(从input输入到Linear输出)
  2. PyTorch搭建LSTM实现时间序列预测(负荷预测)
  3. PyTorch中利用LSTMCell搭建多层LSTM实现时间序列预测
  4. PyTorch搭建LSTM实现多变量时间序列预测(负荷预测)
  5. PyTorch搭建双向LSTM实现时间序列预测(负荷预测)
  6. PyTorch搭建LSTM实现多变量多步长时间序列预测(一):直接多输出
  7. PyTorch搭建LSTM实现多变量多步长时间序列预测(二):单步滚动预测
  8. PyTorch搭建LSTM实现多变量多步长时间序列预测(三):多模型单步预测
  9. PyTorch搭建LSTM实现多变量多步长时间序列预测(四):多模型滚动预测
  10. PyTorch搭建LSTM实现多变量多步长时间序列预测(五):seq2seq
  11. PyTorch中实现LSTM多步长时间序列预测的几种方法总结(负荷预测)
  12. PyTorch-LSTM时间序列预测中如何预测真正的未来值
  13. PyTorch搭建LSTM实现多变量输入多变量输出时间序列预测(多任务学习)
  14. PyTorch搭建ANN实现时间序列预测(风速预测)
  15. PyTorch搭建CNN实现时间序列预测(风速预测)
  16. PyTorch搭建CNN-LSTM混合模型实现多变量多步长时间序列预测(负荷预测)
  17. PyTorch搭建Transformer实现多变量多步长时间序列预测(负荷预测)
  18. PyTorch时间序列预测系列文章总结(代码使用方法)
  19. TensorFlow搭建LSTM实现时间序列预测(负荷预测)
  20. TensorFlow搭建LSTM实现多变量时间序列预测(负荷预测)
  21. TensorFlow搭建双向LSTM实现时间序列预测(负荷预测)
  22. TensorFlow搭建LSTM实现多变量多步长时间序列预测(一):直接多输出
  23. TensorFlow搭建LSTM实现多变量多步长时间序列预测(二):单步滚动预测
  24. TensorFlow搭建LSTM实现多变量多步长时间序列预测(三):多模型单步预测
  25. TensorFlow搭建LSTM实现多变量多步长时间序列预测(四):多模型滚动预测
  26. TensorFlow搭建LSTM实现多变量多步长时间序列预测(五):seq2seq
  27. TensorFlow搭建LSTM实现多变量输入多变量输出时间序列预测(多任务学习)
  28. TensorFlow搭建ANN实现时间序列预测(风速预测)
  29. TensorFlow搭建CNN实现时间序列预测(风速预测)
  30. TensorFlow搭建CNN-LSTM混合模型实现多变量多步长时间序列预测(负荷预测)
  31. PyG搭建图神经网络实现多变量输入多变量输出时间序列预测
  32. PyTorch搭建GNN-LSTM和LSTM-GNN模型实现多变量输入多变量输出时间序列预测
  33. PyG Temporal搭建STGCN实现多变量输入多变量输出时间序列预测
  34. 时序预测中Attention机制是否真的有效?盘点LSTM/RNN中24种Attention机制+效果对比
  35. 详解Transformer在时序预测中的Encoder和Decoder过程:以负荷预测为例
  36. (PyTorch)TCN和RNN/LSTM/GRU结合实现时间序列预测
  37. PyTorch搭建Informer实现长序列时间序列预测
  38. PyTorch搭建Autoformer实现长序列时间序列预测

在前一篇文章PyG搭建图神经网络实现多变量输入多变量输出时间序列预测中我们讲解了如何利用图神经网络进行时间序列预测,其本质是利用GNN来提取各个变量序列间的关系。

不过,在上一篇文章中也提到,仅仅使用GNN进行时序预测没有考虑时间维度上的卷积。因此,这一篇文章中就浅谈一下如何将GNN和LSTM进行结合,以同时实现时间和空间上的卷积。

本篇文章提出了两种不同的思路,一种是GNN-LSTM,即先将时间序列经过图神经网络进行空间上的卷积,然后再将结果输入到LSTM中进行时间上的卷积。另一种是LSTM-GNN,即先利用LSTM提取时序关系,然后再输入到GNN中进行空间上的卷积。当然,我们很容易想到第三种做法:分别利用LSTM和GNN直接对原始时间序列进行操作,然后再将二者结果进行组合,这种方法不再赘述。

以下仅为个人想法,如有原理上的错误欢迎指出!

II. GNN-LSTM

即先进行空间上的卷积,再进行时间上的卷积。

在上一篇文章PyG搭建图神经网络实现多变量输入多变量输出时间序列预测中我们提到了仅用GNN进行时间序列预测的两种方法:

  1. 静态图结构。图的结构不变,是一开始利用训练集计算得到的,每次更换不同的输入特征(input_size, seq_len)进行卷积。
  2. 动态图结构。每次都使用当前变量序列(input_size, seq_len)计算出当前的邻接矩阵,然后再进行卷积。

对于GNN-LSTM,我们采用静态图(动态图也行),然后我们使用torch_geometric.loader.DataLoader对数据进行包装,这样可以将多个图进行拼接以实现GNN的并行处理。数据处理可以参考上一篇文章,下面直接给出模型的搭建细节:

class GAT_LSTM(nn.Module):
    def __init__(self, args, graph):
        super(GAT_LSTM, self).__init__()
        self.args = args
        self.out_feats = 128
        self.edge_index = graph.edge_index
        self.gat = GAT(in_feats=args.seq_len, h_feats=64, out_feats=self.out_feats)
        self.lstm = nn.LSTM(input_size=args.input_size, hidden_size=128,
                            num_layers=args.num_layers, batch_first=True, dropout=0.5)
        self.fcs = nn.ModuleList()
        self.graph = graph
        for k in range(args.input_size):
            self.fcs.append(nn.Sequential(
                nn.Linear(128, 64),
                nn.ReLU(),
                nn.Linear(64, args.output_size)
            ))

    def forward(self, data):
        x, edge_index, batch = data.x, data.edge_index, data.batch
        batch_size = torch.max(batch).item() + 1
        x = self.gat(x, edge_index)   # 6656 128 = 512 * (13, 128)   # y = 6656 1 = 512 * (13 1)
        # print(batch_list)
        # split
        x = x.view(batch_size, -1, x.shape[1])
        y = data.y.view(batch_size, -1, data.y.shape[1])
        
        x = x.permute(0, 2, 1)
        x, _ = self.lstm(x)
        x = x[:, -1, :]
        preds = []
        for fc in self.fcs:
            preds.append(fc(x))

        pred = torch.stack(preds, dim=0)

        return pred, y

这里依然选择图注意力网络GAT。

任意输出一个样本对应的图:

Data(x=[13, 24], edge_index=[2, 40], y=[13, 1])

输出loader中任意一个batch的图数据:

DataBatch(x=[3328, 24], edge_index=[2, 10240], y=[3328, 1], batch=[3328], ptr=[257])

可以看到此时图的特征依然是二维的,但这个图是256个样本对应的小图拼接得到的大图,3328=256*1310240=256*40。此时我们可以将256个图拼接到一起送入GAT,大大提高了计算效率。

利用GAT得到空间上的卷积结果的维度为(3328, 128),即256个图的输出,然后,我们将该输出进行拆分,以还原成256个输出。由于256个图是按照顺序进行拼接,并且每个图的大小都相同,因此可以直接使用view来进行拆分:

x = x.view(batch_size, -1, x.shape[1])
y = data.y.view(batch_size, -1, data.y.shape[1])

最终x=(batch_size=256, input_size=13, hidden_size=128),即我们对初始输入(batch_size=256, seq_len=24, input_size=13)中256个(13, 24)进行了卷积,得到了256个(13, 128)

细心的读者会发现,在这里,GAT的作用与CNN中的一维卷积类似。通过PyTorch搭建CNN-LSTM混合模型实现多变量多步长时间序列预测(负荷预测)我们知道,一维卷积的定义如下:

nn.Conv1d(in_channels=args.in_channels, out_channels=args.out_channels, kernel_size=3)

一维卷积本质上是对seq_len维度进行卷积,然后通过out_channels参数对input_size维度进行变换。这种操作与GNN类似,不过区别是GNN只对seq_len维度进行了变换。因此,本质上GNN也是一种卷积操作。

在得到x=(batch_size=256, input_size=13, hidden_size=128)后,为了满足LSTM(batch_size, seq_len, input_size)的输入要求,我们只需要将x的后两个维度进行交换,将hidden_size当做seq_len,这是因为GNN的作用就是对seq_len维度进行卷积变换,因此我们可以简单地将二者类似。即:

x = x.permute(0, 2, 1)
x, _ = self.lstm(x)
x = x[:, -1, :]
preds = []
for fc in self.fcs:
    preds.append(fc(x))

pred = torch.stack(preds, dim=0)

最终pred中包含了13个变量的预测结果。

III. LSTM-GNN

在LSTM-GNN中,我们先利用LSTM进行时间上的卷积,然后再利用GNN进行空间上的卷积。

为此,我们采用PyTorch搭建LSTM实现时间序列预测(负荷预测)中的数据处理方式,最终得到多个大小为(batch_size, seq_len, input_size)的输出。

LSTM-GNN搭建如下:

class LSTM_GAT(nn.Module):
    def __init__(self, args):
        super(LSTM_GAT, self).__init__()
        self.args = args
        self.out_feats = 128
        self.gat = GAT(in_feats=args.hidden_size, h_feats=128, out_feats=64)
        self.lstm = nn.LSTM(input_size=args.input_size, hidden_size=args.hidden_size,
                            num_layers=args.num_layers, batch_first=True, dropout=0.5)
        self.fcs = nn.ModuleList()
        for k in range(args.input_size):
            self.fcs.append(nn.Sequential(
                nn.Linear(64, 32),
                nn.ReLU(),
                nn.Linear(32, args.output_size)
            ))

    def create_edge_index(self, adj):
        adj = adj.cpu()
        ones = torch.ones_like(adj)
        zeros = torch.zeros_like(adj)
        edge_index = torch.where(adj > 0, ones, zeros)
        #
        edge_index_temp = sp.coo_matrix(edge_index.numpy())
        indices = np.vstack((edge_index_temp.row, edge_index_temp.col))
        edge_index = torch.LongTensor(indices)
        # edge_weight
        edge_weight = []
        t = edge_index.numpy().tolist()
        for x, y in zip(t[0], t[1]):
            edge_weight.append(adj[x, y])
        edge_weight = torch.FloatTensor(edge_weight)
        edge_weight = edge_weight.unsqueeze(1)
        
        return edge_index.to(device), edge_weight.to(device)

    def forward(self, x):
        # x (b, s, i)
        x, _ = self.lstm(x)  # b, s, h
        # s * h conv
        s = torch.randn((x.shape[0], x.shape[1], 64)).to(device)
        for k in range(x.shape[0]):
            feat = x[k, :, :]  # s, h
            # creat edge_index
            adj = torch.matmul(feat, feat.T)  # s * s
            adj = F.softmax(adj, dim=1)
            edge_index, edge_weight = self.create_edge_index(adj)
            feat = self.gat(feat, edge_index, edge_weight)
            s[k, :, :] = feat

        # s(b, s, 64)
        s = s[:, -1, :]
        preds = []
        for fc in self.fcs:
            preds.append(fc(s))

        pred = torch.stack(preds, dim=0)

        return pred

对于LSTM我们已轻车熟路,直接将x=(batch_size, seq_len, input_size)输入到LSTM中,得到的输出为x=(batch_size, seq_len, hidden_size),此时的x中包含了多个时间步的隐输出。

如果我们仅仅使用LSTM进行时间序列预测,那么我们只需要取出x中最后一个时间步的隐输出,然后再简单通过MLP即可得到预测结果。

但这里我们想利用GNN继续进行卷积,因此我们首先要考虑的一个问题是:节点应该是什么?

这个问题的答案很明显,我们应该将时间步当做节点,时间步上的隐藏层输出当做节点的特征变量进行卷积。即我们每次输入一个(seq_len, hidden_size)进行卷积。

一个关键问题是:如何利用(seq_len, hidden_size)构建出图结构?

一个简单的想法是采用完全图,然后计算多个时间步隐藏层输出间的相关性作为边上的权重:

adj = torch.matmul(feat, feat.T)  # s * s
adj = F.softmax(adj, dim=1)
edge_index, edge_weight = self.create_edge_index(adj)

这里采用内积作为相关性判断依据。即首先计算(seq_len, hidden_size)seq_len * seq_len对向量间的内积,然后归一化,得到每个时间步与其他时间步间的权重,这些权重将作为边上的权重。

然后,进行图卷积:

edge_index, edge_weight = self.create_edge_index(adj)
feat = self.gat(feat, edge_index, edge_weight)
s[k, :, :] = feat

最终得到输出s=(batch_size, seq_len, out_feats)。此时,我们再取最后一个时间步上的输出,然后通过多个MLP以得到每个变量的输出。

由于图卷积,最后一个时间步的输出包含了其他时间步输出的信息,这里感觉与attention机制类似。

IV. 模型训练/测试

同前面。

V. 代码

后续考虑整理公开。

  • 36
    点赞
  • 207
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 42
    评论
下面是一个使用PyTorch搭建LSTM进行多变量输入变量输出时间序列预测的示例代码,其中包含数据预处理、模型搭建、训练和预测部分。 首先,假设我们有一个包含多个变量的时间序列数据集,每个变量都有多个时间步。我们需要将数据集分成训练集和测试集,并对数据进行标准化处理,这里使用scikit-learn库中的MinMaxScaler进行归一化操作。 ```python import numpy as np import pandas as pd from sklearn.preprocessing import MinMaxScaler # 读取数据 data = pd.read_csv('data.csv', header=None) data = data.values # 分割数据集 train_size = int(len(data) * 0.8) train_data = data[:train_size] test_data = data[train_size:] # 标准化数据 scaler = MinMaxScaler(feature_range=(0, 1)) train_data = scaler.fit_transform(train_data) test_data = scaler.transform(test_data) ``` 接下来,我们需要将数据转换为输入输出对的形式,其中每个样本的输入包含多个时间步的多个变量输出为多个时间步的多个变量。我们可以定义一个函数来实现这个转换过程。 ```python def create_dataset(dataset, lookback=1, lookahead=1): X, Y = [], [] for i in range(len(dataset)-lookback-lookahead+1): X.append(dataset[i:(i+lookback), :]) Y.append(dataset[(i+lookback):(i+lookback+lookahead), :]) return np.array(X), np.array(Y) # 转换数据 lookback = 10 lookahead = 5 trainX, trainY = create_dataset(train_data, lookback, lookahead) testX, testY = create_dataset(test_data, lookback, lookahead) ``` 接下来,我们可以使用PyTorch搭建LSTM模型。这里使用两层LSTM,每层有64个隐藏单元。注意,输入输出的形状需要与数据集的形状相匹配。 ```python import torch import torch.nn as nn class LSTM(nn.Module): def __init__(self, input_size, output_size, hidden_size, num_layers): super().__init__() self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True) self.fc = nn.Linear(hidden_size, output_size) def forward(self, x): out, _ = self.lstm(x) out = self.fc(out[:, -1, :]) return out # 定义模型 input_size = trainX.shape[-1] output_size = trainY.shape[-1] hidden_size = 64 num_layers = 2 model = LSTM(input_size, output_size, hidden_size, num_layers) ``` 然后,我们需要定义损失函数和优化器。这里使用均方误差损失函数和Adam优化器。 ```python criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=0.001) ``` 现在,我们可以开始训练模型了。训练过程中,我们使用批量梯度下降来更新模型参数。在每个epoch结束时,我们对模型在测试集上的表现进行评估。 ```python # 训练模型 num_epochs = 100 batch_size = 64 train_loss = [] test_loss = [] for epoch in range(num_epochs): # 训练模型 model.train() for i in range(0, len(trainX), batch_size): optimizer.zero_grad() inputs = torch.tensor(trainX[i:i+batch_size]).float() targets = torch.tensor(trainY[i:i+batch_size]).float() outputs = model(inputs) loss = criterion(outputs, targets) loss.backward() optimizer.step() train_loss.append(loss.item()) # 测试模型 model.eval() with torch.no_grad(): inputs = torch.tensor(testX).float() targets = torch.tensor(testY).float() outputs = model(inputs) loss = criterion(outputs, targets) test_loss.append(loss.item()) # 打印损失 print('Epoch [{}/{}], Train Loss: {:.4f}, Test Loss: {:.4f}' .format(epoch+1, num_epochs, train_loss[-1], test_loss[-1])) ``` 最后,我们可以使用训练好的模型进行预测。注意,预测过程中需要将标准化后的输出重新还原为原始数据。 ```python # 预测模型 model.eval() with torch.no_grad(): inputs = torch.tensor(testX).float() outputs = model(inputs) preds = scaler.inverse_transform(outputs.numpy()) ``` 这就是使用PyTorch搭建LSTM进行多变量输入变量输出时间序列预测的完整过程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 42
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cyril_KI

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值