Datawhale 图神经网络task6

基于图神经网络的图表征学习方法

引用自Datawhale

https://github.com/datawhalechina/team-learning-nlp/tree/master/GNN

前面文章:

  1. 图神经网络打卡task1
  2. Datawhale 图神经网络task2
  3. Datawhale 图神经网络task3
  4. Datawhale 图神经网络task4
  5. Datawhale 图神经网络task5

引言

在此篇文章中我们将学习基于图神经网络的图表征学习方法,图表征学习要求在输入节点属性、边(和边的属性如果有的话)得到一个向量作为图的表征,基于图表征进一步的我们可以做图的预测。基于图同构网络(Graph Isomorphism Network, GIN)的图表征网络是当前最经典的图表征学习网络,我们将以它为例,通过该网络的实现、项目实践和理论分析,三个层面来学习基于图神经网络的图表征学习方法

提出图同构网络的论文:How Powerful are Graph Neural Networks?

基于图同构网络(GIN)的图表征网络的实现

基于图同构网络的图表征学习主要包含以下两个过程

  1. 首先计算得到节点表征;
  2. 其次对图上各个节点的表征做图池化(Graph Pooling),或称为图读出(Graph Readout),得到图的表征(Graph Representation)。

在这里,我们将采用自顶向下的方式,来学习基于图同构模型(GIN)的图表征学习方法。我们首先关注如何基于节点表征计算得到图的表征,而忽略计算结点表征的方法

基于图同构网络的图表征模块(GINGraphRepr Module)

此模块首先采用GINNodeEmbedding模块对图上每一个节点做节点嵌入(Node Embedding),得到节点表征,然后对节点表征做图池化得到图的表征,最后用一层线性变换得到图的表征(graph representation)。代码实现如下:

import torch
from torch import nn
from torch_geometric.nn import global_add_pool, global_mean_pool, global_max_pool, GlobalAttention, Set2Set
from gin_node import GINNodeEmbedding

class GINGraphRepr(nn.Module):

    def __init__(self, num_tasks=1, num_layers=5, emb_dim=300, residual=False, drop_ratio=0, JK="last", graph_pooling="sum"):
        """GIN Graph Pooling Module
        Args:
            num_tasks (int, optional): number of labels to be predicted. Defaults to 1 (控制了图表征的维度,dimension of graph representation).
            num_layers (int, optional): number of GINConv layers. Defaults to 5.
            emb_dim (int, optional): dimension of node embedding. Defaults to 300.
            residual (bool, optional): adding residual connection or not. Defaults to False.
            drop_ratio (float, optional): dropout rate. Defaults to 0.
            JK (str, optional): 可选的值为"last"和"sum"。选"last",只取最后一层的结点的嵌入,选"sum"对各层的结点的嵌入求和。Defaults to "last".
            graph_pooling (str, optional): pooling method of node embedding. 可选的值为"sum","mean","max","attention"和"set2set"。 Defaults to "sum".

        Out:
            graph representation
        """
        super(GINGraphPooling, self).__init__()

        self.num_layers = num_layers
        self.drop_ratio = drop_ratio
        self.JK = JK
        self.emb_dim = emb_dim
        self.num_tasks = num_tasks

        if self.num_layers < 2:
            raise ValueError("Number of GNN layers must be greater than 1.")

        self.gnn_node = GINNodeEmbedding(num_layers, emb_dim, JK=JK, drop_ratio=drop_ratio, residual=residual)

        # Pooling function to generate whole-graph embeddings
        if graph_pooling == "sum":
            self.pool = global_add_pool
        elif graph_pooling == "mean":
            self.pool = global_mean_pool
        elif graph_pooling == "max":
            self.pool = global_max_pool
        elif graph_pooling == "attention":
            self.pool = GlobalAttention(gate_nn=nn.Sequential(
                nn.Linear(emb_dim, emb_dim), nn.BatchNorm1d(emb_dim), nn.ReLU(), nn.Linear(emb_dim, 1)))
        elif graph_pooling == "set2set":
            self.pool = Set2Set(emb_dim, processing_steps=2)
        else:
            raise ValueError("Invalid graph pooling type.")

        if graph_pooling == "set2set":
            self.graph_pred_linear = nn.Linear(2*self.emb_dim, self.num_tasks)
        else:
            self.graph_pred_linear = nn.Linear(self.emb_dim, self.num_tasks)

    def forward(self, batched_data):
        h_node = self.gnn_node(batched_data)

        h_graph = self.pool(h_node, batched_data.batch)
        output = self.graph_pred_linear(h_graph)

        if self.training:
            return output
        else:
            # At inference time, relu is applied to output to ensure positivity
            # 因为预测目标的取值范围就在 (0, 50] 内
            return torch.clamp(output, min=0, max=50)

