图神经网络知识总结——归一化
本文以Graph WaveNet为主体,总结其使用到的(图)神经网络知识点以及相应代码实现方式。
对称归一化邻接矩阵
介绍
对称归一化邻接矩阵(Symmetrically normalize adjacency matrix),更适合无向图。
-
作用
- 将邻接矩阵归一化处理,使得每一行/列的和等于1(类比“数独”游戏),同时保持矩阵的对称性。
-
目的
- 度数偏差:指一些结点的度数较大,而另一些结点的度数较小。在使用未经过归一化的邻接矩阵进行训练时,结点的度数会对模型的训练产生影响,即度数较大的结点对应的特征在模型中占比较大,而度数较小的结点对应的特征占比较小,从而导致模型的偏差。
- 梯度消失:进行反向传播算法时,梯度可能会随层数的增加而变得非常小,导致模型参数难以更新。未经归一化的邻接矩阵的梯度消失问题十分明显。邻接矩阵中非常大的元素和非常小的元素会导致梯度计算时数值不稳定。
公式
\[\hat{A} = D^{-\frac{1}{2}}AD^{-\frac{1}{2}}
\]
- A:邻接矩阵。不要求A一定是对称矩阵,不过无向图往往都是对称矩阵。
- D:度矩阵。它是一个对称阵,对角线上的值表示\(v_i\)结点的度。\(D^{-\frac{1}{2}} = \frac{1}{\sqrt{D}}\)
代码
import numpy as np
import scipy.sparse as sp # 提供了一系列用于处理稀疏矩阵的线性代数操作和方法
from scipy.sparse import linalg
adj = sp.coo_matrix(adj) # 转为COO格式的稀疏矩阵,方便计算
rowsum = np.array(adj.sum(axis = 1)).flatten() # 沿列方向,对每一行进行求和。.flatten()将多维数组拉平为一维数组。目的:求结点的度
d_inv_sqrt = np.power(rowsum,-0.5) # np.power()对数组中的每一个元素做幂运算。
d_inv_sqrt[np.isinf(d_inv_sqrt)] = 0. # 因为求1/n(倒数),当n=0时,会是无穷大,所以将无穷大的值修改为0,避免无穷大。
d_mat_inv_sqrt = sp.diags(d_inv_sqrt) # 创建对角阵
result = adj.dot(d_mat_inv_sqrt).transpose().dot(d_mat_inv_sqrt).astypt(np.float32).todense() # 设置数据类型,转为密集型矩阵
代码讲解
函数方法
- COO格式的稀疏矩阵
\[adjacency
=
\begin{pmatrix}
0 & 8 & 0 \\
5 & 0 & 0 \\
0 & 0 & 3 \\
\end{pmatrix}
\]
三个列表分别表示数据、行索引、列索引。与数据结构中的稀疏矩阵三元组存储方式相同。
\[(data,row,col) = ([8,5,3],[0,1,2],[1,0,2])
\]
np.power()
计算数组各元素的幂。
a = np.array([1,2,3,4])
result = np.power(a,2)
print(result) # [1,4,9,16]
-
np.isinf()
:判断列表中各元素是否是无穷大,返回一个布尔值数组,[True,False,False,True]
。 -
\(.dot()\):矩阵乘法
-
.transpose()
可交换指定维度。对于二维矩阵,就是求转置。
import numpy as np
# 创建一个三维数组
array_3d = np.array([[[1, 2], [3, 4]],
[[5, 6], [7, 8]]])
print("原始三维数组:")
print(array_3d)
# 使用 .transpose() 方法进行转置,改变维度顺序
transposed_array_3d = array_3d.transpose(1, 0, 2)
print("\n转置后的三维数组:")
print(transposed_array_3d)
"""
原始三维数组:
[[[1 2]
[3 4]]
[[5 6]
[7 8]]]
转置后的三维数组:
[[[1 2]
[5 6]]
[[3 4]
[7 8]]]
"""
疑问与解答
adj.dot(d_mat_inv_sqrt).transpose().dot(d_mat_inv_sqrt).astypt(np.float32).todense()
为什么代码实现中使用到了转置,而原公式中没有转置?
对应不同的代码实现方式,通过公式推导可以证明是一致的。d_mat_inv_sqrt.dot(adj).dot(d_mat_inv_sqrt).astypt(np.float32).todense()
,这种写法与公式相同,更容易理解。
\[(A D^{-\frac{1}{2}})^T D^{-\frac{1}{2}} = (D^{-\frac{1}{2}})^T A^T D^{-\frac{1}{2}} = D^{-\frac{1}{2}} A D^{-\frac{1}{2}}
\]
\(A\)和\(D\)都是对称阵, \(A^T = A\)、\((D^{-\frac{1}{2}})^T = D^{-\frac{1}{2}}\)
非对称归一化邻接矩阵
介绍
非对称归一化邻接矩阵(Asymmetrically normalize adjacency matrix),更适合有向图。
- 目的
- 消除结点度数对聚合信息的影响,让每个结点在进行特征聚合时考虑到自身的度数。
公式
\[\hat{A} = D^{-1} A
\]
- A:邻接矩阵。不要求A一定是非对称矩阵,不过有向图往往都是非对称矩阵。
- D:度矩阵。它是一个对称阵,对角线上的值表示\(v_i\)结点的度。
代码
详情参照对称归一化邻接矩阵。方法几乎相同。
adj = sp.coo_matrix(adj)
rowsum = np.array(adj.sum(axis = 1)).flatten()
d_inv = np.power(rowsum, -1)
d_inv[np.isinf(d_inv)] = 0.
d_mat = sp.diags(d_inv)
result = d_mat.dot(adj).astype(np.float32).todense()
归一化拉普拉斯矩阵
介绍
详细内容可参考GCN图卷积网络全面理解和GCN图卷积网络本质理解
公式
- 组合拉普拉斯矩阵(Combinatorial Laplacian)
\[L = D - A
\]
- 对称归一化拉普拉斯矩阵(Symmetric Normalized Laplacian)
\[L = D^{-\frac{1}{2}} (I - A) D^{-\frac{1}{2}} = I - D^{-\frac{1}{2}} A D^{-\frac{1}{2}}
\]
很多GCN的论文中应用的就是这种拉普拉斯矩阵。
- 随机游走归一化拉普拉斯矩阵(Random Walk Normalized Laplacian)
\[L = D^{-1}L = I - D^{-1}A
\]