【Pytorch神经网络实战案例】20 基于Cora数据集实现图卷积神经网络论文分类

1 案例说明(图卷积神经网络)

CORA数据集里面含有每一篇论文的关键词以及分类信息,同时还有论文间互相引用的信息。搭建AI模型,对数据集中的论文信息进行分析,根据已有论文的分类特征,从而预测出未知分类的论文类别。

1.1 使用图卷积神经网络的特点

使用图神经网络来实现分类。与深度学习模型的不同之处在于,图神经网通会利用途文本身特征和论文间的关系特征进行处理,仅需要少量样本即可达到很好的效果。

cora数据集2022年-深度学习文档类资源-CSDN下载CORA数据集是由机器学习的论文整理而来的,记录每篇论文用到的关键词,以及论文之间互相引用的关系。C更多下载资源、学习资料请访问CSDN下载频道.https://download.csdn.net/download/qq_39237205/85059035

1.2 CORA数据集

CORA数据集是由机器学习的论文整理而来的,记录每篇论文用到的关键词,以及论文之间互相引用的关系。

1.2.1 CORA的内容

CORA数据集中的论文共分为7类:基于案例、遗传算法、神经网络、概率方法、强化学习、规则学习、理论。

1.2.2 CORA的组成

数据集中共有2708篇论文,每一篇论文都引用或至少被一篇其他论文所引用。整个语料库共有2708篇论文。同时,又将所有论文中的词干、停止词、低频词删除,留下1433个关键词,作为论文的个体特征。

1.2.3 CORA数据集的文件与结构说明

(1)content文件格式的论文说明:

<paper-id><word-attributes><class-label>

每行的第一个条目包含论文的唯一字符串ID,随后用一个二进制值指示词汇表中的每个单词在纸张中存在(由1表示)或不存在(由0表示)。行中的最后一项包含纸张的类标签。

(2)cites文件包含了语料库的引文图,每一行用以下格式描述一个链接:

 <id ofreferencepaper><id ofreference paper>

每行包含两个纸张ID。第一个条目是被引用论文的ID,第二个ID代表包含引用的论文。链接的方向是从右向左的。如果一行用“paper2 paper1”表示,那么其中连接为“paper2->paper1”

2 代码编写

2.1 代码实战:引入基础模块,设置运行环境----Cora_GNN.py(第1部分)

from pathlib import Path # 引入提升路径的兼容性
# 引入矩阵运算的相关库
import numpy as np
import pandas as pd
from scipy.sparse import coo_matrix,csr_matrix,diags,eye
# 引入深度学习框架库
import torch
from torch import nn
import torch.nn.functional as F
# 引入绘图库
import matplotlib.pyplot as plt
import os
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"# 1.1 导入基础模块,并设置运行环境
# 输出计算资源情况
device = torch.device('cuda')if torch.cuda.is_available() else torch.device('cpu')
print(device) # 输出 cuda# 输出样本路径
path = Path('./data/cora')
print(path) # 输出 cuda

输出结果:

2.2 代码实现:读取并解析论文数据----Cora_GNN.py(第2部分)

# 1.2 读取并解析论文数据
# 读取论文内容数据,将其转化为数据
paper_features_label = np.genfromtxt(path/'cora.content',dtype=np.str_) # 使用Path对象的路径构造,实例化的内容为cora.content。path/'cora.content'表示路径为'data/cora/cora.content'的字符串
print(paper_features_label,np.shape(paper_features_label)) # 打印数据集内容与数据的形状# 取出数据集中的第一列:论文ID
papers = paper_features_label[:,0].astype(np.int32)
print("论文ID序列:",papers) # 输出所有论文ID
# 论文重新编号,并将其映射到论文ID中,实现论文的统一管理
paper2idx = {k:v for v,k in enumerate(papers)}# 将数据中间部分的字标签取出,转化成矩阵
features = csr_matrix(paper_features_label[:,1:-1],dtype=np.float32)
print("字标签矩阵的形状:",np.shape(features)) # 字标签矩阵的形状# 将数据的最后一项的文章分类属性取出,转化为分类的索引
labels = paper_features_label[:,-1]
lbl2idx = { k:v for v,k in enumerate(sorted(np.unique(labels)))}
labels = [lbl2idx[e] for e in labels]
print("论文类别的索引号:",lbl2idx,labels[:5])

输出:

2.3 读取并解析论文关系数据

载入论文的关系数据,将数据中用论文ID表示的关系转化成重新编号后的关系,将每篇论文当作一个顶点,论文间的引用关系作为边,这样论文的关系数据就可以用一个图结构来表示。

 计算该图结构的邻接矩阵并将其转化为无向图邻接矩阵。

2.3.1 代码实现:转化矩阵----Cora_GNN.py(第3部分)

# 1.3 读取并解析论文关系数据
# 读取论文关系数据,并将其转化为数据
edges = np.genfromtxt(path/'cora.cites',dtype=np.int32) # 将数据集中论文的引用关系以数据的形式读入
print(edges,np.shape(edges))
# 转化为新编号节点间的关系:将数据集中论文ID表示的关系转化为重新编号后的关系
edges = np.asarray([paper2idx[e] for e in edges.flatten()],np.int32).reshape(edges.shape)
print("新编号节点间的对应关系:",edges,edges.shape)
# 计算邻接矩阵,行与列都是论文个数:由论文引用关系所表示的图结构生成邻接矩阵。
adj = coo_matrix((np.ones(edges.shape[0]), (edges[:, 0], edges[:, 1])),shape=(len(labels), len(labels)), dtype=np.float32)
# 生成无向图对称矩阵:将有向图的邻接矩阵转化为无向图的邻接矩阵。Tip:转化为无向图的原因:主要用于对论文的分类,论文的引用关系主要提供单个特征之间的关联,故更看重是不是有关系,所以无向图即可。
adj_long = adj.multiply(adj.T < adj)
adj = adj_long + adj_long.T

