阅读笔记Graph Representation Learning--Chapter5

系列文章目录

阅读笔记Graph Representation Learning–Chapter2

阅读笔记Graph Representation Learning–Chapter3

阅读笔记Graph Representation Learning–Chapter4

阅读笔记Graph Representation Learning–Chapter5

阅读笔记Graph Representation Learning–Chapter6

阅读笔记Graph Representation Learning–Chapter8


概要

本书的第一部分讨论了学习图中节点的低维嵌入的方法。我们讨论的节点嵌入方法使用shallow embedding的方法来生成节点的表示,其中我们简单地为每个节点优化一个唯一的嵌入向量。在本章中,我们将重点放在更复杂的编码器模型上。我们将介绍图形神经网络(GNN)形式,它是定义图形数据上的深层神经网络的一般框架。关键思想是,我们希望生成实际依赖于图结构的节点表示,以及我们可能拥有的任何特征信息。

5.1 Neural Message Passing

5.1.1 Overview of the Message Passing Framework

在这里插入图片描述
在这里插入图片描述
Node features:注意,不像在这本书的第一部分讨论的shallow embedding方法,GNN框架要求对于图中的任何节点,都要有节点的特征,作为模型的输入。在很多图中,我们有丰富的节点特征来用(比如生物网络中的基因表达特征或社交网络中的文本特征)。但是,没有可用节点特征的情况下,仍然有几个选项。一种选择是使用节点统计信息(如第2.1节中介绍的)来定义节点的特征。另一种流行的方法是使用标识特性,其中我们将每个节点与一个one hot指示符特性关联,该特性唯一地标识该节点。但是,请注意,使用标识特性会使模型变得transductive,并且无法将其泛化到看不见的节点。

5.1.2 Motivations and Intuitions

GNN消息传递框架背后的基本直觉很简单:在每次迭代中,每个节点都从其本地邻居聚集信息,随着这些迭代的进行,每个嵌入的节点都包含来自图中更远处的越来越多的信息。但是这些节点嵌入实际上编码了什么类型的信息呢?
通常,这些信息有两种形式。一方面,有关于图的结构信息。例如,在经过GNN消息的k次迭代之后,节点u的嵌入h(k)u可能会编码有关u的k跳邻域中所有节点的度的信息。 此结构信息对于许多任务可能很有用。 例如,当分析分子图时,我们可以使用度信息来推断原子类型和不同的结构基序,例如苯环。
除了结构信息外,GNN节点嵌入所捕获的另一种关键信息是基于特征的。在GNN消息传递的k次迭代后,每个节点的嵌入也对k-hop邻域中所有特征的信息进行编码。GNNs的这种局部特征聚集行为类似于卷积神经网络(CNNs)中卷积核的行为。然而,当CNNs从图像中空间定义的面片聚集特征信息时,GNNs则基于局部图邻域来聚集信息。作者将在第7章更详细地探讨GNN和卷积之间的联系.。

5.1.3 The Basic GNN

到目前为止,我们已经以相对抽象的方式讨论了GNN框架,作为使用UPDATE和AGGREGATE函数的一系列消息传递迭代。

基本GNN消息传递定义为
在这里插入图片描述
在这里插入图片描述
σ表示 elementwise非线性激活函数(如tanh或ReLU)
在这里插入图片描述
在这里插入图片描述

5.1.4 Message Passing with Self-loops

作为神经消息传递方法的一种简化,通常为输入图添加自循环,而省略显式的更新步骤。在这种方法中,我们将消息传递简单地定义为:
以这种方式简化消息传递通常可以缓解过度拟合,但同时也严重限制了GNN的表示能力,因为无法将来自节点邻居的信息与来自节点本身的信息区分开。
对于basic GNNs书中的分析:
在这里插入图片描述

5.2 Generalized Neighborhood Aggregation

the basic GNN可以在很多方面进行改进和推广。在这里,作者将讨论如何对AGGREGATE运算进行一般化和改进,之后(第5.3节)提供了有关UPDATE操作的类似讨论。

5.2.1 Neighborhood Normalization

