资源经验分享pytorch如何搭建GCN有关的网络

pytorch如何搭建GCN有关的网络

2020-01-03 | |  93 |   0

原标题:pytorch如何搭建GCN有关的网络

原文来自:CSDN      原文链接:https://blog.csdn.net/skj1995/article/details/103780873


前言:本人研究领域为交通方面,做科研需要搭建GCN有关的网络,比如GCN-GAN【1】,基于GCN的权值完成网络【2】,以及基于这些网络的新的GCN网络框架。但是搜索了一些网上使用pytorch搭建GCN网络的资料,只有github上面的无解释代码和最近几年发表的论文,有详细讲解的资料很少,这对于快速入门GCN实战,会有很大的门槛,鉴于此,经过几天的探索实战,我将自己的关于使用pytorch搭建GCN类型网络的经验分享在这里,对论文和代码如何对应做一个解释说明,包括卷积、池化之后特征矩阵、邻接矩阵等维度的变化,对常见的关于GCN和pytorch.geometric的资料进行总结说明,并且拿出一个框架代码进行详细注释,为使用GCN实战入门提供一点帮助,也方便自己日后查阅。

一. 现有资料说明

1.github网站https://github.com/rusty1s/pytorch_geometric上面是最近几年发表的论文源码pytorch的实现:

03.png

 

目录examples下面是常见的论文里面提到的数据集的实验,包括根据酶结构进行图的分类,auto-encoder,诸如此类

后面我会以里面的enzymes_topk_pool.py(使用topk pool池化方式根据酶的结构进行分类,是论文Graph U-Nets提到的一种池化方式)代码为例进行详细说明

examples下面所有py代码的对应实验我也没有完全对应起来,但是都是github下面的论文里面提到的实验,在用到的时候可以自己找一找

2. pytorch下面有一个geometric包专门用来搭建GCN网络,中文文档地址为:

https://pytorch-geometric.readthedocs.io/en/latest/index.html

有些上面提到的github中论文的源码会直接转跳到这个网站,但是这个文档的缺点是对于一些参数的说明比较难懂,很多一笔带过,不知道输出是什么,如何使用,这就需要我们将这个文档的源码和对应的论文结合起来看

这个网站中的一些方法下面都列出了对应的论文和源码

二. 代码详细说明

下面就以github里面examples下面的enzymes_topk_pool.py代码为例,结合论文和源码进行参数设置、维度变化等的说明

(1)首先是用到的包和数据加载部分,加载的数据是TUDataset里面的酶的结构数据,其中的batch_size=80表示每个批次有80张图,因为这是一个分类问题,所以对应的y值是每张图的类别值

import os.path as osp
 
import torch
import torch.nn.functional as F
from torch_geometric.datasets import TUDataset
from torch_geometric.data import DataLoader
from torch_geometric.nn import GraphConv, TopKPooling
from torch_geometric.nn import global_mean_pool as gap, global_max_pool as gmp
 
path = osp.join(osp.dirname(osp.realpath('__file__')), '..', 'data', 'ENZYMES')
dataset = TUDataset(path, name='ENZYMES')
dataset = dataset.shuffle()
#print("dataset.num_classes=",dataset.num_classes)
n = len(dataset) // 10
test_dataset = dataset[:n]#这个表示拿出十分之一用来测试
train_dataset = dataset[n:]#后面的十分之九用来训练
test_loader = DataLoader(test_dataset, batch_size=80)#表示一个批次
train_loader = DataLoader(train_dataset, batch_size=80)

(2)DataLoader函数自动将数据分成了10份,返回的是一个迭代器,下面的代码将train_loader中的内容打印出来看看,并进行解释:

for data in train_loader:
    
    print("data=",data)
    print("data.batch=",data.batch)
    print("data.batch.shape=",data.batch.shape)
    print("data.x.shape=",data.x.shape)
    print("data.num_features=",data.num_features)
    print("n")

这个循环的前两次循环的结果如下:

data= Batch(batch=[2468], edge_index=[2, 9540], x=[2468, 3], y=[80])
data.batch= tensor([ 0,  0,  0,  ..., 79, 79, 79])
data.batch.shape= torch.Size([2468])
data.x.shape= torch.Size([2468, 3])
data.num_features= 3
 
 
data= Batch(batch=[2588], edge_index=[2, 9854], x=[2588, 3], y=[80])
data.batch= tensor([ 0,  0,  0,  ..., 79, 79, 79])
data.batch.shape= torch.Size([2588])
data.x.shape= torch.Size([2588, 3])
data.num_features= 3

