图神经网络(GNN)入门笔记(2)——从谱域理解图卷积,ChebNet和GCN实现

一、谱域图卷积(Spectral Domain Graph Convolution)

与谱域图卷积(Spectral Domain Graph Convolution)对应的是空间域(Spatial Domain)图卷积。本节学习的谱域图卷积指的是通过频率来理解卷积的方法。

二、ChebNet

上一节结尾,我们将谱域图卷积写作:
( g ∗ g f ) = U W U T f (g *_g f)= U W U^T f (ggf)=UWUTf

这其实已经可以做一些任务了,例如对于一个三维点云图,特征为每个点的坐标或者法向量,进行低通滤波可以把这个三维模型变得更加平滑。
而对于另一些任务,就不大合适。例如,我们有一张论文互相引用图,特征为每篇论文的种类,想要预测其中一些未标记的论文的种类。这时,这种图卷积就暴露了一些问题,例如,参数量和图规模相关以至于不适合处理不同的图,以及该处理方法过于全局,缺乏局部信息。
于是我们重新考虑拉普拉斯矩阵。乘一次拉普拉斯矩阵,相当于每个点聚合一次邻居的信息。那么乘两次,就是聚合2跳邻居的信息。乘k次,是k-hop信息。而:
L k = ( U Λ U T ) k = U Λ k U T L^k = (U\Lambda U^T)^k = U\Lambda^kU^T Lk=(UΛUT)k=UΛkUT
因此,我们可以这样修改信号处理方法。我们学习参数 θ 0 , . . . , θ k − 1 \theta_0,...,\theta_{k-1} θ0,...,θk1,代表不同距离邻居的重要程度,令 W = θ 0 Λ 0 + . . . + θ k − 1 Λ k − 1 W=\theta_0 \Lambda^0 +...+\theta_{k-1} \Lambda^{k-1} W=θ0Λ0+...+θk1Λk1,就是一个对这个任务不错的滤波函数。
不过ChebNet之所以叫ChebNet,就是因为它出于种种复杂的原因使用了一个名为Chebyshev polynomial的技巧来拟合上述的 W W W。具体地:
W ≈ ∑ k = 0 K − 1 θ k T k ( Λ ~ ) W \approx \sum_{k=0}^{K-1} \theta_k T_k(\tilde \Lambda) Wk=0K1θkTk(Λ~)
其中 T 0 ( x ) = 1 , T 1 ( x ) = x , T k ( x ) = 2 x T k − 1 ( x ) − T k − 2 ( x ) T_0(x)=1,T_1(x)=x,T_k(x)=2xT_{k-1}(x)-T_{k-2}(x) T0(x)=1,T1(x)=x,Tk(x)=2xTk1(x)Tk2(x)

为什么要使用切比雪夫多项式?我看网上有些人说是为了降低复杂度,但实际上计算 T k ( L ) T_k(L) Tk(L)应该并不会比计算 L k L^k Lk复杂度更低。实际上应该和切比雪夫多项式在信号处理中的性质有关,由于我相关知识不足,所以暂且略过。总之,目前需要学到的是ChebNet引入的把参数量从和图中点数有关的 O ( n ) O(n) O(n)减少到 O ( 1 ) O(1) O(1)级别的思想。

