首页
人工智能
网络安全
手机
搜索
登录
搜索
golden81
累计撰写
154
篇文章
累计收到
0
条评论
首页
栏目
首页
人工智能
网络安全
手机
包含标签 【卷积】 的文章
2025-4-27
基于Pytorch Gemotric在昇腾上实现GAT图神经网络
本实验主要介绍了如何在昇腾上,使用pytorch对经典的图神经网络GAT在论文引用数据集Pubmed上进行分类训练的实战讲解。内容包括GAT网络创新点分析、图注意力机制原理与架构剖析、多头注意力机制分析与GAT网络模型代码实战分析等等。 本实验的目录结构安排如下所示: GAT网络创新点分析 图注意力机制原理与架构分析 多头注意力机制分析 GAT网络用于Pubmed数据集分类实战 GAT网络创新点分析 注意机制已成功用于许多基于序列的任务,例如机器翻译,机器阅读等等。与GCN平等对待节点的所有邻居相比,注意力机制可以为每个邻居分配不同的注意力得分,从而识别出更重要的邻居。将注意力机制纳入图谱神经网络的传播步骤是很直观的。图注意力网络也可以看作是图卷积网络家族中的一种方法。其创新点如下: GAT是一种图结构化数据上操作的新型神经网络架构,利用掩码自注意力层来解决基于图卷积或其近似值的现有方法的缺点。 GAT网络对不同的相邻节点分配相应的权重,既不需要矩阵运算,也不需要事先知道图结构。 GAT网络克服了基于谱神经网络的几个关键挑战,使得模型更加适用于归纳问题以及转导问题。 在Cora、Citeseer和Pubmed引文网络数据集上取得了非常好的效果。 图注意力机制原理与架构分析 图中输入由N个节点组成,每个节点feature数为F(也就是feature vector长度),下述两个公式分别表示输入的节点向量通过GAT网络进行注意力计算后得到的输出。 Input: $h = { \\vec{h_1}, \\vec{h_2}, . . . , \\vec{h_N} },\\vec h_i \\in R\^{F} $ Output: $h = { \\vec{h_1\^{'}}, \\vec{h_2\^{'}}, . . . , \\vec{h_N\^{'}} },\\vec h_i\^{'} \\in R\{F\{'}} $ 为了将输入的特征转换为高维的特征,这里至少需要一个科学系的线性转换。在 (Velickovic et al.,2017)中,作者对于每一个点使用了一个共享的线性转换方式,同时介绍了一个权重矩阵来参数化线性转换。对于节点中的每两个node间都有了相互关注机制(用来做加权平均,卷积时每个node的更新是其他的加权平均)。 计算节点与节点的相关性系数(注意力值): 先初步计算节点i与节点j的相关性:其中a是一个共享的权重系数,执行的是 计算节点i与节点j的相关性(归一化)$ \\alpha_{i, j} = softmax_j(e_{ij}) = \\frac {exp(e_{ij})} {\\sum_{k \\in N_i}exp(e_{ik})} $ 论文中采取取的计算attention coefficient的函数a是一个单层的前馈网络,经LeakyReLU处理得最终的: $\\alpha_{i, j} = \\frac {exp(LeakyReLU({\\vec a\^T}\[W\\vec{h_i} \|\| W \\vec{h_j}\]))} {\\sum_{k \\in N_i}exp(LeakyReLU(\\vec a\^T\[W \\vec{h_i} ,W \\vec{h_k}\])} $ 式中 \|\| 表示串联/连接,一旦获得,归一化的相互注意系数用来计算对应特征的线性组合,以用作每个节点的最终输出特征。 左图表示在模型中应用注意机制a(W \* h_i ,W \* h_j) 通过权重向量参数化,应用LeakyReLU 激活输出。 右图表示在邻域中具有多端连接,不同的箭头样式表示独立的注意力计算,通过直连concat或平均avg获取。 多头注意力机制分析 对于图中多头注意力情况,对应上图中右图情景,不只用一个函数进行attention coefficient的计算,而是设置K个函数,每一个函数都能计算出一组attention coefficient,并能计算出一组加权求和用的系数,每一个卷积层中,K个attention机制独立的工作,分别计算出自己的结果后连接在一起,得到卷积的结果。 我们知道对于单个注意力层输出计算如下: 对于有k个独立的相互注意机制同时计算,则集中其特征,可得到特征表示如下: 分别遍历1\~k个头,每一个上按照下述公式计算: 对于网络的最后一层卷积层,如果还是使用multi-head attention机制,那么就不采取连接的方式合并不同的attention机制的结果了,而是采用求平均的方式进行处理。 GAT网络用于Pubmed数据集分类实战 #导入torch相关库 import torch import torch.nn.functional as F 该实验需要跑在npu上,因此需要导入Npu相关库使得模型快速迁移到npu上运行 import torch_npu from torch_npu.contrib import transfer_to_npu /home/pengyongrong/miniconda3/envs/AscendCExperiments/lib/python3.9/site-packages/torch_npu/dynamo/__init__.py:18: UserWarning: Register eager implementation for the 'npu' backend of dynamo, as torch_npu was not compiled with torchair. warnings.warn( /home/pengyongrong/miniconda3/envs/AscendCExperiments/lib/python3.9/site-packages/torch_npu/contrib/transfer_to_npu.py:164: ImportWarning: ************************************************************************************************************* The torch.Tensor.cuda and torch.nn.Module.cuda are replaced with torch.Tensor.npu and torch.nn.Module.npu now.. The torch.cuda.DoubleTensor is replaced with torch.npu.FloatTensor cause the double type is not supported now.. The backend in torch.distributed.init_process_group set to hccl now.. The torch.cuda.* and torch.cuda.amp.* are replaced with torch.npu.* and torch.npu.amp.* now.. The device parameters have been replaced with npu in the function below: torch.logspace, torch.randint, torch.hann_window, torch.rand, torch.full_like, torch.ones_like, torch.rand_like, torch.randperm, torch.arange, torch.frombuffer, torch.normal, torch._empty_per_channel_affine_quantized, torch.empty_strided, torch.empty_like, torch.scalar_tensor, torch.tril_indices, torch.bartlett_window, torch.ones, torch.sparse_coo_tensor, torch.randn, torch.kaiser_window, torch.tensor, torch.triu_indices, torch.as_tensor, torch.zeros, torch.randint_like, torch.full, torch.eye, torch._sparse_csr_tensor_unsafe, torch.empty, torch._sparse_coo_tensor_unsafe, torch.blackman_window, torch.zeros_like, torch.range, torch.sparse_csr_tensor, torch.randn_like, torch.from_file, torch._cudnn_init_dropout_state, torch._empty_affine_quantized, torch.linspace, torch.hamming_window, torch.empty_quantized, torch._pin_memory, torch.Tensor.new_empty, torch.Tensor.new_empty_strided, torch.Tensor.new_full, torch.Tensor.new_ones, torch.Tensor.new_tensor, torch.Tensor.new_zeros, torch.Tensor.to, torch.nn.Module.to, torch.nn.Module.to_empty ************************************************************************************************************* warnings.warn(msg, ImportWarning) 由于torch_geometric中集成了单层的GATConv模块,这里直接进行导入,若有兴趣可以自行实现该类,注意输入与输出对齐即可。此外,数据集用的是Pubmed,该数据集也直接集成在Planetoid模块中,这里也需要将其import进来。 from torch_geometric.nn import GATConv from torch_geometric.datasets import Planetoid /home/pengyongrong/miniconda3/envs/AscendCExperiments/lib/python3.9/site-packages/torch_npu/contrib/transfer_to_npu.py:124: RuntimeWarning: torch.jit.script will be disabled by transfer_to_npu, which currently does not support it. warnings.warn(msg, RuntimeWarning) Pubmed数据集介绍 PubMed数据集是一个广泛用于图神经网络(GNN)研究的基准数据集,主要用于节点分类任务。其由生物医学文献组成,每篇文献被视为一个节点,引用关系被视为边。该数据集包含三类糖尿病相关的论文,每个节点都带有特征向量和标签。 数据集一共有19717个节点 ,每一个节点代表一篇生物医学文献,每个节点有一个500 维的特征向量,用来表示该医学文献的内容。总共含44338条边 ,每条边表示一篇文献对另一篇文献的引用关系,边与边之间是无向的,因此可以看做是对称的。总类别数包含Diabetes Mellitus Experiment、Diabetes Mellitus Type 1与Diabetes Mellitus Type2三类。 # 加载数据 print("===== begin Download Dadasat=====\n") dataset = Planetoid(root='/home/pengyongrong/workspace/data', name='PubMed') print("===== Download Dadasat finished=====\n") print("dataset num_features is: ", dataset.num_features) print("dataset.num_classes is: ", dataset.num_classes) print("dataset.edge_index is: ", dataset.edge_index) print("train data is: ", dataset.data) print("dataset0 is: ", dataset[0]) print("train data mask is: ", dataset.train_mask, "num train is: ", (dataset.train_mask ==True).sum().item()) print("val data mask is: ",dataset.val_mask, "num val is: ", (dataset.val_mask ==True).sum().item()) print("test data mask is: ",dataset.test_mask, "num test is: ", (dataset.test_mask ==True).sum().item()) ===== begin Download Dadasat===== ===== Download Dadasat finished===== dataset num_features is: 500 dataset.num_classes is: 3 dataset.edge_index is: tensor([[ 1378, 1544, 6092, ..., 12278, 4284, 16030], [ 0, 0, 0, ..., 19714, 19715, 19716]]) train data is: Data(x=[19717, 500], edge_index=[2, 88648], y=[19717], train_mask=[19717], val_mask=[19717], test_mask=[19717]) dataset0 is: Data(x=[19717, 500], edge_index=[2, 88648], y=[19717], train_mask=[19717], val_mask=[19717], test_mask=[19717]) train data mask is: tensor([ True, True, True, ..., False, False, False]) num train is: 60 val data mask is: tensor([False, False, False, ..., False, False, False]) num val is: 500 test data mask is: tensor([False, False, False, ..., True, True, True]) num test is: 1000 /home/pengyongrong/miniconda3/envs/AscendCExperiments/lib/python3.9/site-packages/torch_geometric/data/in_memory_dataset.py:300: UserWarning: It is not recommended to directly access the internal storage format `data` of an 'InMemoryDataset'. If you are absolutely certain what you are doing, access the internal storage via `InMemoryDataset._data` instead to suppress this warning. Alternatively, you can access stacked individual attributes of every graph via `dataset.{attr_name}`. warnings.warn(msg) 下载后的pubmed数据集总共包含8个文件,分别是ind.pubmed.x、ind.pubmed.tx、ind.pubmed.all、ind.pubmed.y、ind.pubmed.ty、ind.pubmed.ally、ind.pubmed.graph与ind.pubmed.test.index。每个文件的作用说明如下: ind.pubmed.x:训练集节点特征向量,大小(140,1433) ind.pubmed.tx:测试集节点特征向量,实际展开后大小为(1000,1433) ind.pubmed.allx:包含标签核无标签的训练节点特征向量(1708,1433) ind.pubmed.y:one-hot表示的训练节点的标签 ind.pubmed.ty:one-hot表示的测试节点的标签 ind.pubmed.ally:one-hot表示的ind.cora.allx对应的标签 ind.pubmed.graph:保存节点之间边的信息 ind.pubmed.test.index:保存测试集节点的索引,用于后面的归纳学习设置 从打印结果可以看出,数据集的特点与上述描述的相对应,GAT_NET网络定义了一个两层的GAT网络,heads的数量设置成4。 开启Pubmed数据训练过程 class GAT_NET(torch.nn.Module): def __init__(self, features, hidden, classes, heads=4): super(GAT_NET, self).__init__() # 定义GAT层,使用多头注意力机制 self.gat1 = GATConv(features, hidden, heads=4) # 因为多头注意力是将向量拼接,所以维度乘以头数。 self.gat2 = GATConv(hidden*heads, classes) def forward(self, data): # 从输入数据集中获取x与边集相关信息 x, edge_index = data.x, data.edge_index # 将输入传入GAT层中,获得第一层Gat层的输出 x = self.gat1(x, edge_index) # 经过非线性激活与dropout,减少过拟合现象,增加模型的泛化能力 x = F.relu(x) x = F.dropout(x, training=self.training) # 第二层GAT层,得到整个网络的输出送给分类器进行分类 x = self.gat2(x, edge_index) return F.log_softmax(x, dim=1) 定义设备跑在Npu上,这里如果需要替换成Gpu或Cpu,则替换成'cuda'或'cpu'即可。 device = 'npu' 定义GAT_NET网络,中间隐藏层节点个数定义为16,'dataset.num_classes'为先前数据集中总的类别数,这里是7类。'to()'的作用是将该加载到指定模型设备上。优化器用的是'optim'中的'Adam'。 model = GAT_NET(dataset.num_node_features, 16, dataset.num_classes).to(device) # 定义GraphSAGE data = dataset[0].to(device) optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4) 开始训练模型,指定训练次数200次,训练后采用极大似然用作损失函数计算损失,然后进行反向传播更新模型的参数,训练完成后,用验证集中的数据对模型效果进行验证,最后打印模型的准确率。 model.train() for epoch in range(200): optimizer.zero_grad() out = model(data) loss = F.nll_loss(out[data.train_mask], data.y[data.train_mask]) loss.backward() optimizer.step() # 模型验证过程,对训练得到的模型效果进行评估,并打印准确率。 model.eval() _, pred = model(data).max(dim=1) correct = int(pred[data.test_mask].eq(data.y[data.test_mask]).sum().item()) acc = correct / int(data.test_mask.sum()) print('GAT Accuracy: {:.4f}'.format(acc)) [W VariableFallbackKernel.cpp:51] Warning: CAUTION: The operator 'aten::scatter_reduce.two_out' is not currently supported on the NPU backend and will fall back to run on the CPU. This may have performance implications. (function npu_cpu_fallback) GAT Accuracy: 0.3660 内存使用情况: 整个训练过程的内存使用情况可以通过"npu-smi info"命令在终端查看,因此本文实验只用到了单个npu卡(也就是chip 0),内存占用约573M,对内存、精度或性能优化有兴趣的可以自行尝试进行优化。 Reference ========= \[1\] Velikovi, Petar , et al. "Graph Attention Networks." (2017).
2025年-4月-27日
12 阅读
0 评论
人工智能
2025-4-27
PyTorch 实现FCN网络用于图像语义分割
本文主要介绍了如何在昇腾上,使用pytorch对图像分割任务的开山之作FCN网络在VOC2012数据集上进行训练的实战过程讲解。主要内容包括FCN网络的创新点分析、FCN网络架构分析,实战训练代码分析等等。 本文的目录结构安排如下所示: FCN创新点分析 FCN网络架构分析 FCN网络搭建过程及代码详解 端到端训练Voc2012数据集全过程分析 FCN(Fully Convolutional Networks)网络创新点分析 采用全卷积结构替换全连接层 FCN将传统分类网络中的全连接层替换为卷积层,使得网络能够接受任意尺寸的输入图像,而不需要固定输入大小。这种设计使得FCN能够直接对图像进行端到端的像素级预测,适用于语义分割任务。 使用多尺度特征融合的思想 通过改进的上采样和下采样技术及高效的跳跃连接结构,FCN能够整合网络中不同层的特有特征。采用多尺度特征融合可以显著地提高模型在复杂场景分析中的精度和鲁棒性。 自适应和动态卷积核 FCN中的卷积核可以根据输入数据的特性动态调整其大小和形状,从而更有效地提取特征。这种自适应能力使得FCN在多种不同类型的图像处理任务中都能表现出色。 跳跃连接(跟第二点结合) FCN使用了类似ResNet的跳跃连接结构,将深层的粗糙语义信息和浅层的精细表征信息融合,以实现更加精细的语义分割。这种跳跃连接结构在上采样过程中融合了不同维度的特征,保留了更多细节,帮助模型更精细地重建图像信息。 使用反卷积操作用于上采样 FCN使用反卷积层进行上采样,将最后一个卷积层的特征图恢复到与输入图像相同的尺寸。反卷积层通过不同尺度的上采样操作,保留了原始输入图像的空间信息,使得网络对每个像素都能实现高效的类别预测。 FCN网络架构分析 从网络的总体架构图中可以看出FCN的结构非常简单。首先,输入图片经过若干个卷积层实现特征提取后,再通过反卷积操作将图像大小还原到指定大小实现像素级别的类别预测。最后输出的channel维度是21,这是因为论文中使用的pascal voc数据集总共20个类别,加上背景一起总共21个类别。 根据这21个值进行softmax处理就能得到图像中每个像素属于这21个类别的概率值,取最大的那个值作为该像素最终的类别预测结果,这样就可以得到整张图所有像素点的类别预测情况,然后,每个类别用不同的颜色区分从而整张图片的背景与类别被清晰的分割开(例如:图中的猫、狗及背景分别用蓝色、棕色与绿色区分)。 上图通过使用全连接层得到最终的维度为1000的向量,由于全连接层要求的输入大小必须是固定的,因此作者将网络中的全连接层转换为卷积层,输入图像的大小可以是任意的。 那么最后的输出就不是一个一维向量了,就变成了(m,n,c),对应每个channel就是一个2D的数据,可以可视化成一个heatmap图。 图中将全连接层全部替换成了卷积层,其中全计算量与卷积层的计算量分别为:全连接是25088 × 4096 = 102760448,卷积的计算量是7 × 7 × 512 × 4096 = 102760448,可以看到他们的计算量是一模一样的相当于把全连接的权重进行了reshape操作。 论文中FCN网络有三种模式的模型,分别是FCN-32s,16s,8s,其中数字的含义是将最后得到的特征图通过上采样多少倍后能够恢复到原图尺寸的大小。图中省略了卷积层与其他层级信息,只保留了池化层用于展示多尺度特征融合的过程。 整个网络第一步通过将特征图上采样32倍得到原图大小的输出,此时得到的是FCN-32s模型。然后将该特征图进行2倍的上采样与pool4层的特征图进行结合得到FCN-16s模型,此时的网络能够预测更精细的细节,同时保留高级语义信息。同理,将得到的FCN-16s模型进行2倍的上采样后与pool3的特征图进行融合得到FCN-8s模型,该模型可以得到更加精准的预测。 除此以外,从上述的分析可以发现,FCN网络在结合不同尺度特征信息的过程中,还可以继续往深层次的继续结合得到FCN-4s,2s模型,这里可以根据需要结合前面pool2与pool1层的信息即可。 FCN网络搭建过程及代码详解 基于torch搭建FCN网络,需要导入torch相关模块,其中nn.Module是各个神经网络模型需要继承的基类。 import torch import torch.nn as nn 由于FCN网络采用的是全类卷积层操作,论文中分别使用Alexnet、VGG16与GoogleNet网络作为backone后用VOC数据集进行微调对比,得到FCN-VGG16的mean IU最高,IU与目标检测模型中的IOU意思一样,用来反映模型预测与框定的效果好坏。 目前PyTorch官方实现中使用ResNet-50作为backbone,原始论文中提出的FCN使用的是VGG16作为backbone,但在PyTorch的官方实现中,由于ResNet-50在性能上有更好的表现,因此一般都会选择ResNet-50作为backbone后用数据做微调。本文的实现不采用任何backbone,从零到一搭建一个FCN8s网络模型。 整个FCN8s网络模型通过一个FCN8类来实现,其中FCN8类中继承了'nn.Module'模块,网络总共包含两部分,前一部分对图像输入进行特征提取并不断降维,后一部分通过对得到的特征图进行不同倍率的上采样,从而融合不同尺度特征得到FCN8s模型。 前一部分总共包含5个stage,每个stage最后都用一个'Maxpool'操作用于特征图提取与降维,对应类中'nn.MaxPool2d(kernel_size=2,padding=0)'。stage1、stage2与stage5均定义了一层'Conv2d'、'Relu'与'BatchNorm2d'操作组合并结合'Maxpool'操作。stage3与stage4分别定义了三层与两层Conv2d、Relu与BatchNorm2d操作组合并结合Maxpool操作。 后一部分定义了upsample_2、upsample_4、upsample_81与upsample_82,也就是2、4与8三种不同倍率的下采样。VOC数据集图片的输入后本文会将其裁剪到224x224,因此网络的输入size是224x224。 class FCN8(nn.Module): def __init__(self, num_classes): # 调用super方法调用父类nn.Module的初始化函数 super(FCN8, self).__init__() ''' 定义stage1, Conv2d中in_channels=3与输入图像3通道相对应,out_channels=96表示输出的通道维度是96,kernel=3表示卷积核大小是3x3,对输入图像padding=1。 根据卷积的size计算公式output= ((i + 2p -k) /s + 1),i表示输入图像的尺寸,p表示padding,k表示卷积核大小,s表示步长。 假设batch=1的情况下输入图像为224x224x3,通过'Conv2d'后输出的size为 (224 + 2 -3)/1 +1 = 224,因此conv2d后输出图像为224x224x96。 BatchNorm2d输入前后不改变图像size大小,通过MaxPool2d操作降维后得到最终输出图像大小为112x112x96。 ''' self.stage1 = nn.Sequential( nn.Conv2d(in_channels=3, out_channels=96, kernel_size=3, padding=1), nn.ReLU(), nn.BatchNorm2d(num_features=96), nn.MaxPool2d(kernel_size=2, padding=0) ) ''' 定义stage2, Conv2d中in_channels=96与stage1中输出维度相对应,out_channels=256表示输出的通道维度是256。同理通过Conv2d后得到输出size为112x112x256 通过'MaxPool2d'操作后变为56x56x256是stage2的最终输出。 ''' self.stage2 = nn.Sequential( nn.Conv2d(in_channels=96, out_channels=256, kernel_size=3, padding=1), nn.ReLU(), nn.BatchNorm2d(num_features=256), nn.MaxPool2d(kernel_size=2, padding=0) ) # 定义stage3, 假设batch=1,input = 56x56x256,则output= 28x28x256。 self.stage3 = nn.Sequential( nn.Conv2d(in_channels=256, out_channels=384, kernel_size=3, padding=1), nn.ReLU(), nn.BatchNorm2d(num_features=384), nn.Conv2d(in_channels=384, out_channels=384, kernel_size=3, padding=1), nn.ReLU(), nn.BatchNorm2d(num_features=384), nn.Conv2d(in_channels=384, out_channels=256, kernel_size=3, padding=1), nn.ReLU(), nn.BatchNorm2d(num_features=256), nn.MaxPool2d(kernel_size=2, padding=0) ) # 定义stage4, 假设batch=1,input = 28x28x256,则output= 14x14x512。 self.stage4 = nn.Sequential( nn.Conv2d(in_channels=256, out_channels=512, kernel_size=3, padding=1), nn.ReLU(), nn.BatchNorm2d(num_features=512), nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, padding=1), nn.ReLU(), nn.BatchNorm2d(num_features=512), nn.MaxPool2d(kernel_size=2, padding=0) ) # 定义stage5, 假设batch=1,input = 14x14x512,则output= 7x7xnum_classes。 self.stage5 = nn.Sequential( nn.Conv2d(in_channels=512, out_channels=num_classes, kernel_size=3, padding=1), nn.ReLU(), nn.BatchNorm2d(num_features=num_classes), nn.MaxPool2d(kernel_size=2,padding=0) ) ''' 定义2倍率的上采样过程,可以分别得到一个2倍率上采样的特征图便于做特征融合。 上采样过程 out_size = (i -1)*S-2P + k,其中i、S、P与k分别表示输入图像size,步长、padding与卷积核大小。 upsample_2结合的是stage4的输出特征图,因此input = 14x14x512,output = 28x28x512。 ''' self.upsample_2 = nn.ConvTranspose2d(in_channels=512, out_channels=512, kernel_size=4, padding= 1, stride=2) ''' 定义4倍率的上采样过程,可以分别得到一个4倍率上采样的特征图便于做特征融合。 upsample_4结合的是stage5的输出特征图,因此input = 7x7xnum_classes,output = 28x28xnum_classes。 ''' self.upsample_4 = nn.ConvTranspose2d(in_channels=num_classes, out_channels=num_classes, kernel_size=4, padding= 0,stride=4) ''' 下述upsample_81与upsample_82的作用是将上述特征融合后的图像分别通过4倍与2的上采样将图像还原成原始输入大小(28 * 4 * 2 = 224)。 其中in_channels与out_channels中512 + num_classes + 256表示三个不同维度的channel进行拼接得到。 ''' self.upsample_81 = nn.ConvTranspose2d(in_channels=512 + num_classes + 256, out_channels=512 + num_classes + 256, kernel_size=4, padding= 0,stride=4) self.upsample_82 = nn.ConvTranspose2d(in_channels=512 + num_classes + 256, out_channels=512 + num_classes + 256, kernel_size=4, padding= 1,stride=2) # 最后的预测模块,input:224x224x(512 + num_classes + 256), output:224x224xnum_classes。 self.final = nn.Sequential( nn.Conv2d(512 + num_classes + 256, num_classes, kernel_size=7, padding=3), ) def forward(self, x): x = x.float() # conv1->pool1->输出 x = self.stage1(x) # conv2->pool2->输出 x = self.stage2(x) # conv3->pool3->输出, 经过上采样后, 需要用pool3暂存 x = self.stage3(x) pool3 = x # conv4->pool4->输出, 经过上采样后, 需要用pool4暂存 x = self.stage4(x) pool4 = self.upsample_2(x) x = self.stage5(x) conv7 = self.upsample_4(x) # 对所有上采样过的特征图进行concat, 在channel维度上进行叠加 x = torch.cat([pool3, pool4, conv7], dim = 1) # 经过一个分类网络,输出结果(这里采样到原图大小,分别一次2倍一次4倍上采样来实现8倍上采样) output = self.upsample_81(x) output = self.upsample_82(output) output = self.final(output) return output 将网络模型结构进行打印,可以看到网络的整体结构与上述描述相一致。至此,FCN8s网络架构全部搭建完成,接下来将用该网络来介绍如何训练VOC数据集。 print(FCN8(21)) FCN8( (stage1): Sequential( (0): Conv2d(3, 96, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): ReLU() (2): BatchNorm2d(96, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) ) (stage2): Sequential( (0): Conv2d(96, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): ReLU() (2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) ) (stage3): Sequential( (0): Conv2d(256, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): ReLU() (2): BatchNorm2d(384, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (3): Conv2d(384, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (4): ReLU() (5): BatchNorm2d(384, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (6): Conv2d(384, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (7): ReLU() (8): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) ) (stage4): Sequential( (0): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): ReLU() (2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (3): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (4): ReLU() (5): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (6): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) ) (stage5): Sequential( (0): Conv2d(512, 21, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): ReLU() (2): BatchNorm2d(21, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) ) (upsample_2): ConvTranspose2d(512, 512, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1)) (upsample_4): ConvTranspose2d(21, 21, kernel_size=(4, 4), stride=(4, 4)) (upsample_81): ConvTranspose2d(789, 789, kernel_size=(4, 4), stride=(4, 4)) (upsample_82): ConvTranspose2d(789, 789, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1)) (final): Sequential( (0): Conv2d(789, 21, kernel_size=(7, 7), stride=(1, 1), padding=(3, 3)) ) ) 端到端训练Voc2012数据集全过程分析 VOC数据集介绍 VOC数据集,全称Visual Object Classes,是一个广泛使用的计算机视觉数据集,主要用于目标检测、图像分割和图像分类等任务。该数据集最初由英国牛津大学的计算机视觉小组创建,并在PASCAL VOC挑战赛中使用。VOC数据集包含了大量不同类别的标记图像,每个图像都有与之相关联的边界框(bounding box)和对象类别的标签。 VOC数据集在类别上可以分为4大类,20小类,涵盖了人、汽车、猫、狗等常见目标类别。此外,VOC数据集还提供了用于图像分割任务的像素级标注,该数据集分为21类,其中20类为前景物体,1类为背景。数据集量级方面,VOC2007和VOC2012是两个最流行的版本,分别包含了约10000张和20000张标注图像,本文采用VOC2012数据集。 下载地址:http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar VOC数据集结构 VOC数据集的结构相对复杂,但非常有序。它主要包含以下几个文件夹: ImageSets:包含三个子文件夹(Layout、Main、Segmentation),用于存放不同数据集划分(训练集、验证集、测试集)的文件名列表。 JPEGImages:存放所有的图片,包括训练、验证和测试用到的所有图片。 SegmentationClass:包含已经标注好的图像。 Annotations:存放每张图片相关的标注信息,以XML格式的文件存储。这些文件包含了图像中每个目标的类别、边界框坐标等详细信息。 SegmentationObject:文件夹中包含实例分割用到的标签图像。 其中本文实验需要用到的3个文件夹均已标粗。 如图所示,图像分割任务需要将图中的物体与物体间,物体与背景间信息区分开来,不同物体标记不同颜色,本文实验用到的VOC数据集总共包含21种类别,'VOC_COLORMAP'定义了每一个类别的颜色信息,包含RGB三个,'VOC_CLASSES'对应数据集中21个类别。 VOC_COLORMAP = [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0], [0, 0, 128], [128, 0, 128], [0, 128, 128], [128, 128, 128], [64, 0, 0], [192, 0, 0], [64, 128, 0], [192, 128, 0], [64, 0, 128], [192, 0, 128], [64, 128, 128], [192, 128, 128], [0, 64, 0], [128, 64, 0], [0, 192, 0], [128, 192, 0], [0, 64, 128]] VOC_CLASSES = ['background', 'aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse', 'motorbike', 'person', 'potted plant', 'sheep', 'sofa', 'train', 'tv/monitor'] # 定义一个一维向量colormap2label,含有256^3个元素,目的是为了让三通道图像的每一点像素特征都有所对应类别所对应。 colormap2label = torch.zeros(256 ** 3, dtype=torch.uint8) # 给包含类别的物体赋予颜色标签,不属于类别内的rgb是全为0,也就是整个图片中除了背景与物体以外的颜色为全黑。 for i, colormap in enumerate(VOC_COLORMAP): colormap2label[(colormap[0] * 256 + colormap[1]) * 256 + colormap[2]] = i VOC数据集patch 为了方便整个训练过程快速有效的进行,我们对输入的数据按照一定的patch输入进行训练,因此需要定义一个数据个数转换类'VOCSegDataset',用来生成每一个批次送给网络所需要的数据。 在定义该类以前,我们需要定义一些对于文件及标签处理操作函数,分别是'voc_label_indices'、'read_file_list'与'voc_rand_crop'。 import numpy as np def voc_label_indices(colormap): """ convert colormap (PIL image) to colormap2label (uint8 tensor). """ colormap = np.array(colormap).astype('int32') idx = ((colormap[:, :, 0] * 256 + colormap[:, :, 1]) * 256 + colormap[:, :, 2]) return colormap2label[idx] # 针对于VOC2012数据集读取训练与验证集文件返回训练集或验集所有图片路径及标签 def read_file_list(root, is_train=True): txt_fname = root + '/ImageSets/Segmentation/' + ('train.txt' if is_train else 'val.txt') with open(txt_fname, 'r') as f: filenames = f.read().split() images = [os.path.join(root, 'JPEGImages', i + '.jpg') for i in filenames] labels = [os.path.join(root, 'SegmentationClass', i + '.png') for i in filenames] return images, labels # file list # 对输入的VOC图片进行裁剪到指定的height与width def voc_rand_crop(image, label, height, width): """ Random crop image (PIL image) and label (PIL image). """ i, j, h, w = transforms.RandomCrop.get_params( image, output_size=(height, width)) image = transforms.functional.crop(image, i, j, h, w) label = transforms.functional.crop(label, i, j, h, w) return image, label 数转换类'VOCSegDataset',继承'torch.utils.data.Dataset'用于迭代送入图片给模型进行训练及验证, class VOCSegDataset(torch.utils.data.Dataset): def __init__(self, is_train, crop_size, voc_root): """ crop_size: (h, w) """ self.transform = transforms.Compose([ transforms.ToTensor(), #transforms.Normalize(mean=self.rgb_mean, std=self.rgb_std) ]) # (h, w) self.crop_size = crop_size images, labels = read_file_list(root=voc_root, is_train=is_train) # images list self.images = self.filter(images) # labels list self.labels = self.filter(labels) print('Read ' + str(len(self.images)) + ' valid examples') # 过滤掉尺寸小于crop_size的图片 def filter(self, imgs): return [img for img in imgs if ( Image.open(img).size[1] >= self.crop_size[0] and Image.open(img).size[0] >= self.crop_size[1])] def __getitem__(self, idx): image = self.images[idx] label = self.labels[idx] image = Image.open(image).convert('RGB') label = Image.open(label).convert('RGB') image, label = voc_rand_crop(image, label, *self.crop_size) image = self.transform(image) label = voc_label_indices(label) # float32 tensor, uint8 tensor return image, label def __len__(self): return len(self.images) 调用上述定义好的数据格式转化类'VOCSegDataset'生成训练集与验证集集合'voc_train'与'voc_val',从打印可以看出本次实验只读取了1456与1436张图片用于训练与测试。 import os from torchvision import transforms from PIL import Image voc_train = VOCSegDataset(is_train = True, crop_size=(224,224), voc_root = '/home/pengyongrong/workspace/VocData') voc_val = VOCSegDataset(is_train = False, crop_size=(224,224), voc_root = '/home/pengyongrong/workspace/VocData') Read 1456 valid examples Read 1436 valid examples 接下来对训练集中的部分图片进行可视化,通过引入matplotlib库来进行可视化,这里展示了5张图片及对应标签,如果想要展示更多,可以设置i的取值即可。从可视化结果可以看出图中每一个不同类别与背景都被用不同颜色区分开啦,例如图一中的飞机、人与背景分别用暗紫色、黄色与紫色区分开来。 import matplotlib.pyplot as plt for i, (img, label) in enumerate(voc_train): plt.figure(figsize=(10,10)) plt.subplot(221) plt.imshow(img.moveaxis(0,2)) plt.subplot(222) plt.imshow(label) plt.show() plt.close() if i ==5: break 导入昇腾npu相关库transfer_to_npu、该模块可以使能模型自动迁移至昇腾上。 import torch_npu from torch_npu.contrib import transfer_to_npu /home/pengyongrong/miniconda3/envs/AscendCExperiments/lib/python3.9/site-packages/torch_npu/dynamo/__init__.py:18: UserWarning: Register eager implementation for the 'npu' backend of dynamo, as torch_npu was not compiled with torchair. warnings.warn( /home/pengyongrong/miniconda3/envs/AscendCExperiments/lib/python3.9/site-packages/torch_npu/contrib/transfer_to_npu.py:164: ImportWarning: ************************************************************************************************************* The torch.Tensor.cuda and torch.nn.Module.cuda are replaced with torch.Tensor.npu and torch.nn.Module.npu now.. The torch.cuda.DoubleTensor is replaced with torch.npu.FloatTensor cause the double type is not supported now.. The backend in torch.distributed.init_process_group set to hccl now.. The torch.cuda.* and torch.cuda.amp.* are replaced with torch.npu.* and torch.npu.amp.* now.. The device parameters have been replaced with npu in the function below: torch.logspace, torch.randint, torch.hann_window, torch.rand, torch.full_like, torch.ones_like, torch.rand_like, torch.randperm, torch.arange, torch.frombuffer, torch.normal, torch._empty_per_channel_affine_quantized, torch.empty_strided, torch.empty_like, torch.scalar_tensor, torch.tril_indices, torch.bartlett_window, torch.ones, torch.sparse_coo_tensor, torch.randn, torch.kaiser_window, torch.tensor, torch.triu_indices, torch.as_tensor, torch.zeros, torch.randint_like, torch.full, torch.eye, torch._sparse_csr_tensor_unsafe, torch.empty, torch._sparse_coo_tensor_unsafe, torch.blackman_window, torch.zeros_like, torch.range, torch.sparse_csr_tensor, torch.randn_like, torch.from_file, torch._cudnn_init_dropout_state, torch._empty_affine_quantized, torch.linspace, torch.hamming_window, torch.empty_quantized, torch._pin_memory, torch.Tensor.new_empty, torch.Tensor.new_empty_strided, torch.Tensor.new_full, torch.Tensor.new_ones, torch.Tensor.new_tensor, torch.Tensor.new_zeros, torch.Tensor.to, torch.nn.Module.to, torch.nn.Module.to_empty ************************************************************************************************************* warnings.warn(msg, ImportWarning) from torch.utils.data import Dataset, DataLoader #创建dataloader,定义每一批次送入模型进行训练的batch_size这里设置成8,也可以根据需要改成任意>=2的取值。 trainloader = DataLoader(voc_train, batch_size = 8, shuffle=True,) testloader = DataLoader(voc_val, batch_size = 4) optim实现了各种优化算法的库(例如:SGD与Adam),在使用optimizer时候需要构建一个optimizer对象,这个对象能够保持当前参数状态并基于计算得到的梯度进行参数更新。 # 导入torch及相关模块库,便于后续搭建神经网络模型使用 import torch.optim as optim import torch.nn.functional as F #定义模型训练在哪种类型的设备上跑 device = 'npu' # 构建模型,这里VOC数据类别是21,因此入参num_classes=21,若是其他的类别,此处可以根据需要进行设置。 net = FCN8(num_classes=21) #将网络模型加载到指定设备上,这里device是昇腾的npu net = net.to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(net.parameters(), lr=1.0, weight_decay=5e-4) lr_scheduler = torch.optim.lr_scheduler.OneCycleLR(optimizer,0.1,steps_per_epoch=len(trainloader), epochs=150,div_factor=25,final_div_factor=10000,pct_start=0.3) 训练模块: 根据传入的迭代次数开始训练网络模型,这里需要在model开始前加入net.train(),使用随机梯度下降算法是将梯度值初始化为0(zero_grad()),计算梯度、通过梯度下降算法更新模型参数的值以及统计每次训练后的loss值(每隔100次打印一次) from tqdm import tqdm def train(epoch): net.train() train_loss = 0.0 epoch_loss = 0.0 for batch_idx, (inputs, targets) in enumerate(tqdm(trainloader, 0)): inputs, targets = inputs.to(device), targets.to(device) optimizer.zero_grad() outputs = net(inputs) loss = criterion(outputs, targets) loss.backward() optimizer.step() lr_scheduler.step() train_loss += loss.item() epoch_loss += loss.item() if batch_idx % 100 == 99: # 每100次迭代打印一次损失 print(f'[Epoch {epoch + 1}, Iteration {batch_idx + 1}] loss: {train_loss / 100:.3f}') train_loss = 0.0 return epoch_loss / len(trainloader) 测试模块: 每训练一轮将会对最新得到的训练模型效果进行测试,使用的是数据集准备时期划分得到的测试集。 def test(): net.eval() val_loss = 0 val_loss_all=[] val_num = 0 total = 0 with torch.no_grad(): for batch_idx, (inputs, targets) in enumerate(tqdm(testloader)): inputs, targets = inputs.to(device), targets.to(device) outputs = net(inputs) out = F.log_softmax(outputs, dim=1) loss = criterion(out, targets) val_loss += loss.item() * len(targets) val_num += len(targets) # 计算一个epoch在验证集上的损失和精度 val_loss_all.append(val_loss / val_num) return val_loss_all[-1] 训练与测试的次数为2次,这里用户可以根据需要自行选择设置更高或更低,每个epoch的准确率都会被打印出来,如果不需要将代码注释掉即可,这里可以看到两个epoch间的loss在下降(从1.94-\>1.63)。 #开启模型训练与测试过程 for epoch in range(2): epoch_loss = train(epoch) test_accuray = test() print(f'Epoch loss for FCN8s at epoch {epoch + 1}: {epoch_loss:.3f}') 55%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▍ | 100/182 [01:49<01:28, 1.08s/it] [Epoch 1, Iteration 100] loss: 1.940 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 182/182 [03:14<00:00, 1.07s/it] 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 359/359 [02:36<00:00, 2.29it/s] Epoch loss for FCN8s at epoch 1: 1.825 55%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▍ | 100/182 [01:42<01:22, 1.01s/it] [Epoch 2, Iteration 100] loss: 1.628 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 182/182 [03:04<00:00, 1.01s/it] 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 359/359 [02:39<00:00, 2.25it/s] Epoch loss for FCN8s at epoch 2: 1.630 内存使用情况: 整个训练过程的内存使用情况可以通过"npu-smi info"命令在终端查看,因此本文实验只用到了单个npu卡(也就是chip 0),内存占用约13G,对内存、精度或性能优化有兴趣的可以自行尝试进行优化。 Reference ========= \[1\] Long, Jonathan , E. Shelhamer , and T. Darrell . "Fully Convolutional Networks for Semantic Segmentation." IEEE Transactions on Pattern Analysis and Machine Intelligence 39.4(2015):640-651.
2025年-4月-27日
11 阅读
0 评论
人工智能
2025-4-27
基于Pytorch 在昇腾上实现GCN图神经网络
本文主要介绍了如何在昇腾上,使用pytorch对经典的图神经网络GCN在论文引用Cora数据集上进行分类训练的实战讲解。内容包括GCN背景介绍、模型特点介绍、GCN网络架构剖析与GCN网络模型代码实战分析等等。 本文的目录结构安排如下所示: GCN网络背景介绍 模型特点介绍 GCN网络架构剖析 GCN网络用于Cora数据集分类实战 GCN网络背景介绍 多层感知机、卷积神经网络、循环神经网络和自编码器等深度学习模型通过对输入的数据进行逐层的特征提取和筛选,可以完成分类和预测等任务,在计算机视觉和语音识别等领域已被广泛应用。但上述模型只能处理具有固定排列规则和顺序的欧氏结构数据,对于一些非规则排布的非欧式数据显得有些则力不从心。随着非欧式数据结构被越来越多的实际问题应用所需要,针对处理非欧氏结构数据的深度学习模型图神经网络(Graph Neural Networks, GNN)应运而生。 图神经网络的本质就是:图中的任何一个节点,都受到和它相连的其他节点的影响,距离越近影响则越大。一个图中的所有节点间的互动关系和每个节点本身的信息,就构成了这整张图的全部信息。 由于CNN已经是个相当成熟的技术了,聚合"邻居"的信息并不是什么少见的思路。显然,从GNN出现开始,就必然会有人尝试在GNN上进行类似CNN的"聚合节点信息"操作。事实上在GCN之前,就已经有一些关于类似的研究了。但不外乎存在计算量大、聚合效果差、卷积核复杂的问题。 模型特点介绍 GCN是GNN的一个分支,全称为图卷积神经网络,顾名思义,GCN是在图上进行"卷积"操作的GNN,这里用引号是因为,GCN的操作并不是卷积神经网络里的那个卷积,这里的卷积,是因为GCN的运算是在聚合节点周围其他节点的信息,与卷积神经网络(CNN)的行为类似。不过话说回来,CNN里的"卷积",也并不是不是数学意义上的卷积。 GCN的创新之处在于,提出了一种简化到在计算量上可行且效果好的"卷积"计算方案。GCN利用拉普拉斯变换变化,利用邻接矩阵算出了这个滤波矩阵,然后利用这个滤波矩阵进行层间传播。 其迭代间节点核心更新计算公式如下: 其中表示邻接矩阵的度矩阵,表示整张图的邻接矩阵(含自回环,也就是加了单位矩阵),X表示节点在k-1层的特征向量,是k-1层的卷积参数。 GCN的上述公式表达的是从整个图的角度来考虑和描述的。从单个节点来说,每个节点的特征向量可以表示为的变换 (前向传播) 的向量形式可以表示为如下: 其中是权重矩阵 (即模型学习过程中要更新的参数),表示节点i在第k次迭代的特征向量,deg(i)表示节点i的度,N(i)表示节点i所有邻接节点的集合。 GCN网络架构剖析 GCN定义了一个两层的模型,中间隐藏的节点个数可以自设,后面输出层可以结合具体数据集类别设置使用,当然也可以跟训练类别设置不一致,只需要在后面接一个分类器即可(后再接一个全连接层)。 MessagePassing模块是图神经网络(Graph Neural Networks,GNNs)的一个基础组件,它被设计用来处理图形数据的问题。在图形数据中,数据点(节点)之间的关系(边)是非常重要的信息。MessagePassing通过在节点之间传递和聚合信息,使得每个节点都能获取其邻居节点的信息,从而更好地理解图形的结构和特性。里面'propagate'函数与'aggregate'函数用于实现节点之间的传播与聚合功能。 # 导入torch及相关库,便于后续搭建网络调用基础算子模块 import torch import torch.nn.functional as F from torch_geometric.nn import MessagePassing 在图神经网络(GNN)的实现中,对图结构的调整和优化是提升模型性能的关键手段之一。'add_self_loops'函数在PyTorch的图处理库中用于向图中添加自环(self-loops),即连接节点自身的边。'degree'用来计算一个无权图的入度矩阵。 from torch_geometric.utils import add\_self\_loops, degree GCN实现继承了'MessagePassing'类,线性变换功能在'init'函数中通过'self.lin'为线性变换函数定义,具体特征维度的逻辑在'forward()'中实现,'init'函数中入参'in_channel'是每个节点输入特征的维度,'out_channels'是每个节点输出特征的维度,这一部分对应上述公式中的X。输入的特征矩阵维度是(N, in_channels),输出的特征矩阵维度是(N, out_channels),其中 N 是节点个数。 在'forward()'函数中'edge_index, _ = add_self_loops(edge_index, num_nodes=x.size(0))'是给邻接矩阵加上self loops,也即构造出矩阵 ,在torch geometric中,无权图的邻接矩阵表示为2维数组(COO存储格式),第1行表示边的起始节点(source 节点),第2行表示边的目标节点(target节点)。 对于'message()'函数而言,函数入参'x_j' 的形状是\[E, out_channels\],其中E表示边的数量。由上面可知,特征矩阵经过线性变换后的输出形状是(N, out_channels),边的矩阵的形状为 \[2, E\]。'row, col = edge_index'表示取出所有边的起始节点和目标节点,row表示边的起始节点的结合,col表示边的目标节点的集合。在无向图中,这两者是等价的。以target节点作为索引,从线性变换后的特征矩阵中索引得到target节点的特征矩阵。 class GCNConv(MessagePassing): def __init__(self, in_channels, out_channels): # "Add" aggregation. super(GCNConv, self).__init__(aggr='add') self.lin = torch.nn.Linear(in\_channels, out\_channels) def forward(self, x, edge_index): # x has shape \[N, in_channels\] # edge_index has shape \[2, E\] # Step 1: Add self-loops to the adjacency matrix. edge_index, _ = add_self_loops(edge_index, num_nodes=x.size(0)) # Step 2: Linearly transform node feature matrix. x = self.lin(x) # Step 3-5: Start propagating messages. return self.propagate(edge_index, size=(x.size(0), x.size(0)), x=x) def message(self, x_j, edge_index, size): # x_j has shape [E, out_channels] # edge_index has shape [2, E] # Step 3: Normalize node features. row, col = edge_index # [N, ] deg = degree(row, size[0], dtype=x_j.dtype) # [N, ] deg_inv_sqrt = deg.pow(-0.5) norm = deg_inv_sqrt[row] * deg_inv_sqrt[col] return norm.view(-1, 1) * x_j def update(self, aggr_out): # aggr_out has shape [N, out_channels] # Step 5: Return new node embeddings. return aggr_out 上述类目前已经集成在torch_geometric.nn模块中,也可以使用下述一行代码替换 'from torch_geometric.nn import GCNConv' 导入GCN层替换GCNConv类定义。 Planetoid集成了论文引用中Cora,CiteSeer,PubMed三个数据集,由于本实验需要用到Cora数据集,因此此处需要导入该模块用于加载数据集。 from torch_geometric.datasets import Planetoid 定义GCN_NET图网络,中间构造一个隐藏层用来辅助实现线性转换过程。 class GCN_NET(torch.nn.Module): def __init__(self, features, hidden, classes): super(GCN_NET, self).__init__() # shape(输入的节点特征维度 * 中间隐藏层的维度) self.conv1 = GCNConv(features, hidden) # shaape(中间隐藏层的维度 * 节点类别) self.conv2 = GCNConv(hidden, classes) def forward(self, data): # 加载节点特征和邻接关系 x, edge_index = data.x, data.edge_index # 传入卷积层 x = self.conv1(x, edge_index) # 激活函数 x = F.relu(x) # dropout层,防止过拟合 x = F.dropout(x, training=self.training) # 第二层卷积层 x = self.conv2(x, edge_index) # 将经过两层卷积得到的特征输入log_softmax函数得到概率分布 return F.log_softmax(x, dim=1) GCN网络用于Cora数据集分类实战 本实验需要跑在npu上,因此需要导入Npu相关库,以便于模型能够跑在Npu上。 import torch_npu from torch_npu.contrib import transfer_to_npu /home/pengyongrong/miniconda3/envs/AscendCExperiments/lib/python3.9/site-packages/torch_npu/dynamo/init.py:18: UserWarning: Register eager implementation for the 'npu' backend of dynamo, as torch_npu was not compiled with torchair.warnings.warn(/home/pengyongrong/miniconda3/envs/AscendCExperiments/lib/python3.9/site-packages/torch_npu/contrib/transfer_to_npu.py:164: ImportWarning: The torch.Tensor.cuda and torch.nn.Module.cuda are replaced with torch.Tensor.npu and torch.nn.Module.npu now..The torch.cuda.DoubleTensor is replaced with torch.npu.FloatTensor cause the double type is not supported now..The backend in torch.distributed.init_process_group set to hccl now.. The torch.cuda.\* and torch.cuda.amp.\* are replaced with torch.npu.\* and torch.npu.amp.\* now.. The device parameters have been replaced with npu in the function below: torch.logspace, torch.randint, torch.hann_window, torch.rand, torch.full_like, torch.ones_like, torch.rand_like, torch.randperm, torch.arange, torch.frombuffer, torch.normal, torch._empty_per_channel_affine_quantized, torch.empty_strided, torch.empty_like, torch.scalar_tensor, torch.tril_indices, torch.bartlett_window, torch.ones, torch.sparse_coo_tensor, torch.randn, torch.kaiser_window, torch.tensor, torch.triu_indices, torch.as_tensor, torch.zeros, torch.randint_like, torch.full, torch.eye, torch._sparse_csr_tensor_unsafe, torch.empty, torch._sparse_coo_tensor_unsafe, torch.blackman_window, torch.zeros_like, torch.range, torch.sparse_csr_tensor, torch.randn_like, torch.from_file, torch._cudnn_init_dropout_state, torch._empty_affine_quantized, torch.linspace, torch.hamming_window, torch.empty_quantized, torch._pin_memory, torch.Tensor.new_empty, torch.Tensor.new_empty_strided, torch.Tensor.new_full, torch.Tensor.new_ones, torch.Tensor.new_tensor, torch.Tensor.new_zeros, torch.Tensor.to, torch.nn.Module.to, torch.nn.Module.to_empty\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*warnings.warn(msg, ImportWarning) /home/pengyongrong/miniconda3/envs/AscendCExperiments/lib/python3.9/site-packages/torch_npu/dynamo/init.py:18: UserWarning: Register eager implementation for the 'npu' backend of dynamo, as torch_npu was not compiled with torchair.warnings.warn(/home/pengyongrong/miniconda3/envs/AscendCExperiments/lib/python3.9/site-packages/torch_npu/contrib/transfer_to_npu.py:164: ImportWarning: The torch.Tensor.cuda and torch.nn.Module.cuda are replaced with torch.Tensor.npu and torch.nn.Module.npu now..The torch.cuda.DoubleTensor is replaced with torch.npu.FloatTensor cause the double type is not supported now..The backend in torch.distributed.init_process_group set to hccl now.. The torch.cuda.\* and torch.cuda.amp.\* are replaced with torch.npu.\* and torch.npu.amp.\* now.. The device parameters have been replaced with npu in the function below: torch.logspace, torch.randint, torch.hann_window, torch.rand, torch.full_like, torch.ones_like, torch.rand_like, torch.randperm, torch.arange, torch.frombuffer, torch.normal, torch._empty_per_channel_affine_quantized, torch.empty_strided, torch.empty_like, torch.scalar_tensor, torch.tril_indices, torch.bartlett_window, torch.ones, torch.sparse_coo_tensor, torch.randn, torch.kaiser_window, torch.tensor, torch.triu_indices, torch.as_tensor, torch.zeros, torch.randint_like, torch.full, torch.eye, torch._sparse_csr_tensor_unsafe, torch.empty, torch._sparse_coo_tensor_unsafe, torch.blackman_window, torch.zeros_like, torch.range, torch.sparse_csr_tensor, torch.randn_like, torch.from_file, torch._cudnn_init_dropout_state, torch._empty_affine_quantized, torch.linspace, torch.hamming_window, torch.empty_quantized, torch._pin_memory, torch.Tensor.new_empty, torch.Tensor.new_empty_strided, torch.Tensor.new_full, torch.Tensor.new_ones, torch.Tensor.new_tensor, torch.Tensor.new_zeros, torch.Tensor.to, torch.nn.Module.to, torch.nn.Module.to_empty warnings.warn(msg, ImportWarning) Cora数据集介绍与加载 Cora数据集包含2708篇科学出版物,10556条边,总共7种类别,数据集中的每个出版物都由一个 0/1 值的词向量描述,表示字典中相应词的缺失/存在。该词典由1433个独特的词组成,意思就是说每一个出版物都由1433个特征构成,每个特征仅由0/1表示。 如图,节点大小对应进出边的数量,节点越大表示进出边的数量越多,边的粗细反映对应两节点之间的相似或关联程度越高,也就是对彼此的影响力权重越大。 由于cora数据集处理的是无向图,所以'in degree count'与'out degree count'分布图一致,底部的'nodes for a given out-degree'与'node degree'图统计的是数据集中出边的分布情况,可以看到峰值点出现在\[2, 4\]范围内,说明大多数的节点之间之间与少量的边进行相连,相连节点最多的边是图中绿色节点,约有169个节点相连。 下载后的数据集总共包含8个文件分别是ind.cora.x、ind.cora.tx、ind.cora.all、ind.cora.y、ind.cora.ty、ind.cora.ally、ind.cora.graph与ind.cora.test.index。 其中: ind.cora.x:训练集节点特征向量,大小(140,1433) ind.cora.tx:测试集节点特征向量,实际展开后大小为(1000,1433) ind.cora.allx:包含标签核无标签的训练节点特征向量(1708,1433) ind.cora.y:one-hot表示的训练节点的标签 ind.cora.ty:one-hot表示的测试节点的标签 ind.cora.ally:one-hot表示的ind.cora.allx对应的标签 ind.cora.graph:保存节点之间边的信息 ind.cora.test.index:保存测试集节点的索引,用于后面的归纳学习设置 上述介绍完cora数据集的基本组成情况,接下来我们通过Planetoid集成库来下载cora数据集,下载好以后对数据集中一些基本的信息进行打印。 import numpy as np # 加载数据,出错可自行下载,解决方案见下文 print("===== begin Download Dadasat=====\\n") dataset = Planetoid(root='/home/pengyongrong/workspace/data', name='Cora') print("===== Download Dadasat finished=====\\n") print("dataset num_features is: ", dataset.num_features) print("dataset.num_classes is: ", dataset.num_classes) print("dataset.edge_index is: ", dataset.edge_index) print("train data is: ", dataset.data) print("dataset0 is: ", dataset\[0\]) print("train data mask is: ", dataset.train_mask, "num train is: ", (dataset.train_mask ==True).sum().item()) print("val data mask is: ",dataset.val_mask, "num val is: ", (dataset.val_mask ==True).sum().item()) print("test data mask is: ",dataset.test_mask, "num test is: ", (dataset.test_mask ==True).sum().item()) ===== begin Download Dadasat===== ===== Download Dadasat finished===== dataset num_features is: 1433 dataset.num_classes is: 7 dataset.edge_index is: tensor(\[\[ 633, 1862, 2582, ..., 598, 1473, 2706\], \[ 0, 0, 0, ..., 2707, 2707, 2707\]\]) train data is: Data(x=\[2708, 1433\], edge_index=\[2, 10556\], y=\[2708\], train_mask=\[2708\], val_mask=\[2708\], test_mask=\[2708\]) dataset0 is: Data(x=\[2708, 1433\], edge_index=\[2, 10556\], y=\[2708\], train_mask=\[2708\], val_mask=\[2708\], test_mask=\[2708\]) train data mask is: tensor(\[ True, True, True, ..., False, False, False\]) num train is: 140 val data mask is: tensor(\[False, False, False, ..., False, False, False\]) num val is: 500 test data mask is: tensor(\[False, False, False, ..., True, True, True\]) num test is: 1000 /home/pengyongrong/miniconda3/envs/AscendCExperiments/lib/python3.9/site-packages/torch_geometric/data/in_memory_dataset.py:300: UserWarning: It is not recommended to directly access the internal storage format \data\ of an 'InMemoryDataset'. If you are absolutely certain what you are doing, access the internal storage via \InMemoryDataset._data\ instead to suppress this warning. Alternatively, you can access stacked individual attributes of every graph via \dataset.{attr_name}\. warnings.warn(msg) 本文用到的实验数据集这里已经下载好并保存在"/home/pengyongrong/workspace/data"目录下,若没有下载好启动该命令会自动下载数据集,整个下载过程可能会比较慢,也可以在https://github.com/D61-IA/FisherGCN/tree/a58c1613f1aca7077ef90af6e51a8021548cdb4c/data 自行选择下载。 从打印的信息可以看出来,每一个节点的特征维度为1433,也就是'datasat.numfeatures'的取值是1433;总的类别数是7,对应'datasat.numclasses';'dataset.edge_index'表示所有边与边之间的互联关系,采用coo存储格式,因为这里是无权边,因此只需要二维数组即可完成对应功能。 上述信息介绍完后就剩下训练集、验证集与测试集相关的信息,dataset\[0\]包含了所有信息,包括输入x,边信息、标签y及'train_mask'、'val_mask'与'test_mask'分别表示2708篇刊物中哪些用于训练,哪些用于验证及哪些用于测试。 开启cora数据训练过程 接下来就是将cora数据集送入搭建好的GCN网络模型中进行训练,训练过程中设置设备在npu上运行,并定义训练的'epoch=200',迭代次数可以根据需要自行更改,训练完成后对模型的效果进行评估并打印预测的准确率约为0.8。 #设置成npu device = 'npu' print("device is: ", device) # 构建模型,设置中间隐藏层维度为16 model = GCN_NET(dataset.num_node_features, 16, dataset.num_classes).to(device) # 加载数据 data = dataset[0].to(device) # 定义优化函数 optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4) model.train() for epoch in range(200): # 梯度设为零 optimizer.zero_grad() # 模型输出 out = model(data) # 计算损失 loss = F.nll_loss(out[data.train_mask], data.y[data.train_mask]) # 反向传播计算梯度 loss.backward() # 一步优化 optimizer.step() # 评估模型 model.eval() # 得到模型输出的类别 _, pred = model(data).max(dim=1) # 计算正确的个数 correct = int(pred[data.test_mask].eq(data.y[data.test_mask]).sum().item()) # 得出准确率 acc = correct / int(data.test_mask.sum()) # 打印准确率及结果 print('GCN Accuracy: {:.4f}'.format(acc)) device is: npuGCN Accuracy: 0.8040 内存使用情况: 整个训练过程的内存使用情况可以通过"npu-smi info"命令在终端查看,因此本文实验只用到了单个npu卡(也就是chip 0),内存占用约167M,对内存、精度或性能优化有兴趣的可以自行尝试进行优化,这里运行过程中也有其他程序在运行,因此本实验用到的网络所需要的内存已单独框出来。 Reference========= \[1\] Heidari, Negar , L. Hedegaard , and A. Iosifidis . "Graph convolutional networks." Deep Learning for Robot Perception and Cognition (2022).
2025年-4月-27日
10 阅读
0 评论
人工智能