(1) the basic GNN将邻居的embeddings相加作为aggregate运算。这种方法的一个问题是,它可能不稳定,并且对节点级别高度敏感。比如,节点u有很多的邻居(超过100),即有很高的度,比节点v高很多,则有:
在这里插入图片描述
该问题的一种解决方案是简单地基于所涉及节点的程度来规范聚合操作。 最简单的方法是求平均值而不是求和
在这里插入图片描述
但研究人员也发现了其他标准化因素的成功,如下面使用的对称标准化:
在这里插入图片描述
例如,在引文图中,分析具有非常高的度的节点(即多次被引用的论文)的信息的数据对于推断图中的社区成员身份可能不是很有用,因为这些论文可以在不同的子领域被引用数千次。

(2) Graph convolutional networks (GCNs)
1) 图卷积网络(GCN)是最受欢迎的基准图神经网络模型之一,它采用对称归一化聚合以及自循环更新方法。
因此,GCN模型将消息传递函数定义为:
在这里插入图片描述
2)To normalize or not to normalize?
使用GNN时,正确规范化对于实现稳定和强大的性能至关重要。 重要的是要注意,但是,标准化也可能导致信息丢失。 例如,在归一化之后,可能很难(甚至不可能)使用学习到的嵌入来区分不同程度的节点,并且归一化会掩盖各种其他结构图特征。 实际上,在等式(5.14)
中使用归一化聚合算子的基本GNN的表征能力被证明比在等式(5.8)
中的基本和聚合函数要弱(请参阅第7章)。 因此,使用规范化是一个特定于应用程序的问题。 通常,在节点特征信息远比结构信息有用或在优化过程中可能导致不稳定性的节点度范围非常大的任务中,归一化最为有用。

5.2.2 Set Aggregators

(1) 概要
邻域规范化是提高GNN性能的有用工具,但是我们是否可以在改进聚合算子方面做更多的工作?有没有什么比仅仅对相邻嵌入进行相加运算更复杂的呢?
邻域聚合操作基本上是一个集合函数。
已知一组相邻的嵌入:
在这里插入图片描述
需将集合映射为单个向量在这里插入图片描述
对于这个集合有一个重要的事实:节点的邻居没有自然的顺序,因此我们定义的任何聚合函数都必须是置换不变的

(2)Set pooling
一种定义聚合函数的原则方法是基于置换不变神经网络的理论。 例如,具有以下形式的聚合函数是一个全集函数逼近器
在这里插入图片描述
当MLP层数少时,可能会导致严重的过拟合。

(3)Janossy pooling
Set pooling方法用于邻居聚合,基本上只是在本节中讨论的更基本的聚合体系结构之上添加额外的MLP层。这个想法很简单,但众所周知,它可以提高GNNs的理论能力。然而,还有另一种方法,称为Janossy pooling,它也被证明比简单地使用邻居嵌入的和或平均值更强大。回想一下邻域聚合的挑战在于我们必须使用置换不变函数,因为节点的邻居没有自然的排序。在集合池方法(方程5.17)中,我们通过依赖求和、平均值或元素最大值来将嵌入集缩减为单个向量,从而实现了这种置换不变性。我们将这种简化与前馈神经网络(即MLPs)相结合,使模型更加强大。Janossy pooling采用了完全不同的方法:我们不使用置换不变约化(例如求和或平均值),而是应用置换敏感函数,将结果平均化到许多可能的置换上。

Janossy池化方法通过以下方式执行邻居聚合
在这里插入图片描述
置换敏感函数通常是一个LSTM。如果方(5.18)中的置换集∏等于所有可能的置换,则方程(5.18)中的聚合器也是集合的通用函数近似器,如方程(5.17)。
在这里插入图片描述
然而,对所有可能的排列求和通常是困难的。因此,在实践中,Janossy pooling采用以下两种方法:

  1. 在每个聚合器应用程序中抽取可能排列的随机子集,并仅对该随机子集进行求和。
  2. 采用邻域集合中节点的规范排序;例如,根据节点的程度,将节点按降序排列,并随机断开连接。

详细的介绍参阅:Janossy pooling: Learningdeep permutation-invariant functions for variable-size inputs. In ICLR, 2018

5.2.3 Neighborhood Attention

