大规模GCN的训练:目前基于SGD的gcn算法,1)面临随着gcn层数增长,计算复杂度呈指数增长;2)需要保存整个Graph和每个node的embedding,存储量巨大。
本文提出了一种基于Graph聚类结构结构,且基于SGD训练的GCN算法:Cluster-GCN。在每一个步骤中,Cluster-GCN通过graph聚类算法来筛选联系紧密的sub-graph,从而在sub-graph中的一组node进行采样,并且限制该sub-graph中的邻居搜索,可以显著提高搜索效率。
在Amazon2M数据集:200万个node,6100万个边,比Reddit大五倍,实验了三层四层gcn。
**对于Graph,GCN网络通过Graph Convolutional Neural逐层地获取节点的embedding:在每一层的每一个节点的embedding,需要采集下一层相邻节点的embedding进行激活。最后一层的embedding将用于一些最终任务。**例如在Graph Classification中,最后一层embedding被输入到分类器(如softmax)来预测各个节点的标签。
需要注意地是,由于GCN运算需要利用graph中节点之间地交互来学习更新embedding,这使得GCN的训练非常有挑战性。与cnn等网络不同,训练loss可以完美地分解为每个样本单独影响(decomposed into individual terms),GCN的损失函数必须依赖大量的其他节点。特别是GCN变深时,由于这种节点依赖,GCN的训练变得非常慢–反向传播时需要计算Graph上的所有embedding。
1、 GCN开山之作:Semi-Supervised Classification with Graph Convolutional Networks,应用全批次梯度下降法(Full-batch Gradient Descent),需要计算整个梯度,存储所有节点的embeddings,导致 O ( N F L ) O(NFL) O(NFL)内存需求。由于每个epoch只更新一次参数,梯度下降的收敛速度非常慢。
2、Graph-SAGE:Inductive Representation Learning on Large Graphs,提出了mini-batch SGD。由于每次更新参数仅仅基于一个batch的梯度,大大降低了内存需求,并且可以在每个epoch执行多次更新,从而加快了收敛速度。Graph-SAGE存在节点邻居扩展问题,需要计算节点的邻居节点在L-1层的embeddings,而这些邻居节点又需要求在L-2层的embeddings,周而复始,这导致大量的计算复杂度。Graph-SAGE同过在各层的反向传播过程中固定大小的邻居节点,降低了复杂度。
3、VR-GCN采用减小Variance方法来减小邻域采样节点的大小。尽管成功地减小了采样大小,但仍然需要将所有节点的中间embedding,导致 O ( N F L ) O(NFL) O(NFL)的内存需求。如果节点数量增加到数百万个,那么对于内存要求过高。
本文利用Graph的聚类结构,目的是划分节点的分区,使同一分区中的节点之间sub-Graph的链接,比不同分区更多。一个mini-batch算法的效率,由embedding utilization(嵌入利用率)来描述:与一个batch或者within-batch的节点之间的链接数量成正比。
GCN中的每一层通过考虑相邻节点的embedding,来更新Graph中的每个节点的特征向量表示。具体来说,GCN的逐层正向传播可以总结为:
X ( l + 1 ) = f ( X l , A ) = δ ( D ^ − 1 2 A ^ D ^ − 1 2 X ( l ) W ( l ) ) X^{(l+1)}=f(X^l,A)=\delta(\hat D^{-\frac{1}{2}}\hat A\hat D^{-\frac{1}{2}}X^{(l)}W^{(l)}) X(l+1)=f(Xl,A)=δ(D^−21A^D^−21X(l)W(l))
Z ( l + 1 ) = A ′ X ( l ) W ( l ) , X ( l + 1 ) = δ ( Z ( l + 1 ) ) . Z^{(l+1)}=A^{'}X^{(l)}W^{(l)},\\ X^{(l+1)}=\delta(Z^{(l+1)}). Z(l+1)=A′X(l)W(l),X(l+1)=δ(Z(l+1)).
损失函数
L = 1 ∣ Y L ∣ ∑ i ∈ Y L Loss ( y i , z i L ) , \mathcal{L}=\frac{1}{|\mathcal{Y}_\mathcal L|\sum_{i\in \mathcal{Y}_\mathcal L}}\text{Loss}(y_i,z^L_i), L=∣YL∣∑i∈YL1Loss(yi,ziL),
batch_size= ∣ B ∣ |\mathcal{B}| ∣B∣表示一个batch的节点,使用基于mini-batch的SGD:
1 ∣ B ∣ ∑ i ∈ B ∇ Loss ( y i , z i L ) , \frac{1}{|\mathcal{B}|}\sum_{i\in \mathcal B}\nabla\text{Loss}(y_i,z^L_i), ∣B∣1i∈B∑∇Loss(yi,ziL),
需要注意的是,尽管基于mini-batch的SGD在每个epoch收敛得更快,但由于引入了另一个计算开销,所以比Full Gradient Descent要慢。
考虑计算一个节点 i i i相关的梯度: ∇ Loss ( y i , z i L ) \nabla\text{Loss}(y_i,z^L_i) ∇Loss(yi,ziL)。显然,需要存储节点 i i i的embedding,而节点 i i i的embedding的计算依赖于, L − 1 L-1 L−1层的邻居节点的embeddings。**假设一个 L L L层的GCN网络,每个节点的平均度数为 d d d,为了获得节点 i i i的相关梯度,需要对Graph中的一个节点聚合 O ( d L ) O(d^L) O(dL)个节点的特征。**换句话说,需要获取Graph中节点的 h o p − k ( k = 1 , … , L ) hop-k(k=1,\dots,L) hop−k(k=1,…,L)邻居节点的信息来执行一次更新。
嵌入利用率用来反应计算效率。
如果节点 i i i在第 l l l层得embedding: z i ( l ) z^{(l)}_i zi(l),在计算第 l + 1 l+1 l+1层得embeddings时被重复使用了 u u u次,那么 z i ( l ) z^{(l)}_i zi(l)的嵌入利用率就是 u u u。
为了使mini-batch SGD工作,以前的方法试图限制邻域扩展的数量,但是这并没有提高嵌入使用率。
在mini-batch SGD更新中,尝试设计一个batch和相应的计算sub-graph来最大限度地提高embedding utilization。Cluste-GCN通过将embedding utilization连接到一个聚类目标上来实现。
对于一个Graph: G \mathcal G G,把节点分成 c c c个组: V = [ V 1 , … , V c ] V=[V_1,\dots,V_c] V=[V1,…,Vc],这样就会得到 c c c个sub-graph:
G ‾ = [ G 1 , … , G c ] = [ { V 1 , E 1 } , … , { V c , E c } ] \overline G=[G_1,\dots,G_c ]=[\{V_1,E_1\},\dots,\{V_c,E_c\}] G=[G1,…,Gc]=[{V1,E1},…,{Vc,Ec}]
每个 E t E_t Et只包含在 V t V_t Vt中的节点之间的边;
对节点进行重组后,邻接矩阵被划分为 c 2 c^2 c2个子矩阵:
KaTeX parse error: Undefined control sequence: \label at position 142: …x} \right] \̲l̲a̲b̲e̲l̲{eq:a}
其中
A ˉ = [ A 11 … 0 ⋮ ⋱ ⋮ 0 … A c c ] , Δ = [ 0 … A 1 c ⋮ ⋱ ⋮ A c 1 … 0 ] \bar A=\left[ \begin{matrix} A_{11}&\dots&0\\ \vdots&\ddots&\vdots\\ 0&\dots&A_{cc} \end{matrix} \right], \Delta=\left[ \begin{matrix} 0&\dots&A_{1c}\\ \vdots&\ddots&\vdots\\ A_{c1}&\dots&0 \end{matrix} \right] Aˉ=⎣⎢⎡A11⋮0…⋱…0⋮Acc⎦⎥⎤,Δ=⎣⎢⎡0⋮Ac1…⋱…A1c⋮0⎦⎥⎤
对角块 A t t A_{tt} Att是一个包含的边在sub-graph G t G_t Gt内 ∣ V t ∣ × ∣ V t ∣ |V_t|\times|V_t| ∣Vt∣×∣Vt∣维的邻接矩阵;
A ˉ \bar A Aˉ是 G ˉ \bar G Gˉ的邻接矩阵;
A s t A_{st} Ast包含了两个部分 V s V_s Vs和 V t V_t Vt之间的边;
Δ \Delta Δ是由 A A A的所有非对角块组成的矩阵。
类似的,将特征矩阵 X X X和训练矩阵 Y Y Y根据区分 [ V 1 , … , V c ] [V_1,\dots,V_c] [V1,…,Vc]分组为 [ X 1 , … , X c ] [X_1,\dots,X_c] [X1,…,Xc]和 [ Y 1 , … , Y c ] [Y_1,\dots,Y_c] [Y1,…,Yc]。
块对角近似的好处是GCN的目标函数可以分解成不同的batches(clusters)。设 A ′ ˉ \bar{A^{'}} A′ˉ为 A ˉ \bar A Aˉ的归一化版本,则最终embedding矩阵由 A ˉ \bar A Aˉ的块对角形式变成( A t t ′ ˉ \bar{A^{'}_{tt}} Att′ˉ是 A ′ ˉ \bar{A^{'}} A′ˉ的块对角):
KaTeX parse error: No such environment: equation at position 8: \begin{̲e̲q̲u̲a̲t̲i̲o̲n̲}̲ \begin{aligned…
损失函数也被分解成:
KaTeX parse error: Undefined control sequence: \label at position 163: …ss}(y_i,x^L_i) \̲l̲a̲b̲e̲l̲{eq:step2}
然后Cluster-GCN基于公式KaTeX parse error: Undefined control sequence: \eqref at position 1: \̲e̲q̲r̲e̲f̲{eq:step1}和KaTeX parse error: Undefined control sequence: \eqref at position 1: \̲e̲q̲r̲e̲f̲{eq:step2}中的分解形式。**在每一步中,对一个聚类 V t V_t Vt进行采样,然后根据 L A ′ ˉ \mathcal L_{\bar{A^{'}}} LA′ˉ的梯度进行SGD更新,**这只需要当前batch上的sub-graph A t t , X t , Y t A_{tt},X_t,Y_t Att,Xt,Yt和模型 { W ( l ) } l L \{W^{(l)}\}^L_l {W(l)}lL。
Cluster-GCN使用了Graph聚类算法来划分Graph。Graph聚类的方法,如metis和graclus等,旨在在Graph中的顶点上构建分区,使得簇内连接远大于簇间连接,从而更好的捕捉聚类和区分结构。
由于在分区 V t V_t Vt中的节点,只连接 V t V_t Vt中的节点,所以每个节点不需要再 A t t A_{tt} Att外部执行邻居搜索。对每个batch的计算将很纯粹的只是 A t t ′ ˉ X ( l ) W ( l ) \bar{A_{tt}^{'}}X^{(l)}W^{(l)} Att′ˉX(l)W(l)的矩阵乘积和一些element-wise的操作,时间复杂度低。并且每个batch只需要计算 O ( b L ) O(bL) O(bL)的embeddings,是线性的,存储要求也低。
Cluster-GCN实现了良好的计算和内存复杂性,却存在两个问题:
随即多聚类方法,在簇与簇之间进行合并,减少batch间的差异(variance)。首先用一个较大的p值把图分割为 V 1 , … , V p V_1,\dots,V_p V1,…,Vp,然后对于SGD更新重新构建一个batch B B B,而不是只考虑一个簇。随机的选择q个簇: t 1 , … , t q t_1,\dots,t_q t1,…,tq,并把它们的节点 V t 1 ∪ ⋯ ∪ V t q V_{t_1}\cup\dots\cup V_{t_q} Vt1∪⋯∪Vtq包含在这个batch B B B中。此外,在选择的簇之间的连接:
A i j ∣ i , j ∈ t 1 , … , t q A_{ij}|i,j\in t_1,\dots,t_q Aij∣i,j∈t1,…,tq
被添加回去。这样,簇间的连接会被重新合并,使得batch之间的差异更小。
类似与Resnet的跳接:
Z ( l + 1 ) = A ′ X ( l ) W ( l ) , X ( l + 1 ) = δ ( Z ( l + 1 ) ) + X ( l ) Z^{(l+1)}=A'X^{(l)}W^{(l)},\\ X^{(l+1)}=\delta(Z^(l+1))+X^{(l)} Z(l+1)=A′X(l)W(l),X(l+1)=δ(Z(l+1))+X(l)
本文通过放大每个GCN层中使用的邻接矩阵 A A A的对角部分,实现在每个GCN层的聚合中对上一层的表示施加更多的权重,如将单位矩阵添加到 A ˉ \bar A Aˉ中:
Z ( l + 1 ) = ( A ′ + I ) X ( l ) W ( l ) , X ( l + 1 ) = δ ( Z ( l + 1 ) ) , X ( l + 1 ) = δ ( ( A ′ + I ) X ( l ) W ( l ) ) Z^{(l+1)}=(A'+I)X^{(l)}W^{(l)},\\ X^{(l+1)}=\delta(Z^{(l+1)}),\\ X^{(l+1)}=\delta((A'+I)X^{(l)}W^{(l)}) Z(l+1)=(A′+I)X(l)W(l),X(l+1)=δ(Z(l+1)),X(l+1)=δ((A′+I)X(l)W(l))
进而提出对角增强(diagonal enhancement):
首先向原始 A A A添加一个单位矩阵,然后标准化:
A ~ = ( D + I ) − 1 ( A + I ) \tilde A=(D+I)^{-1}(A+I) A~=(D+I)−1(A+I)
X ( l + 1 ) = δ ( ( A ~ + λ diag ( A ~ ) ) X ( l ) W ( l ) ) X^{(l+1)}=\delta((\tilde A+\lambda\text{diag}(\tilde A))X^{(l)}W^{(l)}) X(l+1)=δ((A~+λdiag(A~))X(l)W(l))
多层GCNs,在下表中对比了Cluster-GCN和VRGCN:
对比发现,VRGCN由于其邻域查找的代价呈指数增长,而Cluster-GCN线性增长。
通过对Cluster-GCN的归一化方法,可以进行更深的训练,对于PPI数据集,Cluster-GCN可以通过训练一个包含2048个隐藏单元的5层GCN来达到最先进的效果。对于Reddit数据集,使用了一个包含128个隐藏单元的4层GCN。
此文提出了一种新的训练算法Cluster-GCN。实验结果表明,该方法可以在大型图上训练非常深的GCN,例如在一个超过200万个节点的图上,使用2G左右的内存训练时间不到1小时,精度达到90.41 (F1 score)。使用该方法,能够成功地训练更深入的GCNs,它可以在PPI数据集和Reddit数据集上获得最先进的测试F1 score。