可以看出data数据里面包含至少四部分,分别为batch,维度为1*2468维,表示这个batch中的80张图一共有2468个节点,batch这个变量里面的值表示该节点对应的是80张图里面的哪张图,从后面对batch值的打印也可以看到它的值为0-79共80个值

edge_index里面存储的是邻接矩阵信息,具体如何构建的请参考链接【3】

x是输入的特征矩阵,维度为2468*3,表示一共2468个节点,每个节点共3个特征

y的维度为1*80,就表示这一batch中80张图每张图对应的分类类别

(3)最后就是整个网络的构建和训练的代码:

class Net(torch.nn.Module):
    def __init__(self):
        super(Net, self).__init__()
 
        self.conv1 = GraphConv(dataset.num_features, 128)#num_features表示节点的特征数,为3
        self.pool1 = TopKPooling(128, ratio=0.8)
        self.conv2 = GraphConv(128, 128)
        self.pool2 = TopKPooling(128, ratio=0.8)
        self.conv3 = GraphConv(128, 128)
        self.pool3 = TopKPooling(128, ratio=0.8)
 
        self.lin1 = torch.nn.Linear(256, 128)
        self.lin2 = torch.nn.Linear(128, 64)
        self.lin3 = torch.nn.Linear(64, dataset.num_classes)
 
    def forward(self, data):
        x, edge_index, batch = data.x, data.edge_index, data.batch
        #print("data.batch=",data.batch)
        print("raw_x.shape=",x.shape)
        x = F.relu(self.conv1(x, edge_index))
        print("conv1_x.shape=",x.shape)
        x, edge_index, _, batch, _, _ = self.pool1(x, edge_index, None, batch)
        print("x.shape=",x.shape)
        
        x_gmp=gmp(x, batch)
        x_gap=gap(x, batch)
        print("x_gmp.shape=",x_gmp.shape)
        print("x_gap.shape=",x_gap.shape)
        x1 = torch.cat([x_gmp, x_gap], dim=1)#表示在列上合并,增加更多的列
        print("x1.shape=",x1.shape)
 
        x = F.relu(self.conv2(x, edge_index))
        x, edge_index, _, batch, _, _ = self.pool2(x, edge_index, None, batch)
        x2 = torch.cat([gmp(x, batch), gap(x, batch)], dim=1)
        print("x2.shape=",x2.shape)
 
        x = F.relu(self.conv3(x, edge_index))
        x, edge_index, _, batch, _, _ = self.pool3(x, edge_index, None, batch)
        x3 = torch.cat([gmp(x, batch), gap(x, batch)], dim=1)
        print("x3.shape=",x3.shape)
 
        x = x1 + x2 + x3
        print("x+.shape=",x.shape)
 
        x = F.relu(self.lin1(x))
        x = F.dropout(x, p=0.5, training=self.training)
        x = F.relu(self.lin2(x))
        x = F.log_softmax(self.lin3(x), dim=-1)
        
        print("final_x.shape",x.shape)
        return x
 
 
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = Net().to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=0.0005)
 
 
def train(epoch):
    model.train()
 
    loss_all = 0
    for data in train_loader:
        print("raw_data.y.shape",data.y.shape)
        data = data.to(device)
        optimizer.zero_grad()
        output = model(data)
        print("data.y.shape=",data.y.shape)
        loss = F.nll_loss(output, data.y)
        loss.backward()
        loss_all += data.num_graphs * loss.item()
        optimizer.step()
        print("n")
    return loss_all / len(train_dataset)
 
 
def test(loader):
    model.eval()
 
    correct = 0
    for data in loader:
        data = data.to(device)
        pred = model(data).max(dim=1)[1]
        correct += pred.eq(data.y).sum().item()
        print("n")
    return correct / len(loader.dataset)
 
 
for epoch in range(1, 2):
    loss = train(epoch)
    train_acc = test(train_loader)
    test_acc = test(test_loader)
    print('Epoch: {:03d}, Loss: {:.5f}, Train Acc: {:.5f}, Test Acc: {:.5f}'.
          format(epoch, loss, train_acc, test_acc))
    print("n")

部分打印结果如下:

raw_data.y.shape torch.Size([80])
raw_x.shape= torch.Size([2468, 3])
conv1_x.shape= torch.Size([2468, 128])
x.shape= torch.Size([2004, 128])
x_gmp.shape= torch.Size([80, 128])
x_gap.shape= torch.Size([80, 128])
x1.shape= torch.Size([80, 256])
x2.shape= torch.Size([80, 256])
x3.shape= torch.Size([80, 256])
x+.shape= torch.Size([80, 256])
final_x.shape torch.Size([80, 6])
data.y.shape= torch.Size([80])