输出:

2.4 加工图结构的矩阵数据

对图结构的矩阵数据进行加工,使其更好地表现出图结构特征,并参与神经网络的模型计算。

2.4.1 加工图结构的矩阵数据的步骤

1、对每个节点的特征数据进行归一化处理。
2、为邻接矩阵的对角线补1:因为在分类任务中,邻接矩阵主要作用是通过论文间的关联来帮助节点分类。对于对角线上的节点,表示的意义是自己与自己的关联。将对角线节点设为1(自环图)、表明节点也会帮助到分类任务。
3、对补1后的邻接矩阵进行归一化处理。

2.4.2 代码实现:加工图结构的矩阵数据----Cora_GNN.py(第4部分)

# 1.4 加工图结构的矩阵数据
def normalize(mx): # 定义函数,对矩阵的数据进行归一化处理rowsum = np.array(mx.sum(1)) # 计算每一篇论文的字数==>02 对A中的边数求和,计算出矩阵A的度矩阵D^的特征向量r_inv = (rowsum ** -1).flatten() # 取总字数的倒数==>03 对矩阵A的度矩阵D^的特征向量求逆,并得到D^逆的特征向量r_inv[np.isinf(r_inv)] = 0.0 # 将NaN值取为0r_mat_inv = diags(r_inv) # 将总字数的倒数变为对角矩阵===》对图结构的度矩阵求逆==>04 D^逆的特征向量转化为对角矩阵,得到D^逆mx = r_mat_inv.dot(mx) # 左乘一个矩阵,相当于每个元素除以总数===》对每个论文顶点的边进行归一化处理==>05 计算D^逆与A加入自环(对角线为1)的邻接矩阵所得A^的点积,得到拉普拉斯矩阵。return mx
# 对features矩阵进行归一化处理(每行总和为1)
features = normalize(features) #在函数normalize()中,分为两步对邻接矩阵进行处理。1、将每篇论文总字数的倒数变成对角矩阵。该操作相当于对图结构的度矩阵求逆。2、用度矩阵的逆左乘邻接矩阵,相当于对图中每个论文顶点的边进行归一化处理。
# 对邻接矩阵的对角线添1,将其变为自循环图,同时对其进行归一化处理
adj = normalize(adj + eye(adj.shape[0])) # 对角线补1==>01实现加入自环的邻接矩阵A

2.5 将数据转化为张量,并分配运算资源

将加工好的图结构矩阵数据转为PyTorch支持的张量类型,并将其分成3份,分别用来进行训练、测试和验证。

2.5.1 代码实现:将数据转化为张量,并分配运算资源----Cora_GNN.py(第5部分)

# 1.5 将数据转化为张量,并分配运算资源
adj = torch.FloatTensor(adj.todense()) # 节点间关系 todense()方法将其转换回稠密矩阵。
features = torch.FloatTensor(features.todense()) # 节点自身的特征
labels = torch.LongTensor(labels) # 对每个节点的分类标签# 划分数据集
n_train = 200 # 训练数据集大小
n_val = 300 # 验证数据集大小
n_test = len(features) - n_train - n_val # 测试数据集大小
np.random.seed(34)
idxs = np.random.permutation(len(features)) # 将原有的索引打乱顺序# 计算每个数据集的索引
idx_train = torch.LongTensor(idxs[:n_train]) # 根据指定训练数据集的大小并划分出其对应的训练数据集索引
idx_val = torch.LongTensor(idxs[n_train:n_train+n_val])# 根据指定验证数据集的大小并划分出其对应的验证数据集索引
idx_test = torch.LongTensor(idxs[n_train+n_val:])# 根据指定测试数据集的大小并划分出其对应的测试数据集索引# 分配运算资源
adj = adj.to(device)
features = features.to(device)
labels = labels.to(device)
idx_train = idx_train.to(device)
idx_val = idx_val.to(device)
idx_test = idx_test.to(device)

2.6 图卷积

图卷积的本质是维度变换,即将每个含有in维的节点特征数据变换成含有out维的节点特征数据。

图卷积的操作将输入的节点特征、权重参数、加工后的邻接矩阵三者放在一起执行点积运算。

权重参数是个in×out大小的矩阵,其中in代表输入节点的特征维度、out代表最终要输出的特征维度。将权重参数在维度变换中的功能当作一个全连接网络的权重来理解,只不过在图卷积中,它会比全连接网络多了执行节点关系信息的点积运算。

 如上图所示,列出全连接网络和图卷积网络在忽略偏置后的关系。从中可以很清晰地看出,图卷积网络其实就是在全连接网络基础之上增加了节点关系信息。

2.6.1 代码实现:定义Mish激活函数与图卷积操作类----Cora_GNN.py(第6部分)

在上图的所示的算法基础增加偏置,定义GraphConvolution类