Λ ~ \tilde \Lambda Λ~是对原 Λ \Lambda Λ进行放缩的值,因为切比雪夫多项式要求参数取值在 [ − 1 , 1 ] [-1,1] [1,1]之间,所以对其进行了一个 Λ ~ = 2 Λ λ m a x − I \tilde \Lambda=\frac{2\Lambda}{\lambda_{max}}-I Λ~=λmaxI这样的缩放。接着代回到原式,得:
( g ∗ g f ) = ∑ k = 0 K − 1 θ k T k ( L ~ ) f (g*_g f)=\sum_{k=0}^{K-1} \theta_kT_k(\tilde L)f (ggf)=k=0K1θkTk(L~)f
其中 L ~ = 2 L λ m a x − I \tilde L = \frac{2L}{\lambda_{max}}-I L~=λmax2LI
不过等等,这么说我们还是得花费高额复杂度求特征值吗?其实也不必,因为我们可以相信神经网络参数对规模缩放的自适应性(或许也可以使用一些估计方法?),取 l m a x ≈ 2 l_{max} \approx 2 lmax2即可。那么此时, L ~ = D − 1 2 A D − 1 2 \tilde L=D^{-\frac{1}{2}}AD^{-\frac{1}{2}} L~=D21AD21
至此,我们已经解决了上一章讨论的所有问题:

  1. W W W不再与图结构相关。
  2. 不需要计算特征向量。
  3. 因为不需要计算特征向量,不需求对称性保证正交基存在,可以扩展用于有向图)。
  4. 可以拟合局部信息。

三、ChebNet的实现

接下来我们尝试实现一个简单的ChebNet。
在此我使用了小规模论文类别-引用关系数据集Cora,可以使用torch_geometric.datasets来下载这个数据集。下载时如果出现超时问题,可以参考这篇博客。
另外,实际问题中的图一般都是稀疏图,拉普拉斯矩阵也是稀疏矩阵,可以用一些稀疏矩阵乘法方法加速过程。因为本篇笔记尚未讨论相关问题(我还没学会),此处给出使用邻接矩阵进行完整矩阵乘法的实现。

卷积核:

class ChebConv(nn.Module):def __init__(self, in_channels, out_channles, K=2,use_bias=True):super(ChebConv, self).__init__()self.in_channels = in_channelsself.out_channles = out_channlesself.K = Kself.use_bias = use_biasself.weights = nn.Parameter(torch.Tensor(K, 1, in_channels, out_channles))nn.init.xavier_normal_(self.weights)if(use_bias):self.bias = nn.Parameter(torch.FloatTensor(out_channles))else:self.register_parameter('bias', None)# 切比雪夫多项式,其实可以提前实现而不必在卷积核里每次都重算一遍# 放在此处只是为了演示清晰def cheb_polynomial(self, laplacian):N = laplacian.size(0)terms = torch.zeros([self.K, N, N], device=laplacian.device, dtype=torch.float32)terms[0] = torch.eye(N, device=device, dtype=torch.float32)if(self.K == 1):return termsterms[1] = laplacianfor k in range(2, self.K):terms[k] = 2.0 * torch.mm(laplacian, terms[k-1]) - terms[k-2]return terms # [K, N, N]def forward(self, inputs, laplacian):# inputs: [B, N, in_channels]# outputs: [B, N, out_channels]terms = self.cheb_polynomial(laplacian).unsqueeze(1) # [K, 1, N, N]outputs = torch.matmul(terms, inputs) # [K, B, N, in_channels]outputs = torch.matmul(outputs, self.weights) # [K, B, N, out_channels]outputs = torch.sum(outputs, dim=0) #[B, N, out_channels]if(self.use_bias):outputs += self.biasreturn outputs

网络架构:

class ChebNet(nn.Module):def __init__(self, in_channels, hidden_channels, out_channles, K=2, droput=0.5):super(ChebNet, self).__init__()self.conv1 = ChebConv(in_channels, hidden_channels, K)self.conv2 = ChebConv(hidden_channels, out_channles, K)self.dropout = droputdef forward(self, x, laplacian):outputs1 = self.conv1(x, laplacian)outputs1 = outputs1.relu()outputs1 = F.dropout(outputs1, p=self.dropout, training=self.training) outputs2 = self.conv2(outputs1, laplacian)outputs2 = outputs2.relu()return outputs2

数据集处理及训练和测试:

# 获取数据集,Cora只有一组数据,且不分训练和测试
dataset = Planetoid(root='./Cora', name='Cora', transform=NormalizeFeatures())
data = dataset[0]device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')# 定义模型
model = ChebNet(in_channels=dataset.num_node_features,hidden_channels=16,out_channles=dataset.num_classes,K=3, droput=0).to(device)# 邻接矩阵
def edge_index_to_adj(edge_index, num_nodes):  # 构建一个大小为 (num_nodes, num_nodes) 的零矩阵  adj = torch.zeros(num_nodes, num_nodes, dtype=torch.float32)# 使用索引广播机制,一次性将边索引映射到邻接矩阵的相应位置上  adj[edge_index[0], edge_index[1]] = 1.adj[edge_index[1], edge_index[0]] = 1.return adjdef get_laplacian(adj, use_normalize=True):I = torch.eye(adj.size(0), device=adj.device, dtype=adj.dtype)if(use_normalize):D = torch.diag(torch.sum(adj, dim=-1) ** (-1 / 2))L = I - torch.mm(torch.mm(D, adj), D)else:D = torch.diag(torch.sum(adj, dim=-1))L = D - adjL -= Ireturn L# 提前计算拉普拉斯矩阵
adj = edge_index_to_adj(data.edge_index, data.num_nodes)
laplacian = get_laplacian(adj).to(device)# 定义损失函数和优化器
loss = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(params=model.parameters(), lr = 0.001, weight_decay=5e-4)def train():model.train()optimizer.zero_grad()  # 梯度清理y_hat = model(data.x.unsqueeze(0).to(device), laplacian).squeeze(0)l = loss(y_hat[data.train_mask].to(device), data.y[data.train_mask].to(device))l.backward() # 误差反向传播optimizer.step()return ldef test():model.eval()pred = model(data.x.unsqueeze(0).to(device), laplacian).squeeze(0)pred = pred.argmax(dim=1)test_correct = pred[data.test_mask] == data.y[data.test_mask].to(device)test_acc = int(test_correct.sum()) / int(data.test_mask.sum())return test_accepoch = 501
for epoch in range(1, epoch):train_loss = train()test_acc = test()if epoch % 10 == 0:print(f"epoch:{epoch}  loss:{train_loss}  test_acc:{test_acc}")

四、从ChebNet的first-order近似到GCN:从谱域理解GCN

对于ChebNet,在 K = 2 K=2 K=2的情况下,卷积的定义为:
g θ ∗ f = θ 0 f − θ 1 D − 1 2 A D − 1 2 g_\theta * f=\theta_0 f - \theta_1 D^{-\frac{1}{2}}AD^{-\frac{1}{2}} gθf=θ0fθ1D21AD21
如果我们进一步减少参数量,取 θ 1 = − θ 0 \theta_1=-\theta_0 θ1=θ0,则此时有:
g θ ∗ f = θ ( I + D − 1 2 A D − 1 2 ) f g_\theta * f=\theta (I+D^{-\frac{1}{2}}AD^{-\frac{1}{2}})f gθf=θ(I+D21AD21)f

到这一步已经离GCN很近了,只缺少最后一点:Renormalization Trick
在上式中, I + D − 1 2 A D − 1 2 I+D^{-\frac{1}{2}}AD^{-\frac{1}{2}} I+D21AD21的特征值范围在 [ 0 , 2 ] [0,2] [0,2]之间(可以使用盖尔圆理论进行估计),大于1的数反复相乘有可能引起数值不稳定和梯度爆炸的问题。而Renormalization Trick就是通过对图中所有点添加自环,然后再统一归一化,来估计一个特征值在 [ − 1 , 1 ] [-1,1] [1,1]之间的 L ~ \tilde L L~。具体地:
A ~ = A + I , D ~ i i = ∑ j A ~ i j , L ~ = D ~ − 1 2 A ~ D ~ − 1 2 \tilde A = A+I, \tilde D_{ii}=\sum_j \tilde A_{ij}, \tilde L =\tilde D^{-\frac{1}{2}}\tilde A\tilde D^{-\frac{1}{2}} A~=A+I,D~ii=jA~ij,L~=D~21A~D~21

