前面已经写过不少时间序列预测的文章:
从第31篇文章起,本系列开始更新时空预测模型,其中前两篇文章都不是属于论文中的模型,今天介绍一个使用较为广泛的用于时序预测的时空图卷积网络STGCN。
STGCN是北大发表在IJCAI 2018上的论文Spatio-Temporal Graph Convolutional Networks: A Deep Learning Framework for Traffic Forecasting中提出来的,其目的是用于实时的交通预测。
在该论文中使用的数据集为美国加州PeMSD7数据集,里面包含了分布在不同地方的228个传感器观测到的车流量,文章中使用这228个节点构成了一个无向图,然后根据历史的车流量信息预测未来某个时间段的所有传感器所在地的车流量信息。
可以看出,STGCN要解决的问题与前两篇文章要解决的问题基本一致。前两篇问题中,我们给出了13个变量前24小时的数据,目的是预测13个变量未来某几个小时的数据。在这里13个变量类比于228个传感器。
STGCN的原理也较为简单,STGCN由两个时空图卷积块(ST-Conv Block)和一个输出全连接层(Output Layer组成。其中ST-Conv Block又由两个时间门控卷积和中间的一个空间图卷积组成:
从图右边可知,两个Temporal Gated-Conv使用的是1-D卷积,和CNN处理一维时序信号类似,即进行seq_len
维度上的卷积。Spatial Graph-Conv进行的是空域上的卷积,模型为GCN。
关于STGCN详细的原理可以阅读原论文,原理也比较简单。本篇文章不做太多详细的推导过程,主要讲解如何利用STGCN进行多变量输入多变量输出的时间序列预测。
PyG Temporal是PyG的一个扩展库,其主要用于处理时空信号数据,里面实现了许多使用较为广泛的时空图卷积模型如STGCN、DCRNN、T-GCN、LRGCN等。
PyG Temporal的安装也比较简单:
pip install torch-geometric-temporal
PyG Temporal中STGCN的实现如下:
参数解释如下:
in_channels
:节点输入特征的维度大小,这里为1,即每个节点都只有一个特征,我们需要预测的也是该特征。hidden_channels
:字面意思。out_channels
:字面意思。kernel_size
:时域卷积时的卷积核大小,类比CNN即可。K
:将切比雪夫多项式作为图卷积核时的卷积核大小,具体可以参考我之前写的一篇文章:ICML 2019 | SGC:简单图卷积网络。normalization
:拉普拉斯矩阵的归一化选项,前面也讲过了。bias
:无需多述。一个STConv所能接受的输入格式为:
可以看出,一个STConv需要接受三个输入:
X
:维度大小为(batch_size, seq_len, num_nodes, in_channels)
,在本文中即X=(256, 24, 13, 1)
。edge_index
:图的邻接矩阵。edge_weight
:边权重矩阵(可选)。为此,我们可以首先搭建一个STGCN:
class STGCN(nn.Module):
def __init__(self, num_nodes, size, K):
super(STGCN, self).__init__()
self.conv1 = STConv(num_nodes=num_nodes, in_channels=1, hidden_channels=16,
out_channels=32, kernel_size=size, K=K)
self.conv2 = STConv(num_nodes=num_nodes, in_channels=32, hidden_channels=16,
out_channels=32, kernel_size=size, K=K)
def forward(self, x, edge_index):
# x(batch_size, seq_len, num_nodes, in_channels)
x, edge_index = x.to(device), edge_index.to(device)
x = F.elu(self.conv1(x, edge_index))
x = self.conv2(x, edge_index)
return x
然后一个用于多变量输入多变量输出的STGCN模型搭建如下:
class STGCN_MLP(nn.Module):
def __init__(self, args):
super(STGCN_MLP, self).__init__()
self.args = args
self.out_feats = 128
self.stgcn = STGCN(num_nodes=args.input_size, size=3, K=1)
self.fcs = nn.ModuleList()
for k in range(args.input_size):
self.fcs.append(nn.Sequential(
nn.Linear(16 * 32, 64),
nn.ReLU(),
nn.Linear(64, args.output_size)
))
def forward(self, x, edge_index):
# x(batch_size, seq_len, input_size)
# x(512, 24, 13)--->(512, 24, 13, 1)
x = x.unsqueeze(3)
x = self.stgcn(x, edge_index)
preds = []
for k in range(x.shape[2]):
preds.append(self.fcs[k](torch.flatten(x[:, :, k, :], start_dim=1)))
pred = torch.stack(preds, dim=0)
return pred
照例简单分析一下模型的处理过程:
首先我们有x=(batch_size=256, seq_len=24, input_size=13)
,为了满足STGCN的输入要求(batch_size, seq_len, num_nodes, in_channels=1)
,我们需要将x
扩展一个维度:
x = x.unsqueeze(3)
然后经过STGCN:
x = self.stgcn(x, edge_index)
得到x=(256, 16, 13, 32)
。操作过程与CNN类似,一维卷积作用在seq_len=24
维度,最终变成16。随后,为了得到每个变量的输出,我们简单地将13个变量各自的(16, 32)
经过13个不同的全连接层。
这点与前面一致,不再赘述。
后续考虑整理公开。