Graph Classification 实现

前言

记录一下实现的细节。

benchmarks

原则

convlayer内部不加norm 和 activation。

特例

GIN的MLP部分多层时,中间层加BN和ReLU。

必须夸一下DGL的复现员还是相当尊重原始实现的。

1.gin

How Powerful are Graph Neural Networks? ICLR2019
https://arxiv.org/pdf/1810.00826.pdf
https://github.com/weihua916/powerful-gnns

这个东西的关键在于,区分 gin_conv_layer和 gin_readout _layer。

gin conv是一个非常类似gcn的东西。
可以把support写成C = (1+eps)I +A 的形式。(neighborpool =sum)
或者C= (1+eps)I + D^-1A 。(neighbor pool =mean)

区别在于它先做 agg 再做mlp。
显然当MLP只有一层时,就退化到了CHW的形态。

gin readout是一个非常类似jknet的东西。

sum时类似JK。
也可以选 mean。

GIN原文根据不同的dataset选择sum or mean。

sum readout on bioinformatics datasets and mean readout on social datasets due to better test performance

默认版本是

leaneps=True, neigh_pool=sum, graph_pool = sum。

DGL实现版本

https://github.com/dmlc/dgl/blob/master/examples/pytorch/gin/gin.py

OGB实现版本( base on PyG )

https://github.com/snap-stanford/ogb/tree/master/examples/graphproppred/mol

1.1 区别1 share linear predictor weight

注意OGB实现版本默认的JK='last’是只取convlayers[-1]的输出作为node embd。
而哪怕JK=‘sum’,OGB版本也跟DGL版本实现有些区别。

见下列伪代码

# OGB version
if JK=='last':
	h_nodes = h_list[-1]
if JK=='sum':
	h_nodes = sum([ h_list[i] for i in range(n_layers)])

graph_embd = graph_pool(h_nodes)
pred = fc(graph_embd)

作为对比,DGL-GIN的实现是

# DGL version
pred = 0
for i in range(n_layers):
	g_embd = graph_pool(h_list[i])
	pred = pred + self.linears[i](g_embd)

两者的差别,写成矩阵形式。
设Graph Pooling对应一个矩阵P。
P r e d D G L − G I N = ∑ i P H i W i Pred_{DGL-GIN} = \sum_{i} P H_i W_i PredDGLGIN=iPHiWi
P r e d O G B − G I N = P ( ∑ i H i ) W , if JK ==’sum’ Pred_{OGB-GIN} = P (\sum_{i} H_i) W , \text{if JK =='sum'} PredOGBGIN=P(iHi)W,if JK ==’sum’

将P提到最左边后对比,可以看到OGB版本share一个W,参数量更节约。

1.2 区别2 残差和末层激活

其他细节上。

#OGB version
h_list=[h]
for i in range(n_layers):
	h = BN(Conv(..))
	# 对h的最后一层不加relu
	if not i==n_layers-1:
		h = ReLU(h)
	h=drop(h)
	# 残差链接
	if residual:
		h += h_list[i]
	h_list.append(h)
# DGL version
for i in range(n_layers):
	h = BN(Conv(..))
	h = ReLU(h)
#没有消除末层激活
#没有残差

对OGB版本来说,
因为h的最后一层不加relu,
如果JK又恰好为’last’。
相当于直接拿没有activation过的最后一层h去linear里做分类了。
这合理吗?
… 我觉得不合理。

QQ:结论,最后一层h还是要加激活。

再讨论残差连接合理吗?
我觉得也不合理。
用公式展开就知道了

假设下面的Conv操作符号已经融合了BN+ReLU
H O G B − r e s ( l + 1 ) = Conv ( H ( l ) ) + H ( l ) H_{OGB-res}^{(l+1)} = \text{Conv}(H^{(l)}) +H^{(l)} HOGBres(l+1)=Conv(H(l))+H(l)

在计算pred score的时候,可以将总分拆成几个子项的和。
P r e d O G B − G I N = P ( ∑ i H i ) W = ∑ i ( P H i W ) Pred_{OGB-GIN} = P (\sum_{i} H_i) W = \sum_{i} (PH_i W) PredOGBGIN=P(iHi)W=i(PHiW)

pred ( i ) \text{pred}^{(i)} pred(i)表示第i项

pred ( 0 ) = P H ( 0 ) W \text{pred}^{(0)} = P H^{(0)}W pred(0)=PH(0)W
pred ( 1 ) = P ( Conv ( H ( 0 ) ) + H ( 0 ) ) W \text{pred}^{(1)} = P (\text{Conv}(H^{(0)})+H^{(0)})W pred(1)=P(Conv(H(0))+H(0))W
pred ( 2 ) = P ( Conv ( H ( 1 ) ) + H ( 1 ) ) W = P ( Conv ( H ( 1 ) ) W + H ( 1 ) W ) = P ( Conv ( H ( 1 ) ) W + Conv ( H ( 0 ) ) W + H ( 0 ) W ) \text{pred}^{(2)} = P (\text{Conv}(H^{(1)})+H^{(1)})W =P (\text{Conv}(H^{(1)})W+H^{(1)}W) = P (\text{Conv}(H^{(1)})W+\text{Conv}(H^{(0)})W+H^{(0)}W) pred(2)=P(Conv(H(1))+H(1))W=P(Conv(H(1))W+H(1)W)=P(Conv(H(1))W+Conv(H(0))W+H(0)W)