可以看到可选的基于结点表征计算得到图表征的方法有:

  1. “sum”:
  2. “mean”:
  3. “max”:取节点表征的最大值。
  4. “attention”:
  5. “set2set”:
    1. 另一种基于Attention对节点表征加权求和的方法;
    2. 使用模块 torch_geometric.nn.glob.Set2Set
    3. 来自论文 “Order Matters: Sequence to sequence for sets”

PyG中集成的所有的图池化的方法可见于Global Pooling Layers

接下来我们将学习节点嵌入的方法。

基于图同构网络的节点嵌入模块(GINNodeEmbedding Module)

此模块基于多层GINConv实现结点嵌入的计算。此处我们先忽略GINConv的实现。此模块得到的节点属性输入为类别型向量,我们首先用AtomEncoder对其做嵌入得到第0层节点表征(稍后我们再对AtomEncoder做分析)。然后我们逐层计算节点表征,从第1层开始到第num_layers层,每一层节点表征的计算都以上一层的节点表征h_list[layer]、边edge_index和边的属性edge_attr为输入。需要注意的是,GINConv的层数越多,此模块的感受野(receptive field)越大结点i的表征最远能捕获到结点i的距离为num_layers的邻接节点的信息

import torch
from mol_encoder import AtomEncoder
from gin_conv import GINConv
import torch.nn.functional as F

# GNN to generate node embedding
class GINNodeEmbedding(torch.nn.Module):
    """
    Output:
        node representations
    """

    def __init__(self, num_layers, emb_dim, drop_ratio=0.5, JK="last", residual=False):
        """GIN Node Embedding Module"""

        super(GINNodeEmbedding, self).__init__()
        self.num_layers = num_layers
        self.drop_ratio = drop_ratio
        self.JK = JK
        # add residual connection or not
        self.residual = residual

        if self.num_layers < 2:
            raise ValueError("Number of GNN layers must be greater than 1.")

        self.atom_encoder = AtomEncoder(emb_dim)

        # List of GNNs
        self.convs = torch.nn.ModuleList()
        self.batch_norms = torch.nn.ModuleList()

        for layer in range(num_layers):
            self.convs.append(GINConv(emb_dim))
            self.batch_norms.append(torch.nn.BatchNorm1d(emb_dim))

    def forward(self, batched_data):
        x, edge_index, edge_attr = batched_data.x, batched_data.edge_index, batched_data.edge_attr

        # computing input node embedding
        h_list = [self.atom_encoder(x)]  # 先将类别型原子属性转化为原子表征
        for layer in range(self.num_layers):
            h = self.convs[layer](h_list[layer], edge_index, edge_attr)
            h = self.batch_norms[layer](h)
            if layer == self.num_layers - 1:
                # remove relu for the last layer
                h = F.dropout(h, self.drop_ratio, training=self.training)
            else:
                h = F.dropout(F.relu(h), self.drop_ratio, training=self.training)

            if self.residual:
                h += h_list[layer]

            h_list.append(h)

        # Different implementations of Jk-concat
        if self.JK == "last":
            node_representation = h_list[-1]
        elif self.JK == "sum":
            node_representation = 0
            for layer in range(self.num_layers + 1):
                node_representation += h_list[layer]

        return node_representation

接下来我们来学习图同构网络的关键组件GINConv

GINConv–图同构卷积层