根据打印结果对网络结构进行分析:

先根据class Net画出整个变量的流向图:

04.png

图中数字都表示维度

可以看出主要用到GCN网络,Topk pool池化方式,global mean/max pool

GCN网络使用的是论文【4】中提到的方法,根据当前l层得到l+1层主要的公式如下:

05.png

前面的D和A是度矩阵和邻接矩阵跟单元矩阵I处理后的矩阵,整个H(l)的前半段可以看成是邻接矩阵A,这样的话需要学习的只有W这个参数矩阵。H(l)在输入层可以看成是特征矩阵X,这样上面的表达式就成了H(l+1)=AXW,假设输入的节点数为10,每个节点有3个特征,A的维度就为10*10,X为10*3,假设GCN的参数为(3,128),那么W的维度为3*128,最终经过一层GCN之后特征矩阵H的维度变成10*128。因此,GCN的两个重要参数in_channel 和out_channel其实表示的是W的维度,或者说in_channel是特征数,out_channel是输出的每个节点的特征数。

因此,上图中输入的特征矩阵X(2468*3)经过一层GCN(3*128),输出的维度为2468*128,后面经过GCN的输出维度也是按照这样的方式计算。

接着是Topk pool,这是【5】中实现的池化方式。

思想主要是下面这张图:

06.png

采用投影的方式,利用了一个可学习的投影向量p,输入的特征矩阵Xl的维度为num_node*num_feature,p的维度为num_feature*1,先是蓝色的x和黄色的p相乘,然后除以||p||,得到维度为num_node*1的y,在y中取前k个在p的投影方向上值最大的node对应的索引,得到对应的特征,然后和得到的投影值点乘,得到处理后的紫色的特征矩阵。下面就是根据索引得到新的邻接矩阵。

Topk pool(128,0.8)中的参数128的意思是输入的每个节点的特征数,0.8表示保留80%的节点

所以2468*128经过Topk pool(128,0.8)之后保留本batch中80%的2004个节点,每个节点的特征数仍然为128个,输出的维度为2004*128,可能有人会问2468*0.8为什么不是严格等于2004,这是因为这个pool是在本batch共80张图上做的,每个图的节点数乘以0.8都会取结果的上限整数,所以比2468*0.8=1974多几个节点。

最后是gmp/gap:global max pool/global mean pool,这两个操作是在batch层次上进行的操作,输出的维度为80*128,80表示这个批次中有80张图,128表示每个节点的特征数

以global mean pool为例,在某一张输入特征为Xn上面的操作是这样的:

07.png

就是说在每个特征维度上,取所有节点在这个特征维度上面的最大值,如果输入的维度为num_node*num_feature

那么这张图操作之后为1*num_feature

那么为什么除了进行Topk pool之外,这里还要进行global pool呢,我觉得还是跟CNN中池化的作用一样,增加稀疏连接,加入正则化,防止模型过拟合。如果没有这个global pool的操作的话功能也是可以实现的,但是模型可能容易过拟合导致性能不好,就是说这里的global pool并不是必须的。其他的功能我还没有看出来,欢迎有更进一步理解的大佬指教。

 

码字不易,如果有帮助到您,麻烦点个赞或者打赏一点,多少不限,谢谢!

 

三. 参考资料:

【1】GCN-GAN: A Non-linear Temporal Link Prediction Model for Weighted Dynamic Networks

【2】Stochastic Weight Completion for Road Networks using Graph Convolutional Networks

【3】pytorch.geometric中数据格式的构建:(后面补充)

【4】SEMI-SUPERVISED CLASSIFICATION WITH GRAPH CONVOLUTIONAL NETWORKS

【5】Graph U-Nets

免责声明:本文来自互联网新闻客户端自媒体,不代表本网的观点和立场。

合作及投稿邮箱:E-mail:editor@tusaishared.com

上一篇:自动驾驶(六十一)---------蒙特卡洛搜索树

下一篇:CUDA 动态并行

用户评价
全部评价

热门资源

  • Python 爬虫(二)...

    所谓爬虫就是模拟客户端发送网络请求,获取网络响...

  • TensorFlow从1到2...

    原文第四篇中,我们介绍了官方的入门案例MNIST,功...

  • TensorFlow从1到2...

    “回归”这个词,既是Regression算法的名称,也代表...

  • 机器学习中的熵、...

    熵 (entropy) 这一词最初来源于热力学。1948年,克...

  • TensorFlow2.0(10...

    前面的博客中我们说过,在加载数据和预处理数据时...