盖尔圆理论
A = ( a i j ) ∈ C A=(a_{ij}) \in \mathbb{C} A=(aij)C,则第 i i i的盖尔圆为以 a i i a_{ii} aii为圆心,以 R i = ∑ j ≠ i ∣ a i j ∣ R_i=\sum_{j \not=i} |a_{ij}| Ri=j=iaij为半径的圆。 A A A的所有特征值落在盖尔圆的并集之内。
I + D − 1 2 A D − 1 2 I+D^{-\frac{1}{2}}AD^{-\frac{1}{2}} I+D21AD21的每一个盖尔圆都是以1为圆心,以1为半径,特征值取值范围: [ 0 , 2 ] [0,2] [0,2]。而 L ~ \tilde L L~的盖尔圆是以 1 D i i \frac{1}{D_{ii}} Dii1为圆心,以 1 − 1 D i i 1-\frac{1}{D_{ii}} 1Dii1为半径的,特征值取值范围: [ − 1 , 1 ] [-1,1] [1,1]

好的,恭喜你也学会GCN了,让我们来实现吧!

五、GCN的实现

卷积核:

class GCNConv(nn.Module):def __init__(self, in_channels, out_channles, use_bias=True):super(GCNConv, self).__init__()self.in_channels = in_channelsself.out_channles = out_channlesself.use_bias = use_biasself.weights = nn.Parameter(torch.Tensor(in_channels, out_channles))nn.init.xavier_normal_(self.weights)if(use_bias):self.bias = nn.Parameter(torch.FloatTensor(out_channles))else:self.register_parameter('bias', None)def forward(self, inputs, laplacian):# inputs: [B, N, in_channels]# outputs: [B, N, out_channels]outputs = torch.matmul(inputs, self.weights) # [B, N, out_channels]outputs = torch.matmul(laplacian, outputs) # [B, N, out_channels]if(self.use_bias):outputs += self.biasreturn outputs

网络:

class GCN(nn.Module):def __init__(self, in_channels, hidden_channels, out_channles, droput=0.5):super(GCN, self).__init__()self.conv1 = GCNConv(in_channels, hidden_channels)self.conv2 = GCNConv(hidden_channels, out_channles)self.dropout = droputdef forward(self, x, laplacian):outputs1 = self.conv1(x, laplacian)outputs1 = outputs1.relu()outputs1 = F.dropout(outputs1, p=self.dropout, training=self.training) outputs2 = self.conv2(outputs1, laplacian)outputs2 = F.softmax(outputs2, dim=2)return outputs2

renormalization trick后的拉普拉斯矩阵计算:

def get_laplacian(adj):I = torch.eye(adj.size(0), device=adj.device, dtype=adj.dtype)adj = adj + ID = torch.diag(torch.sum(adj, dim=-1) ** (-1 / 2))L = torch.mm(torch.mm(D, adj), D)return L

除了自己手写GCN卷积核以外,还可以使用torch_geometric中的GCNConv实现,此时传入的不再是拉普拉斯矩阵,而是所有的边集(edge index),包中会用一些针对拉普拉斯矩阵的稀疏性质的方法加速计算:

class GCN(nn.Module):def __init__(self, in_channels, hidden_channels, out_channles, droput=0.5):super(GCN, self).__init__()self.conv1 = GCNConv(in_channels, hidden_channels)self.conv2 = GCNConv(hidden_channels, out_channles)self.dropout = droputdef forward(self, x, edge_index):outputs1 = self.conv1(x, edge_index)outputs1 = outputs1.relu()outputs1 = F.dropout(outputs1, p=self.dropout, training=self.training) outputs2 = self.conv2(outputs1, edge_index)outputs2 = F.softmax(outputs2, dim=2)return outputs2

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/58757.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

leetcode92:反转链表||

给你单链表的头指针 head 和两个整数 left 和 right &#xff0c;其中 left < right 。请你反转从位置 left 到位置 right 的链表节点&#xff0c;返回 反转后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], left 2, right 4 输出&#xff1a;[1,4,3,2…

