葡萄书--图注意力网络

同质图中的注意力网络

注意力网络

GAT 核心就是对于每个顶点都计算其与邻居节点的注意力系数,通过注意力系数来聚合节点的特征,而非认为每个邻居节点的贡献程度都是相同的

单头注意力机制

为了计算注意力权重 eij​,我们首先将即节点特征 hi​ 和 hj​ 通过 W 映射到一个高维空间,然后将这两个向量拼接在一起,之后通过一个全连接层 afc​ 将拼接后的向量映射到一个标量,再应用 LeakyReLU 激活函数得到注意力系数 eij

,其中afc​ 的参数是被共享的
假设目标节点 i 有 k 个邻居节点 j,因此我们需要用 softmax 对得到的所有注意力系数进行归一化,就可以用其对邻居节点特征进行线性组合,得到最终的目标节点特征

多头注意力机制

使用 K 个 Wk 将原特征进行映射到 K 个不同的高维空间,然后重复上述计算单头注意力的步骤

最后concat或者加权组合起来

注意力网络代码

class GATLayer(nn.Module):
    def __init__(self, g, in_dim, out_dim):
        super(GATLayer, self).__init__()
        '''
        g : dgl的grapg实例
        in_dim : 节点embedding维度
        out_dim : attention编码维度
        '''
        self.g = g #dgl的一个graph实例
        self.fc = nn.Linear(in_dim, out_dim, bias=False) # 对节点进行通用的映射的fc
        self.attn_fc = nn.Linear(2 * out_dim, 1, bias=False) # 计算edge attention的fc
        self.reset_parameters() # 参数初始化
        
    def edge_attention(self, edges):
                '''
                edges 的src表示源节点,dst表示目标节点
                '''
        z2 = torch.cat([edges.src['z'], edges.dst['z']], dim=1) # eq.1 里面的拼接操作
        a = self.attn_fc(z2) # eq.1 里面对e_{ij}的计算
        return {'e' : F.leaky_relu(a)} # 这里的return实际上等价于 edges.data['e'] =  F.leaky_relu(a),这样后面才能将这个 e 传递给 源节点 
      
    def reset_parameters(self):
        gain = nn.init.calculate_gain('relu')
        nn.init.xavier_normal_(self.fc.weight, gain=gain)
        nn.init.xavier_normal_(self.attn_fc.weight, gain=gain)
    
    def message_func(self, edges):
        return {'z' : edges.src['z'], 'e' : edges.data['e']} #将前面 edge_attention 算出来的 e 以及 edges的源节点的 node embedding 都传给 nodes.mailbox 
    
    def reduce_func(self, nodes):
          # 通过 message_func 之后即可得到 源节点的node embedding 与 edges 的 e
        alpha = F.softmax(nodes.mailbox['e'], dim=1) # softmax归一化,得到 a
        h = torch.sum(alpha * nodes.mailbox['z'], dim=1) # 根据a进行加权求和
        return {'h' : h}
    
    def forward(self, h):
        z = self.fc(h) # eq. 1 这里的h就是输入的node embedding
        self.g.ndata['z'] = z 
        self.g.apply_edges(self.edge_attention) # eq. 2
        self.g.update_all(self.message_func, self.reduce_func) # eq. 3 and 4
        return self.g.ndata.pop('h') # 返回经过attention的 node embedding

class MultiHeadGATLayer(nn.Module):
    def __init__(self, g, in_dim, out_dim, num_heads, merge='cat'):
        super(MultiHeadGATLayer, self).__init__()
        '''
        g : dgl的grapg实例
        in_dim : 节点embedding维度
        out_dim : attention编码维度
        num_heads : 头的个数
        merge : 最后一层为'mean',其他层为'cat'
        '''
        self.heads = nn.ModuleList()
        for i in range(num_heads):
              # 这里简单粗暴,直接声明 num_heads个GATLayer达到 Multi-Head的效果
            self.heads.append(GATLayer(g, in_dim, out_dim))
        self.merge = merge
        
    def forward(self, h):
        head_outs = [attn_head(h) for attn_head in self.heads]
        # 根据merge的类别来处理 Multi-Head的逻辑
        if self.merge == 'cat':
            return torch.cat(head_outs, dim=1)
        else:
            return torch.mean(torch.stack(head_outs))