图同构卷积层的数学定义如下:
x i ′ = h Θ ( ( 1 + ϵ ) ⋅ x i + ∑ j ∈ N ( i ) x j ) \mathbf{x}^{\prime}_i = h_{\mathbf{\Theta}} \left( (1 + \epsilon) \cdot \mathbf{x}_i + \sum_{j \in \mathcal{N}(i)} \mathbf{x}_j \right) xi=hΘ(1+ϵ)xi+jN(i)xj
PyG中已经实现了此模块,我们可以通过torch_geometric.nn.GINConv来使用PyG定义好的图同构卷积层,然而该实现不支持存在边属性的图。在这里我们自己自定义一个支持边属性的GINConv模块

由于输入的边属性为类别型,因此我们需要先将类别型边属性转换为边表征。我们定义的GINConv模块遵循“消息传递、消息聚合、消息更新”这一过程

  • 这一过程随着self.propagate的调用开始执行,该函数接收edge_index, x, edge_attr此三个函数。edge_index是形状为2,num_edges的张量(tensor)。
  • 在消息传递过程中,此张量首先被按行拆分为x_ix_j张量,x_j表示了消息传递的源节点,x_i表示了消息传递的目标节点。
  • 接着message函数被调用,此函数定义了从源节点传入到目标节点的消息,在这里要传递的消息是源节点表征与边表征之和的relu。我们在super(GINConv, self).__init__(aggr = "add")中定义了消息聚合方式为add,那么传入给任一个目标节点的所有消息被求和得到aggr_out,它是目标节点的中间过程的信息。
  • 接着执行消息更新过程,我们的类GINConv继承了MessagePassing类,因此update函数被调用。然而我们希望对节点做消息更新中加入目标节点自身的消息,因此在update函数中我们只简单返回输入的aggr_out
  • 然后在forward函数中我们执行out = self.mlp((1 + self.eps) *x + self.propagate(edge_index, x=x, edge_attr=edge_embedding))实现消息的更新。
import torch
from torch import nn
from torch_geometric.nn import MessagePassing
import torch.nn.functional as F
from ogb.graphproppred.mol_encoder import BondEncoder


### GIN convolution along the graph structure
class GINConv(MessagePassing):
    def __init__(self, emb_dim):
        '''
            emb_dim (int): node embedding dimensionality
        '''
        super(GINConv, self).__init__(aggr = "add")

        self.mlp = nn.Sequential(nn.Linear(emb_dim, emb_dim), nn.BatchNorm1d(emb_dim), nn.ReLU(), nn.Linear(emb_dim, emb_dim))
        self.eps = nn.Parameter(torch.Tensor([0]))
        self.bond_encoder = BondEncoder(emb_dim = emb_dim)

    def forward(self, x, edge_index, edge_attr):
        edge_embedding = self.bond_encoder(edge_attr) # 先将类别型边属性转换为边表征
        out = self.mlp((1 + self.eps) *x + self.propagate(edge_index, x=x, edge_attr=edge_embedding))
        return out

    def message(self, x_j, edge_attr):
        return F.relu(x_j + edge_attr)
        
    def update(self, aggr_out):
        return aggr_out

理论分析

动机(Motivation)

论文原话:“The design of new GNNs is mostly based on empirical intuition, heuristics, and experimental trial-and-error. There is little understanding of the properties and limitations of GNNs, and formal analysis of GNNs’ representational capacity is limited.” 新的图神经网络的设计大多基于经验性的直觉、启发式的方法和实验性的试错。人们对图神经网络的特性和局限性了解甚少,对图神经网络的表征能力学习的正式分析也很有限。

贡献与结论

  1. (理论上)图神经网络在区分图结构方面最高只能达到与WL Test一样的能力。
  2. 确定了邻接点聚合方法和图池化方法的应具备的条件,在这些条件下,所产生的图神经网络能达到与WL Test一样的能力。
  3. 确定了过去流行的图神经网络变体(如GCN和GraphSAGE)无法区分的图结构,并描述了这种基于图神经网络的模型能够捕获的图结构类型。
  4. 开发了一个简单的神经结构–图形同构网络(GIN),并证明其分辨/表示能力与WL Test相当。

背景:Weisfeiler-Lehman Test (WL Test)

图同构性测试