除了更一般的集合聚合形式外,改进GNNs聚合层的一个流行策略是应用attention机制。其基本思想是为每个邻居分配一个关注权重或重要性,用于在聚合步骤中衡量该邻居的影响。如下:
在这里插入图片描述
在这里插入图片描述
在Graph Attention Network中,注意力权重的定义如下:
在这里插入图片描述
a为可训练注意向量,W为可训练矩阵,表示连接算符。
常见的注意力机制的变种包括双线性注意力模型:
在这里插入图片描述
以及使用MLP的注意力层的变体,例如:
在这里插入图片描述
MLP被限制为标量输出。
在transformer中,使用了多个注意力机制,对于graph中的注意力机制也有如下拓展:
在这里插入图片描述
在模型里增加注意力机制是提高GNN模型的表示能力的一种有用的策略,特别是在事先知道一些邻居可能比其他邻居更有用的情况下。

5.3 Generalized Update Methods

Over-smoothing and neighbourhood influence

广义更新方法可以帮助解决的GNNs的一个常见问题是过度平滑。Over-smoothing的基本思想是,经过几次GNN消息传递后,图中所有节点的表示可以变得非常相似。这种趋势在基本GNN模型和采用自循环更新方法的模型中尤其常见。过度平滑是有问题的,因为它使得不可能构建更深层次的GNN模型来利用图中的长期依赖关系,因为这些深度GNN模型往往只生成过平滑的嵌入。通过定义每个节点输入特征的影响,可以形式化GNN中的过度平滑问题。

GNNs中过度平滑的问题可以通过定义每个节点最初的输入特征向量对某个最终输出的embedding的影响来形式化。特别地,对于任意一对节点u和v,我们可以通过检查相应的雅可比矩阵的大小来量化节点u对GNN中节点v的影响,如下所示:

在这里插入图片描述
在这里插入图片描述
定理3:对于任何使用自循环更新方法和聚合函数的GNN模型。

在这里插入图片描述
当建立的gnn模型越来越深时,将会失去更多的局部邻域信息,使得我们学习到的embeddings变得过度平滑,即不同节点之间的embeddings变得相似。

5.3.1 Concatenation and Skip-Connections

如上所述,过度平滑是GNNs中的一个核心问题。当特定节点的信息在经过几次GNN消息传递后被“冲刷”或“丢失”时,就会发生过度平滑。直观地说,在消息传递期间从节点邻居聚合的信息开始主导更新的节点表示的情况下,我们可以预期过度平滑。更新的节点表征在牺牲上层的节点信息,而依赖于聚合来的节点信息。缓解此问题的一种自然方法是使用向量连接或跳过连接,它们尝试在更新步骤中直接保存来自前几轮消息传递的信息。

在这里插入图片描述
这里的关键直觉是,我们鼓励模型在消息传递过程中分离信息,将来自邻居的信息从每个节点的当前表示中分离出来。然而,除了串联,我们也可以采用其他形式的跳跃连接,例如线性插值法
在这里插入图片描述
这些方法在节点分类这样的任务重表现很好。它们擅长于表现出同质性的任务,即每个节点的预测与其局部邻域的特征密切相关。

5.3.2 Gated Updates

一般来说,为RNNs定义的任何更新函数都可以在GNNs上下文中使用。比如,
在这里插入图片描述
这些门控更新在促进深度GNN架构(例如超过10层)并防止过度平滑方面非常有效。通常,它们对于预测任务需要对图的全局结构进行复杂推理的GNN应用最为有用,例如程序分析应用或组合优化应用。

5.3.3 Jumping Knowledge Connections

在前面的部分中,我们隐式地假设我们正在使用GNN最后一层的输出。 换句话说,我们一直假设我们在下游任务中使用的节点表示z_u等于GNN中的最终层节点嵌入。
在这里插入图片描述
这个假设是由许多GNN方法得出的,并且此策略的局限性促使人们对残留更新和门控更新进行限制,以限制过度平滑。但是,提高最终节点表示质量的一种补充策略是在消息传递的每一层简单地利用表示,而不是仅使用最终层输出。 在这种方法中,我们定义了最终的节点表示形式,如下:
在这里插入图片描述
在这里插入图片描述
这种策略被称为添加跳跃知识(JK)连接。这种方法通常可以在各种任务上实现一致的改进,并且是非常有用策略。

5.4 Edge Features and Multi-relational GNNs

到目前为止,对GNNs和神经消息传递的讨论都是隐含地假设我们有简单的图。然而,在许多应用中,所讨论的图是多关系的或其他异构的(例如,知识图)。在本节中,作者将回顾一些最流行的策略,这些策略是为适应这些数据而开发的。

5.4.1 Realational Graph Neural Networks

