一、论文简介
- 论文作者——Felix Wu ,Tianyi Zhang et al.
- 论文链接 ——Simplifying Graph Convolutional Networks
二、论文核心概述
本篇论文是针对GCN论文的改进。
GCN采用分层的结构进行信息传播(Propagation)和线性变变换(Transformation),并在每层之后采用ReLU非线性激活函数,最后一层采用Softmax激活函数来进行预测。本文作者认为受深度学习方法启发的GCN模型,存在固有的复杂度和冗余的计算,这些是由于非线性激活函数引起的,而且降低了模型的可解释性。从这个角度出发,作者重复地移除GCN每层地非线性激活函数并保留最终的Softmax激活函数进行预测,对于重复的矩阵乘积形式,采用矩阵幂的形式来表示,用一个统一的可学习矩阵
W
W
W来进行线性变换。
GCN模型
第
K
层定义的传播规则为:
H
k
←
R
e
L
U
(
S
H
(
K
−
1
)
W
(
K
)
)
;
H
(
0
)
=
X
;
K
层
G
C
N
的模型预测为:
Y
^
G
C
N
=
S
o
f
t
m
a
x
(
S
H
(
k
−
1
)
W
(
K
)
)
第K层定义的传播规则为:H^{k} \gets ReLU(SH^{(K-1)}W^{(K)}) ; \;\;\;H^{(0)}=X; \\ K层GCN的模型预测为:\hat Y_{GCN} = Softmax(SH^{(k-1)}W^{(K)})
第K层定义的传播规则为:Hk←ReLU(SH(K−1)W(K));H(0)=X;K层GCN的模型预测为:Y^GCN=Softmax(SH(k−1)W(K))
重复地移除非线性激活函数后,结果如下:
Y
^
=
S
o
f
t
m
a
x
(
S
.
.
.
S
X
W
(
1
)
W
(
2
)
.
.
.
W
(
K
)
)
\hat Y = Softmax(S...SXW^{(1)}W^{(2)}...W^{(K)})
Y^=Softmax(S...SXW(1)W(2)...W(K))
将重复地
S
S
S采用矩阵乘积地形式表示,并将每层地线性变换矩阵用一个统一的矩阵
W
W
W来表示,得到的结果为:
Y
^
S
G
C
=
S
o
f
t
m
a
x
(
S
K
X
W
)
\hat Y_{SGC} = Softmax(S^KXW)
Y^SGC=Softmax(SKXW)
这就是SGC最终地模型,作者通过大量的实验分析,与GCN模型在分类准确性上势均力敌,但在代码执行速度上比GCN快了几个数量级。SGC模型看似非常简单,但思想的创新实则不易。下图就是SGC与GCN模型的比较。
同时,本文作者也从谱域对SGC进行分析,表明重整归一化的传播矩阵
S
=
D
−
1
2
(
A
+
I
)
D
−
1
2
S=D^{-\frac{1}{2}}(A+I)D^{-\frac{1}{2}}
S=D−21(A+I)D−21是一种低通滤波器,S的最大值从原来的2近似为原来的1.5左右,并且消除了负数系数的影响,而且,这种低通滤波器能够产生局部平滑的效果。
三、Pyorch源码
模型核心代码
可以理解为一种简单的线性回归问题(完整代码详见文章末尾)。
注释部分是另外一种写法,等价于Linear方法
import torch
from torch.nn import Module,Linear
from torch.nn import functional as F
from torch.nn import Parameter
class SGC(Module):
def __init__(self,input_dim,output_dim):
super(SGC, self).__init__()
self.input_feature = input_dim
# 输出特征
self.output_feature = output_dim
self.layer = Linear(input_dim,output_dim,bias=False)
# self.weight = Parameter(torch.FloatTensor(input_dim,output_dim))
# torch.nn.init.xavier_uniform_(self.weight.data,gain=1)
def forward(self,x):
#x = S^k * H
#output = torch.mm(x,self.weight)
return F.log_softmax(self.layer(x),dim=1)
#return F.log_softmax(output,dim=1)
SGC模型输入的 X = S k X X= S^kX X=SkX,其计算方法如下:
#对邻接矩阵进行预处理:S^k
def sgc_precompute(features,adj,k):
for i in range(k):
features = torch.mm(adj,features)
return features
链接:https://pan.baidu.com/s/1EzhVCVKWzI3hRjuPpPeZGA
提取码:6666
四、DGL复现SGC模型
import os
os.environ["DGLBACKEND"] = "pytorch"
import dgl
import dgl.function as fn
import torch
import torch.nn as nn
import torch.nn.functional as F
from dgl.nn.pytorch.conv import SGConv,GraphConv
class SGCLayer(nn.Module):
def __init__(self,infeat,outfeat,k) -> None:
super(SGCLayer,self).__init__()
self.fn = nn.Linear(infeat,outfeat,bias=False)
self.fn.reset_parameters()
self.weight = nn.Parameter(torch.Tensor(infeat, outfeat))
nn.init.xavier_uniform_(self.weight.data,gain=1.414)
self.k = k
def forward(self,g,feat):
with g.local_scope():
g = dgl.add_self_loop(g)
# degs = g.in_degrees().to(feat).clamp(min=1)
# norm = torch.pow(degs,-0.5)
# norm = norm.to(feat.device).unsqueeze(1)
degs = g.out_degrees().to(feat).clamp(min=1)
norm = torch.pow(degs,-0.5)
shp = norm.shape + (1,) * (feat.dim() -1 )
norm = torch.reshape(norm,shp)
for _ in range(self.k):
feat = feat * norm
g.ndata['h'] = feat
g.update_all(fn.copy_u('h','m'),fn.sum(msg='m',out ='h'))
feat = g.ndata.pop('h')
feat = feat * norm
#return torch.matmul(feat,self.weight)
return self.fn(feat)
class SGC(nn.Module):
def __init__(self,infeat,outfeat ,dropout,k) -> None:
super(SGC,self).__init__()
self.dropout = dropout
self.layer = SGCLayer(infeat,outfeat,k)
def forward(self,g,features):
x = F.dropout(features,self.dropout,training=self.training)
x = self.layer(g,x)
return F.log_softmax(x,dim=1)