知识图谱是如何通过数据集构建的,比如通过在MSCOCO和Flickr30k数据集和Visual Genome数据集

系列博客目录 文章目录 系列博客目录1. 数据准备与实体识别2. 关系抽取3. 图结构构建4. 图嵌入学习5. 知识图谱存储与查询示例&#xff1a;通过 Visual Genome 构建一个简单的知识图谱 构建知识图谱通常涉及从数据集中提取实体和关系&#xff0c;并将其结构化为图的形式。在 MS…

Python-安装与PyCharm的安装配置(1)

目录 安装 打开运行 PyCharm的安装 新建项目 安装 找到官网下载对应的电脑对应的版本 Welcome to Python.org -- 官网 下载稳定版的 安装记得勾选配置环境&#xff0c;这样自己就不需要再配置环境了 安装成功 至此python的运行环境就安装好了 打开运行 在开始菜单中可以…

XGBoost算法Python代码实现(单棵树类)

### XGBoost单棵树类 class XGBoost_Single_Tree(BinaryDecisionTree):# 结点分裂方法def node_split(self, y):# 中间特征所在列feature int(np.shape(y)[1]/2)# 左子树为真实值&#xff0c;右子树为预测值y_true, y_pred y[:, :feature], y[:, feature:]return y_true, y_p…

python的编程基础分支,循环与函数的应用知识

编程基础是学习任何编程语言的必备知识之一。在Python中&#xff0c;分支、循环和函数是常用的编程概念&#xff0c;它们可以让我们编写出更复杂、更灵活的程序。 分支 分支是根据条件来决定程序执行的不同路径。在Python中&#xff0c;我们使用if语句来实现分支。 if 条件:# …

营业执照OCR识别API接口如何用C#调用

服务器和计算设备的性能不断提升&#xff0c;为 OCR 识别提供了更强大的计算能力支持。更快的 CPU、GPU 以及分布式计算技术的应用&#xff0c;使得营业执照图片的处理速度大幅加快&#xff0c;能够在更短的时间内完成大量营业执照的识别工作。 研发人员不断对 OCR 识别算法进…

qt QLocale详解

1、概述 QLocale是Qt框架中的一个类&#xff0c;用于处理与本地化相关的操作。它能够方便地实现日期、时间、数字和货币的格式化和解析&#xff0c;支持不同的语言、区域设置和字符集。QLocale提供了一种跨平台的方式来获取当前系统的语言设置&#xff0c;并返回该语言的本地化…

微服务架构面试内容整理-Eureka

Spring Cloud Netflix 是一个为构建基于 Spring Cloud 的微服务应用提供的解决方案,利用 Netflix 的开源组件来实现常见的分布式系统功能。以下是 Spring Cloud Netflix 的一些主要组件和特点: 服务注册与发现:Eureka 是一个 RESTful 服务,用于注册和发现微服务。服务实例在…

缓存、注解、分页

一.缓存 作用&#xff1a;应用查询上&#xff0c;内存中的块区域。 缓存查询结果&#xff0c;减少与数据库的交互&#xff0c;从而提高运行效率。 1.SqlSession 缓存 1. 又称为一级缓存&#xff0c;mybatis自动开启。 2. 作用范围&#xff1a;同一…

uniapp vue3 使用echarts-gl 绘画3d图表

我自己翻遍了网上&#xff0c;以及插件市场&#xff0c;其实并没有uniapp 上使用echarts-gl的样例&#xff0c;大多数都是使用插件市场的echarts的插件 开始自己尝试直接用echartsgl 没有成功&#xff0c;后来尝试使用threejs 但是也遇到一些问题&#xff0c;最后我看官网的时…

【言语理解】片段阅读整体概述

1.1 题型分类 片段阅读一般有以下六种&#xff1a; 中心理解题 “这段文字意在说明&#xff1a;” “这段文字意在强调&#xff1a;” “这段文字主要介绍了&#xff1a;” “下列对文意概括最恰当的是&#xff1a;”标题拟定题 “最适合做这段文字标题的是&#xff1a;”下文…