# 1.6 定义Mish激活函数与图卷积操作类
def mish(x): # 性能优于RElu函数return x * (torch.tanh(F.softplus(x)))
# 图卷积类
class GraphConvolution(nn.Module):def __init__(self,f_in,f_out,use_bias = True,activation=mish):# super(GraphConvolution, self).__init__()super().__init__()self.f_in = f_inself.f_out = f_outself.use_bias = use_biasself.activation = activationself.weight = nn.Parameter(torch.FloatTensor(f_in, f_out))self.bias = nn.Parameter(torch.FloatTensor(f_out)) if use_bias else Noneself.initialize_weights()def initialize_weights(self):# 对参数进行初始化if self.activation is None: # 初始化权重nn.init.xavier_uniform_(self.weight)else:nn.init.kaiming_uniform_(self.weight, nonlinearity='leaky_relu')if self.use_bias:nn.init.zeros_(self.bias)def forward(self,input,adj): # 实现模型的正向处理流程support = torch.mm(input,self.weight) # 节点特征与权重点积:torch.mm()实现矩阵的相乘,仅支持二位矩阵。若是多维矩则使用torch.matmul()output = torch.mm(adj,support) # 将加工后的邻接矩阵放入点积运算if self.use_bias:output.add_(self.bias) # 加入偏置if self.activation is not None:output = self.activation(output) # 激活函数处理return output

2.7 搭建多层图卷积

定义GCN类将GraphConvolution类完成的图卷积层叠加起来,形成多层图卷积网络。同时,为该网络模型实现训练和评估函数。

2.7.1 代码实现:多层图卷积----Cora_GNN.py(第7部分)

# 1.7 搭建多层图卷积网络模型
class GCN(nn.Module):def __init__(self, f_in, n_classes, hidden=[16], dropout_p=0.5): # 实现多层图卷积网络,该网的搭建方法与全连接网络的搭建一致,只是将全连接层转化成GraphConvolution所实现的图卷积层# super(GCN, self).__init__()super().__init__()layers = []# 根据参数构建多层网络for f_in, f_out in zip([f_in] + hidden[:-1], hidden):# python 在list上的“+=”的重载函数是extend()函数,而不是+# layers = [GraphConvolution(f_in, f_out)] + layerslayers += [GraphConvolution(f_in, f_out)]self.layers = nn.Sequential(*layers)self.dropout_p = dropout_p# 构建输出层self.out_layer = GraphConvolution(f_out, n_classes, activation=None)def forward(self, x, adj): # 实现前向处理过程for layer in self.layers:x = layer(x,adj)# 函数方式调用dropout():必须指定模型的运行状态,即Training标志,这样可减少很多麻烦F.dropout(x,self.dropout_p,training=self.training,inplace=True)return self.out_layer(x,adj)n_labels = labels.max().item() + 1 # 获取分类个数7
n_features = features.shape[1] # 获取节点特征维度 1433
print(n_labels,n_features) # 输出7与1433def accuracy(output,y): # 定义函数计算准确率return (output.argmax(1) == y).type(torch.float32).mean().item()### 定义函数来实现模型的训练过程。与深度学习任务不同,图卷积在训练时需要传入样本间的关系数据。
# 因为该关系数据是与节点数相等的方阵,所以传入的样本数也要与节点数相同,在计算loss值时,可以通过索引从总的运算结果中取出训练集的结果。
def step(): # 定义函数来训练模型 Tip:在图卷积任务中,无论是用模型进行预测还是训练,都需要将全部的图结构方阵输入model.train()optimizer.zero_grad()output = model(features,adj) # 将全部数据载入模型,只用训练数据计算损失loss = F.cross_entropy(output[idx_train],labels[idx_train])acc = accuracy(output[idx_train],labels[idx_train]) # 计算准确率loss.backward()optimizer.step()return loss.item(),accdef evaluate(idx): # 定义函数来评估模型 Tip:在图卷积任务中,无论是用模型进行预测还是训练,都需要将全部的图结构方阵输入model.eval()output = model(features, adj) # 将全部数据载入模型,用指定索引评估模型结果loss = F.cross_entropy(output[idx], labels[idx]).item()return loss, accuracy(output[idx], labels[idx])

2.8 Ranger优化器

图卷积神经网络的层数不宜过多,一般在3层左右即可。本例将实现一个3层的图卷积神经网络,每层的维度变化如图9-15所示。

使用循环语句训练模型,并将模型结果可视化。

2.8.1 代码实现:用Ranger优化器训练模型并可视化结果----Cora_GNN.py(第8部分)

# 1.8 使用Ranger优化器训练模型并可视化
model = GCN(n_features, n_labels, hidden=[16, 32, 16]).to(device)from tqdm import tqdm
from Cora_ranger import * # 引入Ranger优化器
optimizer = Ranger(model.parameters()) # 使用Ranger优化器# 训练模型
epochs = 1000
print_steps = 50
train_loss, train_acc = [], []
val_loss, val_acc = [], []
for i in tqdm(range(epochs)):tl,ta = step()train_loss = train_loss + [tl]train_acc = train_acc + [ta]if (i+1) % print_steps == 0 or i == 0:tl,ta = evaluate(idx_train)vl,va = evaluate(idx_val)val_loss = val_loss + [vl]val_acc = val_acc + [va]print(f'{i + 1:6d}/{epochs}: train_loss={tl:.4f}, train_acc={ta:.4f}' + f', val_loss={vl:.4f}, val_acc={va:.4f}')# 输出最终结果
final_train, final_val, final_test = evaluate(idx_train), evaluate(idx_val), evaluate(idx_test)
print(f'Train     : loss={final_train[0]:.4f}, accuracy={final_train[1]:.4f}')
print(f'Validation: loss={final_val[0]:.4f}, accuracy={final_val[1]:.4f}')
print(f'Test      : loss={final_test[0]:.4f}, accuracy={final_test[1]:.4f}')# 可视化训练过程
fig, axes = plt.subplots(1, 2, figsize=(15,5))
ax = axes[0]
axes[0].plot(train_loss[::print_steps] + [train_loss[-1]], label='Train')
axes[0].plot(val_loss, label='Validation')
axes[1].plot(train_acc[::print_steps] + [train_acc[-1]], label='Train')
axes[1].plot(val_acc, label='Validation')
for ax,t in zip(axes, ['Loss', 'Accuracy']): ax.legend(), ax.set_title(t, size=15)# 输出模型的预测结果
output = model(features, adj)
samples = 10
idx_sample = idx_test[torch.randperm(len(idx_test))[:samples]]
# 将样本标签与预测结果进行比较
idx2lbl = {v:k for k,v in lbl2idx.items()}
df = pd.DataFrame({'Real': [idx2lbl[e] for e in labels[idx_sample].tolist()],'Pred': [idx2lbl[e] for e in output[idx_sample].argmax(1).tolist()]})
print(df)

