GCN(Graph convolution Network)是Convets在图结构上的自然推广。卷积神经网络是采用局部感知区域、共享权值和空间域上的降采样,相对于位移、缩放和扭曲,具有稳定不变的特性,能够很好的提取图像的空间特征。图结构不具备图片的平移不变性,传统的卷积方式不适用于图结构。图中每个节点的邻域节点数目不一致,无法用同样尺寸的卷积核进行提取特。
而GCN的本质就是提取图的结构特征,关键在于如何定义局部接受域(receptive field),主要有两种方式:
- Spatial approach 如何定义局部感受域或者是邻居和节点的顺序 比如给节点的边指定方向
- Spectral approach 通过图的拉普拉斯矩阵的特征值和特征向量对图结构进行处理。
简单的理论
是节点的集合,就是上图的V=[0,1,2,3,4,5] E是连接节点的边集合[1->2,2->4 …] 定义 为节点度的对角矩阵。GCN公式为
其中
上图一共有6个节点,可以通过下面代码生成
import networkx as nx
g=nx.random_graphs.watts_strogatz_graph(6,3,0.3)
colors = [1, 2, 3, 4, 5, 6]
nx.draw_kamada_kawai(g,with_labels=True,node_color=colors,alpha=0.7,node_size=600,font_size =18)
是图的邻接矩阵。
A=nx.adjacency_matrix(g).todense()
#
matrix([[0, 0, 0, 1, 0, 1],
[0, 0, 1, 0, 0, 0],
[0, 1, 0, 1, 1, 0],
[1, 0, 1, 0, 0, 1],
[0, 0, 1, 0, 0, 0],
[1, 0, 0, 1, 0, 0]], dtype=int64)
是节点的信息。现实中每个node都有自己的特征,在之家网站中,你可以把每个节点想象成user 或者 session,特征可以简单的理解为user 产生pv ,或者浏览时长等等。我们生成一些简单的feature ,方便大家更好的验证
X = np.matrix([
[i, -i]
for i in range(A.shape[0])
], dtype=float)
matrix([[ 0., 0.],
[ 1., -1.],
[ 2., -2.],
[ 3., -3.],
[ 4., -4.],
[ 5., -5.]])
我们看一下
A*X
matrix([[ 8., -8.],
[ 2., -2.],
[ 8., -8.],
[ 7., -7.],
[ 2., -2.],
[ 3., -3.]])
很容易看出$AX$只是对邻接域节点进行sum. - 简单的sum对特征提取具有关于节点度的倾向性即节点度越大,聚合的信息就越大. - $A$中不存在self-loop 提取的信息不包含节点本身的信息
我们可以通过 解决self-loop问题
通过节点信息进行归一化
最终的对X的转换操作为
I=np.matrix(np.eye(A.shape[0])) #单位矩阵
A_hat=A+I
matrix([[ 8., -8.],
[ 3., -3.],
[ 10., -10.],
[ 10., -10.],
[ 6., -6.],
[ 8., -8.]])
D=np.matrix(np.diag(np.array(A_hat.sum(axis=0))[0]))
matrix([[3., 0., 0., 0., 0., 0.],
[0., 2., 0., 0., 0., 0.],
[0., 0., 4., 0., 0., 0.],
[0., 0., 0., 4., 0., 0.],
[0., 0., 0., 0., 2., 0.],
[0., 0., 0., 0., 0., 3.]])
D**-1*A_hat
matrix([[0.33333333, 0. , 0. , 0.33333333, 0. ,
0.33333333],
[0. , 0.5 , 0.5 , 0. , 0. ,
0. ],
[0. , 0.25 , 0.25 , 0.25 , 0.25 ,
0. ],
[0.25 , 0. , 0.25 , 0.25 , 0. ,
0.25 ],
[0. , 0. , 0.5 , 0. , 0.5 ,
0. ],
[0.33333333, 0. , 0. , 0.33333333, 0. ,
0.33333333]])
我们得到了特征提取的公式
当然你也可以尝试其他的归一化方式比如对称归一化$D^{-1} A \rightarrow D^{-\frac{1}{2}} \hat{A} D^{-\frac{1}{2}} $
而谱图理论所操作的对象是图的Laplacian矩阵 或者标准化Laplacian
是半正定矩阵有三个性质:
- 对称矩阵一定具有n个线性无关的特征向量
- 半正定矩阵的特征值一定非负
- 对称矩阵的特征向量相互正交即所有特征向量构成的矩阵为正交矩阵
是的特征值,是特征向量矩阵。 是对的图傅立叶变换。
将图的卷积操作定义为 与 其中 :
特征分解复杂度较高,Kipf通过切比雪夫多项式直到第k阶的阶段来展开近似来降低计算量
是的最大特征值 是切比雪夫系数向量
切比雪夫多项式被定义为
因此可以有如下的表达式
其中$\hat{L}=\frac{2}{\lambda_{max}}L-I_N $ 函数复杂度为
层次的线形模型
图卷积神经网络的模型可以由堆叠多层公式(5)形式的卷积层构成。先将卷积层限制在k=1即
现在取近似 则有
进一步通过约束参数的数量来避免过拟合,同时最小化每一层的操作数,得到如下:
即令(4)中$\theta =\theta^\prime_0 = - \theta^\prime_1 $ 但是的特征值的范围是在深度神经网络模型中,这个运算的复杂操作有可能导致数值的不稳定和梯度爆炸或者消失,为了解决这个问题,引入了再归一化其中
下面我们实现一个基于karate_club网络GCN demo
from networkx import karate_club_graph,to_numpy_matrix
import numpy as np
# step1: Data preparation
zkc=karate_club_graph()
order=sorted(list(zkc.nodes()))
NODE_SIZE=len(order)
#Adjacency matrix
A=to_numpy_matrix(zkc,nodelist=order)
#identity matrix
I=np.eye(zkc.number_of_nodes())
node_label = []
for i in range(34):
label = zkc.node[i]
if label['club'] == 'Officer':
node_label.append(1)
else:
node_label.append(0)
# step2: Parameter Settings
NODE_SIZE = 34
NODE_FEATURE_DIM = 34
HIDDEN_DIM1 = 10
num_classes = 2
training_epochs = 100
step = 10
lr=0.1
# step3: network define
X = tf.placeholder(tf.float32, shape=[NODE_SIZE, NODE_FEATURE_DIM])
Y = tf.placeholder(tf.int32, shape=[NODE_SIZE])
label = tf.one_hot(Y, num_classes)
Y_enc = tf.one_hot(Y, 2)
adj = tf.placeholder(tf.float32, shape=[NODE_SIZE, NODE_SIZE])
weights = {"hidden1": tf.Variable(tf.random_normal(dtype=tf.float32, shape=[NODE_FEATURE_DIM, HIDDEN_DIM1]), name='w1'),
"hidden2": tf.Variable(tf.random_normal(dtype=tf.float32, shape=[HIDDEN_DIM1, num_classes]), 'w1')}
D_hat = tf.matrix_inverse(tf.matrix_diag(tf.reduce_sum(adj, axis=0)))
# GCN layer1
l1 = tf.matmul(tf.matmul(tf.matmul(D_hat, adj), X), weights['hidden1'])
# GCN layer2
output = tf.matmul(tf.matmul(tf.matmul(D_hat, adj), l1), weights['hidden2'])
# step4:define loss func and train
loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(labels=Y_enc, logits=output))
train_op = tf.train.AdamOptimizer(learning_rate=lr).minimize(loss)
init_op = tf.global_variables_initializer()
feed_dict = {adj: A, X: I, Y: node_label}
with tf.Session() as sess:
sess.run(init_op)
# dynamic display
plt.ion()
for epoch in range(training_epochs):
c, _ = sess.run([loss, train_op], feed_dict)
if epoch % step == 0:
print(f'Epoch:{epoch} Loss {c}')
represent = sess.run(output, feed_dict)
plt.scatter(represent[:, 0], represent[:, 1], s=200, c=g.node_label)
plt.pause(0.1)
plt.cla()
模型效果如下
GCN 能够充分的整合节点的信息,借鉴PinSage,可以将item_id进行嵌入embedding,根据邻居节点的信息实现的嵌入会更加的准确和强大,能够发现文章、视频、图片之间的潜在语义关系,而不仅仅只是内容上的相似。