PyTorch搭建GNN(GCN、GraphSAGE和GAT)实现多节点、单节点内多变量输入多变量输出时空预测

news2024/12/23 14:42:37

目录

  • I. 前言
  • II. 数据集说明
  • III. 模型
    • 3.1 GCN
    • 3.2 GraphSAGE
    • 3.3 GAT
  • IV. 训练与测试
  • V. 实验结果

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实现长序列时间序列预测
  39. PyTorch搭建GNN(GCN、GraphSAGE和GAT)实现多节点、单节点内多变量输入多变量输出时空预测

前边已经有两篇文章讲解了如何利用PyG搭建GNN以及GNN-LSTM进行时间序列预测,这两部分内容都只是针对多变量进行预测,即将每个变量当成一个节点,然后利用皮尔逊相关系数构建变量间的邻接矩阵。

上述过程有以下两个问题:
(1)不少人使用时不会安装PyG(后台经常有人询问如何安装PyG),这个其实不困难,具体安装命令为:

pip install torch_scatter torch_sparse torch_cluster torch_spline_conv torch-geometric -f https://data.pyg.org/whl/torch-1.10.0+cu113.html

使用时将命令中的torch-1.10.0+cu113.html换成自己的torch版本和CUDA版本即可。

(2)使用PyG有诸多限制,例如PyG中每个节点只能拥有单个变量序列。当然,可以将每个节点的多条变量序列用神经网络或者注意力机制等方式转换为一个变量序列,这里不再细说。

有不少人要求出一期多站点多变量预测,前期由于实习+秋招+毕设缠身,事情较多,所以没来得及写。现在有了一些空闲时间,因此在这篇文章里做一些详细的说明。

II. 数据集说明

本次使用的数据集为交通流量预测领域常见的PEMS系列数据集,包括PEMS03、PEMS04、PEMS07和PEMS08四个数据集。

其中,PEMS04是由307个探测器(节点数)每隔5分钟采集一次数据,共采集59天产生的交通流量数据;PEMS08是由170个探测器每隔5分钟采集一次,共采集62天产生的数据。每个探测器每次采集的数据包含三个维度的特征,分别为:流量、平均速度和平均占有率。因此,数据集的格式应该为一个矩阵,大小为num * num_nodes * 3,其中PSMS04的num=59*24*12=16992num_nodes=307,而PEMS08的num=62*24*12=17856num_nodes=170。PEMS03和PEMS07两个数据集中只包含流量这一个变量,二者的的大小分别为26208*358*1进和28224*883*1

在这篇文章中,使用前2小时的数据预测未来半小时的数据,即历史24个时刻的多个变量预测未来6个时刻的多个变量。

数据处理代码与前面类似:

def nn_seq(args):
    seq_len = args.seq_len
    batch_size, pred_len = args.batch_size, args.pred_len

    root_path = os.path.abspath(os.path.dirname(os.getcwd()))
    file_name = args.file_name
    data_path = root_path + "/data/" + file_name + "/"

    npz = np.load(data_path + file_name + ".npz")
    data = npz['data']  # lens num_nodes, in_feats
    print(data.shape)
    # data = data[:2000]
    # 3表示:车流量、平均车速、平均车道占用率
    num_nodes = data.shape[1]
    # 加载邻接矩阵
    adj_data = pd.read_csv(data_path + file_name + ".csv")
    adj_data = adj_data[["from", "to"]].values.tolist()
    # 找出最大最小值
    all_elements = [element for row in adj_data for element in row]
    all_elements = list(set(all_elements))
    all_elements.sort()
    print(len(all_elements) == num_nodes)
    node_dict = dict(zip(all_elements, [x for x in range(num_nodes)]))
    # print(max_val, min_val)
    adj = torch.zeros((num_nodes, num_nodes))

    for src, dst in adj_data:
        src = node_dict[src]
        dst = node_dict[dst]
        adj[src, dst] = adj[dst, src] = 1
    #
    # split
    train = data[:int(len(data) * 0.6)]
    val = data[int(len(data) * 0.6):int(len(data) * 0.8)]
    test = data[int(len(data) * 0.8):]
    # 归一化 要求在站点内部,对按照时间列进行归一化

    scalers = []
    for idx in range(num_nodes):
        cur_train = train[:, idx, :]
        cur_val = val[:, idx, :]
        cur_test = test[:, idx, :]
        scaler = MinMaxScaler()
        train[:, idx, :] = scaler.fit_transform(cur_train)
        val[:, idx, :] = scaler.transform(cur_val)
        test[:, idx, :] = scaler.transform(cur_test)
        scalers.append(scaler)

    def process(dataset, step_size, shuffle):
        # dataset: num num_nodes dim
        seq = []
        for i in tqdm(range(0, len(dataset) - seq_len - pred_len + 1, step_size)):
            x = dataset[i:i + seq_len]
            y = dataset[i + seq_len:i + seq_len + pred_len]
            # tensor
            x = torch.FloatTensor(x)
            y = torch.FloatTensor(y)
            seq.append((x, y))

        seq = MyDataset(seq)
        seq = DataLoader(dataset=seq, batch_size=batch_size, shuffle=shuffle, num_workers=0, drop_last=False)

        return seq

    Dtr = process(train, step_size=1, shuffle=True)
    Val = process(val, step_size=1, shuffle=True)
    Dte = process(test, step_size=pred_len, shuffle=False)

    return Dtr, Val, Dte, adj, scalers