2.7 程序输出汇总

2.7.1 训练过程 

2.7.3 验证结果

2.8 结论

从训练结果中可以看出,该模型具有很好的拟合能力。值得一提的是,图卷积模型所使用的训练样本非常少,只使用了2708个样本中的200个进行训练。因为加入了样本间的关系信息,所以模型对样本量的依赖大幅下降。这也正是图神经网络模型的优势。

3 代码汇总

3.1 Cora_GNN.py

from pathlib import Path # 引入提升路径的兼容性
# 引入矩阵运算的相关库
import numpy as np
import pandas as pd
from scipy.sparse import coo_matrix,csr_matrix,diags,eye
# 引入深度学习框架库
import torch
from torch import nn
import torch.nn.functional as F
# 引入绘图库
import matplotlib.pyplot as plt
import os
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"# 1.1 导入基础模块,并设置运行环境
# 输出计算资源情况
device = torch.device('cuda')if torch.cuda.is_available() else torch.device('cpu')
print(device) # 输出 cuda# 输出样本路径
path = Path('./data/cora')
print(path) # 输出 cuda# 1.2 读取并解析论文数据
# 读取论文内容数据,将其转化为数据
paper_features_label = np.genfromtxt(path/'cora.content',dtype=np.str_) # 使用Path对象的路径构造,实例化的内容为cora.content。path/'cora.content'表示路径为'data/cora/cora.content'的字符串
print(paper_features_label,np.shape(paper_features_label)) # 打印数据集内容与数据的形状# 取出数据集中的第一列:论文ID
papers = paper_features_label[:,0].astype(np.int32)
print("论文ID序列:",papers) # 输出所有论文ID
# 论文重新编号,并将其映射到论文ID中,实现论文的统一管理
paper2idx = {k:v for v,k in enumerate(papers)}# 将数据中间部分的字标签取出,转化成矩阵
features = csr_matrix(paper_features_label[:,1:-1],dtype=np.float32)
print("字标签矩阵的形状:",np.shape(features)) # 字标签矩阵的形状# 将数据的最后一项的文章分类属性取出,转化为分类的索引
labels = paper_features_label[:,-1]
lbl2idx = { k:v for v,k in enumerate(sorted(np.unique(labels)))}
labels = [lbl2idx[e] for e in labels]
print("论文类别的索引号:",lbl2idx,labels[:5])# 1.3 读取并解析论文关系数据
# 读取论文关系数据,并将其转化为数据
edges = np.genfromtxt(path/'cora.cites',dtype=np.int32) # 将数据集中论文的引用关系以数据的形式读入
print(edges,np.shape(edges))
# 转化为新编号节点间的关系:将数据集中论文ID表示的关系转化为重新编号后的关系
edges = np.asarray([paper2idx[e] for e in edges.flatten()],np.int32).reshape(edges.shape)
print("新编号节点间的对应关系:",edges,edges.shape)
# 计算邻接矩阵,行与列都是论文个数:由论文引用关系所表示的图结构生成邻接矩阵。
adj = coo_matrix((np.ones(edges.shape[0]), (edges[:, 0], edges[:, 1])),shape=(len(labels), len(labels)), dtype=np.float32)
# 生成无向图对称矩阵:将有向图的邻接矩阵转化为无向图的邻接矩阵。Tip:转化为无向图的原因:主要用于对论文的分类,论文的引用关系主要提供单个特征之间的关联,故更看重是不是有关系,所以无向图即可。
adj_long = adj.multiply(adj.T < adj)
adj = adj_long + adj_long.T# 1.4 加工图结构的矩阵数据
def normalize(mx): # 定义函数,对矩阵的数据进行归一化处理rowsum = np.array(mx.sum(1)) # 计算每一篇论文的字数==>02 对A中的边数求和,计算出矩阵A的度矩阵D^的特征向量r_inv = (rowsum ** -1).flatten() # 取总字数的倒数==>03 对矩阵A的度矩阵D^的特征向量求逆,并得到D^逆的特征向量r_inv[np.isinf(r_inv)] = 0.0 # 将NaN值取为0r_mat_inv = diags(r_inv) # 将总字数的倒数变为对角矩阵===》对图结构的度矩阵求逆==>04 D^逆的特征向量转化为对角矩阵,得到D^逆mx = r_mat_inv.dot(mx) # 左乘一个矩阵,相当于每个元素除以总数===》对每个论文顶点的边进行归一化处理==>05 计算D^逆与A加入自环(对角线为1)的邻接矩阵所得A^的点积,得到拉普拉斯矩阵。return mx
# 对features矩阵进行归一化处理(每行总和为1)
features = normalize(features) #在函数normalize()中,分为两步对邻接矩阵进行处理。1、将每篇论文总字数的倒数变成对角矩阵。该操作相当于对图结构的度矩阵求逆。2、用度矩阵的逆左乘邻接矩阵,相当于对图中每个论文顶点的边进行归一化处理。
# 对邻接矩阵的对角线添1,将其变为自循环图,同时对其进行归一化处理
adj = normalize(adj + eye(adj.shape[0])) # 对角线补1==>01实现加入自环的邻接矩阵A# 1.5 将数据转化为张量,并分配运算资源
adj = torch.FloatTensor(adj.todense()) # 节点间关系 todense()方法将其转换回稠密矩阵。
features = torch.FloatTensor(features.todense()) # 节点自身的特征
labels = torch.LongTensor(labels) # 对每个节点的分类标签# 划分数据集
n_train = 200 # 训练数据集大小
n_val = 300 # 验证数据集大小
n_test = len(features) - n_train - n_val # 测试数据集大小
np.random.seed(34)
idxs = np.random.permutation(len(features)) # 将原有的索引打乱顺序# 计算每个数据集的索引
idx_train = torch.LongTensor(idxs[:n_train]) # 根据指定训练数据集的大小并划分出其对应的训练数据集索引
idx_val = torch.LongTensor(idxs[n_train:n_train+n_val])# 根据指定验证数据集的大小并划分出其对应的验证数据集索引
idx_test = torch.LongTensor(idxs[n_train+n_val:])# 根据指定测试数据集的大小并划分出其对应的测试数据集索引# 分配运算资源
adj = adj.to(device)
features = features.to(device)
labels = labels.to(device)
idx_train = idx_train.to(device)
idx_val = idx_val.to(device)
idx_test = idx_test.to(device)# 1.6 定义Mish激活函数与图卷积操作类
def mish(x): # 性能优于RElu函数return x * (torch.tanh(F.softplus(x)))
# 图卷积类
class GraphConvolution(nn.Module):def __init__(self,f_in,f_out,use_bias = True,activation=mish):# super(GraphConvolution, self).__init__()super().__init__()self.f_in = f_inself.f_out = f_outself.use_bias = use_biasself.activation = activationself.weight = nn.Parameter(torch.FloatTensor(f_in, f_out))self.bias = nn.Parameter(torch.FloatTensor(f_out)) if use_bias else Noneself.initialize_weights()def initialize_weights(self):# 对参数进行初始化if self.activation is None: # 初始化权重nn.init.xavier_uniform_(self.weight)else:nn.init.kaiming_uniform_(self.weight, nonlinearity='leaky_relu')if self.use_bias:nn.init.zeros_(self.bias)def forward(self,input,adj): # 实现模型的正向处理流程support = torch.mm(input,self.weight) # 节点特征与权重点积:torch.mm()实现矩阵的相乘,仅支持二位矩阵。若是多维矩则使用torch.matmul()output = torch.mm(adj,support) # 将加工后的邻接矩阵放入点积运算if self.use_bias:output.add_(self.bias) # 加入偏置if self.activation is not None:output = self.activation(output) # 激活函数处理return output# 1.7 搭建多层图卷积网络模型
class GCN(nn.Module):def __init__(self, f_in, n_classes, hidden=[16], dropout_p=0.5): # 实现多层图卷积网络,该网的搭建方法与全连接网络的搭建一致,只是将全连接层转化成GraphConvolution所实现的图卷积层# super(GCN, self).__init__()super().__init__()layers = []# 根据参数构建多层网络for f_in, f_out in zip([f_in] + hidden[:-1], hidden):# python 在list上的“+=”的重载函数是extend()函数,而不是+# layers = [GraphConvolution(f_in, f_out)] + layerslayers += [GraphConvolution(f_in, f_out)]self.layers = nn.Sequential(*layers)self.dropout_p = dropout_p# 构建输出层self.out_layer = GraphConvolution(f_out, n_classes, activation=None)def forward(self, x, adj): # 实现前向处理过程for layer in self.layers:x = layer(x,adj)# 函数方式调用dropout():必须指定模型的运行状态,即Training标志,这样可减少很多麻烦F.dropout(x,self.dropout_p,training=self.training,inplace=True)return self.out_layer(x,adj)n_labels = labels.max().item() + 1 # 获取分类个数7
n_features = features.shape[1] # 获取节点特征维度 1433
print(n_labels,n_features) # 输出7与1433def accuracy(output,y): # 定义函数计算准确率return (output.argmax(1) == y).type(torch.float32).mean().item()### 定义函数来实现模型的训练过程。与深度学习任务不同,图卷积在训练时需要传入样本间的关系数据。
# 因为该关系数据是与节点数相等的方阵,所以传入的样本数也要与节点数相同,在计算loss值时,可以通过索引从总的运算结果中取出训练集的结果。
def step(): # 定义函数来训练模型 Tip:在图卷积任务中,无论是用模型进行预测还是训练,都需要将全部的图结构方阵输入model.train()optimizer.zero_grad()output = model(features,adj) # 将全部数据载入模型,只用训练数据计算损失loss = F.cross_entropy(output[idx_train],labels[idx_train])acc = accuracy(output[idx_train],labels[idx_train]) # 计算准确率loss.backward()optimizer.step()return loss.item(),accdef evaluate(idx): # 定义函数来评估模型 Tip:在图卷积任务中,无论是用模型进行预测还是训练,都需要将全部的图结构方阵输入model.eval()output = model(features, adj) # 将全部数据载入模型,用指定索引评估模型结果loss = F.cross_entropy(output[idx], labels[idx]).item()return loss, accuracy(output[idx], labels[idx])# 1.8 使用Ranger优化器训练模型并可视化
model = GCN(n_features, n_labels, hidden=[16, 32, 16]).to(device)from tqdm import tqdm
from Cora_ranger import * # 引入Ranger优化器
optimizer = Ranger(model.parameters()) # 使用Ranger优化器# 训练模型
epochs = 1000
print_steps = 50
train_loss, train_acc = [], []
val_loss, val_acc = [], []
for i in tqdm(range(epochs)):tl,ta = step()train_loss = train_loss + [tl]train_acc = train_acc + [ta]if (i+1) % print_steps == 0 or i == 0:tl,ta = evaluate(idx_train)vl,va = evaluate(idx_val)val_loss = val_loss + [vl]val_acc = val_acc + [va]print(f'{i + 1:6d}/{epochs}: train_loss={tl:.4f}, train_acc={ta:.4f}' + f', val_loss={vl:.4f}, val_acc={va:.4f}')# 输出最终结果
final_train, final_val, final_test = evaluate(idx_train), evaluate(idx_val), evaluate(idx_test)
print(f'Train     : loss={final_train[0]:.4f}, accuracy={final_train[1]:.4f}')
print(f'Validation: loss={final_val[0]:.4f}, accuracy={final_val[1]:.4f}')
print(f'Test      : loss={final_test[0]:.4f}, accuracy={final_test[1]:.4f}')# 可视化训练过程
fig, axes = plt.subplots(1, 2, figsize=(15,5))
ax = axes[0]
axes[0].plot(train_loss[::print_steps] + [train_loss[-1]], label='Train')
axes[0].plot(val_loss, label='Validation')
axes[1].plot(train_acc[::print_steps] + [train_acc[-1]], label='Train')
axes[1].plot(val_acc, label='Validation')
for ax,t in zip(axes, ['Loss', 'Accuracy']): ax.legend(), ax.set_title(t, size=15)# 输出模型的预测结果
output = model(features, adj)
samples = 10
idx_sample = idx_test[torch.randperm(len(idx_test))[:samples]]
# 将样本标签与预测结果进行比较
idx2lbl = {v:k for k,v in lbl2idx.items()}
df = pd.DataFrame({'Real': [idx2lbl[e] for e in labels[idx_sample].tolist()],'Pred': [idx2lbl[e] for e in output[idx_sample].argmax(1).tolist()]})
print(df)