两个图是同构的,意思是两个图拥有一样的拓扑结构,也就是说,我们可以通过重新标记节点从一个图中得到另外一个图。Weisfeiler-Lehman 图的同构性测试算法,简称WL Test,是一种用于测试两个图是否同构的算法。

WL Test 的一维形式,类似于图神经网络中的邻接节点聚合。WL Test 1)迭代地聚合节点及其邻接节点的标签,然后2)将聚合的标签散列成唯一的新标签,该过程形式化为下方的公示。在迭代过程中,发现两个图之间的节点的标签不同时,就可以确定这两个图是非同构的。需要注意的是节点标签可能的取值只能是有限个数。
L u h ← hash ⁡ ( L u h − 1 + ∑ v ∈ N ( U ) L v h − 1 ) L^{h}_{u} \leftarrow \operatorname{hash}\left(L^{h-1}_{u} + \sum_{v \in \mathcal{N}(U)} L^{h-1}_{v}\right) LuhhashLuh1+vN(U)Lvh1
在上方的公示中, L u h L^{h}_{u} Luh表示节点 u u u的第 h h h次迭代的标签,第 0 0 0次迭代的标签为节点原始标签。

WL测试不能保证对所有图都有效,特别是对于具有高度对称性的图,如链式图、完全图、环图和星图,它会判断错误。

Weisfeiler-Lehman Graph Kernels 方法提出用WL子树核衡量图之间相似性。该方法使用WL Test不同迭代中的节点标签计数作为图的表征向量,它具有与WL Test相同的判别能力。直观地说,在WL Test的第 k k k次迭代中,一个节点的标签代表了以该节点为根的高度为 k k k的子树结构。

Weisfeiler-Leman Test 算法举例说明:给定两个图 G G G G ′ G^{\prime} G,每个节点拥有标签(实际中,一些图没有节点标签,我们可以以节点的度作为标签)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X1z5vDd9-1625500617598)(images/image-20210602134450946-1622612692316.png)]

Weisfeiler-Leman Test 算法通过重复执行以下给节点打标签的过程来实现图是否同构的判断

  1. 聚合自身与邻接节点的标签得到一串字符串,自身标签与邻接节点的标签中间用,分隔,邻接节点的标签按升序排序。排序的原因在于要保证单射性,即保证获得的结果不因邻接节点的顺序改变而改变。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F2mCtgc7-1625500617601)(images/image-20210602143017735.png)]

  1. 标签散列,即标签压缩,将较长的字符串映射到一个简短的标签。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4QoiFdZm-1625500617602)(images/image-20210602143718013.png)]

  1. 给节点重新打上标签。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7kQfQHb3-1625500617603)(images/image-20210602143932099.png)]

每重复一次以上的过程,就完成一次节点自身标签与邻接节点标签的聚合。

当出现两个图相同节点标签的出现次数不一致时,即可判断两个图不相似。如果上述的步骤重复一定的次数后,没有发现有相同节点标签的出现次数不一致的情况,那么我们无法判断两个图是否同构。

当两个节点的 h h h层的标签一样时,表示分别以这两个节点为根节点的WL子树是一致的。WL子树与普通子树不同,WL子树包含重复的节点。下图展示了一棵以1节点为根节点高为2的WL子树。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iiNF8s99-1625500617605)(images/image-20210602145047464.png)]

图相似性评估

此方法来自于Weisfeiler-Lehman Graph Kernels

WL Test 算法的一点局限性是,它只能判断两个图的相似性,无法衡量图之间的相似性。要衡量两个图的相似性,我们用WL Subtree Kernel方法。该方法的思想是用WL Test算法得到节点的多层的标签,然后我们可以分别统计图中各类标签出现的次数,存于一个向量,这个向量可以作为图的表征。两个图的这样的向量的内积,即可作为这两个图的相似性的估计。

图同构网络模型的构建

能实现判断图同构性的图神经网络需要满足,只在两个节点自身标签一样且它们的邻接节点一样时,图神经网络将这两个节点映射到相同的表征,即映射是单射性的。可重复集合(Multisets)指的是元素可重复的集合,元素在集合中没有顺序关系。 **一个节点的所有邻接节点是一个可重复集合,一个节点可以有重复的邻接节点,邻接节点没有顺序关系。**因此GIN模型中生成节点表征的方法遵循WL Test算法更新节点标签的过程。