归一化时,由于不同站点间的数据没有太大关联,因此需要单独对每个站点内部的数据进行归一化,这里采用了MinMaxSacler归一化。

III. 模型

在这篇文章中将使用常见的三个GNN模型进行预测,即GCN、GraphSAGE和GAT。

图卷积网络(Graph Convolutional Network,GCN)是最早提出的图神经网络之一,GCN通过在图的邻域内进行信息聚合来学习节点的低维表示。具体来说,GCN利用了拉普拉斯矩阵的特征值分解,通过图傅里叶变换将卷积操作转换为频域上的滤波操作。GCN的核心公式为:
h ( l + 1 ) = σ ( D ~ − 1 2 A ~ D ~ − 1 2 h ( l ) W ( l ) ) h^{(l+1)} = \sigma(\tilde{D}^{-\frac{1}{2}} \tilde{A} \tilde{D}^{-\frac{1}{2}} h^{(l)} W^{(l)}) h(l+1)=σ(D~21A~D~21h(l)W(l))

其中, A ~ \tilde{A} A~是带有自环的邻接矩阵, D ~ \tilde{D} D~是对应的度矩阵, h ( l ) h^{(l)} h(l) 是第 l l l层的隐藏状态, W ( l ) W^{(l)} W(l)是权重矩阵, σ \sigma σ是激活函数。

GraphSAGE是由Hamilton等人在2017年提出的,旨在解决大规模图上的节点表示学习问题。GraphSAGE通过采样节点的邻居,并在局部邻域内进行信息聚合,从而生成节点表示。GraphSAGE支持多种聚合方法,包括 Mean Aggregator、LSTM Aggregator 和 Max Pooling Aggregator。GraphSAGE的核心公式为:
h i ( k + 1 ) = σ ( W f ( h i ( k ) , { h j ( k ) ∣ j ∈ N ( i ) } ) ) h_i^{(k+1)} = \sigma(W f(h_i^{(k)}, \{h_j^{(k)} | j \in \mathcal{N}(i)\})) hi(k+1)=σ(Wf(hi(k),{hj(k)jN(i)}))

其中 h i ( k ) h_i^{(k)} hi(k)是第 k k k层节点 i i i的隐藏状态, N ( i ) \mathcal{N}(i) N(i)是节点 i i i的邻居集合, f f f是聚合函数。GraphSAGE通过多层聚合操作,能够有效地捕捉节点的局部结构信息。

图注意力网络GAT(Graph Attention Networks)通过整合注意力机制实现了对图中不同邻居节点的动态加权。其主要创新之处在于,为每个邻接节点分配一个注意力得分,从而使模型可以聚焦于那些更为重要的邻近节点。GAT的核心公式为:
h i ( l + 1 ) = σ ( ∑ j ∈ N ( i ) α i j W h j ( l ) ) h_i^{(l+1)} = \sigma \left( \sum_{j \in \mathcal{N}(i)} \alpha_{ij} W h_j^{(l)} \right) hi(l+1)=σ jN(i)αijWhj(l)

其中 α i j \alpha_{ij} αij是节点 i i i j j j之间的注意力分数, W W W是权重矩阵。GAT通过自注意力机制,能够更好地捕捉节点之间的关系。

上述三种模型的原理十分简单,下边将依次介绍如何使用三种模型进行多站点、多变量输入、多变量输出的时空预测。

在进行模型讲解之前,先规定一下模型的输入和输出维度。在本文中,模型的输入尺寸为:batch_size * seq_len * num_nodes * in_feats,表示每个站点的多变量历史数据,输出为batch_size * pred_len * num_nodes * in_feats,表示多个站点未来的多变量数据。

3.1 GCN

GCN的代码实现十分优雅简洁,可以先看一下原作者的代码实现:

