4,762 次浏览

图卷积的pytorch实现

很羡慕嫉妒周围大佬的学习速度,对于图神经网络的学习到应用到发表论文速度之快,表示跟不上。

这段时间忙着弄老师的不平衡数据分类的问题,但是还是在抽时间看图神经网络的论文以及博文。网络上的图卷积文章已经十分多了,推荐可以先看reference 3,比较简单易懂,然后reference 2,总结的内容蛮详近的, 再去知乎(reference 1)看看大佬们的相关讨论,对于在discrete graph上的梯度,散度的解释可以结合reference 9(quora上的一个问题,给了计算的例子)。本文主要是从代码角度来看GCN的卷积要了解的多深

图神经网络的开端是从2009年reference 7的论文开始的,以不动点理论为核心,通过结点信息的传播使整张图达到收敛(要进行循环迭代),在其基础上再进行预测。而GCN则可以通过学习优化参数(应用梯度下降算法)提取特征。

现在GCN的卷积方式主要有两种:
1. 空域卷积
2. 频域卷积
空域主要处理图像方面的问题,现在GCN主要使用的是频域卷积。

下述程序来自reference 6,8,使用下述的图进行模拟。

import math
import torch
from torch.nn.parameter import Parameter
from torch import nn

class GraphConv(nn.Module):
    def __init__(self, in_features, out_features, bias=True):
        """
        :param in_features: 输入特征
        :param out_features: 输出特征
        :param bias: 偏置
        """
        super(GraphConv, self).__init__()
        self.in_features = in_features
        self.out_features = out_features
        # Parameter用于将参数自动加入到参数列表
        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)
        # 最新spmm函数是在torch.sparse模块下,但是不能用
        # 使用稀疏矩阵乘法,
        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) + ')'


if __name__ == '__main__':
    inputs = torch.FloatTensor([[1, 0], [2, 1], [3, 2], [4, 3], [5, 4], [6,5]])
    adjs = torch.FloatTensor([
        [0, 1, 0, 0, 1, 0],
        [1, 0, 1, 0, 1, 0],
        [0, 1, 0, 1, 0, 0],
        [0, 0, 1, 0, 1, 1],
        [1, 1, 0, 1, 0, 0],
        [0, 0, 0, 1, 0, 0]
    ])
    g_conv = GraphConv(2, 10)
    outputs = g_conv.forward(inputs, adjs)
    print(outputs)

看到程序的forward函数,我们可以知道传递规则是: \(\)\(O = AIW\)\(\)(其中\(\)\(O\)\(\)为output,\(\)\(I\)\(\)为input),也即更新规则: \(\)\(H_{l+1} = \sigma (A H_l W)\)\(\),但在reference 8论文中写的为:\(\)\(H_{l+1} = \sigma (\hat{D}^{-1/2} \hat{A} \hat{D}^{-1/2} H_l W)\)\(\),大佬似乎还是喜欢使用邻接矩阵变换。

下面是个人写的邻接矩阵的处理,内容参考知乎reference 1中纵横回答

def processAdj(adj, self_loop=True, **kwargs):
    """
    :param adj: 邻接矩阵,type:np.array(float)
    :param self_loop: boolean
    :return: adj
    """
    adj = adj.astype(float)
    opt = {}
    opt['normal'] = kwargs.get('normal', False)
    opt['sym_normal'] = kwargs.get('sym_normal', False)
    if not opt['normal'] and not opt['sym_normal']:
        return adj
    
    if self_loop:
        adj = adj + np.eye(adj.shape[0])
    if opt['sym_normal']:  # 对称归一化
        degree = np.diag(np.sum(adj, axis=1)) ** -0.5
        return np.matmul(np.matmul(degree, adj), degree)
    if opt['normal']:  # 归一化
        degree = np.diag(np.sum(adj, axis=1)) ** -1
        return np.matmul(degree, adj)

看了一堆的论文文章,对于傅里叶变化那块,还是处于记住的阶段,通过上述的代码,不负责任的说:其实知道传递规则就可以编写gcn(看知乎reference 1中纵横的回答就可以)了,但是要对gcn有更深的理解,则需要对傅里叶转化到拉普拉斯算子到gcn进行公式推导。傅里叶可以看看马同学的文章。

referenece
1. https://www.zhihu.com/question/54504471
2. https://www.cnblogs.com/SivilTaram/p/graph_neural_network_1.html
3. https://zhuanlan.zhihu.com/p/37091549
4. https://mayi1996.top/2019/03/14/%E5%9B%BE%E7%BD%91%E7%BB%9C/
5. https://victorzhou.com/blog/intro-to-cnns-part-1/
6. https://github.com/tkipf/pygcn/blob/master/pygcn/layers.py
7. Scarselli F, Gori M, Tsoi A C, et al. The graph neural network model[J]. IEEE Transactions on Neural Networks, 2008, 20(1): 61-80.
8. Kipf T N, Welling M. Semi-supervised classification with graph convolutional networks[J]. arXiv preprint arXiv:1609.02907, 2016.
9. https://www.quora.com/Whats-the-intuition-behind-a-Laplacian-matrix-Im-not-so-much-interested-in-mathematical-details-or-technical-applications-Im-trying-to-grasp-what-a-laplacian-matrix-actually-represents-and-what-aspects-of-a-graph-it-makes-accessible