对图卷积网络的公式,已经非常熟悉了,并且对公式表示的意思也能理解即:实现图中节点之间的消息传递或者称做特征传递。但是在dgl框架的学习过程中,对于单向二部图的图卷积操作的使用过程中,需要深入的理解图邻接矩阵的对称归一化操作也即图的拉普拉斯正则。
H ( l + 1 ) = σ ( D ~ − 1 / 2 A ~ D ~ − 1 / 2 H l W l ) H^{(l+1)}=\sigma\left(\tilde{D}^{-1 / 2} \tilde{A} \tilde{D}^{-1 / 2} H^{l} W^{l}\right) H(l+1)=σ(D~−1/2A~D~−1/2HlWl)
其中 D ~ = D + I , A ~ = A + I \tilde{D}=D+I, \tilde{A}=A+I D~=D+I,A~=A+I。
通过查询各个博主的观点,图卷积的对称归一化主要有以下意义:
(图来自参考文献[7])
A = { 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 } , D = { 2 0 0 0 0 0 0 3 0 0 0 0 0 0 2 0 0 0 0 0 0 3 0 0 0 0 0 0 3 0 0 0 0 0 0 1 } A=\left\{\begin{array}{llllll} 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 \end{array}\right\}, D=\left\{\begin{array}{llllll} 2 & 0 & 0 & 0 & 0 & 0 \\ 0 & 3 & 0 & 0 & 0 & 0 \\ 0 & 0 & 2 & 0 & 0 & 0 \\ 0 & 0 & 0 & 3 & 0 & 0 \\ 0 & 0 & 0 & 0 & 3 & 0 \\ 0 & 0 & 0 & 0 & 0 & 1 \end{array}\right\} A=⎩ ⎨ ⎧010010101010010100001011110100000100⎭ ⎬ ⎫,D=⎩ ⎨ ⎧200000030000002000000300000030000001⎭ ⎬ ⎫
A ~ = A + I N = { 1 1 0 0 1 0 1 1 1 0 1 0 0 1 1 1 0 0 0 0 1 1 1 1 1 1 0 1 1 0 0 0 0 1 0 1 } , D ~ = ∑ j A ~ i j = D + I N = { 3 0 0 0 0 0 0 4 0 0 0 0 0 0 3 0 0 0 0 0 0 4 0 0 0 0 0 0 4 0 0 0 0 0 0 2 } \tilde{A}=A+I_{N}=\left\{\begin{array}{llllll} 1 & 1 & 0 & 0 & 1 & 0 \\ 1 & 1 & 1 & 0 & 1 & 0 \\ 0 & 1 & 1 & 1 & 0 & 0 \\ 0 & 0 & 1 & 1 & 1 & 1 \\ 1 & 1 & 0 & 1 & 1 & 0 \\ 0 & 0 & 0 & 1 & 0 & 1 \end{array}\right\}, \tilde{D}=\sum_{j} \tilde{A}_{i j}=D+I_{N}=\left\{\begin{array}{cccccc} 3 & 0 & 0 & 0 & 0 & 0 \\ 0 & 4 & 0 & 0 & 0 & 0 \\ 0 & 0 & 3 & 0 & 0 & 0 \\ 0 & 0 & 0 & 4 & 0 & 0 \\ 0 & 0 & 0 & 0 & 4 & 0 \\ 0 & 0 & 0 & 0 & 0 & 2 \end{array}\right\} A~=A+IN=⎩ ⎨ ⎧110010111010011100001111110110000101⎭ ⎬ ⎫,D~=j∑A~ij=D+IN=⎩ ⎨ ⎧300000040000003000000400000040000002⎭ ⎬ ⎫
import numpy as np
A = np.array([[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]])
A_ = A + np.eye(A.shape[0])
print(A_)
print(A_.sum(axis=1))
# print(A_.sum(axis=0))
# D = np.diag(1/np.sqrt(A_.sum(axis=1)))
# print("D", D)
# print(A_ / np.sqrt(A_.sum(axis=1)))
C = A_ / A_.sum(axis=1)
print("---")
print("C", C)
print(C.T / np.sqrt(A_.sum(axis=0)))
# R = D @ A_ @ D
# print(R)
从文献【4】的角度,对称归一化可以从行的标准化合列的标准化两个方面理解。
(1)首先是横向标准化,这一步的意义从深度学习的角度来说是很容易理解的,因为graph中,不同节点之间的edge的weight差异可能很大,比如节点0和其他节点的edge的weights范围在0~1之间,而节点1和其他节点的weights范围在100~1000之间,对于金融中通过user之间的交易关系来构建的图尤其如此,交易的金额作为weights则不同用户之间的交易金额(即edge)权重差异可能非常大。这对于nn的训练来说问题比较大,比如GCN处理有权图问题,不对邻接矩阵进行横向标准化,则sum之后,不同节点的领域的sum结果量纲差异可能会很大,比如节点A和领域的edge weights范围在0~1,sum的结果可能也就是10以内,节点B和领域的edge weights范围在1000~10000,sum的结果就非常大了,这样gnn中做节点非线性变换的dense层会很头疼,难收敛啊。
那么显然,我们做个横向标准化就可以解决这样的问题了,因为对于GNN来说,我们无非是想学习到所有节点和领域的某种隐藏的关联模式,而对于不同用户来说,edge weights的绝对大小不重要,相对大小才重要。这和时间序列预测中的多序列预测问题是一样的,不同商品的销量的绝对值差异很大,但是我们要学习到的是相对的模式而不是绝对的模式,因此在多序列的时间序列预测问题中也常常会做横向标准化,即将所有商品的销量放缩到大致相同的区间。
(2)列的标准化,这里也非常有意思,我们做纵向标准化实际上是希望将节点的领节点对其的“贡献”进行标准化。比如说脉脉上的用户之间的关注关系就是天然的同构图,假设我们要做节点分类判定某个用户A是不是算法工程师,并且假设A用户仅仅和另一个算法工程师B以及10个猎头有关注的关系,直观上,猎头对用户A的节点类别的判定的贡献应该是很小的,因为猎头往往会和算法,开发,测试,产品等不同类型的用户有关联关系,他们对于用户A的“忠诚关联度”是很低的,而对于算法工程师B而言,假设他仅仅和A有关联, 那么明显,B对A的“忠诚关联度”是很高的,B的node features以及B和A的关联关系在对A进行节点分类的时候应该是更重要的。
那么,纵向标准化就较好的考虑到了上面的问题,思想很简单,假设节点1和节点2有一个权重为1的edge相连,节点2和其他1000个节点也有关联,那么节点2对于节点1的贡献度为1/1000,即用edge weights除以节点2的度(有权图上用加权度)。
而对称归一化拉普拉斯矩阵,实际上是横纵向标准化之后又开了根号,开根号不影响计算结果的相对大小,不改变实际的物理意义。
因此,对称归一化拉普拉斯矩阵可以看作是对原始的邻接矩阵的一种横纵向标准化。(注:上述的计算是包括了自环的,不影响理解)
但是按照文献【4】给出的计算的例子从矩阵计算的角度,还是不理解行列的标准化,有理解的朋友评论区留言。
采用加法规则时,对于度大的节点特征越来越大,而对于度小的节点却相反,这可能导致网络训练过程中梯度爆炸或者消失的问题。(PageRank的迭代优化过程)。在不考虑对称归一化时, D − 1 A ^ \hat{D^{-1}A} D−1A^是最简单的归一化方式。但是这样得到的矩阵是非对称阵, 所以出现了如下的对称归一化的形式。
L ^ s y m = D ^ − 1 / 2 A ^ D ^ − 1 / 2 A ^ = A + I D ^ = D + I \begin{array}{l} \hat{L}_{s y m}=\hat{D}^{-1 / 2} \hat{A} \hat{D}^{-1 / 2} \\ \hat{A}=A+I \\ \hat{D}=D+I \end{array} L^sym=D^−1/2A^D^−1/2A^=A+ID^=D+I
我们先不考虑,对称归一化,先看一下在传统归一化下,矩阵乘法到底做了什么。
( D ~ − 1 A ~ H ) i = ( D ~ − 1 A ~ ) i H = ( ∑ k D ~ i k − 1 A ~ i ) H = ( D ~ i i − 1 A ~ i ) H = D ~ i i − 1 ∑ j A ~ i j H j = ∑ j 1 D ~ i i A ~ i j H j \begin{aligned} \left(\tilde{D}^{-1} \tilde{A} H\right)_{i} &=\left(\tilde{D}^{-1} \tilde{A}\right)_{i} H \\ &=\left(\sum_{k} \tilde{D}_{i k}^{-1} \tilde{A}_{i}\right) H \\ &=\left(\tilde{D}_{i i}^{-1} \tilde{A}_{i}\right) H \\ &=\tilde{D}_{i i}^{-1} \sum_{j} \tilde{A}_{i j} H_{j} \\ &=\sum_{j} \frac{1}{\tilde{D}_{i i}} \tilde{A}_{i j} H_{j} \end{aligned} (D~−1A~H)i=(D~−1A~)iH=(k∑D~ik−1A~i)H=(D~ii−1A~i)H=D~ii−1j∑A~ijHj=j∑D~ii1A~ijHj
上式中, i i i是行 k k k是列,由于 D D D是对角矩阵只有对角线才有值所以直接取 i i i。这种聚合方式实际上就是在对邻居求和取平均。从近似归一化的角度理解 D ^ − 1 / 2 A ^ D ^ − 1 / 2 \hat{D}^{-1 / 2} \hat{A} \hat{D}^{-1 / 2} D^−1/2A^D^−1/2,这种归一化方式,将不再单单地对领域节特征点取平均,它不仅考虑了节点i对度,也考虑了邻接节点j的度,当邻居节点j度数较大时,它在聚合时贡献地会更少。这也比较好理解,存在B->A<-C<-[D,E,F] 这么一个图,当使用这种归一化矩阵,在对A这个节点的特征进行更新时,B占的权重会更大,C占的权重会更小。因为B对A产生影响,与A对B产生对影响是一样的(无向图),由于B只有A这个邻居,所以A对B影响很大;同样地,C对A产生影响,与A对C产生对影响是一样的,因为C有很多节点,所以A对C节点对影响没那么大,即C对A节点对影响没那么大。【参考文献8】
( D ~ − 0.5 A ~ D ~ − 0.5 H ) i = ( D ~ − 0.5 A ~ ) i D ~ − 0.5 H = ( ∑ k D ~ i k − 0.5 A ~ i ) D ~ − 0.5 H = D ~ i i − 0.5 ∑ j A ~ i j ∑ k D ~ j k − 0.5 H j = D ~ i i − 0.5 ∑ j A ~ i j D ~ j j − 0.5 H j = ∑ j 1 D ~ i i D ~ j j A ~ i j H j \begin{aligned} \left(\tilde{D}^{-0.5} \tilde{A} \tilde{D}^{-0.5} H\right)_{i} &=\left(\tilde{D}^{-0.5} \tilde{A}\right)_{i} \tilde{D}^{-0.5} H \\ &=\left(\sum_{k} \tilde{D}_{i k}^{-0.5} \tilde{A}_{i}\right) \tilde{D}^{-0.5} H \\ &=\tilde{D}_{i i}^{-0.5} \sum_{j} \tilde{A}_{i j} \sum_{k} \tilde{D}_{j k}^{-0.5} H_{j} \\ &=\tilde{D}_{i i}^{-0.5} \sum_{j} \tilde{A}_{i j} \tilde{D}_{j j}^{-0.5} H_{j} \\ &=\sum_{j} \frac{1}{\sqrt{\tilde{D}_{i i} \tilde{D}_{j j}}} \tilde{A}_{i j} H_{j} \end{aligned} (D~−0.5A~D~−0.5H)i=(D~−0.5A~)iD~−0.5H=(k∑D~ik−0.5A~i)D~−0.5H=D~ii−0.5j∑A~ijk∑D~jk−0.5Hj=D~ii−0.5j∑A~ijD~jj−0.5Hj=j∑D~iiD~jj1A~ijHj
来自参考文献【9】
任何一个图卷积层都可以写成一个非线性函数:
H l + 1 = f ( H l , A ) H^{l+1}=f\left(H^{l}, A\right) Hl+1=f(Hl,A)
H 0 = X H^{0}=X H0=X 为第一层的输入, X ∈ R N × D X\in R^{N\times D} X∈RN×D , N N N为图中的节点个数, D D D为每个节点特征向量的维度, A A A为邻接矩阵。不同模型的差异点在于函数 f f f的实现不同。下面是几种图卷积的实现形式,但是每一种实现都统称拉普拉斯矩阵。
[1]【Graph Neural Network】GCN: 算法原理,实现和应用
[2]GCN实现及其中的归一化
[3]关于GCN对称归一化的手动推导
[4]对称归一化拉普拉斯矩阵的意义
[5]神经网络为什么要归一化
[6]神经网络输入数据预处理——数据标准化(归一化)——python
[7]最全面的图卷积网络GCN的理解和详细推导,都在这里了!
[8]GCN中的拉普拉斯矩阵如何归一化?
[9]一文读懂图卷积GCN