3.2 Cora_ranger.py

#Ranger deep learning optimizer - RAdam + Lookahead combined.
#https://github.com/lessw2020/Ranger-Deep-Learning-Optimizer#Ranger has now been used to capture 12 records on the FastAI leaderboard.#This version = 9.3.19  #Credits:
#RAdam -->  https://github.com/LiyuanLucasLiu/RAdam
#Lookahead --> rewritten by lessw2020, but big thanks to Github @LonePatient and @RWightman for ideas from their code.
#Lookahead paper --> MZhang,G Hinton  https://arxiv.org/abs/1907.08610#summary of changes: 
#full code integration with all updates at param level instead of group, moves slow weights into state dict (from generic weights), 
#supports group learning rates (thanks @SHolderbach), fixes sporadic load from saved model issues.
#changes 8/31/19 - fix references to *self*.N_sma_threshold; #changed eps to 1e-5 as better default than 1e-8.import math
import torch
from torch.optim.optimizer import Optimizer, required
import itertools as itclass Ranger(Optimizer):def __init__(self, params, lr=1e-3, alpha=0.5, k=6, N_sma_threshhold=5, betas=(.95,0.999), eps=1e-5, weight_decay=0):#parameter checksif not 0.0 <= alpha <= 1.0:raise ValueError(f'Invalid slow update rate: {alpha}')if not 1 <= k:raise ValueError(f'Invalid lookahead steps: {k}')if not lr > 0:raise ValueError(f'Invalid Learning Rate: {lr}')if not eps > 0:raise ValueError(f'Invalid eps: {eps}')#parameter comments:# beta1 (momentum) of .95 seems to work better than .90...#N_sma_threshold of 5 seems better in testing than 4.#In both cases, worth testing on your dataset (.90 vs .95, 4 vs 5) to make sure which works best for you.#prep defaults and init torch.optim basedefaults = dict(lr=lr, alpha=alpha, k=k, step_counter=0, betas=betas, N_sma_threshhold=N_sma_threshhold, eps=eps, weight_decay=weight_decay)super().__init__(params,defaults)#adjustable thresholdself.N_sma_threshhold = N_sma_threshhold#now we can get to work...#removed as we now use step from RAdam...no need for duplicate step counting#for group in self.param_groups:#    group["step_counter"] = 0#print("group step counter init")#look ahead paramsself.alpha = alphaself.k = k #radam buffer for stateself.radam_buffer = [[None,None,None] for ind in range(10)]#self.first_run_check=0#lookahead weights#9/2/19 - lookahead param tensors have been moved to state storage.  #This should resolve issues with load/save where weights were left in GPU memory from first load, slowing down future runs.#self.slow_weights = [[p.clone().detach() for p in group['params']]#                     for group in self.param_groups]#don't use grad for lookahead weights#for w in it.chain(*self.slow_weights):#    w.requires_grad = Falsedef __setstate__(self, state):print("set state called")super(Ranger, self).__setstate__(state)def step(self, closure=None):loss = None#note - below is commented out b/c I have other work that passes back the loss as a float, and thus not a callable closure.  #Uncomment if you need to use the actual closure...#if closure is not None:#loss = closure()#Evaluate averages and grad, update param tensorsfor group in self.param_groups:for p in group['params']:if p.grad is None:continuegrad = p.grad.data.float()if grad.is_sparse:raise RuntimeError('Ranger optimizer does not support sparse gradients')p_data_fp32 = p.data.float()state = self.state[p]  #get state dict for this paramif len(state) == 0:   #if first time to run...init dictionary with our desired entries#if self.first_run_check==0:#self.first_run_check=1#print("Initializing slow buffer...should not see this at load from saved model!")state['step'] = 0state['exp_avg'] = torch.zeros_like(p_data_fp32)state['exp_avg_sq'] = torch.zeros_like(p_data_fp32)#look ahead weight storage now in state dict state['slow_buffer'] = torch.empty_like(p.data)state['slow_buffer'].copy_(p.data)else:state['exp_avg'] = state['exp_avg'].type_as(p_data_fp32)state['exp_avg_sq'] = state['exp_avg_sq'].type_as(p_data_fp32)#begin computations exp_avg, exp_avg_sq = state['exp_avg'], state['exp_avg_sq']beta1, beta2 = group['betas']#compute variance mov avgexp_avg_sq.mul_(beta2).addcmul_(1 - beta2, grad, grad)#compute mean moving avgexp_avg.mul_(beta1).add_(1 - beta1, grad)state['step'] += 1buffered = self.radam_buffer[int(state['step'] % 10)]if state['step'] == buffered[0]:N_sma, step_size = buffered[1], buffered[2]else:buffered[0] = state['step']beta2_t = beta2 ** state['step']N_sma_max = 2 / (1 - beta2) - 1N_sma = N_sma_max - 2 * state['step'] * beta2_t / (1 - beta2_t)buffered[1] = N_smaif N_sma > self.N_sma_threshhold:step_size = math.sqrt((1 - beta2_t) * (N_sma - 4) / (N_sma_max - 4) * (N_sma - 2) / N_sma * N_sma_max / (N_sma_max - 2)) / (1 - beta1 ** state['step'])else:step_size = 1.0 / (1 - beta1 ** state['step'])buffered[2] = step_sizeif group['weight_decay'] != 0:p_data_fp32.add_(-group['weight_decay'] * group['lr'], p_data_fp32)if N_sma > self.N_sma_threshhold:denom = exp_avg_sq.sqrt().add_(group['eps'])p_data_fp32.addcdiv_(-step_size * group['lr'], exp_avg, denom)else:p_data_fp32.add_(-step_size * group['lr'], exp_avg)p.data.copy_(p_data_fp32)#integrated look ahead...#we do it at the param level instead of group levelif state['step'] % group['k'] == 0:slow_p = state['slow_buffer'] #get access to slow param tensorslow_p.add_(self.alpha, p.data - slow_p)  #(fast weights - slow weights) * alphap.data.copy_(slow_p)  #copy interpolated weights to RAdam param tensorreturn loss

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

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

