四时宝库

程序员的知识宝库

GCN入门代码实战(gcode g代码详解)

GCN入门代码实战

本文是《深入浅出图神经网络——GNN原理解析》第5.6节的代码,使用GCN实现对节点的分类

代码地址:

https://github.com/FighterLYL/GraphNeuralNetwork/tree/master/chapter5

前言

最近多模态领域进展神速,CV和NLP领域融合的趋势越来越明显,例如何凯明大神的MAE,以及亚研院和北大联合发布的女娲模型,效果都非常惊艳。看着领域内进展神速,自己却一直沉落在平庸的业务做着增删改查,默默留下羡慕的口水。

(我总认为,一个人的价值,就在于Ta能何种程度的改变世界、改善生活,而不是在于什么职级、期权、总包、title。)

深度学习,从数据形式的角度讲,可以分为NLP,CV,其实还有一种数据结构,那就是图结构,不是图像的图,而是图形的图。比如社交网络、知识图谱等。

所以这里计划从头开始写一篇图嵌入(graph embedding)的专栏,从Trans系列讲起。时间精力缘故,一直没有动笔,今天这篇,从一段入门代码开始吧。

数据集介绍

该代码使用的是Cora数据集,包括2708篇论文,论文间引用关系5429条。论文即节点,引用关系即边。节点分为7类。每篇论文的特征是通过词袋模型得到的,维度为1433,每一个维度表示一个词,1表示该词在这篇文章中出现过,0表示未出现。

    @property
    def data(self):
        """返回Data数据对象,包括x, y, adjacency, train_mask, val_mask, test_mask"""
        return self._data

可以看到,数据集的核心内容,包括x,y,adjacent,train_mask,val_mask和test_mask。

x的维度是2708*1433,即所有2708个节点,每个节点维度是1433。

y是这2708个节点的类别,标签。

adjacent是2708*2708的稀疏矩阵,根据论文之间的引用关系,描述了一个引用图。从实际意义角度讲,互相引用的论文,一般是相同领域的继承研究,或者是对比研究。这个数据内容,是图网络的核心体现。

train_mask,val_mask,test_mask,代码中的默认数量是140,500,1000,分别表示了训练集、验证集、测试集的数量。这个数量划分也挺有意思的。

核心代码

  1. GCN层的定义
class GraphConvolution(nn.Module):
    def __init__(self, input_dim, output_dim, use_bias=True):
        """图卷积:L*X*\theta

        Args:
        ----------
            input_dim: int
                节点输入特征的维度
            output_dim: int
                输出特征维度
            use_bias : bool, optional
                是否使用偏置
        """
        super(GraphConvolution, self).__init__()
        self.input_dim = input_dim
        self.output_dim = output_dim
        self.use_bias = use_bias
        self.weight = nn.Parameter(torch.Tensor(input_dim, output_dim))
        if self.use_bias:
            self.bias = nn.Parameter(torch.Tensor(output_dim))
        else:
            self.register_parameter('bias', None)
        self.reset_parameters()

    def reset_parameters(self):
        init.kaiming_uniform_(self.weight)
        if self.use_bias:
            init.zeros_(self.bias)

    def forward(self, adjacency, input_feature):
        """邻接矩阵是稀疏矩阵,因此在计算时使用稀疏矩阵乘法
    
        Args: 
        -------
            adjacency: torch.sparse.FloatTensor
                邻接矩阵
            input_feature: torch.Tensor
                输入特征
        """
        support = torch.mm(input_feature, self.weight)
        output = torch.sparse.mm(adjacency, support)
        if self.use_bias:
            output += self.bias
        return output

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

这份代码中,亮点还是挺多的。其中最核心,最体现GCN计算过程的,就是第41行。在这里,上一层的结果,和邻接矩阵相乘,之后作为输出。torch.sparse.mm是torch的稀疏矩阵相乘函数。

  1. 模型层的定义
class GcnNet(nn.Module):
    """
    定义一个包含两层GraphConvolution的模型
    """
    def __init__(self, input_dim=1433):
        super(GcnNet, self).__init__()
        self.gcn1 = GraphConvolution(input_dim, 16)
        self.gcn2 = GraphConvolution(16, 7)
    
    def forward(self, adjacency, feature):
        h = F.relu(self.gcn1(adjacency, feature))
        logits = self.gcn2(adjacency, h)
        return logits

包含了两层图卷积层,简洁清晰,没有什么可说的。

实验和结果

  1. 默认参数

可以看到,大约50轮以后,模型就收敛到比较稳定的状态了,acc大约是0.8。

上图是TSNE降维图,可以看到一些不合群的点,可能是错误分类的点。如果这些点录入到neo4j中,结合图数据库可视化,可能会有一些有趣的点(也可能页面炸了)。

  1. 一些尝试

因为acc=80%,所以尝试了一些感兴趣的超参,看看是否会有提升,以及为什么。

实验组

1

2

3

4

5

6

7

8

test_acc

train=140,

val=500,

test=1000

Lr=0.1

train=140,

val=500,

test=1000

Lr = 0.01

train=140,

val=500,

test=1000

Lr = 0.001

Random

train=140,

val=500,

test=1000

Lr = 0.01

train=1000,

val=500,

test=1000

Lr = 0.01

Random,

train=1000

val=500,

test=1000

Lr = 0.01

Random,

train=1000

val=500,

test=1000

Lr = 0.01

gcn1_output=32

Random,

train=1000

val=500,

test=1000

Lr = 0.01

gcn1_output=64

use_bias=True

80.8%

81.8%

79.5%

84.1%

87.3%

87.8%

87.99%

87.5%

use_bias=False

80.8%

80.5%

78.5%

83.9%

87.3%

87.8%

87.8%

87.6%

首先测试了Learning rate。当调低学习率时,收敛需要的训练也更多。前三组实验可以看出,Lr = 0.01时,学习效果较好,后面的实验也都使用了该参数。

然后尝试调整数据集。因为原始代码中,使用了前140个作为训练集,中间500个作为验证集,后1000个作为测试集,大体比例约为1:3:6。所以先简单做了一个随机抽样,按照数据编号模10的余数,伪随机的抽取,作为数据集。实验4中看出,模型效果有较大的提高。

实验5、6继续尝试调整数据集。实验5,取前1000条数据作为训练集,中间500条作为验证集,后1000条作为测试集。实验6,按照2:1:2的比例,伪随机的抽取,作为训练集、验证集、测试集。相比其它组的实验,这两组的效果有较大提高。其中伪随机的抽取,相比分段截取,仍有一定的提高。

实验7、8,尝试调整GCN层的参数,就是第一层的输出、第二层的输入的维度。原维度是16,现分别尝试32、64。其他参数与实验6相同。可以看出维度为32的时候,略有提高,维度为64的时候,反而有些下降。可能是过拟合了(这个结论需要更仔细的论证)。

最后对照了是否使用bias,整体上使用bias会有更好的表现。

后续

这是一个非常好的入门示例,代码中的细节是非常多的。我最关心的是,如果数据量大的话,效果、性能如何。其实如果动手能力强的话,可以爬取更多的论文,及其之间的引用关系,然后整理成数据集,进行实验。时间关系,这里就算挖一个坑,有机会再填。

另外,这个GCN的效果,相对于Trans系列,相对于游走系列的效果如何,更适合什么样的数据,也值得做一下对照实验。

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接