Relational Graph Convolutional Network(RGCN):
在这种方法中,我们通过对每一种关系指定一个单独的转换矩阵来扩充聚合函数以适应多种关系类型。
在这里插入图片描述
f_n是一个依赖于节点u的邻域和节点v的正则化函数。

参数共享
朴素的RGCN方法的一个缺点是参数的数量急剧增加,因为针对每个关系类型都有一个可训练的矩阵。对于知识图谱方面的应用时,这可能会导致过拟合和较慢的训练速度。
解决方法有一种基于基矩阵的参数共享方案:
在这里插入图片描述
在这种基矩阵方法中,所有的关系矩阵都是通过若干基本矩阵的线性组合来定义。通过不同的权重来定义得到不同的关系矩阵。

扩展和变化
RGCN体系结构可以以多种方式进行扩展,通常,我们将为每个关系定义单独聚合矩阵的方法称为关系图神经网络。例如,Zitnik et al。[2018]对药物、疾病和蛋白质相关的多关系数据集进行建模,Marcheggiani和Titov[2017]利用类似策略分析语言依赖图。其他作品也成功地将RGCN风格的聚合与注意力结合起来【Teru等人,2020年】

5.4.2 Attention and Feature Concatenation

在关系GNN方法中,我们为每个关系定义一个单独的聚合参数,它适用于多关系图和具有离散边缘特征的情况。为了适应具有更一般形式的边缘特征的情况,我们可以在注意力机制中利用这些特征,或者在消息传递期间将这些信息与邻居嵌入相连接。举个例子子,给定任何一种基本聚合方法,一种利用边的特征的简单策略是将新的聚合函数定义为:

在这里插入图片描述
在这里插入图片描述

5.5 Graph Pooling

相比于之前章节一直关注的如何学到节点级别的embedding ,Graph Pooling是为了学习到Graph级别的即整个图的embedding。

Set pooling 方法
可以将graph embedding看做针对将图中的节点集映射为一个单一向量。第一个方法是简单的将节点的embedding相加或者求平均值:
在这里插入图片描述

f_n是正则化函数。虽然非常简单,但对于涉及小图的应用程序,基于节点嵌入的总和或平均值的合并通常就足够了。

第二种方法是利用LSTM以及注意力机制来完成图池化。
在这种池化方法中,我们迭代由以下方程组定义的一系列基于关注的聚合,这些迭代针对t = 1,…,T步进行迭代:
在这里插入图片描述
在这里插入图片描述
这种方法代表了一种复杂的基于注意力的池化架构,并且已经成为许多图级分类任务中流行的池化方法。

Graph coarsening approaches

集合池方法的一个局限性是它们不利用图的结构。虽然将图池的任务看作是一个简单的集学习问题是合理的,但是在池化阶段利用图拓扑也会有好处。实现这一点的一个流行策略是执行图聚类或粗化,作为汇集节点表示的一种手段。
假设我们具有以下聚类函数,将图中的所有节点映射到c集群上的一个赋值:

在这里插入图片描述
在这里插入图片描述

因此,这个新的邻接矩阵现在表示图中簇之间的关联强度(即边),新的特征矩阵表示分配给每个节点的所有节点的聚合嵌入集群。我们然后可以在这个粗化的图上运行一个GNN,并将整个粗化过程重复若干次,其中每一步图的大小都会减小。图的最终表示是通过一个集合池在一个足够粗化的图中嵌入的节点上计算出来的。
这种基于粗化的方法是受卷积神经网络(CNN)中使用的池化方法启发的,它依赖于我们可以构建对输入图的不同粒度进行操作的分层GNN的直觉。 在实践中,这些粗化方法可导致强大的性能,但它们也可能不稳定且难以训练。 例如,为了使整个学习过程在端到端之间是可区分的,聚类函数fc必须是可区分的,这排除了大多数现成的聚类算法,例如光谱聚类。 Cangea等。 [2018]提供了该领域不同方法的深入比较,重点介绍了学习稀疏聚类分配的好处以及实施这些粗化方法的一些挑战。

5.6 Generalized Message Passing

到目前为止,本章中的介绍主要集中在最流行的GNN消息传递样式上,它主要在节点级运行。然而,GNN消息传递方法也可以被推广到利用消息传递的每个阶段的边缘和图形级信息。例如,在Battaglia等人提出的更一般的方法中。[2018],我们根据以下等式定义消息传递的每次迭代:
在这里插入图片描述