linux搭建大数据环境

前期准备工作 友情提醒提前安装好vmware软件,准备好连接虚拟机的客户端 一. 基础环境 1.配置ip地址 修改ip配置文件 [rootnode1 /]# vim /etc/sysconfig/network-scripts/ifcfg-ens33 TYPE"Ethernet" PROXY_METHOD"none" BROWSER_ONLY"no" # …

什么是 OpenTelemetry?

OpenTelemetry 定义 OpenTelemetry (OTel) 是一个开源可观测性框架&#xff0c;允许开发团队以单一、统一的格式生成、处理和传输遥测数据&#xff08;telemetry data&#xff09;。它由云原生计算基金会 (CNCF) 开发&#xff0c;旨在提供标准化协议和工具&#xff0c;用于收集…

ESP32 gptimer通用定时器初始化报错:assert failed: timer_ll_set_clock_prescale

背景&#xff1a;IDF版本V5.1.2 &#xff0c;配置ESP32 通用定时器&#xff0c;实现100HZ&#xff0c;占空比50% 的PWM波形。 根据乐鑫官方的IDF指导文档设置内部计数器的分辨率&#xff0c;计数器每滴答一次相当于 1 / resolution_hz 秒。 &#xff08;ESP-IDF编程指导文档&a…

AIGC在游戏设计中的应用及影响

文章目录 一、AIGC的基本概念与背景AIGC的主要应用领域AIGC技术背景 二、AIGC在游戏设计中的应用1. 自动化游戏地图与关卡设计示例&#xff1a;自动生成2D平台游戏关卡 2. 角色与物品生成示例&#xff1a;使用GAN生成虚拟角色 3. 游戏剧情与任务文本生成示例&#xff1a;基于GP…

【NOIP普及组】统计单词数

【NOIP普及组】统计单词数 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 一般的文本编辑器都有查找单词的功能&#xff0c;该功能可以快速定位特定单词在文章中的位置&#xff0c;有的还能统计出特定单词在文章中出现的次数。 现在&#x…

Spring Security(5.x, 6.x ) RBAC访问控制

在 Spring Security 中&#xff0c;基于不同版本实现 RBAC&#xff08;基于角色的访问控制&#xff09;功能有一些不同的方式。RBAC 的基本原理是&#xff1a;定义用户、角色和权限的关系&#xff0c;并控制不同用户对资源的访问。 Spring Security 不同版本的实现主要在配置方…

Unity 如何优雅的限定文本长度, 包含对特殊字符,汉字,数字的处理。实际的案例包括 用户昵称

常规限定文本长度 ( 通过 UntiyEngine.UI.Inputfiled 附带的长度限定 ) 痛点1 无法对中文&#xff0c;数字&#xff0c;英文进行识别&#xff0c;同样数量的汉字和同样数量的英文像素长度是不一样的&#xff0c;当我们限定固定长度后&#xff0c;在界面上的排版不够美观 痛点2…

多个服务器共享同一个Redis Cluster集群,并且可以使用Redisson分布式锁

Redisson 是一个高级的 Redis 客户端&#xff0c;它支持多种分布式 Java 对象和服务。其中之一就是分布式锁&#xff08;RLock&#xff09;&#xff0c;它可以跨多个应用实例在多个服务器上使用同一个 Redis 集群&#xff0c;为这些实例提供锁服务。 当你在不同服务器上运行的…

jmeter常用配置元件介绍总结之函数助手

系列文章目录 1.windows、linux安装jmeter及设置中文显示 2.jmeter常用配置元件介绍总结之安装插件 3.jmeter常用配置元件介绍总结之取样器 jmeter常用配置元件介绍总结之函数助手 1.进入函数助手对话框2.常用函数的使用介绍2.1.RandomFromMultipleVars函数2.2.Random函数2.3.R…