class GraphConvolution(Module):
    """
    Simple GCN layer, similar to https://arxiv.org/abs/1609.02907
    """

    def __init__(self, in_features, out_features, bias=True):
        super(GraphConvolution, self).__init__()
        self.in_features = in_features
        self.out_features = out_features
        self.weight = Parameter(torch.FloatTensor(in_features, out_features))
        if bias:
            self.bias = Parameter(torch.FloatTensor(out_features))
        else:
            self.register_parameter('bias', None)
        self.reset_parameters()

    def reset_parameters(self):
        stdv = 1. / math.sqrt(self.weight.size(1))
        self.weight.data.uniform_(-stdv, stdv)
        if self.bias is not None:
            self.bias.data.uniform_(-stdv, stdv)

    def forward(self, input, adj):
        support = torch.mm(input, self.weight
        output = torch.spmm(adj, support)
        if self.bias is not None:
            return output + self.bias
        else:
            return output

    def __repr__(self):
        return self.__class__.__name__ + ' (' \
               + str(self.in_features) + ' -> ' \
               + str(self.out_features) + ')'

可以看到,GCN的本质就是将归一化后的邻接矩阵和节点特征矩阵执行矩阵乘法,即(num_nodes, num_nodes) * (num_nodes, feats) -> (num_nodes, feats)

因此,对于大小为batch_size * seq_len * num_nodes * in_feats的输入,可以直接对后两个维度进行计算。代码如下:

class GCNConv(nn.Module):
    def __init__(self, in_features, out_features, bias=True):
        super(GCNConv, self).__init__()
        self.in_features = in_features
        self.out_features = out_features
        self.weight = Parameter(torch.FloatTensor(in_features, out_features))
        if bias:
            self.bias = Parameter(torch.FloatTensor(out_features))
        else:
            self.register_parameter('bias', None)
        self.reset_parameters()

    def reset_parameters(self):
        stdv = 1. / math.sqrt(self.weight.size(1))
        self.weight.data.uniform_(-stdv, stdv)
        if self.bias is not None:
            self.bias.data.uniform_(-stdv, stdv)

    def forward(self, x, adj):
        support = torch.matmul(x, self.weight)
        # 输入的数据是x = b s n d, adj = n * n
        output = torch.einsum("tn,bsnd->bstd", adj, support)   # bsnd
        if self.bias is not None:
            output + self.bias

        return output

具体来讲,首先将batch_size * seq_len * num_nodes * in_feats利用self.weight变成batch_size * seq_len * num_nodes * out_feats,然后再与归一化后的邻接矩阵相乘,这里用到了torch.einsum()函数来指定参与计算的维度。

接着,便可以基于GCNConv来定义用于多站点。多变量输入、多变量输出的时刻预测GCN模型:

class GCN(torch.nn.Module):
    def __init__(self, args):
        super(GCN, self).__init__()
        self.args = args
        self.conv1 = GCNConv(args.in_feats, args.h_feats)
        self.conv2 = GCNConv(args.h_feats, args.out_feats)
        self.dropout = 0.5
        self.fcs = nn.ModuleList()
        for _ in range(args.in_feats):
            self.fcs.append(
                nn.Sequential(
                    nn.Linear(args.seq_len * args.out_feats, args.out_feats),
                    nn.ReLU(),
                    nn.Linear(args.out_feats, args.pred_len)
                )
            )

    def forward(self, x, adj):
        # bsnd
        x = F.dropout(x, self.dropout, training=self.training)
        x = F.elu(self.conv1(x, adj))
        x = self.conv2(x, adj)
        # b s n d  --> b s n 3
        x = x.permute(0, 2, 1, 3)  # bnsd
        x = torch.flatten(x, start_dim=2)  # bn s*d
        pred = []
        for idx in range(self.args.in_feats):
            sub_pred = self.fcs[idx](x)   # b n pred_len
            pred.append(sub_pred)  # b pred_len 3

        pred = torch.stack(pred, dim=-1)  # b n pred_len 3
        # 变成和y一样的维度,即b pred_len num_node 3
        pred = pred.permute(0, 2, 1, 3)

        return pred

该模型由2个GCN层和一个预测层组成。输入batch_size * seq_len * num_nodes * in_feats(以下简称bsni)经过两层GCN变成bsno。接着,为了预测所有站点的多个变量,采用多任务学习中的思路,每个变量使用一个线性层进行预测。

预测时,首先将bsno的进行维度交换变成bnso,与LSTM等模型类似,可以将所有时刻的隐状态展开变成一个bn(s*d),然后使用多个线性层得到多个bn(pred_len),然后将多个变量的预测值拼接变成bn(pred_len)(in_feats)。最后,为了与真实值的batch_size * pred_len * num_nodes * in_feats相匹配,需要交换1和2两个维度。

需要注意的是,forward中传入的邻接矩阵是归一化后的邻接矩阵,归一化操作可以参考如下代码:

def normalize_adj(adj):
    """
    归一化邻接矩阵,适用于图卷积网络(GCN)。
    :param adj: 原始邻接矩阵,形状为 (N, N)
    :return: 归一化后的邻接矩阵,形状为 (N, N)
    """
    # 添加自环
    adj = adj + torch.eye(adj.size(0))
    # 计算度矩阵 D
    degree = adj.sum(1)
    # 计算 D 的逆平方根
    d_inv_sqrt = torch.pow(degree, -0.5)
    d_inv_sqrt[torch.isinf(d_inv_sqrt)] = 0  # 防止出现无穷大
    # 构建 D 的逆平方根矩阵
    d_mat_inv_sqrt = torch.diag(d_inv_sqrt)
    # 归一化邻接矩阵
    adj_normalized = d_mat_inv_sqrt @ adj @ d_mat_inv_sqrt

    return adj_normalized

3.2 GraphSAGE

GraphSAGE的本质是将一个节点的邻居节点聚合后再与自身进行拼接变换,单层代码实现如下:

class SAGEConv(nn.Module):
    def __init__(self, in_features, out_features):
        super(SAGEConv, self).__init__()
        self.in_features = in_features
        self.out_features = out_features
        self.proj = nn.Linear(in_features, out_features)
        self.out_proj = nn.Linear(2 * out_features, out_features)

    def forward(self, x, adj):
        # 假设有多个站点
        support = self.proj(x)
        # 输入的数据是x = b s n d, adj = n * n
        # 邻居平均 设定一个很小的正数eps
        eps = torch.tensor(1e-8)
        # 计算每一行的和,并确保不会除以零
        row_sums = adj.sum(dim=1, keepdim=True)
        row_sums = torch.max(row_sums, eps)
        # 对每一行进行规范化
        normalized_adj = adj / row_sums
        output = torch.einsum("tn,bsnd->bstd", normalized_adj, support)   # bsnd
        # cat
        cat_x = torch.cat((support, output), dim=-1)  # bsn 2d
        z = self.out_proj(cat_x)
        # norm
        z_norm = z.norm(p=2, dim=-1, keepdim=True)
        z_norm = torch.where(z_norm == 0, torch.tensor(1.).to(z_norm), z_norm)
        z = z / z_norm

        return z

上述代码采用的是平均聚合。与GCN类似,可以搭建GraphSAGE如下:

class GraphSAGE(torch.nn.Module):
    def __init__(self, args):
        super(GraphSAGE, self).__init__()
        self.args = args
        self.conv1 = SAGEConv(args.in_feats, args.h_feats)
        self.conv2 = SAGEConv(args.h_feats, args.out_feats)
        self.dropout = 0.5
        self.fcs = nn.ModuleList()
        for _ in range(args.in_feats):
            self.fcs.append(
                nn.Sequential(
                    nn.Linear(args.seq_len * args.out_feats, args.out_feats),
                    nn.ReLU(),
                    nn.Linear(args.out_feats, args.pred_len)
                )
            )

    def forward(self, x, adj):
        # bsnd
        # x = F.dropout(x, self.dropout, training=self.training)
        x = F.relu(self.conv1(x, adj))
        x = self.conv2(x, adj)
        # b s n d  --> b s n 3
        x = x.permute(0, 2, 1, 3)  # bnsd
        x = torch.flatten(x, start_dim=2)  # bn s*d
        pred = []
        for idx in range(self.args.in_feats):
            sub_pred = self.fcs[idx](x)   # b n pred_len
            pred.append(sub_pred)  # b pred_len 3

        pred = torch.stack(pred, dim=-1)  # b n pred_len 3
        pred = pred.permute(0, 2, 1, 3)

        return pred

3.3 GAT

GAT的代码稍显复杂,其本质是将节点的特征和邻居特征进行拼接然后变换得到这条边上的权重,最后再对邻居的特征进行加权。这里可以先参考一下GitHub上的GAT代码:

class GraphAttentionLayer(nn.Module):
    """
    Simple GAT layer, similar to https://arxiv.org/abs/1710.10903
    """
    def __init__(self, in_features, out_features, dropout, alpha, concat=True):
        super(GraphAttentionLayer, self).__init__()
        self.dropout = dropout
        self.in_features = in_features
        self.out_features = out_features
        self.alpha = alpha
        self.concat = concat

        self.W = nn.Parameter(torch.empty(size=(in_features, out_features)))
        nn.init.xavier_uniform_(self.W.data, gain=1.414)
        self.a = nn.Parameter(torch.empty(size=(2*out_features, 1)))
        nn.init.xavier_uniform_(self.a.data, gain=1.414)

        self.leakyrelu = nn.LeakyReLU(self.alpha)

    def forward(self, h, adj):
        Wh = torch.mm(h, self.W) # h.shape: (N, in_features), Wh.shape: (N, out_features)
        e = self._prepare_attentional_mechanism_input(Wh)

        zero_vec = -9e15*torch.ones_like(e)
        attention = torch.where(adj > 0, e, zero_vec)
        attention = F.softmax(attention, dim=1)
        attention = F.dropout(attention, self.dropout, training=self.training)
        h_prime = torch.matmul(attention, Wh)

        if self.concat:
            return F.elu(h_prime)
        else:
            return h_prime

    def _prepare_attentional_mechanism_input(self, Wh):
        # Wh.shape (N, out_feature)
        # self.a.shape (2 * out_feature, 1)
        # Wh1&2.shape (N, 1)
        # e.shape (N, N)
        Wh1 = torch.matmul(Wh, self.a[:self.out_features, :])
        Wh2 = torch.matmul(Wh, self.a[self.out_features:, :])
        # broadcast add
        e = Wh1 + Wh2.T
        return self.leakyrelu(e)

    def __repr__(self):
        return self.__class__.__name__ + ' (' + str(self.in_features) + ' -> ' + str(self.out_features) + ')'

上述代码使用了broadcast add技巧来得到每个节点与其他所有节点的权重,然后再使用adj来将不存在边的权重变成一个很小的负数。

基于上述思想,可以将本文的图注意力层定义如下:

class GraphAttentionLayer(nn.Module):
    def __init__(self, in_features, out_features, dropout, alpha, concat=True):
        super(GraphAttentionLayer, self).__init__()
        self.dropout = dropout
        self.in_features = in_features
        self.out_features = out_features
        self.alpha = alpha
        self.concat = concat

        self.W = nn.Parameter(torch.empty(size=(in_features, out_features)))
        nn.init.xavier_uniform_(self.W.data, gain=1.414)
        self.a = nn.Parameter(torch.empty(size=(2*out_features, 1)))
        nn.init.xavier_uniform_(self.a.data, gain=1.414)

        self.leakyrelu = nn.LeakyReLU(self.alpha)

    def forward(self, h, adj):
        # bsnd nn
        Wh = torch.matmul(h, self.W)
        e = self._prepare_attentional_mechanism_input(Wh)  # bsnn
        # 掩码操作
        mask = (adj == 0)
        # 广播掩码矩阵
        mask = mask.unsqueeze(0).unsqueeze(0)
        mask = mask.expand_as(e)
        # 应用掩码
        e[mask] = -9e15
        e = F.softmax(e, dim=1)
        e = F.dropout(e, self.dropout, training=self.training)
        h_prime = torch.einsum("bstn,bsnd->bstd", e, Wh)  # bsnd

        if self.concat:
            return F.elu(h_prime)
        else:
            return h_prime

    def _prepare_attentional_mechanism_input(self, Wh):
        # Wh.shape (bsz, seq_len, N, out_feature)
        # self.a.shape (2 * out_feature, 1)
        # Wh1&2.shape (N, 1)
        # e.shape (bsz, seq_len, N, N)
        Wh1 = torch.matmul(Wh, self.a[:self.out_features, :])
        Wh2 = torch.matmul(Wh, self.a[self.out_features:, :])
        # broadcast add
        # 只是最后两个维度相加
        e = Wh1 + Wh2.permute(0, 1, 3, 2)
        return self.leakyrelu(e)

    def __repr__(self):
        return self.__class__.__name__ + ' (' + str(self.in_features) + ' -> ' + str(self.out_features) + ')'

区别在于:

  1. 其一,执行broadcast add时候,只是后两个维度进行操作(e = Wh1 + Wh2.permute(0, 1, 3, 2)),即bsnd+bsdn。
  2. 得到attention矩阵大小为bsnn,而不是二维的nn。因此,同样需要进行广播来实现掩码操作。

最后,基于GATConv,可以搭建一个简易版本(不使用多头注意力机制)的GAT如下:

class GAT(torch.nn.Module):
    def __init__(self, args):
        super(GAT, self).__init__()
        self.args = args
        alpha = 0.2
        self.dropout = args.dropout
        self.conv1 = GraphAttentionLayer(
            args.in_feats,
            args.h_feats,
            dropout=self.dropout, alpha=alpha, concat=False
        )
        self.conv2 = GraphAttentionLayer(
            args.h_feats,
            args.out_feats,
            dropout=self.dropout, alpha=alpha, concat=False
        )
        self.fcs = nn.ModuleList()
        for _ in range(args.in_feats):
            self.fcs.append(
                nn.Sequential(
                    nn.Linear(args.seq_len * args.out_feats, args.out_feats),
                    nn.ReLU(),
                    nn.Linear(args.out_feats, args.pred_len)
                )
            )

    def forward(self, x, adj):
        # bsnd
        x = F.dropout(x, self.dropout, training=self.training)
        x = F.elu(self.conv1(x, adj))
        x = self.conv2(x, adj)
        # b s n d  --> b s n 3
        x = x.permute(0, 2, 1, 3)  # bnsd
        x = torch.flatten(x, start_dim=2)  # bn s*d
        pred = []
        for idx in range(self.args.in_feats):
            sub_pred = self.fcs[idx](x)   # b n pred_len
            pred.append(sub_pred)  # b pred_len 3

        pred = torch.stack(pred, dim=-1)  # b n pred_len 3
        # 变成和y一样的维度,即b pred_len num_node 3
        pred = pred.permute(0, 2, 1, 3)

        return pred

当然,也可以使用多头注意力机制:

class GAT(nn.Module):
    def __init__(self, args):
        super(GAT, self).__init__()
        self.args = args
        self.dropout = args.dropout
        alpha = 0.2
        self.attentions = nn.ModuleList()
        for _ in range(args.heads):
            layer = GraphAttentionLayer(args.in_feats, args.h_feats, dropout=self.dropout, alpha=alpha, concat=True)
            self.attentions.append(layer)

        self.out_att = GraphAttentionLayer(
            args.h_feats * args.heads,
            args.out_feats,
            dropout=self.dropout, alpha=alpha, concat=False
        )
        # fc
        self.fcs = nn.ModuleList()
        for _ in range(args.in_feats):
            self.fcs.append(
                nn.Sequential(
                    nn.Linear(args.seq_len * args.out_feats, args.out_feats),
                    nn.ReLU(),
                    nn.Linear(args.out_feats, args.pred_len)
                )
            )

    def forward(self, x, adj):
        x = F.dropout(x, self.dropout, training=self.training)
        x = torch.cat([att(x, adj) for att in self.attentions], dim=-1)
        x = F.dropout(x, self.dropout, training=self.training)
        x = self.out_att(x, adj)

        # b s n d  --> b s n 3
        x = x.permute(0, 2, 1, 3)  # bnsd
        x = torch.flatten(x, start_dim=2)  # bn s*d
        pred = []
        for idx in range(self.args.in_feats):
            sub_pred = self.fcs[idx](x)  # b n pred_len
            pred.append(sub_pred)  # b pred_len 3

        pred = torch.stack(pred, dim=-1)  # b n pred_len 3
        pred = pred.permute(0, 2, 1, 3)

        return pred

IV. 训练与测试

训练测试代码与之前差不太多,训练函数定义如下:

def train(args, Dtr, Val, adj, path, model_type):
    if model_type == "gcn":
        adj = normalize_adj(adj)
        model = GCN(args).to(device)
    elif model_type == "sage":
        model = GraphSAGE(args).to(device)
    elif model_type == "gat":
        model = GAT(args).to(device)
    else:
        raise ValueError("model_type has to be one of ('gcn', 'sage', 'gat')")

    adj = adj.to(device)
    loss_function = nn.MSELoss().to(device)
    optimizer = torch.optim.Adam(model.parameters(), lr=args.lr,
                                 weight_decay=args.weight_decay)
    scheduler = StepLR(optimizer, step_size=args.step_size, gamma=args.gamma)
    # training
    min_epochs = 2
    best_model = None
    min_val_loss = np.Inf
    for epoch in tqdm(range(args.epochs)):
        model.train()
        train_loss = []
        for (seq, label) in Dtr:
            optimizer.zero_grad()
            seq = seq.to(device)
            label = label.to(device)  # b pred_len num_node 3
            pred = model(seq, adj)  # b pred_len num_node 3
            # print(label.shape, pred.shape)
            loss = loss_function(pred, label)
            loss.backward()
            optimizer.step()
            train_loss.append(loss.item())

        scheduler.step()
        # validation
        val_loss = get_val_loss(args, model, Val, adj)
        if epoch + 1 >= min_epochs and val_loss < min_val_loss:
            min_val_loss = val_loss
            best_model = copy.deepcopy(model)
            state = {'model': best_model.state_dict()}
            torch.save(state, path + '/models/' + model_type + '.pkl')

        print('epoch {:03d} train_loss {:.8f} val_loss {:.8f}'.format(epoch, np.mean(train_loss), val_loss))

    state = {'model': best_model.state_dict()}
    torch.save(state, path + '/models/' + model_type + '.pkl')

测试代码:

@torch.no_grad()
def test(args, Dte, adj, path, model_type, scalers):
    if model_type == "gcn":
        adj = normalize_adj(adj)
        model = GCN(args).to(device)
    elif model_type == "sage":
        model = GraphSAGE(args).to(device)
    elif model_type == "gat":
        model = GAT(args).to(device)
    else:
        raise ValueError("model_type has to be one of ('gcn', 'sage', 'gat')")

    model.load_state_dict(torch.load(path + '/models/' + model_type + '.pkl')['model'])
    adj = adj.to(device)

    y, pred = [], []
    for seq, label in Dte:
        seq = seq.to(device)
        y.append(label)
        sub_pred = model(seq, adj)  # b pred_len num_node 3
        pred.append(sub_pred.cpu())

    #
    y = torch.concat(y, dim=0)
    y = y.view(-1, y.size(2), y.size(3)).numpy()
    pred = torch.concat(pred, dim=0)  # num num_node 3
    pred = pred.view(-1, pred.size(2), pred.size(3)).numpy()
    # flatten

    # scaler
    for idx in range(y.shape[1]):
        y[:, idx, :] = scalers[idx].inverse_transform(y[:, idx, :])
        pred[:, idx, :] = scalers[idx].inverse_transform(pred[:, idx, :])

    for idx in range(y.shape[1]):
        cur_y, cur_pred = y[:, idx, :], pred[:, idx, :]
        # 输出各种指标
        print('第{}个站点的指标为:'.format(idx + 1))
        maes, mses, rmses, mapes, r2s = get_metric(cur_y, cur_pred)
        print('mae:', maes)
        print('mse', mses)
        print('rmse:', rmses)
        print('mape:', mapes)
        print('r2:', r2s)
        # plot
        for i in range(cur_y.shape[1]):
            plt.plot(cur_y[:, i], label="第{}个站点的第{}个变量的真实值".format(idx + 1, i + 1))
            plt.plot(cur_pred[:, i], label="第{}个站点的第{}个变量的预测值".format(idx + 1, i + 1))
            plt.legend()
            plt.show()

测试反归一化时注意在站点内进行归一化。

V. 实验结果

以PEMS04为例,下图展示了一些预测结果:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2200995.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IO相关,标准输入输出及错误提示

一、IO简介 1.1 IO的过程 操作系统的概念&#xff1a;向下统筹控制硬件&#xff0c;向上为用户提供接口。 操作系统的组成 内核 外壳&#xff08;shell&#xff09; linux的五大功能&#xff1a;进程管理、内存管理、文件管理、设备管理、网络管理。 最早接触的IO&#xf…

01背包,CF 1974E - Money Buys Happiness

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 1974E - Money Buys Happiness 二、解题报告 1、思路分析 问我们能够到达…

docker简述

1.安装dockers&#xff0c;配置docker软件仓库 安装&#xff0c;可能需要开代理&#xff0c;这里我提前使用了下好的包安装 启动docker systemctl enable --now docker查看是否安装成功 2.简单命令 拉取镜像&#xff0c;也可以提前下载使用以下命令上传 docker load -i imag…

深度学习笔记(持续更新)

注&#xff1a;本文所有深度学习内容都是基于PyTorch&#xff0c;PyTorch作为一个开源的深度学习框架&#xff0c;具有可以动态计算图、拥有简洁易用的API、支持GPU加速等特点&#xff0c;在计算机视觉、自然语言处理、强化学习等方面有广泛应用。 使用matplotlib绘图&#xff…

Linux 常用命令详解,线上问题排查必备

comm 比较文件行 comm 是 Linux 系统下的用于比较两个已排序文件的命令行工具。主要用于找出文件之间的差异或相同之处&#xff0c;例如两个文件中相同的行、仅在第一个文件中的行以及仅在第二个文件中的行。 基本语法 comm [OPTION] FILE1 FILE2可选参数OPTION如下&#xf…

图像分类-demo(Lenet),tensorflow和Alexnet

目录 demo(Lenet) 代码实现基本步骤&#xff1a; TensorFlow 一、核心概念 二、主要特点 三、简单实现 参数: 模型编译 模型训练 模型评估 Alexnet model.py train.py predict.py demo(Lenet) PyTorch提供了一个名为“torchvision”的附加库&#xff0c;其中包含…

芯课堂 | FatFs文件系统的移植及应用指南

1、FatFs文件系统简介 FatFs是用于小型嵌入式系统的通用FAT/exFAT文件系统模块。FatFs模块是按照ANSI C&#xff08;C89&#xff09;编写的&#xff0c;与磁盘控制层完全分离。因此&#xff0c;它独立于平台和存储设备&#xff0c;具有良好的硬件平台独立性。它可以集成到资源有…

这个问题做项目的时给某些客户普及过,这里再给你普及一下

有些因素不是地理概念&#xff0c;没错&#xff01;但与地理有关&#xff01;可以通过地理位置将他们链接起来&#xff0c;再结合其它业务数据&#xff0c;完成数据分析&#xff01;例如百度地图会将&#xff1a;餐饮、文化、交通、住宿、甚至价格、天气与位置关联分析&#xf…

S7---基本介绍

目录 高通S7和S7 Pro Gen 1声音平台 音频性能的新层次 高通XPAN技术 卓越的听力增强 高通第四代ANC 特征 QualcommS7 Pro Gen 1附加功能 QualcommS7 Pro Gen 1框图 高通S7和S7 Pro Gen 1声音平台 声音被重新想象。QualcommS7声音平台旨在开启一个新的高级音频性能级别。…

Unity转Unreal5之从入门到精通 Spline(样条曲线)组件的使用

前言 Spline 组件 能编辑 样条曲线,定义一条路径,路径上的点可以通过距离起点的长度获取,因此可以实现 物体沿路径连续移动 的效果或者 物体沿路径分布 的效果。 今天我们就来实现一个简单的Spline样条曲线的Demo 实现一个沿路径运动的功能 1.新建一个基于 Actor 的蓝图…

JavaSE——集合1:Collection接口(Iterator和增强for遍历集合)

目录 一、集合框架体系(重要) 二、集合引入 (一)集合的理解与好处 三、Collection接口 (一)Collection接口实现类的特点 (二)Collection接口常用方法 (三)Collection接口遍历元素的方式(Iterator和增强for) 1.使用Iterator(迭代器) 1.1Iterator(迭代器)介绍 1.2Itera…

使用cv::FileStorage对yaml文件进行读写

问题描述&#xff1a;记录使用cv::FileStorage对yaml文件进行读写 参考官网&#xff1a;OpenCV: cv::FileStorage Class Reference WRITE&#xff1a;根据文件路径写文件&#xff0c;如果文件不存在会新建&#xff0c;文件存在则变空白 FileStorage fs(filepath, FileStorag…

新增数据集 SDK、“关系抽取”文本标注、优化模型监控和管理|ModelWhale 版本更新

ModelWhale 带来了新一轮的版本更新&#xff0c;期待为大家带来更优质的使用体验。 本次更新中&#xff0c;ModelWhale 主要进行了以下功能迭代&#xff1a; 数据管理&#xff1a;新增 mw_python_sdk 支持通过查看、下载、制作、更新数据集 文本标注&#xff1a;新增“关系抽取…

【DFDT】DFDT: An End-to-End DeepFake Detection Framework Using Vision Transformer

文章目录 DFDT: An End-to-End DeepFake Detection Framework Using Vision Transformerkey points贡献方法补丁提取和嵌入基于注意力的补丁选择多流transformer块多尺度分类器实验DFDT: An End-to-End DeepFake Detection Framework Using Vision Transformer 会议/期刊:App…

Apache Linkis + OceanBase:如何提升数据分析效率

计算中间件 Apache Linkis 构建了一个计算中间件层&#xff0c;以实现上层应用程序和底层数据引擎之间的连接、治理和编排。目前&#xff0c;已经支持通过数据源的功能&#xff0c;实现用户通过Linkis 对接并使用 OceanBase数据库。 本文详细阐述了在 Apache Linkis v1.3.2中&a…

【虚拟化】内核级虚拟化技术KVM介绍,全/半虚拟化的区别,使用libvirt搭建虚拟化平台(go/java/c++)

【虚拟化】内核级虚拟化技术KVM介绍&#xff0c;全/半虚拟化的区别&#xff0c;使用libvirt搭建虚拟化平台&#xff08;go/java/c&#xff09; 文章目录 1、虚拟化技术分类与架构&#xff08;KVM&#xff0c;Xen&#xff09;&#xff0c;全/半虚拟化的区别2、libvirt介绍3、使用…

【北京迅为】《STM32MP157开发板嵌入式开发指南》-第二十四章 安装 Samba

iTOP-STM32MP157开发板采用ST推出的双核cortex-A7单核cortex-M4异构处理器&#xff0c;既可用Linux、又可以用于STM32单片机开发。开发板采用核心板底板结构&#xff0c;主频650M、1G内存、8G存储&#xff0c;核心板采用工业级板对板连接器&#xff0c;高可靠&#xff0c;牢固耐…

举个栗子!Tableau 技巧(283):用山丘图呈现项目周期

人们常常用爬山来比喻工作中做项目的过程&#xff1a;明确目标、规划路线、团队合作、应对挑战&#xff0c;然后享受登顶并在下山后总结经验教训。 图片来自网络 在 Tableau 中做项目分析时&#xff0c;将一段时期的项目用山丘图来呈现&#xff0c;山丘大小代表项目周期的时间…

RAG(Retrieval-Augmented Generation,检索增强生成)

简介&#xff1a;个人学习分享&#xff0c;如有错误&#xff0c;欢迎批评指正。 RAG&#xff08;Retrieval-Augmented Generation&#xff09;是一种结合信息检索与生成式模型的混合架构&#xff0c;旨在提升自然语言生成任务的准确性、丰富性和知识覆盖范围。它通过在生成过程…

sqli-labs less-20 less-21 less-22 cookie注入

COOKIE 作用&#xff1a;是由网络服务器存储在你电脑硬盘上的一个txt类型的小文件&#xff0c;它和你的网络行为有关&#xff0c;记录了当前用户的状态 形式&#xff1a;keyvalue 例如&#xff1a;当我们登录某个账号后&#xff0c;服务器会在cookies进行记录 个人理解&#xf…