图卷积神经网络(GCN)理解与 tensorflow2.0 代码实现

图(Graph),一般用 tensorflow 一维卷积 训练 tensorflow图卷积_tensorflow 表示,这里的tensorflow 一维卷积 训练 tensorflow图卷积_人工智能_02是图中节点的集合,tensorflow 一维卷积 训练 tensorflow图卷积_人工智能_03 为边的集合,节点的个数用tensorflow 一维卷积 训练 tensorflow图卷积_cnn_04表示。在一个图中,有三个比较重要的矩阵:

  1. 特征矩阵tensorflow 一维卷积 训练 tensorflow图卷积_tensorflow_05:维度为 tensorflow 一维卷积 训练 tensorflow图卷积_tensorflow_06
  2. 邻居矩阵tensorflow 一维卷积 训练 tensorflow图卷积_tensorflow_07:维度为 tensorflow 一维卷积 训练 tensorflow图卷积_图卷积神经网络_08
  3. 度矩阵 tensorflow 一维卷积 训练 tensorflow图卷积_图卷积神经网络_09:维度为 tensorflow 一维卷积 训练 tensorflow图卷积_图卷积神经网络_08 ,是一个对角矩阵,即只有对角线上不为零,其他位置元素都是 0 ,表示图中 N 个节点与其他节点相连的边的个数。对于无权图而言,tensorflow 一维卷积 训练 tensorflow图卷积_tensorflow_11

邻接矩阵与度矩阵例子如下图所示:

tensorflow 一维卷积 训练 tensorflow图卷积_人工智能_12

对于图像(Image)数据,我们可以用卷积核来提取特征,无论卷积核覆盖在图像的哪个部分,其内部结构都是一样的,这是因为图片结构具有平移不变性,如下图左半部分所示:

tensorflow 一维卷积 训练 tensorflow图卷积_cnn_13

但是对于图(Graph)数据而言,其形状是不规则的,不具有平移不变性。于是 GCN,也就是图卷积神经网络,其目标就是设计一种特征提取器,进而完成节点分类、变预测等任务,还顺便可以得到每个节点的 embedding 表示。

上面展示了一个简单的 3x3 的卷积核,每次自左向右,自上而下扫描图片(Image)时,都是将 3x3 的像素进行加权求和,即:tensorflow 一维卷积 训练 tensorflow图卷积_tensorflow_14,然后将求和的结果作为该 3x3 区域的特征。

那么在图(graph)中要怎么提取特征?这里给出两种思路。

图卷积

思路一

CNN 加权求和的思想也可以应用到图(Graph)的特征提取上,如下图所示:

tensorflow 一维卷积 训练 tensorflow图卷积_人工智能_15

对于节点 tensorflow 一维卷积 训练 tensorflow图卷积_人工智能_16,我们可以用其邻接节点加权求和的结果来表示当前节点,这个操作我们称为“聚合(aggregate)”:

tensorflow 一维卷积 训练 tensorflow图卷积_tensorflow_17

考虑到与节点 tensorflow 一维卷积 训练 tensorflow图卷积_人工智能_16 没有边连接的节点 tensorflow 一维卷积 训练 tensorflow图卷积_tensorflow 一维卷积 训练_19 ,对应的权重 tensorflow 一维卷积 训练 tensorflow图卷积_人工智能_20

tensorflow 一维卷积 训练 tensorflow图卷积_人工智能_21

那么,对于所有的节点而言,其聚合的结果可以用下面的公式表示:

tensorflow 一维卷积 训练 tensorflow图卷积_人工智能_22

上面的公式只考虑了邻居加权求和的结果,很多情况下,节点自身的信息是不可忽略的,因此一般情况下会把自身的特征也加回来:

tensorflow 一维卷积 训练 tensorflow图卷积_tensorflow_23

于是有:

tensorflow 一维卷积 训练 tensorflow图卷积_cnn_24

其中,tensorflow 一维卷积 训练 tensorflow图卷积_tensorflow 一维卷积 训练_25

tensorflow 一维卷积 训练 tensorflow图卷积_cnn_26

则有:

tensorflow 一维卷积 训练 tensorflow图卷积_图卷积神经网络_27

也就是说把单位矩阵 tensorflow 一维卷积 训练 tensorflow图卷积_tensorflow 一维卷积 训练_25 加到邻接矩阵 tensorflow 一维卷积 训练 tensorflow图卷积_tensorflow_29

现在有个问题,只能用自身节点以及邻居节点加权求和的结果来表示某个节点的特征吗?其实还有另一种思路。

思路二

在某些情况下,我们更关注节点之间的差值,因此可以对差值进行加权求和:

tensorflow 一维卷积 训练 tensorflow图卷积_tensorflow_30

其中,D 表示度矩阵,表示节点与其他节点相连的边的个数,对于无权图而言,tensorflow 一维卷积 训练 tensorflow图卷积_tensorflow 一维卷积 训练_31

对于整个图的节点而言,上面的公式可以转换为矩阵化的表示:

tensorflow 一维卷积 训练 tensorflow图卷积_图卷积神经网络_32

