##图卷积网络的简单是实现
1.图卷积神经网络
GCN 是一类非常强大的用于图数据的神经网络架构。事实上,它非常强大,即使是随机初始化的两层 GCN 也可以生成图网络中节点的有用特征表征。下图展示了这种两层 GCN 生成的每个节点的二维表征。请注意,即使没有经过任何训练,这些二维表征也能够保存图中节点的相对邻近性。
更形式化地说,图卷积网络(GCN)是一个对图数据进行操作的神经网络。给定图 G = (V, E),GCN 的输入为:
1,一个输入维度为 N × F⁰ 的特征矩阵 X,其中 N 是图网络中的节点数而 F⁰ 是每个节点的输入特征数。
2,一个图结构的维度为 N × N 的矩阵表征,例如图 G 的邻接矩阵 A。[1]
因此,GCN 中的隐藏层可以写作 Hⁱ = f(Hⁱ⁻¹, A))。其中,H⁰ = X,f 是一种传播规则 [1]。每一个隐藏层 Hⁱ 都对应一个维度为 N × Fⁱ 的特征矩阵,该矩阵中的每一行都是某个节点的特征表征。在每一层中,GCN 会使用传播规则 f 将这些信息聚合起来,从而形成下一层的特征。这样一来,在每个连续的层中特征就会变得越来越抽象。在该框架下,GCN 的各种变体只不过是在传播规则 f 的选择上有所不同 [1]。
2.propation
f(Hⁱ, A) = σ(AHⁱWⁱ)
其中,Wⁱ 是第 i 层的权重矩阵,σ 是非线性激活函数(如 ReLU 函数)。权重矩阵的维度为 Fⁱ × Fⁱ⁺¹,即权重矩阵第二个维度的大小决定了下一层的特征数。如果你对卷积神经网络很熟悉,那么你会发现由于这些权重在图中的节点间共享,该操作与卷积核滤波操作类似。
简化
接下来我们在最简单的层次上研究传播规则。令:
1,i = 1,(约束条件 f 是作用于输入特征矩阵的函数)
2,σ 为恒等函数
3,选择权重(约束条件: AH⁰W⁰ =AXW⁰ = AX)
换言之,f(X, A) = AX。该传播规则可能过于简单,本文后面会补充缺失的部分。此外,AX 等价于多层感知机的输入层。
numpy 编写
A = np.matrix([
[0, 1, 0, 0],
[0, 0, 1, 1],
[0, 1, 0, 0],
[1, 0, 1, 0]],
dtype=float
)
In [3]: X = np.matrix([
[i, -i]
for i in range(A.shape[0])
], dtype=float)
X
Out[3]: matrix([
[ 0., 0.],
[ 1., -1.],
[ 2., -2.],
[ 3., -3.]
])
现在已经建立了一个图,其邻接矩阵为 A,输入特征的集合为 X。下面让我们来看看,当我们对其应用传播规则后会发生什么:
我们现在已经建立了一个图,其邻接矩阵为 A,输入特征的集合为 X。下面让我们来看看,当我们对其应用传播规则后会发生什么
In [6]: A * X
Out[6]: matrix([
[ 1., -1.],
[ 5., -5.],
[ 1., -1.],
[ 2., -2.]]
每个节点的表征(每一行)现在是其相邻节点特征的和!换句话说,图卷积层将每个节点表示为其相邻节点的聚合。大家可以自己动手验证这个计算过程。请注意,在这种情况下,如果存在从 v 到 n 的边,则节点 n 是节点 v 的邻居
3.问题
1,节点的聚合表征不包含它自己的特征!该表征是相邻节点的特征聚合,因此只有具有自环(self-loop)的节点才会在该聚合中包含自己的特征 [1]。
2,度大的节点在其特征表征中将具有较大的值,度小的节点将具有较小的值。这可能会导致梯度消失或梯度爆炸 [1, 2],也会影响随机梯度下降算法(随机梯度下降算法通常被用于训练这类网络,且对每个输入特征的规模(或值的范围)都很敏感)。
4.解决方法
(1)增加自环
In [4]: I = np.matrix(np.eye(A.shape[0]))
I
Out[4]: matrix([
[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 1.]
])
In [8]: A_hat = A + I
A_hat * X
Out[8]: matrix([
[ 1., -1.],
[ 6., -6.],
[ 3., -3.],
[ 5., -5.]])
(2)对特征表征进行归一化处理
通过将邻接矩阵 A 与度矩阵 D 的逆相乘,对其进行变换,从而通过节点的度对特征表征进行归一化。因此,我们简化后的传播规则如下:
f(X, A) = D⁻¹AX
让我们看看发生了什么。我们首先计算出节点的度矩阵。注意:此处计算节点的度是用节点的入度,也可以根据自身的任务特点用出度,在本文中,这个选择是任意的。一般来说,您应该考虑节点之间的关系是如何与您的具体任务相关。例如,您可以使用in-degree来进行度矩阵计算,前提是只有关于节点的in-neighbors的信息与预测其具体任务中的标签相关。相反,如果只有关于外部邻居的信息是相关的,则可以使用out-degree。最后,如果节点的out-和in-邻居都与您的预测相关,那么您可以基于in-和out-度的组合来计算度矩阵。
In [9]: D = np.array(np.sum(A, axis=0))[0]
D = np.matrix(np.diag(D))
D
Out[9]: matrix([
[1., 0., 0., 0.],
[0., 2., 0., 0.],
[0., 0., 2., 0.],
[0., 0., 0., 1.]
])
可以观察到,邻接矩阵中每一行的权重(值)都除以该行对应节点的度。我们接下来对变换后的邻接矩阵应用传播规则:
得到与相邻节点的特征均值对应的节点表征。这是因为(变换后)邻接矩阵的权重对应于相邻节点特征加权和的权重。大家可以自己动手验证这个结果。
5.添加权重
首先要做的是应用权重。请注意,这里的 D_hat 是 A_hat = A + I 对应的度矩阵,即具有强制自环的矩阵 A 的度矩阵
In [45]: W = np.matrix([
[1, -1],
[-1, 1]
])
D_hat**-1 * A_hat * X * W
Out[45]: matrix([
[ 1., -1.],
[ 4., -4.],
[ 2., -2.],
[ 5., -5.]
])
如果我们想要减小输出特征表征的维度,我们可以减小权重矩阵 W 的规模:
In [46]: W = np.matrix([
[1],
[-1]
])
D_hat**-1 * A_hat * X * W
Out[46]: matrix([[1.],
[4.],
[2.],
[5.]]
)
6.添加激活函数:
Relu函数的公式是f(x)=max(0,x),代码为:
def relu(x):
return (abs(x) + x) / 2
一个带有邻接矩阵、输入特征、权重和激活函数的完整隐藏层如下:
In [51]: W = np.matrix([
[1, -1],
[-1, 1]
])
relu(D_hat**-1 * A_hat * X * W)
Out[51]: matrix([[1., 0.],
[4., 0.],
[2., 0.],
[5., 0.]])
典例:空手道俱乐部数据集
import networx as nx
from networkx import to_numpy_matrix
zkc = nx.karate_club_graph()
order = sorted(list(zkc.nodes()))
A = to_numpy_matrix(zkc, nodelist=order)
I = np.eye(zkc.number_of_nodes())
A_hat = A + I
D_hat = np.array(np.sum(A_hat, axis=0))[0]
D_hat = np.matrix(np.diag(D_hat))
W_1 = np.random.normal(
loc=0, scale=1, size=(zkc.number_of_nodes(), 4))
W_1
array([[-1.60850738, -1.81286031, -1.21488112, -0.67512344],
[ 0.4728306 , 0.48590465, 0.46768218, -1.28452497],
[ 0.48404091, 0.36123352, -0.28535282, 1.3567935 ],
[ 0.5454595 , 1.47414699, -0.2436099 , 1.8448905 ],
[ 0.35601084, 0.17007017, -1.08012856, 0.12243646],
[ 1.08218031, -0.86061025, 0.2198768 , -0.88965361],
[ 0.77173117, 1.0753156 , 0.80771322, -0.44305522],
[ 2.32292136, -1.11497939, -0.08832334, -0.48259347],
[-1.14989919, -1.07299688, 0.35833233, -1.02271864],
[-1.26604098, 1.11403405, -1.27368651, 0.68473466],
[ 0.03711537, -0.75404804, 2.41704567, -0.26348625],
[-0.82774307, -1.27096915, 0.01414155, -0.26087749],
[-0.47360135, 2.2111597 , -0.2785656 , -1.19886492],
[-0.39920476, 0.37976039, 0.37631679, -0.85434706],
[-1.06777751, 1.37249251, -1.5528223 , -0.7082742 ],
[ 1.21119404, -1.00086211, 0.79906034, 1.51296967],
[-1.93495147, -1.41661229, -1.46847639, -0.88607286],
[-0.57851013, 0.91926198, -0.4840015 , 0.3120885 ],
[-0.1431892 , 0.66630315, 0.37068425, 0.65605851],
[ 1.73521822, -1.78790492, -0.08257555, 1.30993567],
[-1.45908455, 0.12757605, 0.57191581, 0.02318818],
[ 0.7415613 , 1.26621066, -0.51246359, 0.8104835 ],
[ 0.91050774, 0.05019478, -0.95328867, -0.17870357],
[ 1.208966 , 0.04708878, 0.2219424 , -1.04159587],
[ 0.1054095 , -0.41581914, -1.09173746, -0.55462029],
[-1.21917691, 0.9778717 , -0.54145143, -2.0725276 ],
[-0.61375621, 0.98126815, 0.07500444, -0.76939277],
[-0.94490006, 1.98768759, 0.1917666 , -0.64337003],
[-1.47937171, 0.09036739, 0.57247029, 1.32377866],
[ 0.24979308, 0.65461977, 0.84397059, -2.47825603],
[-1.65513804, 0.49083851, -1.52508566, -1.12353088],
[-0.42206369, -0.54915933, -0.03701411, -1.96757786],
[ 0.75976155, 1.28285357, 0.44180894, 1.92307764],
[-1.0557052 , -0.7521497 , -1.97767775, 0.01836153]])
W_2 = np.random.normal(
loc=0, size=(W_1.shape[1], 2))
W_2
array([[-0.96457918, 0.45900237],
[-0.55060714, -1.08875363],
[-1.07701631, -0.71475231],
[ 0.45233694, -0.26491149]])
def gcn_layer(A_hat, D_hat, X, W):
return relu(D_hat**-1 * A_hat * X * W)
H_1 = gcn_layer(A_hat, D_hat, I, W_1)
H_2 = gcn_layer(A_hat, D_hat, H_1, W_2)
output = H_2
output
matrix([[0.17394349, 0.12613323],
[0.21514969, 0.1248979 ],
[0.2200705 , 0.07164891],
[0.20434201, 0.16982548],
[0.17972395, 0.26591497],
[0.13314916, 0.17843399],
[0.08887072, 0.11894365],
[0.24585268, 0.25294519],
[0.17443997, 0.14387933],
[0.27246705, 0.07764017],
[0.18066635, 0.25648615],
[0.2912415 , 0.46735709],
[0.23301799, 0.20337298],
[0.20719444, 0.21210805],
[0.10766761, 0.02108594],
[0.1048165 , 0.02436026],
[0.01142164, 0.0006152 ],
[0.23678846, 0.06878243],
[0.14423298, 0. ],
[0.16002963, 0.10097575],
[0.13788295, 0.11606215],
[0.34037411, 0.27910508],
[0.14169454, 0.09565058],
[0.16444515, 0. ],
[0.13790554, 0. ],
[0.13951285, 0. ],
[0.08406175, 0.04714129],
[0.18639611, 0.04326001],
[0.20328647, 0.26678944],
[0.11548443, 0.04857866],
[0.11449148, 0. ],
[0.17931359, 0.1911088 ],
[0.14008594, 0. ],
[0.14183489, 0. ]])