在生成节点的表征后仍需要执行图池化(或称为图读出)操作得到图表征,最简单的图读出操作是做求和。由于每一层的节点表征都可能是重要的,因此在图同构网络中,不同层的节点表征在求和后被拼接,其数学定义如下,
h G = CONCAT ( READOUT ( { h v ( k ) ∣ v ∈ G } ) ∣ k = 0 , 1 , ⋯   , K ) h_{G} = \text{CONCAT}(\text{READOUT}\left(\{h_{v}^{(k)}|v\in G\}\right)|k=0,1,\cdots, K) hG=CONCAT(READOUT({hv(k)vG})k=0,1,,K)
**采用拼接而不是相加的原因在于不同层节点的表征属于不同的特征空间。**未做严格的证明,这样得到的图的表示与WL Subtree Kernel得到的图的表征是等价的。

结语

在此篇文章中,我们学习了基于图同构网络(GIN)的图表征网络,为了得到图表征首先需要做节点表征,然后做图读出。GIN中节点表征的计算遵循WL Test算法中节点标签的更新方法,因此它的上界是WL Test算法。在图读出中,我们对所有的节点表征(加权,如果用Attention的话)求和,这会造成节点分布信息的丢失。

补充

在这里插入图片描述
在这里插入图片描述
从上面两张图可以看到为什么GIN用sum作为邻居的聚合器了,均值和最大值聚合器都会有一些信息损失或者无法区分的图结构。
在这里插入图片描述
为什么节点分类上WSL方法要比GIN要好呢?

看看WSL用到的算法

在这里插入图片描述在这里插入图片描述
GIN算法
在这里插入图片描述

WSL给出transform-sum-cat比aggregate-transform更好的例子
在这里插入图片描述

作业

  • 请画出下方图片中的6号、3号和5号节点的从1层到3层到WL子树。

    6号:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cSH0llkK-1625500617606)(C:\Users\lailai\Desktop\6号.jpg)]

    3号:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kG3KKFfy-1625500617606)(C:\Users\lailai\Desktop\未命名文件.jpg)]

    5号:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TNjdXnBJ-1625500617606)(C:\Users\lailai\Desktop\5号.jpg)]

参考资料

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
神经网络(Graph Neural Networks,GNN)是一种近年来兴起的学科,被广泛应用于推荐算法中,并且取得了良好的效果。要理解基于神经网络的推荐算法,首先需要对神经网络本身有一定的了解。 神经网络是一种在结构数据上进行学习和推理的神经网络。与传统的神经网络主要关注于处理向量或矩阵数据不同,神经网络通过考虑节点之间的关系和连接来处理数据。它可以对节点和边进行特征建模,从而捕捉中的结构和局部信息。 在推荐算法中,神经网络可以利用用户之间的交互行为构建用户-物品,在中表示用户和物品之间的关系。通过学习中的节点和边的特征表示,可以将用户的兴趣和物品的属性编码成向量形式,并使用这些向量进行推荐。 一种基于神经网络的推荐算法是基于会话的推荐算法。该算法通过考虑用户的历史行为序列,构建会话,其中节点表示用户在不同时间点的行为,边表示行为之间的关系。通过学习会话中的节点和边的特征表示,可以预测用户的下一个行为并进行推荐。 参考文献[15]中提出了一种基于神经网络的会话推荐算法。该算法利用神经网络模型对会话进行建模,并结合注意力机制来捕捉重要的上下文信息。实验证明,该算法在推荐效果上取得了显著的改进。 综上所述,神经网络是一种用于推荐算法的新兴学科,通过建模结构数据和学习节点和边的特征表示,可以实现更准确和个性化的推荐。而基于神经网络的推荐算法中,会话推荐算法是一种常见的应用方式,通过对用户历史会话进行建模,提高了推荐算法的效果和准确性。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [秒懂算法 | 基于神经网络的推荐算法](https://blog.csdn.net/qq_41640218/article/details/129257309)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [一文了解推荐系统中的神经网络](https://blog.csdn.net/tMb8Z9Vdm66wH68VX1/article/details/119769201)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值