最后要把这些pred全部加起来。
你发现了吗,在OGB版本里
if residual==True and JK=='sum'

H ( 0 ) W H^{(0)}W H(0)W 会被 add n_layers 次。
不同层的表征之间天然存在不平衡,相当于告诉模型重点在 H ( 0 ) H^{(0)} H(0)上学。
直觉上这是非常不好的事情。

相反,DGL的版本就很好。
P r e d D G L − G I N = ∑ i P H i W i Pred_{DGL-GIN} = \sum_{i} P H_i W_i PredDGLGIN=iPHiWi
每个层有自己的 W i W_i Wi
如果对模型来说,后面的深层表征真的不重要,而 H ( 0 ) H^{(0)} H(0)重要,模型可以自适应地学到 W i → 0 , i ≠ 0 W_i \to 0 , i\ne0 Wi0,i=0

而不是像OGB版本一样,增加一个非常强的先验权重。

QQ: residual 和 JK sum 存在强相互作用。
if residual , 建议每层一个linear predictor,再JK。或者不要JK只取last做linear。
如果要节约参数,sum(h) 后再 linear predictor,建议不要residual。
这种trick少用…
懒人结论,应该保持跟原始repo一样的独立linear predictor,拒绝残差连接。

注意OGB版本(1.3.2)的默认参数是
JK=‘last’ ,residual=False。
所以直接按readme.md里的命令跑,不会出现上面说的问题。
总之,还是抄DGL版本为好。

1.3 区别3 Drop位置

OGB版本

h = Drop(ReLU(BN(Conv(h))))
pred = linear(graph_pool( sum(h) )) if JK==‘sum’
pred = linear(graph_pool( last_h )) if JK==‘last’ , default set

DGL版本

h= ReLU(BN(Conv(h)))
pred = sum( Drop(linear(graph_pool(h) ) ) )

这个应该不是很重要,因为我们主要只需要利用到GIN ConvLayer。
Conv之外的处理可以自定义的。

2.GCN

别人是怎么做的?

OGB官方实现的GCN Convlayer(base on PyG)
https://github.com/snap-stanford/ogb/blob/master/examples/graphproppred/mol/conv.py

QQ:

实现GC上的GCN的接口时遇到一个问题。
基于GIN写的第一个框架,model的input其实包含3个部分。
一个特别的adj (based on neighbor pooling type, raw_adj for sum or D^-1adj for mean) with no self-edge
但gcn的默认其实是 D-1adj/2 D-1/2 , with self-edge。

可以在config里写好需要的adj norm type , left right,both,none, 以及 selfedge。
但是这样就需要我自己去记model specific parameters。。
我需要找一个地方去记这些东西。

3.Deeper GCN (base PyG

https://github.com/lightaime/deep_gcns_torch/tree/master/examples/ogb

4.DGN (base DGL)

这个 repo给了非常多benchmark,非常好。

https://github.com/Saro00/DGN/tree/master/realworld_benchmark

实验设置非常细腻!
好评如潮!

Data

mol onehot embed

from ogb.graphproppred.mol_encoder import AtomEncoder,BondEncoder

from ogb.utils.features import get_atom_feature_dims, get_bond_feature_dims 


full_atom_feature_dims = get_atom_feature_dims()
full_bond_feature_dims = get_bond_feature_dims()

#9项。[119, 4, 12, 12, 10, 6, 6, 2, 2]
#3项。[5, 6, 2]

Q要不要加JK

修改项

2处norm非L2时,应该分离。于是写出了 norm1,norm2。
调整了norm和relu的顺序,先norm后relu。

collate_fn

返回sparse_raw_adj, feat, label。

batch train

name = ‘ogbg-molhiv’
base_dir = ‘./Data/DatasetRaw/OGB/ogbg-molhiv’

train,val,test, 3.2w,4k,4k

bz=20
tr pred 0.0136
tr bp 0.0368
val pred+loss 0.008 *200 = 1.6

bz=100
tr pred+loss = 0.0044
tr bp=0.023
val pred+loss = 0.02*40 = 0.8s

bz=500
tr pred+loss =0.005
tr vp 0.029
val pred+loss 0.05*8 =0.5s

合并val和test之后,单次eval infer只需要0.01s
bz100
tr pred+loss 0.023 * 320 = 7s

bz20
tr pred_loss 0.004
tr bp 0.018
大概需要3.2w/20 = 1.6k iters
0.018*1600 = 28.8s

单词计算val rocauc开销0.03s

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值