相关文章

mybatis基础学习3---特殊sql语句(备忘)

1&#xff1a; 2&#xff1a; 3:resultMap的用法 转载于:https://www.cnblogs.com/kaiwen/p/6486283.html

使用gdb

1、gdb调试 gdb 对于看系统内部是非常有用. 在这个级别精通调试器的使用要求对 gdb 命令有信心, 需要理解目标平台的汇编代码, 以及对应源码和优化的汇编码的能力. LINUX DEVICE DRIVERS,3RD EDITION 调试器必须把内核作为一个应用程序来调用. 除了指定内核映象的文件名之外…

【Pytorch神经网络实战案例】21 基于Cora数据集实现Multi_Sample Dropout图卷积网络模型的论文分类

Multi-sample Dropout是Dropout的一个变种方法&#xff0c;该方法比普通Dropout的泛化能力更好&#xff0c;同时又可以缩短模型的训练时间。XMuli-sampleDropout还可以降低训练集和验证集的错误率和损失&#xff0c;参见的论文编号为arXⅳ:1905.09788,2019 1 实例说明 本例就…

linux自旋锁

1、为什么要有自旋锁 我们写驱动代码的时候,往往忽略这一点,然后版本发布后会经常遇到一些异常的问题,资源的竞争相当重要,很容易出现空指针 引用: if (!dptr->data[s_pos]) { dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL); if (!dptr->data[s_pos])…

