目录
复杂GCN6层使用BN+残差 (每层考虑初始特征和前一层输入+最后一层聚合所有层)
复杂GCN6层使用BN+残差+数据增广 (attacks,在模型训练中调用,也就是模型计算损失)
简介
由于本人不太会写代码,也不晓得如何学习,发现自己过了这么久才有点入门。因此准备写一个博客来记录自己的学习过程,同时硬着头皮准备给自己搞一个框架,准备之后按照这个框架来快速套入代码进行学习,以下全部基于pyg框架在win10下进行的学习测试。想要改进模型,又不会,偶然间看到有JK和gcn和sage的结合
- https://github.com/ytchx1999(gnn-test)
- https://github.com/rusty1s/pytorch_geometric/blob/master/examples/ogbn_proteins_deepgcn.py-deepgcn在OGB上的实现
- https://pytorch-geometric.readthedocs.io/en/latest/modules/nn.html#torch_geometric.nn.models.JumpingKnowledge-jk分析
参数问题
- 经过propagate得到的是x_j
- x[0]就是x_j,对应线性变换是lin_l
- x[1]就是x_i,对应线性变换是lin_r
DeepGCN
基础
已知现有三大经典,他们的优缺点如下
- GCN
- 缺点在于它灵活性差,transductive,并且扩展性非常差,除此之外这篇论文借助验证集来早停帮助性能提升,跟它半监督学习的初中有点相悖。
- 训练是full-batch的,难以扩展到大规模网络,并且收敛较慢
- gcn增加深度会降低模型效果主要是因为过度平滑的问题。
- GraphSage
- 这篇论文旨在提升gcn扩展性和改进训练方法缺陷。它将模型目标定于学习一个聚合器而不是为每个节点学习到一个表示,这中思想可以提升模型的灵活性和泛化能力。除此之外,得益于灵活性,它可以分批训练,提升收敛速度。
- 但是它的问题是因为节点采样个数随层数
指数增长,会造成模型在time per batch上表现很差,弱于GCN,这方面的详细讨论可以参考Cluster-GCN这篇论文。 - 虽然支持mini-batch方式训练,但是训练较慢,固定邻居数目的node-wise采样,精度和效率较低。
- GAT
- 这篇论文创新之处是加入attention机制,给节点之间的边给予重要性,帮助模型学习结构信息。
- 相对的缺点就是
训练方式不是很好,其实这个模型可以进一步改,用attention做排序来选取采样节点,这样效果和效率方面应该会有提升。 - 参数量比GCN多,也是full-batch训练;
- 只用到1-hop的邻居,没有利用高阶邻居,当利用2阶以上邻居,容易发生过度平滑(over-smoothing)
这三种模型共同缺点
- 针对的图结构都是homogeneous的,也就是只有同一种节点和连边,如果是异质网络(heterogeneous)则不能直接处理。能解决的任务目前来说主要是通过embedding做节点分类和连边预测,图上的优化问题等其他任务则尚未看到可以应用。
动机
为了增加层数,目前大多都是基于GCN和GAT使用残差连接的方式来提升性能
为什么选择这个开始学习?
- 是因为这个模型改进了GCN,将层数加深,其实就是考虑了残差连接,也就是模型每一层都考虑前一层
现在解决这个问题的方法主要就是skip-connection的方法,其中包括残差网络。这方面推荐几篇论文:
- 1.DeepGCNs: Can GCNs Gobas Deep as CNNs?这篇论文主要讨论了GCN的深度问题,文中才用了ResGCN,DenseGCN和Dilation等方法,最后效果比较明显。网络层数可以达到56层,并且有3.7个点的提升。
- 2.Deep insights into Graph Convolution Networks for Semi-supervised Learning.这篇论文只看前面对于过度平滑的分析即可。
- 3.Representation learning on graphs with jumping knowledge networks.这篇论文建立一个相对比较深的网络,在网络的最后当一个层聚合器来从所有层的输出中进行选择,来抑制noise information的问题。
- 4.即便使用了残差连接(没有使用BN和res下),GCN也不可能做的太深,基本就是3-5层左右的样子。这是因为GCN可以被看作低通滤波器,叠加低通滤波器具有明显的过度平滑现象。如果想要做的更深,可以考虑一下DropEdge的方法,通过在训练过程中随机扔掉一些边来缓解过度平滑的现象,这种方法最近被证明是有效的。
- 在采用了BN层和residual连接时,gcn可以在使用res下做很深。
源论文源码学习查找(代码核心!!!)
参考论文,查考代码(其中这个论文地址也可以通过pyg里面,如图点击论文名称来找到,极其方便学习的)
发现代码真的好齐全,有各种代码,甚至还有视频!!!
res
dense
膨胀卷积
膨胀率
最终模型图
以obgn_arxiv数据集的deepgcn+deepergcn(deepergcn,参考genconv,后面讲)代码,参考,核心代码如下,发现其实和jk一样的思想
- 每一层卷积后的输出hN=conv0+conv1+...convN,每一层都是前几层的结果和,最后的h和JK一样,但是中间层h_i和JK中间层不一样
- JK:每一层是啥样还是啥样,就只是将中间层结果全部拿出来,在最后一层相加,也就是h=conv0+conv1+...convN
class DeeperGCN(torch.nn.Module):
def __init__(self, args):
super(DeeperGCN, self).__init__()
self.num_layers = args.num_layers
self.dropout = args.dropout
self.block = args.block
self.checkpoint_grad = False
in_channels = args.in_channels
hidden_channels = args.hidden_channels
num_tasks = args.num_tasks
conv = args.conv
aggr = args.gcn_aggr
t = args.t
self.learn_t = args.learn_t
p = args.p
self.learn_p = args.learn_p
y = args.y
self.learn_y = args.learn_y
self.msg_norm = args.msg_norm
learn_msg_scale = args.learn_msg_scale
norm = args.norm
mlp_layers = args.mlp_layers
if aggr in ['softmax_sg', 'softmax', 'power'] and self.num_layers > 7:
self.checkpoint_grad = True
self.ckp_k = self.num_layers // 2
print('The number of layers {}'.format(self.num_layers),
'Aggregation method {}'.format(aggr),
'block: {}'.format(self.block))
if self.block == 'res+':
print('LN/BN->ReLU->GraphConv->Res')
elif self.block == 'res':
print('GraphConv->LN/BN->ReLU->Res')
elif self.block == 'dense':
raise NotImplementedError('To be implemented')
elif self.block == "plain":
print('GraphConv->LN/BN->ReLU')
else:
raise Exception('Unknown block Type')
self.gcns = torch.nn.ModuleList()
self.norms = torch.nn.ModuleList()
self.node_features_encoder = torch.nn.Linear(in_channels, hidden_channels)
self.node_pred_linear = torch.nn.Linear(hidden_channels, num_tasks)
for layer in range(self.num_layers):
if conv == 'gen':
gcn = GENConv(hidden_channels, hidden_channels,
aggr=aggr,
t=t, learn_t=self.learn_t,
p=p, learn_p=self.learn_p,
y=y, learn_y=self.learn_y,
msg_norm=self.msg_norm, learn_msg_scale=learn_msg_scale,
norm=norm, mlp_layers=mlp_layers)
else:
raise Exception('Unknown Conv Type')
self.gcns.append(gcn)
self.norms.append(norm_layer(norm, hidden_channels))
def forward(self, x, edge_index):
h = self.node_features_encoder(x)
if self.block == 'res+':
h = self.gcns[0](h, edge_index)
if self.checkpoint_grad:
#每一层卷积后的输出=conv0+conv1+...convN,每一层都是前几层的结果和
for layer in range(1, self.num_layers):
h1 = self.norms[layer - 1](h)
h2 = F.relu(h1)
h2 = F.dropout(h2, p=self.dropout, training=self.training)
if layer % self.ckp_k != 0:
res = checkpoint(self.gcns[layer], h2, edge_index)
h = res + h#deeprGCN中h=res+h是从第一层到最后一层的和,因为每次h也变了,直接加到h上了;JK中layer_out存的是每层结果,同上面相加的每个元素,因为是append追加元素,相当于=layer_out+=layer_out[i]追加元素。要想和上面一样,需要判断i不等于0下执行layer_out[0]+=layer_out[i]即可
else:
h = self.gcns[layer](h2, edge_index) + h
else:
for layer in range(1, self.num_layers):
h1 = self.norms[layer - 1](h)
h2 = F.relu(h1)
h2 = F.dropout(h2, p=self.dropout, training=self.training)
h = self.gcns[layer](h2, edge_index) + h
h = F.relu(self.norms[self.num_layers - 1](h))
h = F.dropout(h, p=self.dropout, training=self.training)
elif self.block == 'res':
h = F.relu(self.norms[0](self.gcns[0](h, edge_index)))
h = F.dropout(h, p=self.dropout, training=self.training)
for layer in range(1, self.num_layers):
h1 = self.gcns[layer](h, edge_index)
h2 = self.norms[layer](h1)
h = F.relu(h2) + h
h = F.dropout(h, p=self.dropout, training=self.training)
elif self.block == 'dense':
raise NotImplementedError('To be implemented')
elif self.block == 'plain':
h = F.relu(self.norms[0](self.gcns[0](h, edge_index)))
h = F.dropout(h, p=self.dropout, training=self.training)
for layer in range(1, self.num_layers):
h1 = self.gcns[layer](h, edge_index)
h2 = self.norms[layer](h1)
h = F.relu(h2)
h = F.dropout(h, p=self.dropout, training=self.training)
else:
raise Exception('Unknown block Type')
h = self.node_pred_linear(h)
return torch.log_softmax(h, dim=-1)
def print_params(self, epoch=None, final=False):
if self.learn_t:
ts = []
for gcn in self.gcns:
ts.append(gcn.t.item())
if final:
print('Final t {}'.format(ts))
else:
logging.info('Epoch {}, t {}'.format(epoch, ts))
if self.learn_p:
ps = []
for gcn in self.gcns:
ps.append(gcn.p.item())
if final:
print('Final p {}'.format(ps))
else:
logging.info('Epoch {}, p {}'.format(epoch, ps))
if self.learn_y:
ys = []
for gcn in self.gcns:
ys.append(gcn.sigmoid_y.item())
if final:
print('Final sigmoid(y) {}'.format(ys))
else:
logging.info('Epoch {}, sigmoid(y) {}'.format(epoch, ys))
if self.msg_norm:
ss = []
for gcn in self.gcns:
ss.append(gcn.msg_norm.msg_scale.item())
if final:
print('Final s {}'.format(ss))
else:
logging.info('Epoch {}, s {}'.format(epoch, ss))
PyG查找DeepGCN+DeeperGCN
DeepGCN(DeepGCNLayer)分析
首先以学习deepgcn为例子,首先去pyg官网找到nn模块里面的model
图1
然后点击上面图1中的DeepGCNLayer,也就可以看到pyg内置DeepGCNLayer
图2
发现有两种实现方法,甚至详细写出了书写框架和模型组成,简直不要太详细
图3
发现也就是两种实现方式:
- Res+:
- x预先取出来
- 进行norm+act+dropout+conv(全都h表示)
- 最后res也就是每层结果都考虑前一层:h+x
- Res:
- x预先取出来
- 上来就是直接conv+norm+act(全都h表示)
- 开始进行判断连接块:
- res就x+h
- dense就torch.cat([x, h], dim=-1)
- plain就Pass
- 最后drop
还给出了参考代码实现: examples/ogbn_proteins_deepgcn.py,后面讲解<