HAN论文阅读与代码详解

HAN学习

本文包含的代码,以及省略的工具类,批次训练代码都在我的Github仓库中https://github.com/wanli6/GNN_algorithm

论文思路

摘要

本文首先提出了一种新的基于分层注意力的异构图神经网络,包括节点级语义级的注意力。具体来说,节点级注意力旨在学习节点与其基于元路径的邻居之间的重要性,而语义级注意力能够学习不同元路径的重要性。通过学习节点级和语义级注意力的重要性,可以充分考虑节点和元路径的重要性。然后,所提出的模型可以以分层的方式通过基于源路径的邻居节点聚合特征来生成节点嵌入

HAN模型

HAN模型使用分层的注意力结构:分为节点级别的注意力和语义级别的注意力。

节点级别:学习基于元路径的邻居的权重聚合它们得到语义特定的节点嵌入

语义级别:判断元路径的不同,并针对特定任务得到语义特定节点嵌入的最优加权组合

下图为HAN的整体结构图。

HAN结构

节点级别的注意力

由于每个类型的节点具有不同类型的特征,于是本文设计了类型特定的**转换矩阵 M ϕ i M_{\phi_{i}} Mϕi**将不同类型的节点特征映射到同一个特征空间中。映射过程如下公式1:

h i ′ = M ϕ i ⋅ h i \mathbf{h}_i^{\prime}=\mathbf{M}_{\phi_i}\cdot\mathbf{h}_i hi=Mϕihi

  • h i \mathbf{h}_i hi h i ′ \mathbf{h}_{i}^{\prime} hi 分别为节点 i i i 的原始特征和映射后的特征
  • 经过映射之后,节点级别的注意力机制可以处理任意类型的节点。

接着,本文使用自我注意力学习各种类型节点的权重,给定一个节点对 ( i , j ) (i,j) (i,j) ,他们是通过元路径 Φ \Phi Φ e i j Φ e_{ij}^{\Phi} eijΦ代表节点 j j j对节点 i i i的重要性。如下公式2

e i j Φ = a t t n o d e ( h i ′ , h j ′ ; Φ ) e_{ij}^{\Phi}=att_{node}(\mathbf{h}_i^{\prime},\mathbf{h}_j^{\prime};\Phi) eijΦ=attnode(hi,hj;Φ)

  • 这里的 a t t n o d e att_{node} attnode表示执行节点级注意力的深度神经网络
  • 对于指定的 Φ \Phi Φ, a t t n o d e att_{node} attnode是共享的
  • e i j Φ e_{ij}^{\Phi} eijΦ 不是对称的

然后,通过隐蔽注意力将结构信息注入模型(只需要对 j ∈ N i Φ j\in\mathcal{N}_{i}^{\Phi} jNiΦ 计算 e i j Φ e_{ij}^{\Phi} eijΦ), N i Φ \mathcal{N}_{i}^{\Phi} NiΦ代表节点 i i i基于元路径的邻居(包括自己),然后通过softmax函数进行归一化得到重要性系数 α i j Φ \alpha_{ij}^{\Phi} αijΦ。如下公式3

α i j Φ = s o f t m a x j ( e i j Φ ) = exp ⁡ ( σ ( a Φ T ⋅ [ h i ′ ∥ h j ′ ] ) ) ∑ k ∈ N i Φ exp ⁡ ( σ ( a Φ T ⋅ [ h i ′ ∥ h k ′ ] ) ) \alpha_{ij}^{\Phi}=softmax_{j}(e_{ij}^{\Phi})=\frac{\exp(\sigma(\mathbf{a}_{\Phi}^{\mathrm{T}}\cdot[\mathbf{h}_{i}^{\prime}\|\mathbf{h}_{j}^{\prime}]))}{\sum_{k\in\mathcal{N}_{i}^{\Phi}}\exp(\sigma(\mathbf{a}_{\Phi}^{\mathrm{T}}\cdot[\mathbf{h}_{i}^{\prime}\|\mathbf{h}_{k}^{\prime}]))} αijΦ=softmaxj(eijΦ)=kNiΦexp(σ(aΦT[hihk]))exp(σ(aΦT[hihj]))

  • σ \sigma σ 代表激活函数, ∣ ∣ || ∣∣ 代表级联
  • a Φ \mathbf{a}_{\Phi} aΦ 是元路径 Φ \Phi Φ对应的节点级别注意力向量