java基础-public/private/protected的具体区别

在说明这四个关键字之前&#xff0c;我想就class之间的关系做一个简单的定义&#xff0c;对于继承自己的class&#xff0c;base class可以认为他们都是自己的子女&#xff0c;而对于和自己一个目录下的classes&#xff0c;认为都是自己的朋友。 1、public&#xff1a;publi…

【Pytorch神经网络理论篇】 26 基于空间域的图卷积GCNs(ConvGNNs):定点域+谱域+图卷积的操作步骤

同学你好&#xff01;本文章于2021年末编写&#xff0c;获得广泛的好评&#xff01; 故在2022年末对本系列进行填充与更新&#xff0c;欢迎大家订阅最新的专栏&#xff0c;获取基于Pytorch1.10版本的理论代码(2023版)实现&#xff0c; Pytorch深度学习理论篇(2023版)目录地址…

Linux设备驱动之mmap设备操作

1.mmap系统调用 void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset); 功能&#xff1a;负责把文件内容映射到进程的虚拟地址空间&#xff0c;通过对这段内存的读取和修改来实现对文件的读取和修改&#xff0c;而不需要再调用read和write&#xff…

hadoop安装以及Java API操作hdfs

因为工作需求&#xff0c;需要我这边实现一个大文件上传到HDFS的功能&#xff0c;因为本机无法连接公司内网的集群&#xff0c;无奈只好自己动手搭建一个单节点的Hadoop来满足工作的需求。下面简单介绍下安装过程中遇到的坑我的机器是阿里云的最低配 安装文件&#xff1a;hadoo…

