PyG搭建GCN实现节点分类(GCNConv参数详解)

目录

  • 前言
  • 模型搭建
  • 1. 前向传播
  • 2. 反向传播
  • 3. 训练
  • 4. 测试
  • 完整代码
  • 前言

    在上一篇文章PyG搭建GCN前的准备:了解PyG中的数据格式中大致了解了PyG中的数据格式,这篇文章主要是简单搭建GCN来实现节点分类,主要目的是了解PyG中GCN的参数情况。

    模型搭建

    首先导入包:

    from torch_geometric.nn import GCNConv
    

    模型参数:

    1. in_channels:输入通道,比如节点分类中表示每个节点的特征数。
    2. out_channels:输出通道,最后一层GCNConv的输出通道为节点类别数(节点分类)。
    3. improved:如果为True表示自环增加,也就是原始邻接矩阵加上2I而不是I,默认为False。
    4. cached:如果为True,GCNConv在第一次对邻接矩阵进行归一化时会进行缓存,以后将不再重复计算。
    5. add_self_loops:如果为False不再强制添加自环,默认为True。
    6. normalize:默认为True,表示对邻接矩阵进行归一化。
    7. bias:默认添加偏置。

    于是模型搭建如下:

    class GCN(torch.nn.Module):
        def __init__(self, num_node_features, num_classes):
            super(GCN, self).__init__()
            self.conv1 = GCNConv(num_node_features, 16)
            self.conv2 = GCNConv(16, num_classes)
    
        def forward(self, data):
            x, edge_index = data.x, data.edge_index
            x = self.conv1(x, edge_index)
            x = F.relu(x)
            x = F.dropout(x, training=self.training)
            x = self.conv2(x, edge_index)
            x = F.relu(x)
            x = F.dropout(x, training=self.training)
            x = F.softmax(x, dim=1)
    
            return x
    

    输出一下模型:

    data = Planetoid(root='/data/CiteSeer', name='CiteSeer')
    model = GCN(data.num_node_features, data.num_classes).to(device)
    print(model)
    

    输出为:

    GCN(
      (conv1): GCNConv(3703, 16)
      (conv2): GCNConv(16, 6)
    )
    

    1. 前向传播

    查看官方文档中GCNConv的输入输出要求:

    可以发现,GCNConv中需要输入的是节点特征矩阵x和邻接关系edge_index,还有一个可选项edge_weight。因此我们首先:

    x, edge_index = data.x, data.edge_index
    x = self.conv1(x, edge_index)
    x = F.relu(x)
    x = F.dropout(x, training=self.training)
    

    此时我们不妨输出一下x及其size:

    tensor([[0.0000, 0.1630, 0.0000,  ..., 0.0000, 0.0488, 0.0000],
            [0.0000, 0.2451, 0.1614,  ..., 0.0000, 0.0125, 0.0000],
            [0.1175, 0.0262, 0.2141,  ..., 0.2592, 0.0000, 0.0000],
            ...,
            [0.0000, 0.0000, 0.0000,  ..., 0.0000, 0.1825, 0.0000],
            [0.0000, 0.1024, 0.0000,  ..., 0.0498, 0.0000, 0.0000],
            [0.0000, 0.3263, 0.0000,  ..., 0.0000, 0.0000, 0.0000]],
           device='cuda:0', grad_fn=<FusedDropoutBackward0>)
    
    torch.Size([3327, 16])
    

    此时的x一共3327行,每一行表示一个节点经过第一层卷积更新后的状态向量。

    那么同理,由于:

    self.conv2 = GCNConv(16, num_classes)
    

    所以经过第二层卷积后:

    x = self.conv2(x, edge_index)
    x = F.relu(x)
    x = F.dropout(x, training=self.training)
    

    此时得到的x的size应该为:

    torch.Size([3327, 6])
    

    即每个节点的维度为6的状态向量。

    由于我们需要进行6分类,所以最后需要加上一个softmax:

    x = F.softmax(x, dim=1)
    

    dim=1表示对每一行进行运算,最终每一行之和加起来为1,也就表示了该节点为每一类的概率。输出此时的x:

    tensor([[0.1607, 0.1727, 0.1607, 0.1607, 0.1607, 0.1846],
            [0.1654, 0.1654, 0.1654, 0.1654, 0.1654, 0.1731],
            [0.1778, 0.1622, 0.1733, 0.1622, 0.1622, 0.1622],
            ...,
            [0.1659, 0.1659, 0.1659, 0.1704, 0.1659, 0.1659],
            [0.1667, 0.1667, 0.1667, 0.1667, 0.1667, 0.1667],
            [0.1641, 0.1641, 0.1658, 0.1766, 0.1653, 0.1641]], device='cuda:0',
           grad_fn=<SoftmaxBackward0>)
    

    2. 反向传播

    在训练时,我们首先利用前向传播计算出输出:

    out = model(data)
    

    out即为最终得到的每个节点的6个概率值,但在实际训练中,我们只需要计算出训练集的损失,所以损失函数这样写:

    loss = loss_function(out[data.train_mask], data.y[data.train_mask])
    

    然后计算梯度,反向更新!

    3. 训练

    训练的完整代码:

    def train():
        optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4)
        loss_function = torch.nn.CrossEntropyLoss().to(device)
        model.train()
        for epoch in range(500):
            out = model(data)
            optimizer.zero_grad()
            loss = loss_function(out[data.train_mask], data.y[data.train_mask])
            loss.backward()
            optimizer.step()
    
            print('Epoch {:03d} loss {:.4f}'.format(epoch, loss.item()))
    

    4. 测试

    我们首先需要算出模型对所有节点的预测值:

    model(data)
    

    此时得到的是每个节点的6个概率值,我们需要在每一行上取其最大值:

    model(data).max(dim=1)
    

    输出一下:

    torch.return_types.max(
    values=tensor([0.9100, 0.9071, 0.9786,  ..., 0.4321, 0.4009, 0.8779], device='cuda:0',
           grad_fn=<MaxBackward0>),
    indices=tensor([3, 1, 5,  ..., 3, 1, 5], device='cuda:0'))
    

    返回的第一项是每一行的最大值,第二项为最大值在这一行中的索引,我们只需要取第二项,那么最终的预测值应该写为:

    _, pred = model(data).max(dim=1)
    

    然后计算预测精度:

    correct = int(pred[data.test_mask].eq(data.y[data.test_mask]).sum().item())
    acc = correct / int(data.test_mask.sum())
    print('GCN Accuracy: {:.4f}'.format(acc))
    

    完整代码

    完整代码中实现了论文中提到的四种数据集,代码地址:PyG-GCN。原创不易,下载时请给个follow和star!感谢!!

    来源:Cyril_KI

    物联沃分享整理
    物联沃-IOTWORD物联网 » PyG搭建GCN实现节点分类(GCNConv参数详解)

    发表评论