class GAT(nn.Module):
    def __init__(self, g, in_dim, hidden_dim, out_dim, num_heads):
        super(GAT, self).__init__()
        '''
        g : dgl的grapg实例
        in_dim : 节点embedding维度
        hidden_dim : 隐层的维度
        out_dim : attention编码维度
        num_heads : 头的个数
        '''
        # 这里简简单单的写了一个两层的 MultiHeadGATLayer 
        self.layer1 = MultiHeadGATLayer(g, in_dim, hidden_dim, num_heads)
        self.layer2 = MultiHeadGATLayer(g, hidden_dim * num_heads, out_dim, 1)
        
    def forward(self, h):
        h = self.layer1(h)
        h = F.elu(h)
        h = self.layer2(h)
        return h

结果可通过 t-SNE 降维来进行可视化

异质图中的注意力网络

异质图注意力网络

元路径及依据元路径定义的邻居

在异质图中会有非常复杂的节点之间的联系,但是这种联系并不全是有效的,所以通过定义元路径来定义一些有意义的连接方式

节点 i 在通过元路径生成的图中的邻居就是依据元路径定义的邻居

如图元路径可定义为MAM和MDM,便得到了依据元路径定义的邻居d

异质图注意力网络整体架构

  • (a)节点级注意力的目的是学习节点与基于元路径的邻居节点之间的重要性。
  • (b)语义级注意力的目的是学习不同元路径的重要性。
  • (c)通过接 MLP 输出节点 i 的预测 y~​i​。

节点级注意力

对每一条元路径形成的邻居节点子图进行一次同质图卷积网络 GAT 的注意力计算

语义级注意力

首先,初始化一个语义级别的可学习向量 q,然后用 q 和每个元路径的节点级特征的高维映射 WZiΦp​​ 求内积,并且平均了所有的节点 i 后,得到元路径 p 的语义级注意力系数

 

最后对进行softmax归一化后的注意力系数进行加权求和

异质图注意力网络代码

class SemanticAttention(nn.Module):
    def __init__(self, in_size, hidden_size=128):
        super(SemanticAttention, self).__init__()

        self.project = nn.Sequential(
            nn.Linear(in_size, hidden_size),
            nn.Tanh(),
            nn.Linear(hidden_size, 1, bias=False)
        )

    def forward(self, z):
        w = self.project(z).mean(0)                    # (M, 1)
        beta = torch.softmax(w, dim=0)                 # (M, 1)
        beta = beta.expand((z.shape[0],) + beta.shape) # (N, M, 1)

        return (beta * z).sum(1)                       # (N, D * K)

class HANLayer(nn.Module):
    """
    HAN layer.

    Arguments
    ---------
    num_meta_paths : number of homogeneous graphs generated from the metapaths.
    in_size : input feature dimension
    out_size : output feature dimension
    layer_num_heads : number of attention heads
    dropout : Dropout probability

    Inputs
    ------
    g : list[DGLGraph]
        List of graphs
    h : tensor
        Input features

    Outputs
    -------
    tensor
        The output feature
    """
    def __init__(self, num_meta_paths, in_size, out_size, layer_num_heads, dropout):
        super(HANLayer, self).__init__()

        # One GAT layer for each meta path based adjacency matrix
        self.gat_layers = nn.ModuleList()
        for i in range(num_meta_paths):
            self.gat_layers.append(GATConv(in_size, out_size, layer_num_heads,
                                           dropout, dropout, activation=F.elu))
        self.semantic_attention = SemanticAttention(in_size=out_size * layer_num_heads)
        self.num_meta_paths = num_meta_paths

    def forward(self, gs, h):
        semantic_embeddings = []

        for i, g in enumerate(gs):
            semantic_embeddings.append(self.gat_layers[i](g, h).flatten(1))
        semantic_embeddings = torch.stack(semantic_embeddings, dim=1)                  # (N, M, D * K)

        return self.semantic_attention(semantic_embeddings)                            # (N, D * K)
      
class HAN(nn.Module):
    def __init__(self, num_meta_paths, in_size, hidden_size, out_size, num_heads, dropout):
        super(HAN, self).__init__()

        self.layers = nn.ModuleList()
        self.layers.append(HANLayer(num_meta_paths, in_size, hidden_size, num_heads[0], dropout))
        for l in range(1, len(num_heads)):
            self.layers.append(HANLayer(num_meta_paths, hidden_size * num_heads[l-1],
                                        hidden_size, num_heads[l], dropout))
        self.predict = nn.Linear(hidden_size * num_heads[-1], out_size)

    def forward(self, g, h, is_training=True):
        for gnn in self.layers:
            h = gnn(g, h)
            
        if is_training:
            return self.predict(h)
        else:
            return h
  • 22
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值