摘要:
深度学习还没学完,怎么图深度学习又来了?别怕,这里有份系统教程,可以将0基础的你直接送到图深度学习。还会定期更新哦。
本教程是一个系列免费教程,争取每月更新2到4篇。
主要是基于图深度学习的入门内容。讲述最基本的基础知识,其中包括深度学习、数学、图神经网络等相关内容。该教程由代码医生工作室出版的全部书籍混编节选而成。偏重完整的知识体系和学习指南。在实践方面不会涉及太多基础内容 (实践和经验方面的内容,请参看原书)。
文章涉及使用到的框架以PyTorch和TensorFlow为主。默认读者已经掌握Python和TensorFlow基础。如有涉及到PyTorch的部分,会顺带介绍相关的入门使用。
本教程主要针对的人群:
本篇文章主要通过一个实例介绍如何在DGL中,搭建带有残差结构的多层GAT模型。它是在教程的第六篇GAT模型 基础上进行的延申。
1. 什么是残差结构
残差结构最早源自于ResNet50模型。
ResNet50模型是ResNet(残差网络)的第1个版本,该模型于2015年由何凯明等提出,模型有50层。
残差结构是ResNet50模型的核心特点,它解决了当时深层神经网络难于的训练问题。该网络借鉴了Highway Network(高速通道网络)的思想。在网络的主处理层旁边开了个额外的通道,使得输入可以直达输出。其结构如图所示。
假设x经过神经网络层处理之后,输出的结果为H(x),则结构中的残差网络输出的结果为Y(x)= H(x)+x。
在2015年的ILSVRC(大规模视觉识别挑战赛)中ResNet模型以成绩为:79.26%的Top-1准确率和94.75%的Top-5准确率,取得了当年比赛的第一名。这个模型简单实用,经常被嵌入其它深层网络结构中,作为特征提取层使用。
2.残差结构的原理
残差网络结构是由若干个残差块组成的深度卷积网络结构,如图所示是一个残差块。
在图中,x是该残差块输入,H(x)是期望输出。identity表示恒等映射,即输入是x,输出也是x。F(x)表示期望输出H(x)与输入x的残差,即F(x) =H(x) -x。
残差结构的基本想法是:假设已经有了一个深度神经网络,在其中再增加几个恒等映射,那么不仅增加了网络的深度,并且至少不会增加误差,这样更深的网络不应该导致误差的增加。因此残差结构学习的是残差。
从图中可以看出,当残差F(x)=0时,H(x) =x,这时网络没有误差。
利用这种残差结构,可以使得网络达到上百层的深度。详情请参阅原始论文《Deep ResidualLearning for Image Recognition》,该论文网址是:
https://arxiv.org/abs/1512.03385
这种方式看似解决的梯度越传越小的问题,但是残差连接在正向同样也起到作用,由于正向的作用,导致网络结构已经不再是深层了。而是一个并行的模型,即残差连接的作用是将网络串行改成了并行。本质上起到与多通道卷积一致的效果。
3.残差结构在图神经网络中的应用
如果将图卷积或是图注意力卷积层,当作一个普通的卷积层。则也可以搭建出带有残差结构的图神经网络。在这种神经网络中残差结构同样有效,可以使图神经网络模型的层数达到很深。而它的性能更由于对图卷积或是图注意力卷积层进行简单堆叠的图神经网络模型。
4 实例:用带有残差结构的多层GAT模型实现论文分类
在教程三——全连接神经网络与图卷积中介绍过DGL库中有多种数据集。本例就来使用其中的论文数据集——CORA。
并使用带有残差结构的多层GAT模型对其进行分类。
4.1 代码实现:下载CORA数据集
直接使用dgl.data库中的citation_graph模块即可实现CORA数据集的下载。具体代码如下:
代码文件:code_30_dglGAT.py
import dgl
import torch
from torch import nn
from dgl.data import citation_graph
from dgl.nn.pytorch import GATConv
data = citation_graph.CoraDataset()#下载并加载数据集
代码第6行会自动在后台对CORA数据集进行下载。待数据集下载完成之后,对其进行加载返回data对象。
代码运行后输出如下内容:
Downloading C:\Users\ljh\.dgl/cora.zip from https://s3.us-east-2.amazonaws.com/dgl.ai/dataset/cora_raw.zip...
#Extracting file to C:\Users\ljh\.dgl/cora
系统默认的下载路径为当前用户的.dgl文件夹。以作者的本机为例,下载路径为C:\Users\ljh\.dgl/cora.zip。
代码第6行返回的data对象中含有数据集的样本(features)、标签(labels)以及论文中引用关系的邻接矩阵,还有拆分好的训练、测试、验证数据集掩码。
其中,数据集的样本(features)已经被归一化处理,邻接矩阵是以NetWorkx图的形式存在的。
4.2. 代码实现:加工图数据
编写代码查看data对象中的样本数据。具体代码如下:
代码文件:code_30_dglGAT.py(续)
#输出运算资源请况
device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')
print(device)
features = torch.FloatTensor(data.features).to(device)#获得样本特征
labels = torch.LongTensor(data.labels).to(device)#获得标签
train_mask = torch.BoolTensor(data.train_mask).to(device)#获得训练集掩码
val_mask = torch.BoolTensor(data.val_mask).to(device) #获得验证集掩码
test_mask = torch.BoolTensor(data.test_mask).to(device) #获得测试集掩码
feats_dim = features.shape[1]#获得特征维度
n_classes = data.num_labels#获得类别个数
n_edges = data.graph.number_of_edges()#获得邻接矩阵边数
print("""----数据统计------
#边数 %d
#样本特征维度 %d
#类别数 %d
#训练样本 %d
#验证样本 %d
#测试样本 %d""" % (n_edges, feats_dim,n_classes,
train_mask.int().sum().item(),val_mask.int().sum().item(),
test_mask.int().sum().item()))#输出结果
g = dgl.DGLGraph(data.graph)#将networkx图转成DGL图
g.add_edges(g.nodes(), g.nodes()) #添加自环
n_edges = g.number_of_edges()
代码第25~27行对邻接矩阵进行加工。需要为邻接矩阵加上自环边。
代码运行后输出如下内容:
----数据统计------
#边数 10556
#样本特征维度 1433
#类别数 7
#训练样本 140
#验证样本 300
#测试样本 1000
4.3 代码实现:用DGL库中的GATConv搭建多层GAT模型
在使用DGL库中的GATConv层时,可以将GATConv层直接当作深度学习中的卷积层,搭建多层图卷积网络。具体代码如下:
代码文件:code_30_dglGAT.py(续)
class GAT(nn.Module):#定义多层GAT模型
def __init__(self,
num_layers,#层数
in_dim, #输入维度
num_hidden,#隐藏层维度
num_classes,#类别个数
heads,#多头注意力的计算次数
activation,#激活函数
feat_drop,#特征层的丢弃率
attn_drop,#注意力分数的丢弃率
negative_slope,#LeakyReLU激活函数的负向参数
residual):#是否使用残差网络结构
super(GAT, self).__init__()
self.num_layers = num_layers
self.gat_layers = nn.ModuleList()
self.activation = activation
self.gat_layers.append(GATConv(in_dim, num_hidden, heads[0],
feat_drop, attn_drop, negative_slope, False, self.activation))
#定义隐藏层
for l in range(1, num_layers):
#多头注意力 the in_dim = num_hidden * num_heads
self.gat_layers.append(GATConv(
num_hidden * heads[l-1], num_hidden, heads[l],
feat_drop, attn_drop, negative_slope, residual, self.activation))
#输出层
self.gat_layers.append(GATConv(
num_hidden * heads[-2], num_classes, heads[-1],
feat_drop, attn_drop, negative_slope, residual, None))
def forward(self, g,inputs):
h = inputs
for l in range(self.num_layers):#隐藏层
h = self.gat_layers[l](g, h).flatten(1)
#输出层
logits = self.gat_layers[-1](g, h).mean(1)
return logits
def getmodel( GAT ): #定义函数实例化模型
#定义模型参数
num_heads = 8
num_layers = 1
num_out_heads =1
heads = ([num_heads] * num_layers) + [num_out_heads]
#实例化模型
model = GAT( num_layers, num_feats, num_hidden= 8,
num_classes = n_classes,
heads = ([num_heads] * num_layers) + [num_out_heads],#总的注意力头数
activation = F.elu, feat_drop=0.6, attn_drop=0.6,
negative_slope = 0.2, residual = True) #使用残差结构
return model
代码第11行设置了激活函数leaky_relu的负向参数,该激活函数在DGL库中的GATConv类在计算注意力时的非线性变换使用。这部分内容请参考教程三——全连接神经网络与图卷积
本节代码所实现的多层GAT网络模型主要结构分为两部分,隐藏层和输出层:
通过如下两行代码即可将模型结构打印出来:
model = getmodel(GAT)
print(model)#输出模型
代码运行后输出如下结果:
GAT(
(gat_layers): ModuleList(
(0): GATConv(
(fc): Linear(in_features=1433, out_features=64, bias=False)
(feat_drop): Dropout(p=0.6, inplace=False)
(attn_drop): Dropout(p=0.6, inplace=False)
(leaky_relu): LeakyReLU(negative_slope=0.2)
)
(1): GATConv(
(fc): Linear(in_features=64, out_features=7, bias=False)
(feat_drop): Dropout(p=0.6, inplace=False)
(attn_drop): Dropout(p=0.6, inplace=False)
(leaky_relu): LeakyReLU(negative_slope=0.2)
(res_fc): Linear(in_features=64, out_features=7, bias=False)
)
)
)
结果中的“(0): GATConv”是隐藏层部分;“(1): GATConv”是输出层部分。
4.4 训练模型
训练模型与正常的深度学习训练过程完全一致。具体细节如下:
将前面准备好的图对象g和节点特征features传入模型中model(g,features)即可输出预测结果。
代码运行后,输出如下结果:
Epoch 00000 | Time(s) nan | Loss 1.9382 | TrainAcc 0.1643 | ValAcc 0.1967 | ETputs(KTEPS) nan
Epoch 00001 | Time(s) nan | Loss 1.9359 | TrainAcc 0.2143 | ValAcc 0.2300 | ETputs(KTEPS) nan
Epoch 00002 | Time(s) nan | Loss 1.9063 | TrainAcc 0.3214 | ValAcc 0.3033 | ETputs(KTEPS) nan
……
Epoch 00198 | Time(s) 0.0268 | Loss 0.2543 | TrainAcc 0.9643 | ValAcc 0.7700 | ETputs(KTEPS) 495.68
EarlyStopping counter: 71 out of 100
Epoch 00199 | Time(s) 0.0268 | Loss 0.2421 | TrainAcc 0.9714 | ValAcc 0.7633 | ETputs(KTEPS) 495.76
Test Accuracy 0.8350
如果直接使用单层的GAT模型,其准确率只有0.7800。没有本例中模型的准确率0.8350高。