GCN算法及实例分析

一.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 =jA 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)=D1AX
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()

在这里插入图片描述

  • 11
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值