文章目录

  • 一、图卷积网络(Graph Convolutional Network)
  • 1.核心公式
  • 2.算法流程
  • 二、图注意力算法(GAT)
  • 1.计算方法
  • 2.多头Attention
  • 三、空间GNN
  • 四、编程实践
  • GAT网络实现


一、图卷积网络(Graph Convolutional Network)

在深度学习中,可以对图像进行卷积操作,即卷积神经网络。图像的卷积操作是将一个像素点周围的像素按照不同的权重叠加起来。

将卷积的概念类比到图结构中,我们将图结构的卷积定义为将一个节点周围的邻居按照不同的权重叠加起来。

1.核心公式

图卷积网络的核心公式如下:

图神经网络画图工具 图神经网络算法_消息传递


以下图为例,对公式中的各项进行理解:

图神经网络画图工具 图神经网络算法_权重_02

  • H代表每一层的节点表示,第0层即为最开始的节点表示
  • A表示邻接矩阵,如下图所示,两个节点存在邻居关系就将值设为1,对角线默认为1
  • D表示度矩阵,该矩阵除对角线外均为0,对角线的值表示每个节点的度,等价于邻接矩阵对行求和
  • W表示可学习的权重

将上式进行拆分,A*H可以理解成将上一层每个节点的节点表示进行聚合,如下图,0号节点就是对上一层与其相邻的1号、2号节点和它本身进行聚合。而度矩阵D存在的意义是每个节点的邻居的重要性不同,根据该节点的度来对这些相邻节点的节点表示进行加权,d越大,说明信息量越小。

图神经网络画图工具 图神经网络算法_神经网络_03

2.算法流程

GCN算法主要包括以下几步:

  • 第一步是利用上面的核心公式进行节点间特征传递
  • 第二步对每个节点过一层DNN
  • 重复以上两步得到L层的GCN
  • 获得的最终节点表示H送入分类器进行分类

二、图注意力算法(GAT)

GCN网络中的一个缺点是边的权重与节点的度度相关而且不可学习,因此有了图注意力算法。在GAT中,边的权重变成节点间的可学习的函数并且与两个节点之间的相关性有关。

1.计算方法

注意力机制的计算方法如下:

图神经网络画图工具 图神经网络算法_权重_04

首先将目标节点和源节点的表示拼接到一起,通过网络计算相关性,之后通过LeakyReLu激活函数和softmax归一化得到注意力分数,最后用得到的α进行聚合,后续步骤和GCN一致

2.多头Attention

上述方法为单头GAT,多头Attention公式如下:

图神经网络画图工具 图神经网络算法_神经网络_05

三、空间GNN

空间GNN(Spatial GNN):基于邻居聚合的图模型称为空间GNN,例如GCN、GAT等等。大部分的空间GNN都可以用消息传递实现,消息传递包括消息的发送和消息的接受。

基于消息传递的图神经网络的通用公式:

图神经网络画图工具 图神经网络算法_图神经网络画图工具_06

四、编程实践

GAT网络实现

def single_head_gat(graph_wrapper, node_feature, hidden_size, name):
    # 实现单头GAT

    def send_func(src_feat, dst_feat, edge_feat):
        ##################################
        # 按照提示一步步理解代码吧,你只需要填###的地方

        # 1. 将源节点特征与目标节点特征concat 起来,对应公式当中的 concat 符号,可能用到的 API: fluid.layers.concat
        Wh = fluid.layers.concat(input=[src_feat["Wh"], dst_feat["Wh"]], axis=1)
    
        # 2. 将上述 Wh 结果通过全连接层,也就对应公式中的a^T

        alpha = fluid.layers.fc(Wh, 
                            size=1, 
                            name=name + "_alpha", 
                            bias_attr=False)

        # 3. 将计算好的 alpha 利用 LeakyReLU 函数激活,可能用到的 API: fluid.layers.leaky_relu
        alpha = fluid.layers.leaky_relu(alpha, 0.2)
        
        ##################################
        return {"alpha": alpha, "Wh": src_feat["Wh"]}
    
    def recv_func(msg):
        ##################################
        # 按照提示一步步理解代码吧,你只需要填###的地方

        # 1. 对接收到的 logits 信息进行 softmax 操作,形成归一化分数,可能用到的 API: paddle_helper.sequence_softmax
        alpha = msg["alpha"]
        norm_alpha = paddle_helper.sequence_softmax(alpha)

        # 2. 对 msg["Wh"],也就是节点特征,用上述结果进行加权
        output = norm_alpha * msg["Wh"]

        # 3. 对加权后的结果进行相加的邻居聚合,可能用到的API: fluid.layers.sequence_pool
        output = fluid.layers.sequence_pool(output, pool_type="sum")
        ##################################
        return output
    
    # 这一步,其实对应了求解公式当中的Whi, Whj,相当于对node feature加了一个全连接层

    Wh = fluid.layers.fc(node_feature, hidden_size, bias_attr=False, name=name + "_hidden")
    # 消息传递机制执行过程
    message = graph_wrapper.send(send_func, nfeat_list=[("Wh", Wh)])
    output = graph_wrapper.recv(message, recv_func)
    output = fluid.layers.elu(output)
    return output

单头GAT实现

def gat(graph_wrapper, node_feature, hidden_size):
    # 完整多头GAT

    # 这里配置多个头,每个头的输出concat在一起,构成多头GAT
    heads_output = []
    # 可以调整头数 (8 head x 8 hidden_size)的效果较好 
    n_heads = 8
    for head_no in range(n_heads):
        # 请完成单头的GAT的代码
        single_output = single_head_gat(graph_wrapper, 
                            node_feature, 
                            hidden_size, 
                            name="head_%s" % (head_no) )
        heads_output.append(single_output)
    
    output = fluid.layers.concat(heads_output, -1)
    return output

多头GAT实现