实际上,上面公式中的 tensorflow 一维卷积 训练 tensorflow图卷积_tensorflow_33 是拉普拉斯矩阵(用 tensorflow 一维卷积 训练 tensorflow图卷积_cnn_34

tensorflow 一维卷积 训练 tensorflow图卷积_cnn_35

拉普拉斯矩阵如下图所示:

tensorflow 一维卷积 训练 tensorflow图卷积_人工智能_36

如果想更多地了解拉普拉斯矩阵在 GCN 中的作用,可以参考:如何理解 Graph Convolutional Network(GCN)?

归一化

无论是思路一的 tensorflow 一维卷积 训练 tensorflow图卷积_tensorflow_37 还是思路二的 tensorflow 一维卷积 训练 tensorflow图卷积_tensorflow 一维卷积 训练_38,与 CNN 的卷积相似之处都是局部数据的聚合操作,只不过 CNN 中卷积的局部连接数是固定的。但是在 Graph 中每个节点的邻居个数都可能不同,进行聚合操作后,对于度较大的节点,得到的特征比较大,度较少的节点得到的特征就比较小,因此还需要进行归一化的处理。

归一化的思路有两种:

(1)算数平均

tensorflow 一维卷积 训练 tensorflow图卷积_人工智能_39

(2)几何平均

tensorflow 一维卷积 训练 tensorflow图卷积_人工智能_40

几何平均受极端值影响较小,因此是 GCN 中比较常用的归一化方法,于是有:

tensorflow 一维卷积 训练 tensorflow图卷积_cnn_41

当然也可以是:

tensorflow 一维卷积 训练 tensorflow图卷积_图卷积神经网络_42

在实际的 GCN 代码实现中,会对聚合结果进行一些变换,第 tensorflow 一维卷积 训练 tensorflow图卷积_cnn_43 层到第 tensorflow 一维卷积 训练 tensorflow图卷积_cnn_44

tensorflow 一维卷积 训练 tensorflow图卷积_图卷积神经网络_45

其中:

  • tensorflow 一维卷积 训练 tensorflow图卷积_图卷积神经网络_46 ,也可以是 tensorflow 一维卷积 训练 tensorflow图卷积_cnn_47
  • tensorflow 一维卷积 训练 tensorflow图卷积_人工智能_48tensorflow 一维卷积 训练 tensorflow图卷积_tensorflow 一维卷积 训练_49 的度矩阵,每个元素为:tensorflow 一维卷积 训练 tensorflow图卷积_人工智能_50
  • tensorflow 一维卷积 训练 tensorflow图卷积_人工智能_51 是每一层的特征,对于输入层而言,tensorflow 一维卷积 训练 tensorflow图卷积_人工智能_51 就是 tensorflow 一维卷积 训练 tensorflow图卷积_tensorflow_05
  • σ 是 sigmoid 函数

由于 D 是在矩阵 A 的基础上得到的,因此在给定矩阵 A 之后,tensorflow 一维卷积 训练 tensorflow图卷积_tensorflow 一维卷积 训练_54

代码实现

Cora 数据集介绍

Cora 数据集由机器学习论文组成,是近年来图深度学习很喜欢使用的数据集。整个数据集有 2708 篇论文,所有样本点被分为 8 个类别,类别分别是 1)基于案例;2)遗传算法;3)神经网络;4)概率方法;5)强化学习;6)规则学习;7)理论。每篇论文都由一个 1433 维的词向量表示,所以,每个样本点具有 1433 个特征。词向量的每个元素都对应一个词,且该元素只有 0 或 1 两个取值。取 0 表示该元素对应的词不在论文中,取 1 表示在论文中。

训练模型

# 计算标准化的邻接矩阵:根号D * A * 根号D
def preprocess_graph(adj):
    # _A = A + I
    _adj = adj + sp.eye(adj.shape[0])
    # _dseq:各个节点的度构成的列表
    _dseq = _adj.sum(1).A1
    # 构造开根号的度矩阵
    _D_half = sp.diags(np.power(_dseq, -0.5))
    # 计算标准化的邻接矩阵, @ 表示矩阵乘法
    adj_normalized = _D_half @ _adj @ _D_half
    return adj_normalized.tocsr()

if __name__ == "__main__":
    # 读取数据
    # A_mat:邻接矩阵,以scipy的csr形式存储
    # X_mat:特征矩阵,以scipy的csr形式存储
    # z_vec:label
    # train_idx,val_idx,test_idx: 要使用的节点序号
    A_mat, X_mat, z_vec, train_idx, val_idx, test_idx = load_data_planetoid(FLAGS.dataset)
    # 邻居矩阵标准化
    An_mat = preprocess_graph(A_mat)

    # 节点的类别个数
    K = z_vec.max() + 1

    # 构造GCN模型
    gcn = GCN(An_mat, X_mat, [FLAGS.hidden1, K])
    # 训练
    gcn.train(train_idx, z_vec[train_idx], val_idx, z_vec[val_idx])
    # 测试
    test_res = gcn.evaluate(test_idx, z_vec[test_idx], training=False)
    print("Dataset {}".format(FLAGS.dataset),
          "Test loss {:.4f}".format(test_res[0]),
          "test acc {:.4f}".format(test_res[1]))

GCN 小结

GCN 的优点: 可以捕捉 graph 的全局信息,从而很好地表示 node 的特征。

GCN 的缺点:属于直推式(transductive)的学习方式,需要把所有节点都参与训练才能得到 node embedding,无法快速得到新 node 的 embedding。如果要得到新节点的表示,很可能就需要重新训练所有节点的表示。

参考文章:

如何理解 Graph Convolutional Network(GCN)?

2020 年,我终于决定入门 GCN

GCN(Graph Convolutional Network)的理解