"Structure-Aware Transformer for Graph Representation Learning"是一篇使用Transformer模型进行图表示学习的论文。这篇论文提出了一种名为SAT(Structure-Aware Transformer)的模型,它利用了图中节点之间的结构信息,以及节点自身的特征信息。SAT模型在多个图数据集上都取得了非常好的结果。 以下是SAT模型的dgl实现代码,代码中使用了Cora数据集进行示例: ``` import dgl import numpy as np import torch import torch.nn as nn import torch.nn.functional as F class GraphAttentionLayer(nn.Module): def __init__(self, in_dim, out_dim, num_heads): super(GraphAttentionLayer, self).__init__() self.num_heads = num_heads self.out_dim = out_dim self.W = nn.Linear(in_dim, out_dim*num_heads, bias=False) nn.init.xavier_uniform_(self.W.weight) self.a = nn.Parameter(torch.zeros(size=(2*out_dim, 1))) nn.init.xavier_uniform_(self.a.data) def forward(self, g, h): h = self.W(h).view(-1, self.num_heads, self.out_dim) # Compute attention scores with g.local_scope(): g.ndata['h'] = h g.apply_edges(fn.u_dot_v('h', 'h', 'e')) e = F.leaky_relu(g.edata.pop('e'), negative_slope=0.2) g.edata['a'] = torch.cat([e, e], dim=1) g.edata['a'] = torch.matmul(g.edata['a'], self.a).squeeze() g.edata['a'] = F.leaky_relu(g.edata['a'], negative_slope=0.2) g.apply_edges(fn.e_softmax('a', 'w')) # Compute output features g.ndata['h'] = h g.update_all(fn.u_mul_e('h', 'w', 'm'), fn.sum('m', 'h')) h = g.ndata['h'] return h.view(-1, self.num_heads*self.out_dim) class SATLayer(nn.Module): def __init__(self, in_dim, out_dim, num_heads): super(SATLayer, self).__init__() self.attention = GraphAttentionLayer(in_dim, out_dim, num_heads) self.dropout = nn.Dropout(0.5) self.norm = nn.LayerNorm(out_dim*num_heads) def forward(self, g, h): h = self.attention(g, h) h = self.norm(h) h = F.relu(h) h = self.dropout(h) return h class SAT(nn.Module): def __init__(self, in_dim, hidden_dim, out_dim, num_heads): super(SAT, self).__init__() self.layer1 = SATLayer(in_dim, hidden_dim, num_heads) self.layer2 = SATLayer(hidden_dim*num_heads, out_dim, 1) def forward(self, g, h): h = self.layer1(g, h) h = self.layer2(g, h) return h.mean(0) # Load Cora dataset from dgl.data import citation_graph as citegrh data = citegrh.load_cora() g = data.graph features = torch.FloatTensor(data.features) labels = torch.LongTensor(data.labels) train_mask = torch.BoolTensor(data.train_mask) val_mask = torch.BoolTensor(data.val_mask) test_mask = torch.BoolTensor(data.test_mask) # Add self loop g = dgl.remove_self_loop(g) g = dgl.add_self_loop(g) # Define model and optimizer model = SAT(features.shape[1], 64, data.num_classes, 8) optimizer = torch.optim.Adam(model.parameters(), lr=0.005, weight_decay=5e-4) # Train model for epoch in range(200): model.train() logits = model(g, features) loss = F.cross_entropy(logits[train_mask], labels[train_mask]) optimizer.zero_grad() loss.backward() optimizer.step() acc = (logits[val_mask].argmax(1) == labels[val_mask]).float().mean() if epoch % 10 == 0: print('Epoch {:03d} | Loss {:.4f} | Accuracy {:.4f}'.format(epoch, loss.item(), acc.item())) # Test model model.eval() logits = model(g, features) acc = (logits[test_mask].argmax(1) == labels[test_mask]).float().mean() print('Test accuracy {:.4f}'.format(acc.item())) ``` 在这个示例中,我们首先加载了Cora数据集,并将其转换为一个DGL图。然后,我们定义了一个包含两个SAT层的模型,以及Adam优化器。在训练过程中,我们使用交叉熵损失函数和验证集上的准确率来监控模型的性能。在测试阶段,我们计算测试集上的准确率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值