【Pytorch神经网络理论篇】 27 图神经网络DGL库:简介+安装+卸载+数据集+PYG库+NetWorkx库

同学你好&#xff01;本文章于2021年末编写&#xff0c;获得广泛的好评&#xff01; 故在2022年末对本系列进行填充与更新&#xff0c;欢迎大家订阅最新的专栏&#xff0c;获取基于Pytorch1.10版本的理论代码(2023版)实现&#xff0c; Pytorch深度学习理论篇(2023版)目录地址…

给ubuntuGedit安装gedit-source-code-browser

插件链接下载 https://bugs.launchpad.net/ubuntu/source/gedit-source-code-browser-plugin/bug/1242126 下载这个安装完后还用不了&#xff0c;还要添加下面的补丁 diff -Naur old-plugins/sourcecodebrowser/ctags.py plugins/sourcecodebrowser/ctags.py --- old-plugin…

Hint: This means that multiple copies of the OpenMP runtime have been linked into the program.

解决OMP: Hint: This means that multiple copies of the OpenMP runtime have been linked into the program. That is dangerous, since it can degrade performance or cause incorrect results. The best thing to do is to ensure that only a single OpenMP runtime is l…

Linux 系统版本信息

1、# uname &#xff0d;a &#xff08;Linux查看版本当前操作系统内核信息&#xff09; 2、# cat /proc/version &#xff08;Linux查看当前操作系统版本信息&#xff09; 3、# cat /etc/issue 或cat /etc/redhat-release&#xff08;Linux查看版本当前操作系统发行版信息&…

DEVICE_ATTR的使用

使用DEVICE_ATTR&#xff0c;可以在sys fs中添加“文件”&#xff0c;通过修改该文件内容&#xff0c;可以实现在运行过程中动态控制device的目的。类似的还有DRIVER_ATTR&#xff0c;BUS_ATTR&#xff0c;CLASS_ATTR。 这几个东东的区别就是&#xff0c;DEVICE_ATTR对应的文件…

【Pytorch神经网络理论篇】 28 DGLGraph图的基本操作(缺一部分 明天补)

同学你好&#xff01;本文章于2021年末编写&#xff0c;获得广泛的好评&#xff01; 故在2022年末对本系列进行填充与更新&#xff0c;欢迎大家订阅最新的专栏&#xff0c;获取基于Pytorch1.10版本的理论代码(2023版)实现&#xff0c; Pytorch深度学习理论篇(2023版)目录地址…

泛型(模拟list)

package Test; import java.util.Arrays; public class genericity<T> { private Object[] t; public genericity() { super(); t new Object[0]; } //增加数据 public void add(T t1) { t Arrays.copyOf(t, t.length 1); t[t.length - 1] t1; } //查询数据 public i…

中断处理的tasklet(小任务)机制-不过如此

中断服务程序一般都是在中断请求关闭的条件下执行的,以避免嵌套而使中断控制复杂化。但是&#xff0c;中断是一个随机事件&#xff0c;它随时会到来&#xff0c;如果关中断的时间太长&#xff0c;CPU就不能及时响应其他的中断请求&#xff0c;从而造成中断的丢失。因此&#xf…

【Pytorch神经网络理论篇】 29 图卷积模型的缺陷+弥补方案

同学你好&#xff01;本文章于2021年末编写&#xff0c;获得广泛的好评&#xff01; 故在2022年末对本系列进行填充与更新&#xff0c;欢迎大家订阅最新的专栏&#xff0c;获取基于Pytorch1.10版本的理论代码(2023版)实现&#xff0c; Pytorch深度学习理论篇(2023版)目录地址…

【Pytorch神经网络实战案例】22 基于Cora数据集实现图注意力神经网络GAT的论文分类

注意力机制的特点是&#xff0c;它的输入向量长度可变&#xff0c;通过将注意力集中在最相关的部分来做出决定。注意力机制结合RNN或者CNN的方法。 1 实战描述 【主要目的&#xff1a;将注意力机制用在图神经网络中&#xff0c;完成图注意力神经网络的结构和搭建】 1.1 实现…

用OC和Swift一起说说二叉树

前言&#xff1a; 一&#xff1a;在计算机科学中&#xff0c;二叉树是每个节点最多有两个子树的树结构。通常子树被称作“左子树”&#xff08;left subtree&#xff09;和“右子树”&#xff08;right subtree&#xff09;。二叉树常被用于实现二叉查找树和二叉堆。二&#xf…

【Pytorch神经网络理论篇】 30 图片分类模型:Inception模型

同学你好&#xff01;本文章于2021年末编写&#xff0c;获得广泛的好评&#xff01; 故在2022年末对本系列进行填充与更新&#xff0c;欢迎大家订阅最新的专栏&#xff0c;获取基于Pytorch1.10版本的理论代码(2023版)实现&#xff0c; Pytorch深度学习理论篇(2023版)目录地址…