一.GCN前瞻知识
1.图卷积计算公式:
H
(
l
+
1
)
=
σ
(
D
~
−
1
2
A
~
D
~
−
1
2
H
(
l
)
W
(
l
)
)
\mathrm{H}^{(l+1)}=σ(\widetilde{D}^{-\frac{1}{2}}\widetilde{A}\widetilde{D}^{-\frac{1}{2}}H^{(l)}W^{(l)})
H(l+1)=σ(D
−21A
D
−21H(l)W(l))
A
~
=
A
+
I
\widetilde{A}=A+I
A
=A+I
D
~
=
∑
j
A
~
i
j
\widetilde{D}=\sum_j\widetilde{A}_{ij}
D
=j∑A
ij
H
(
l
)
H^{(l)}
H(l):节点在
l
l
l层的特征向量
W
(
l
)
W^{(l)}
W(l):第
l
l
l层卷积的参数
A
~
\widetilde{A}
A
:特征矩阵的邻接矩阵
D
~
\widetilde{D}
D
:邻接矩阵的度矩阵
I
I
I:单位矩阵
σ:激活函数
A
~
\widetilde{A}
A
D
~
\widetilde{D}
D
:拉普拉斯矩阵
2.邻接矩阵:邻接矩阵表示顶点间关系,是n阶方阵。
邻接矩阵分为有向图邻接矩阵和无向图邻接矩阵。无向图邻接矩阵是对称矩阵且对角线一定为零,而有向图的邻接矩阵不一定对称。
两点之间有几根线则为几无线则为0。
3.度矩阵:度矩阵是对角阵,对角上的元素为各个顶点的度。顶点vi的度表示和该顶点相关联的边的数量。
无向图中顶点vi的度d(vi)=N(i)。
∆
(
ζ
)
=
[
d
(
v
1
)
0
0
0
d
(
v
2
)
0
0
0
d
(
v
3
)
]
∆(\zeta)=\begin{gathered} \begin{bmatrix}d(v_1)&0&0\\0&d(v_2)&0\\0&0&d(v_3)&\end{bmatrix} \end{gathered}
∆(ζ)=⎣⎡d(v1)000d(v2)000d(v3)⎦⎤
有向图中,顶点vi的度分为顶点vi的出度和入度,即从顶点vi出去的有向边的数量和进入顶点vi的有向边的数量
4.单位矩阵:对角线都是1的矩阵。
[
1
0
0
0
1
0
0
0
1
]
\begin{gathered} \begin{bmatrix}1&0&0\\0&1&0\\0&0&1\end{bmatrix} \end{gathered}
⎣⎡100010001⎦⎤
5.熵: 就是描述信息的不确定的程度,统计学中,对事件的 发生情况可以通过概率P定量的描述出来,熵也是一种统计学 定量描述,是对信息的不确定程度的描述,这种描述也是通过“概率P”来描述的。
计算公式:
H
(
X
)
=
−
∑
i
=
1
n
p
(
x
i
)
l
o
g
(
p
(
x
i
)
)
H(X)=-\sum^n_{i=1}p(x_i)log(p(x_i))
H(X)=−∑i=1np(xi)log(p(xi))
6.交叉熵:主要度量两个概率分布间的差异性信息。
计算公式:
H
(
p
,
q
)
=
−
∑
i
=
1
n
p
(
x
i
)
l
o
g
(
q
(
x
i
)
)
H(p,q)=-\sum^n_{i=1}p(x_i)log(q(x_i))
H(p,q)=−∑i=1np(xi)log(q(xi))
二.GCN概述
1.欧几里得结构:CNN处理的数据是矩阵形式,就是以像素点排列成的矩阵为基础。称为Euclidean Structure,欧几里得结构。
拓扑结构(图结构):GCN处理的数据是图结构,即Non Euclidean Structure非欧几里得结构,拓扑结构。如社交网络连接,信息网络等等。对于Non euclidean structure的数据,卷积神经网络就没有用了。
2.GCN 主要是将卷积操作应用到拓扑结构上,如下图所示,GCN 输入的 chanel 为 C (即节点 Xi 特征向量的维度),GCN 输出的 chanel 为 F,即每个节点 (Zi) 的特征向量维度为 F,最后用节点的特征对节点进行分类预测等:
3.特征提取:对图的特征提取分为vertex domain(spatial domain)空域和spectral domain频域。(GCN用的是频域,即通过拉普拉斯变换和傅里叶变换进行处理)
三.对于GCN的理解
以此图为例:
1.构建邻接矩阵A:
import numpy as np
A = np.matrix([
[0, 1, 0, 0],
[0, 0, 1, 1],
[0, 1, 0, 0],
[1, 0, 1, 0]],
dtype=float
)
2.获取特征矩阵X:
X = np.matrix([
[i, -i]
for i in range(A.shape[0])
], dtype=float)
X
//输出结果:matrix(
// [0.,0.];
// [1.,-1.];
// [2.,-2.];
// [3.,-3.];)
3.A*X
A*X
//输出结果:matrix(
// [1.,-1.];
// [5.,-5.];
// [1.,-1.];
// [2.,-2.];)
每个节点的表示(每一行)现在等于他的邻居的特征的和。图卷积层将每个节点表示成它的邻居的总数。
4.加入自循环
目的:<font A*X 的结点表示中,并没有加自己的特征值。> font color=#cc0033一个节点的聚集表示不包括它自己的特征!这个表示只是它的邻居节点特征的聚集,所以只有有一个自循环的节点才会包括它自己的特征在聚集中。
I = np.matrix(np.eye(A.shape[0]))
I
A_hat = A + I
A_hat * X
//输出结果:matrix(
// [1.,-1.];
// [6.,-6.];
// [3.,-3.];
// [5.,-5.];)
5.归一化
特征表示可以通过节点的度来进行归一化,方法是将邻接矩阵 A 转换为 A 和度矩阵 D 的逆的乘积。
f
(
X
,
A
)
=
D
−
1
A
X
f(X,A)=D^{-1}AX
f(X,A)=D−1AX
1.计算度矩阵
D = np.array(np.sum(A,axis=0))[0]
D = np.matrix(np.diag(D))
D
//输出结果:matrix(
// [1.,0.,0.,0.];
// [0.,2.,0.,0.];
// [0.,0.,2.,0.];
// [0.,0.,0.,1.];)
2.归一化
D**-1*A*X
//输出结果:matrix(
// [1.,-1.];
// [2.5.,-2.5.];
// [0.5.,-0.5.];
// [2.,-2.];)
我们将邻居节点特征均值作为节点的表示。
5.加入权重
注意到这里 D_hat 是矩阵 A_hat = A + I 的度矩阵。
A_hat = A + I
D_hat=np.array(np.sum(A_hat,axis=0))[0]
D_hat=np.matrix(np.diag(D_hat))
D_hat
W = np.matrix([
[1, -1],
[-1, 1]
])
D_hat**-1 * A_hat * X * W
//输出结果:matrix(
// [1.,-1.];
// [4.,-4.];
// [2.,-2.];
// [5.,-5.];)
如果我们想要减少输出特征表示的维度,我们可以减少权重矩阵的规模:
W = np.matrix([
[1],
[-1]
])
5.加入激活函数(Relu函数)
relu(D_hat**-1 * A_hat * X * W)
//输出结果:matrix(
// [1.,0.];
// [4.,0.];
// [2.,0.];
// [5.,0.];)
以上就是一个带有邻接矩阵,输入特征,权重和激活函数的完整的隐藏层。
四.实例分析
Zachary 空手道俱乐部是一个被广泛使用的社交网络,其中的节点代表空手道俱乐部的成员,边代表成员之间的相互关系。当年,Zachary 在研究空手道俱乐部的时候,管理员和教员发生了冲突,导致俱乐部一分为二。下图显示了该网络的图表征,其中的节点标注是根据节点属于俱乐部的哪个部分而得到的,[蓝色]和[红色]分别表示属于管理员和
教员阵营的节点。
Python实现:
#构建GCN
from networkx import to_numpy_matrix
import networkx as nx
import numpy as np
import matplotlib.pyplot as plt
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#加入self-loops:A*X(特征集合) 的结点表示中,并没有加自己的特征值。
D_hat = np.array(np.sum(A_hat, axis=0))[0]#计算度矩阵
D_hat = np.matrix(np.diag(D_hat))
//定义plot_graph
def plot_graph(G, weight_name=None):
plt.figure()
pos = nx.spring_layout(G)
edges = G.edges()
weights = None
if weight_name:
weights = [int(G[u][v][weight_name]) for u,v in edges]
labels = nx.get_edge_attributes(G,weight_name)
nx.draw_networkx_edge_labels(G,pos,edge_labels=labels)
nx.draw_networkx(G, pos, edges=edges, width=weights);
else:
nodelist1 = []
nodelist2 = []
for i in range (34):
if zkc.nodes[i]['club'] == 'Mr. Hi':
nodelist1.append(i)
else:
nodelist2.append(i)
#nx.draw_networkx(G, pos, edges=edges);
nx.draw_networkx_nodes(G, pos, nodelist=nodelist1, node_size=300, node_color='r',alpha = 0.8)
nx.draw_networkx_nodes(G, pos, nodelist=nodelist2, node_size=300, node_color='b',alpha = 0.8)
nx.draw_networkx_edges(G, pos, edgelist=edges,alpha =0.4)
plot_graph(zkc)
#添加权重
W_1 = np.random.normal(
loc=0, scale=1, size=(zkc.number_of_nodes(), 4))
W_2 = np.random.normal(
loc=0, size=(W_1.shape[1], 2))
#定义激活函数relu
def relu(x):
return (abs(x) + x) / 2
#计算
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
feature_representations = {
node: np.array(output)[node]
for node in zkc.nodes()}
feature_representations
{0: array([0.1147038, 0. ]),
1: array([0.14167385, 0. ]),
2: array([0.21506346, 0. ]),
3: array([0.17264544, 0. ]),
4: array([0.07014692, 0. ]),
5: array([0.07839704, 0. ]),
6: array([0.09259637, 0. ]),
7: array([0.18973729, 0. ]),
8: array([0.1060538, 0. ]),
9: array([0.24986647, 0. ]),
10: array([0.07100438, 0. ]),
11: array([0.19087212, 0. ]),
12: array([0.18105727, 0. ]),
13: array([0.18164152, 0. ]),
14: array([0.11193338, 0. ]),
15: array([0.18693482, 0. ]),
16: array([0.07581785, 0. ]),
17: array([0.14319508, 0. ]),
18: array([0.15272265, 0. ]),
19: array([0.23208766, 0. ]),
20: array([0.1166792, 0. ]),
21: array([0.12611112, 0. ]),
22: array([0.19804465, 0. ]),
23: array([0.13229868, 0. ]),
24: array([0.07667983, 0. ]),
25: array([0., 0.]),
26: array([0.34727446, 0.0181446 ]),
27: array([0.1333751, 0. ]),
28: array([0.22384532, 0. ]),
29: array([0.21192201, 0. ]),
30: array([0.12617326, 0. ]),
31: array([0.14564459, 0. ]),
32: array([0.10218087, 0. ]),
33: array([0.19122005, 0. ])}
plt.figure()
for i in range (34):
if zkc.nodes[i]['club'] == 'Mr. Hi':
plt.scatter(np.array(output)[i,0],np.array(output)[i,1] ,color = 'b',alpha=0.5,s = 100)
else:
plt.scatter(np.array(output)[i,0],np.array(output)[i,1] ,color = 'r',alpha=0.5,s = 100)
plt.show()