然后,节点 i i i基于元路径的嵌入可以通过邻居的映射后的特征根据重要性系数进行聚合决定。如下公式4

z i Φ = σ ( ∑ j ∈ N i Φ α i j Φ ⋅ h j ′ ) \mathbf{z}_i^\Phi=\sigma\bigg(\sum_{j\in\mathcal{N}_i^\Phi}\alpha_{ij}^\Phi\cdot\mathbf{h}_j'\bigg) ziΦ=σ(jNiΦαijΦhj)

  • z i Φ \mathbf{z}_i^\Phi ziΦ 就是学习到的嵌入向量

由于异构图具有无标度特性,因此图数据的方差很大。为了解决上述挑战,我们将节点级注意力扩展到多头注意力,使训练过程更加稳定。如下公式5

z i Φ = ∏ k = 1 K σ ( ∑ j ∈ N i Φ α i j Φ ⋅ h j ′ ) \mathbf{z}_{i}^{\Phi}=\prod\limits_{k=1}^{K}\sigma\bigg(\sum\limits_{j\in\mathcal{N}_{i}^{\Phi}}\alpha_{ij}^{\Phi}\cdot\mathbf{h}_{j}'\bigg) ziΦ=k=1Kσ(jNiΦαijΦhj)

对于元路径集 { Φ 1 , … , Φ P } \{\Phi_{1},\ldots,\Phi_{P}\} {Φ1,,ΦP}, 在节点的特征经过节点级别注意力之后,我们可以得到 P P P组语义特定的节点特征,为 { Z Φ 1 , … , Z Φ P } \left\{\mathbf{Z}_{\Phi_{1}},\ldots,\mathbf{Z}_{\Phi_{P}}\right\} {ZΦ1,,ZΦP}

语义级别的注意力

异构图中的每个节点通常包含多种类型的语义信息,语义特定的节点嵌入只能从一个方面反映节点。为了学习一个更全面的节点嵌入,我们需要融合多种语义,这些语义可以通过元路径来揭示。为了应对异构图中的元路径选择和语义融合挑战,作者提出了一种新颖的语义级注意力机制,用于自动学习不同元路径的重要性,并将它们融合到特定任务中。

将节点级注意力得到的 P P P组语义特定的节点嵌入作为输入,每个元路径的权重可以被描述如下公式6:

( β Φ 1 , … , β Φ P ) = a t t s e m ( Z Φ 1 , … , Z Φ P ) (\beta_{\Phi_1},\ldots,\beta_{\Phi_P})=att_{sem}(\mathrm{Z}_{\Phi_1},\ldots,\mathrm{Z}_{\Phi_P}) (βΦ1,,βΦP)=attsem(ZΦ1,,ZΦP)

为了学习每个元路径的重要性,首先通过非线性变换(例如,单层MLP)变换语义特定的嵌入向量

然后,我们通过变换嵌入与语义级注意向量 q \mathbf q q相似性度量语义特定嵌入的重要性。此外,对所有语义特定节点嵌入的重要性进行平均,这可以解释为每个元路径的重要性。如下公式7

w Φ p = 1 ∣ V ∣ ∑ i ∈ V q T ⋅ tanh ⁡ ( W ⋅ z i Φ p + b ) w_{\Phi_{p}}=\frac{1}{|\mathcal{V}|}\sum_{i\in\mathcal{V}}\mathbf{q}^{\mathrm{T}}\cdot\tanh(\mathbf{W}\cdot\mathbf{z}_{i}^{\Phi_{p}}+\mathbf{b}) wΦp=V1iVqTtanh(WziΦp+b)

  • W \mathbf W W是权重矩阵, b \mathbf b b是偏置向量, q \mathbf q q是语义级别的注意力向量
  • 上边的参数都是所有元路径共享的

在得到每个元路径的重要性后,通过softmax函数对它们进行归一化。如下公式8

β Φ p = exp ⁡ ( w Φ p ) ∑ p = 1 P exp ⁡ ( w Φ p ) \beta_{\Phi_p}=\frac{\exp(w_{\Phi_p})}{\sum_{p=1}^{P}\exp(w_{\Phi_p})} βΦp=p=1Pexp(wΦp)exp(wΦp)

  • 这可以解释为每个元路径在特定任务中的贡献

使用学习的权重作为系数,我们可以融合这些语义特定的嵌入以获得最终的嵌入。如下公式9

Z = ∑ p = 1 P β Φ p ⋅ Z Φ p Z=\sum_{p=1}^{P}\beta_{\Phi_{p}}\cdot Z_{\Phi_{p}} Z=p=1PβΦpZΦp

然后,我们可以将最终的嵌入应用于特定的任务,并设计不同的损失函数。

比如,对于半监督的节点分类任务,可以使用交叉熵损失函数。如下公式10

L = − ∑ l ∈ Y L Y l ln ⁡ ( C ⋅ Z l ) L=-\sum\limits_{l\in\mathcal{Y}_{L}}\mathrm{Y}^{l}\ln(\mathrm{C}\cdot\mathrm{Z}^{l}) L=lYLYlln(CZl)

节点级别的注意力和语义级别的注意力聚合过程图示:

节点级别的注意力和语义级别的注意力聚合过程图示

HAN的整个过程:

HAN算法整个过程

代码实现

此模型具体的实现有两种方式:主要区别在于对数据集的处理以及基于元路径邻居的获取。

  • 直接读取作者处理过的数据集, 可以直接获取基于不同元路径的图

  • dgl可以通过metapath_reachable_graph方法从一个异质图以及给定的元路径获取源路径的可达图

实现的具体思路相同,下边以第二种方式为例,给出代码(注释中详细解释了实现的思路和过程):

下列代码中用到的其他工具类可以在Github仓库中找到

import dgl
import torch
import torch.nn as nn
import torch.nn.functional as F

from dgl.nn.pytorch import GATConv


"""
注释中的字母代表的含义:
N : 节点数量
M : 元路径数量
D : 嵌入向量维度
K : 多头注意力总数
"""


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

        # 语义层次的注意力
        # 对应论文公式(7),最终得到每条元路径的重要性权重
        self.projection = nn.Sequential(
            nn.Linear(in_size, hidden_size),
            nn.Tanh(),
            nn.Linear(hidden_size, 1, bias=False)
        )

    def forward(self, z):
        # 输入的z为(N, M, K*D)
        # 经过映射之后的w形状为 (M , 1)
        w = self.projection(z).mean(0)
        # beta (M ,1)
        beta = torch.softmax(w, dim=0)
        # beta (N, M, 1)
        beta = beta.expand((z.shape[0],) + beta.shape)
        # (N, D*K)
        return (beta * z).sum(1)


class HANLayer(nn.Module):
    """
    meta_paths : list of metapaths, each as a list of edge types
    in_size : input feature dimension
    out_size : output feature dimension
    layer_num_heads : number of attention heads
    dropout : Dropout probability
    """

    def __init__(self,
                 meta_paths,
                 in_size,
                 out_size,
                 layer_num_heads,
                 drop_out):
        super(HANLayer, self).__init__()
        self.gat_layers = nn.ModuleList()

        for i in range(len(meta_paths)):
            # 使用GAT对应的GATConv层,完成节点层面的注意力
            # 之所以能够之间使用GATConv,是因为在forward中生成了每个元路径对应的可达图
            # 那么在进行节点级注意力的时候,节点的所有邻居都是它基于元路径的邻居
            # 节点级注意力以及聚合的过程就等同于GATConv的过程
            self.gat_layers.append(
                GATConv(
                    in_size,
                    out_size,
                    layer_num_heads,
                    drop_out,
                    drop_out,
                    activation=F.elu,
                    allow_zero_in_degree=True
                )
            )

            # 语义级注意力层
            self.semantic_attention = SemanticAttention(
                in_size=out_size * layer_num_heads
            )

            self.meta_paths = list(tuple(meta_path) for meta_path in meta_paths)

            # 缓存图
            self._cached_graph = None
            # 缓存每个元路径对应的可达图
            self._cached_coalesced_graph = {}

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

        if self._cached_graph is None or self._cached_graph is not g:
            self._cached_graph = g
            self._cached_coalesced_graph.clear()
            # 存储每个元路径对应的元路径可达图
            for meta_path in self.meta_paths:
                self._cached_coalesced_graph[
                    meta_path
                ] = dgl.metapath_reachable_graph(g, meta_path)

        for i, meta_path in enumerate(self.meta_paths):
            new_g = self._cached_coalesced_graph[meta_path]
            semantic_embeddings.append(self.gat_layers[i](new_g, h).flatten(1))
        # 经过对每个元路径进行节点级聚合
        # semantic_embeddings 为一个长度为M的列表
        # 其中的元素为每个节点级注意力的输出,形状为(N, D*K)

        # 将该列表在维度1堆叠,得到所有元路径的节点级注意力
        # 形状为(N, M, D * K)
        semantic_embeddings = torch.stack(
            semantic_embeddings, dim=1
        )

        # 最终经过语义级注意力聚合不同元路径的表示,得到了该层的输出
        # 形状为(N, D * K)
        return self.semantic_attention(semantic_embeddings)


class HAN(nn.Module):
    """
    参数:
    meta_paths : 元路径,使用边类型列表表示
    in_size : 输入大小(特征维度)
    out_size : 输出大小(节点种类数)
    num_heads : 多头注意力头数(列表形式,对应每层的头数)
    dropout : dropout概率
    """
    def __init__(
            self, meta_paths, in_size, hidden_size, out_size, num_heads, dropout
    ):
        super(HAN, self).__init__()

        self.layers = nn.ModuleList()
        # 第一个HAN层的输入输出需要单独定义
        self.layers.append(
            HANLayer(meta_paths, in_size, hidden_size, num_heads[0], dropout)
        )
        # 从第二个HAN层开始,每一个层的输入都是hidden_size * 上一个层的头数
        # 输出大小为 hidden_size * 当前层的头数
        for l in range(1, len(num_heads)):
            self.layers.append(
                HANLayer(
                    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):
        for gnn in self.layers:
            h = gnn(g, h)

        return self.predict(h)
  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
PyTorch HAN,全称为PyTorch Hierarchical Attention Network(PyTorch层次化注意力网络),是基于深度学习框架PyTorch开发的一种层次化注意力模型。 HAN模型在处理文本分类问题时,能够有效地考虑文本的层次结构特点。在语义理解任务中,通常需要考虑文本的段落、句子和单词层次的信息。HAN模型通过使用两层的注意力机制,能够在不同层次上对文本进行建模并自动学习到不同层次的重要信息。 HAN模型的结构主要分为两个层次:文档层次和句子层次。在文档层次中,通过使用GRU(门控循环单元)或LSTM(长短时记忆网络)对文档进行编码。然后,通过注意力机制对不同句子的重要程度进行学习,将句子向量进行加权平均得到文档向量。在句子层次中,再次使用GRU或LSTM对每个句子进行编码,并通过注意力机制学习句子中不同单词的重要程度得到句子向量。 HAN模型的注意力机制能够自动学习到不同层次的重要信息,并将其融合到向量表示中。通过考虑文本的层次结构,模型可以更好地理解文本中的内容和语义。与传统的词袋模型或循环神经网络相比,HAN模型通过学习到不同层次的重要信息,能够提取更加丰富和准确的特征。 总之,PyTorch HAN是一个强大的文本分类模型,通过层次化的注意力机制,在处理文本数据时能够更好地建模和学习不同层次的重要信息,进而提升模型的性能和准确度。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Liwan95

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值