GCN实战深入浅出图神经网络第五章:基于Cora数据集的GCN节点分类 代码分析

文章目录

  • GCN实战深入浅出图神经网络第五章:基于Cora数据集的GCN节点分类 代码分析
    • SetUp,库声明
    • 数据准备
    • 图卷积层定义
    • 模型定义
    • 模型训练

SetUp,库声明

In [2]:

import itertools
import os
import os.path as osp
import pickle
import urllib
from collections import namedtupleimport numpy as np
import scipy.sparse as sp
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.nn.init as init
import torch.optim as optim
import matplotlib.pyplot as plt
%matplotlib inline

数据准备

Cora数据集说明

├── gcn
│   ├── data          //图数据
│   │   ├── ind.citeseer.allx
│   │   ├── ind.citeseer.ally
│   │   ├── ind.citeseer.graph
│   │   ├── ind.citeseer.test.index
│   │   ├── ind.citeseer.tx  # 1
│   │   ├── ind.citeseer.ty  # 2
│   │   ├── ind.citeseer.x   # 3
│   │   ├── ind.citeseer.y   # 4
│   │   ├── ind.cora.allx
│   │   ├── ind.cora.ally
│   │   ├── ind.cora.graph
│   │   ├── ind.cora.test.index
│   │   ├── ind.cora.tx
│   │   ├── ind.cora.ty
│   │   ├── ind.cora.x
│   │   ├── ind.cora.y
│   │   ├── ind.pubmed.allx
│   │   ├── ind.pubmed.ally
│   │   ├── ind.pubmed.graph
│   │   ├── ind.pubmed.test.index
│   │   ├── ind.pubmed.tx
│   │   ├── ind.pubmed.ty
│   │   ├── ind.pubmed.x
│   │   └── ind.pubmed.y
│   ├── __init__.py
│   ├── inits.py    //初始化的公用函数
│   ├── layers.py   //GCN层定义
│   ├── metrics.py  //评测指标的计算
│   ├── models.py   //模型结构定义
│   ├── train.py    //训练
│   └── utils.py    //工具函数的定义
├── LICENCE
├── README.md
├── requirements.txt
└── setup.py

实验时可能出现数据集下载出错的问题,可以自行下载,放进程序预定的文件夹即可

download_url = https://wwe.lanzous.com/iFiGKib18fi

cora读取的文件说明:ind.cora.x => 训练实例的特征向量,是scipy.sparse.csr.csr_matrix类对象,shape:(140, 1433),由于ind.cora.x的数据包含于 allx 中,所以实验中没有读取 x
ind.cora.tx => 测试实例的特征向量,shape:(1000, 1433)
ind.cora.allx => 有标签的+无无标签训练实例的特征向量,是ind.dataset_str.x的超集,shape:(1708, 1433)# 实验中的完整的特征向量是有(allx,tx)拼接而成,(2708,1433),在实际训练是整体训练,只有当要计算损失值和精确度时才用掩码从(allx,tx)截取相应的输出ind.cora.y => 训练实例的 标签,独热编码,numpy.ndarray类的实例,是numpy.ndarray对象,shape:(140, 7)
ind.cora.ty => 测试实例的标签,独热编码,numpy.ndarray类的实例,shape:(1000, 7)
ind.cora.ally => 对应于ind.dataset_str.allx的标签,独热编码,shape:(1708, 7)# 同样是(ally,ty)拼接ind.cora.graph => 图数据,collections.defaultdict类的实例,格式为 {index:[index_of_neighbor_nodes]}ind.cora.test.index => 测试实例的id,(1000,)

In [3]:

Data = namedtuple('Data', ['x', 'y', 'adjacency','train_mask', 'val_mask', 'test_mask'])def tensor_from_numpy(x, device):return torch.from_numpy(x).to(device)class CoraData(object):download_url = "https://github.com/kimiyoung/planetoid/raw/master/data"filenames = ["ind.cora.{}".format(name) for name in['x', 'tx', 'allx', 'y', 'ty', 'ally', 'graph', 'test.index']]def __init__(self, data_root="cora", rebuild=False):"""Cora数据,包括数据下载,处理,加载等功能当数据的缓存文件存在时,将使用缓存文件,否则将下载、进行处理,并缓存到磁盘处理之后的数据可以通过属性 .data 获得,它将返回一个数据对象,包括如下几部分:* x: 节点的特征,维度为 2708 * 1433,类型为 np.ndarray* y: 节点的标签,总共包括7个类别,类型为 np.ndarray* adjacency: 邻接矩阵,维度为 2708 * 2708,类型为 scipy.sparse.coo.coo_matrix* train_mask: 训练集掩码向量,维度为 2708,当节点属于训练集时,相应位置为True,否则False* val_mask: 验证集掩码向量,维度为 2708,当节点属于验证集时,相应位置为True,否则False* test_mask: 测试集掩码向量,维度为 2708,当节点属于测试集时,相应位置为True,否则FalseArgs:-------data_root: string, optional存放数据的目录,原始数据路径: {data_root}/raw缓存数据路径: {data_root}/processed_cora.pklrebuild: boolean, optional是否需要重新构建数据集,当设为True时,如果存在缓存数据也会重建数据"""self.data_root = data_rootsave_file = osp.join(self.data_root, "processed_cora.pkl")if osp.exists(save_file) and not rebuild:print("Using Cached file: {}".format(save_file))self._data = pickle.load(open(save_file, "rb"))else:self.maybe_download()self._data = self.process_data()with open(save_file, "wb") as f:pickle.dump(self.data, f)print("Cached file: {}".format(save_file))@propertydef data(self):"""返回Data数据对象,包括x, y, adjacency, train_mask, val_mask, test_mask"""return self._datadef process_data(self):"""处理数据,得到节点特征和标签,邻接矩阵,训练集、验证集以及测试集引用自:https://github.com/rusty1s/pytorch_geometric"""print("Process data ...")_, tx, allx, y, ty, ally, graph, test_index = [self.read_data(osp.join(self.data_root, "raw", name)) for name in self.filenames]# 测试test_index的形状(1000,),如果那里不明白可以测试输出一下矩阵形状print('test_index',test_index.shape)train_index = np.arange(y.shape[0])  # [0,...139] 140个元素print('train_index',train_index.shape) val_index = np.arange(y.shape[0], y.shape[0] + 500) # [140 - 640] 500个元素print('val_index',val_index.shape)  sorted_test_index = sorted(test_index)  # #test_index就是随机选取的下标,排下序
#         print('test_index',sorted_test_index)x = np.concatenate((allx, tx), axis=0)  # 1708 +1000 =2708 特征向量y = np.concatenate((ally, ty), axis=0).argmax(axis=1) # 把最大值的下标重新作为一个数组 标签向量x[test_index] = x[sorted_test_index]  # 打乱顺序,单纯给test_index 的数据排个序,不清楚具体效果y[test_index] = y[sorted_test_index]num_nodes = x.shape[0]  #2078train_mask = np.zeros(num_nodes, dtype=np.bool)  # 生成零向量val_mask = np.zeros(num_nodes, dtype=np.bool)test_mask = np.zeros(num_nodes, dtype=np.bool)train_mask[train_index] = True  # 前140个元素为训练集val_mask[val_index] = True  # 140 -639 500个test_mask[test_index] = True  # 1708-2708 1000个元素#下面两句是我测试用的,本来代码没有#是为了知道使用掩码后,y_train_mask 的形状,由输出来说是(140,)# 这就是后面划分训练集的方法y_train_mask = y[train_mask]print('y_train_mask',y_train_mask.shape)#构建邻接矩阵adjacency = self.build_adjacency(graph)print("Node's feature shape: ", x.shape)print("Node's label shape: ", y.shape)print("Adjacency's shape: ", adjacency.shape)print("Number of training nodes: ", train_mask.sum())print("Number of validation nodes: ", val_mask.sum())print("Number of test nodes: ", test_mask.sum())return Data(x=x, y=y, adjacency=adjacency,train_mask=train_mask, val_mask=val_mask, test_mask=test_mask)def maybe_download(self):save_path = os.path.join(self.data_root, "raw")for name in self.filenames:if not osp.exists(osp.join(save_path, name)):self.download_data("{}/{}".format(self.download_url, name), save_path)@staticmethoddef build_adjacency(adj_dict):"""根据邻接表创建邻接矩阵"""edge_index = []num_nodes = len(adj_dict)print('num_nodesaaaaaaaaaaaa',num_nodes)for src, dst in adj_dict.items():  # 格式为 {index:[index_of_neighbor_nodes]}edge_index.extend([src, v] for v in dst)  # edge_index.extend([v, src] for v in dst)# 去除重复的边edge_index = list(k for k, _ in itertools.groupby(sorted(edge_index)))  # 以轮到的元素为k,每个k对应一个数组,和k相同放进数组,不# 同再生成一个k,sorted()是以第一个元素大小排序edge_index = np.asarray(edge_index)#稀疏矩阵 存储非0值 节省空间adjacency = sp.coo_matrix((np.ones(len(edge_index)), (edge_index[:, 0], edge_index[:, 1])),shape=(num_nodes, num_nodes), dtype="float32")return adjacency@staticmethoddef read_data(path):"""使用不同的方式读取原始数据以进一步处理"""name = osp.basename(path)if name == "ind.cora.test.index":out = np.genfromtxt(path, dtype="int64")return outelse:out = pickle.load(open(path, "rb"), encoding="latin1")out = out.toarray() if hasattr(out, "toarray") else outreturn out@staticmethoddef download_data(url, save_path):"""数据下载工具,当原始数据不存在时将会进行下载"""if not os.path.exists(save_path):os.makedirs(save_path)data = urllib.request.urlopen(url)filename = os.path.split(url)[-1]with open(os.path.join(save_path, filename), 'wb') as f:f.write(data.read())return True@staticmethoddef normalization(adjacency):"""计算 L=D^-0.5 * (A+I) * D^-0.5"""adjacency += sp.eye(adjacency.shape[0])    # 增加自连接degree = np.array(adjacency.sum(1))d_hat = sp.diags(np.power(degree, -0.5).flatten())return d_hat.dot(adjacency).dot(d_hat).tocoo()  #返回稀疏矩阵的coo_matrix形式# 这样可以单独测试Process_data函数
a = CoraData()
a.process_data()

Out[3]:

Using Cached file: cora\processed_cora.pkl
Process data ...
test_index (1000,)
train_index (140,)
val_index (500,)
y_train_mask (140,)
num_nodesaaaaaaaaaaaa 2708
Node's feature shape:  (2708, 1433)
Node's label shape:  (2708,)
Adjacency's shape:  (2708, 2708)
Number of training nodes:  140
Number of validation nodes:  500
Number of test nodes:  1000
Data(x=array([[0., 0., 0., ..., 0., 0., 0.],[0., 0., 0., ..., 0., 0., 0.],[0., 0., 0., ..., 0., 0., 0.],...,[0., 0., 0., ..., 0., 0., 0.],[0., 0., 0., ..., 0., 0., 0.],[0., 0., 0., ..., 0., 0., 0.]], dtype=float32), y=array([3, 4, 4, ..., 3, 3, 3], dtype=int64), adjacency=<2708x2708 sparse matrix of type '<class 'numpy.float32'>'with 10556 stored elements in COOrdinate format>, train_mask=array([ True,  True,  True, ..., False, False, False]), val_mask=array([False, False, False, ..., False, False, False]), test_mask=array([False, False, False, ...,  True,  True,  True]))

图卷积层定义

In [13]:

class GraphConvolution(nn.Module):def __init__(self, input_dim, output_dim, use_bias=True):"""图卷积:L*X*\theta完整GCN函数f = sigma(D^-1/2 A D^-1/2 * H * W)卷积是D^-1/2 A D^-1/2 * H * Wadjacency = D^-1/2 A D^-1/2 已经经过归一化,标准化的拉普拉斯矩阵这样就把傅里叶变化和拉普拉斯矩阵结合起来了.Args:----------input_dim: int节点输入特征的维度output_dim: int输出特征维度use_bias : bool, optional是否使用偏置"""super(GraphConvolution, self).__init__()self.input_dim = input_dimself.output_dim = output_dimself.use_bias = use_bias# 定义GCN层的 W 权重形状self.weight = nn.Parameter(torch.Tensor(input_dim, output_dim))#定义GCN层的 b 权重矩阵if self.use_bias:self.bias = nn.Parameter(torch.Tensor(output_dim))else:self.register_parameter('bias', None)self.reset_parameters()# 这里才是声明初始化 nn.Module 类里面的W,b参数def reset_parameters(self):init.kaiming_uniform_(self.weight)if self.use_bias:init.zeros_(self.bias)def forward(self, adjacency, input_feature):"""邻接矩阵是稀疏矩阵,因此在计算时使用稀疏矩阵乘法Args: -------adjacency: torch.sparse.FloatTensor邻接矩阵input_feature: torch.Tensor输入特征"""support = torch.mm(input_feature, self.weight)  # 矩阵相乘 m由matrix缩写output = torch.sparse.mm(adjacency, support)  # sparse 稀疏的if self.use_bias:output += self.bias  # bias 偏置,偏见return output# 一般是为了打印类实例的信息而重写的内置函数def __repr__(self):return self.__class__.__name__ + ' (' \+ str(self.input_dim) + ' -> ' \+ str(self.output_dim) + ')'

模型定义

读者可以自己对GCN模型结构进行修改和实验

In [14]:

class GcnNet(nn.Module):"""定义一个包含两层GraphConvolution的模型"""def __init__(self, input_dim=1433):super(GcnNet, self).__init__()self.gcn1 = GraphConvolution(input_dim, 16) #(N,1433)->(N,16)self.gcn2 = GraphConvolution(16, 7) #(N,16)->(N,7)def forward(self, adjacency, feature):h = F.relu(self.gcn1(adjacency, feature)) #(N,1433)->(N,16),经过relu函数logits = self.gcn2(adjacency, h) #(N,16)->(N,7)return logits

模型训练

In [16]:

# 超参数定义
LEARNING_RATE = 0.1 #学习率
WEIGHT_DACAY = 5e-4  #正则化系数 weight_dacay
EPOCHS = 200 #完整遍历训练集的次数
DEVICE = "cuda" if torch.cuda.is_available() else "cpu" #设备
# 如果当前显卡忙于其他工作,可以设置为 DEVICE = "cpu",使用cpu运行

In [17]:

# 加载数据,并转换为torch.Tensor
dataset = CoraData().datanode_feature = dataset.x / dataset.x.sum(1, keepdims=True)  # 归一化数据,使得每一行和为1tensor_x = tensor_from_numpy(node_feature, DEVICE)  # (2708,1433)
tensor_y = tensor_from_numpy(dataset.y, DEVICE)  #(2708,)tensor_train_mask = tensor_from_numpy(dataset.train_mask, DEVICE) #前140个为True
tensor_val_mask = tensor_from_numpy(dataset.val_mask, DEVICE)  # 140 - 639  500个
tensor_test_mask = tensor_from_numpy(dataset.test_mask, DEVICE) # 1708 - 2707 1000个normalize_adjacency = CoraData.normalization(dataset.adjacency)   # 规范化邻接矩阵 计算 L=D^-0.5 * (A+I) * D^-0.5num_nodes, input_dim = node_feature.shape  # 2708,1433# 原始创建coo_matrix((data, (row, col)), shape=(4, 4)) indices为index复数 https://blog.csdn.net/chao2016/article/details/80344828?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522160509865819724838529777%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=160509865819724838529777&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_v2~rank_v28-2-80344828.pc_first_rank_v2_rank_v28&utm_term=%E7%A8%80%E7%96%8F%E7%9F%A9%E9%98%B5%E7%9A%84coo_matrix&spm=1018.2118.3001.4449
indices = torch.from_numpy(np.asarray([normalize_adjacency.row, normalize_adjacency.col]).astype('int64')).long()values = torch.from_numpy(normalize_adjacency.data.astype(np.float32))tensor_adjacency = torch.sparse.FloatTensor(indices, values, (num_nodes, num_nodes)).to(DEVICE)
#根据三元组 构造 稀疏矩阵向量,构造出来的张量是 (2708,2708)

In [18]:

# 模型定义:Model, Loss, Optimizer
model = GcnNet(input_dim).to(DEVICE)
criterion = nn.CrossEntropyLoss().to(DEVICE)  # criterion评判标准
optimizer = optim.Adam(model.parameters(),    # optimizer 优化程序 ,使用Adam 优化方法,权重衰减https://blog.csdn.net/program_developer/article/details/80867468lr=LEARNING_RATE, weight_decay=WEIGHT_DACAY)

In [8]:

# 训练主体函数
def train():loss_history = []val_acc_history = []model.train() train_y = tensor_y[tensor_train_mask]  # shape=(140,)不是(2708,)了# 共进行200次训练for epoch in range(EPOCHS):logits = model(tensor_adjacency, tensor_x)  # 前向传播,认为因为声明了 model.train(),不用forward了train_mask_logits = logits[tensor_train_mask]   # 只选择训练节点进行监督 (140,)loss = criterion(train_mask_logits, train_y)    # 计算损失值  optimizer.zero_grad()  # 梯度归零loss.backward()     # 反向传播计算参数的梯度optimizer.step()    # 使用优化方法进行梯度更新train_acc, _, _ = test(tensor_train_mask)     # 计算当前模型训练集上的准确率  调用test函数val_acc, _, _ = test(tensor_val_mask)     # 计算当前模型在验证集上的准确率# 记录训练过程中损失值和准确率的变化,用于画图loss_history.append(loss.item())val_acc_history.append(val_acc.item())print("Epoch {:03d}: Loss {:.4f}, TrainAcc {:.4}, ValAcc {:.4f}".format(epoch, loss.item(), train_acc.item(), val_acc.item()))return loss_history, val_acc_history

In [9]:

# 测试函数
def test(mask):model.eval()  # 表示将模型转变为evaluation(测试)模式,这样就可以排除BN和Dropout对测试的干扰with torch.no_grad():  # 显著减少显存占用logits = model(tensor_adjacency, tensor_x) #(N,16)->(N,7) N节点数test_mask_logits = logits[mask]  # 矩阵形状和mask一样predict_y = test_mask_logits.max(1)[1]  # 返回每一行的最大值中索引(返回最大元素在各行的列索引)accuarcy = torch.eq(predict_y, tensor_y[mask]).float().mean()return accuarcy, test_mask_logits.cpu().numpy(), tensor_y[mask].cpu().numpy()

In [13]:

def plot_loss_with_acc(loss_history, val_acc_history):fig = plt.figure()# 坐标系ax1画曲线1ax1 = fig.add_subplot(111)  # 指的是将plot界面分成1行1列,此子图占据从左到右从上到下的1位置ax1.plot(range(len(loss_history)), loss_history,c=np.array([255, 71, 90]) / 255.)  # c为颜色plt.ylabel('Loss')# 坐标系ax2画曲线2ax2 = fig.add_subplot(111, sharex=ax1, frameon=False)  # 其本质就是添加坐标系,设置共享ax1的x轴,ax2背景透明ax2.plot(range(len(val_acc_history)), val_acc_history,c=np.array([79, 179, 255]) / 255.)ax2.yaxis.tick_right()  # 开启右边的y坐标ax2.yaxis.set_label_position("right")plt.ylabel('ValAcc')plt.xlabel('Epoch')plt.title('Training Loss & Validation Accuracy')plt.show()

In [ ]:

loss, val_acc = train()
test_acc, test_logits, test_label = test(tensor_test_mask)
print("Test accuarcy: ", test_acc.item())

In [14]:

plot_loss_with_acc(loss, val_acc)

GCN实战深入浅出图神经网络第五章:基于Cora数据集的GCN节点分类 代码分析相关推荐

  1. 《深入浅出图神经网络》读书笔记 1-2

    <深入浅出图神经网络>读书笔记 1-2 第1章 图的概述 第2章 神经网络基础 2.1 机器学习基本概念 2.2 神经网络 2.4 训练神经网络 第1章 图的概述 图神经网络(Graph ...

  2. Datawhale组队学习-图神经网络(五)

    Datawhale组队学习-图神经网络(五) 此内容出自Cluster-GCN的论文:Cluster-GCN: An Efficient Algorithm for Training Deep and ...

  3. 深入浅出图神经网络|GNN原理解析☄学习笔记(四)表示学习

    深入浅出图神经网络|GNN原理解析☄学习笔记(四)表示学习 文章目录 深入浅出图神经网络|GNN原理解析☄学习笔记(四)表示学习 表示学习 表示学习的意义 离散表示与分布式表示 端到端学习 基于重构损 ...

  4. 深入浅出图神经网络书本 GCN源码实战

    使用的Cora数据集,该数据集由2708篇论文,以及它们之间引用关系构成5429条边组成.根据主题这些论文被分为七类:神经网络.强化学习.规则学习.概率方法.遗传算法.理论研究.案例相关.废话不多说. ...

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

    注意力机制的特点是,它的输入向量长度可变,通过将注意力集中在最相关的部分来做出决定.注意力机制结合RNN或者CNN的方法. 1 实战描述 [主要目的:将注意力机制用在图神经网络中,完成图注意力神经网络 ...

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

    Multi-sample Dropout是Dropout的一个变种方法,该方法比普通Dropout的泛化能力更好,同时又可以缩短模型的训练时间.XMuli-sampleDropout还可以降低训练集和 ...

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

    1 案例说明(图卷积神经网络) CORA数据集里面含有每一篇论文的关键词以及分类信息,同时还有论文间互相引用的信息.搭建AI模型,对数据集中的论文信息进行分析,根据已有论文的分类特征,从而预测出未知分 ...

  8. 深入浅出图神经网络【阅读笔记】

    主要内容 基础篇: 1 图的概述 2 神经网络基础 3 卷积神经网络 4 表示学习 高级篇 5 图信号处理与图卷积神经网络 6 GCN的性质 7 GNN的变体与框架 8 图分类 9 基于GNN的图表示 ...

  9. .NET Core实战项目之CMS 第五章 入门篇-Dapper的快速入门看这篇就够了

    写在前面 上篇文章我们讲了如在在实际项目开发中使用Git来进行代码的版本控制,当然介绍的都是比较常用的功能.今天我再带着大家一起熟悉下一个ORM框架Dapper,实例代码的演示编写完成后我会通过Git ...

最新文章

  1. 晶体管逻辑芯片设计微缩图形化
  2. DIV+CSS圆角边框
  3. 如何判断外设使用的地址空间
  4. oracle job如何执行存储过程,oracle定时执行存储过程的job
  5. 神还原女神照片!GAN为百年旧照上色
  6. std::string删除首字符
  7. 转载:JAVA获取时间戳,哪个更快
  8. 变相裁员??全员营销!程序员被要求卖房~
  9. 把照片存QQ相册会越来越模糊,你们会把照片存在哪里?
  10. 绑定图片路径处理img 或asp:image
  11. MySql中创建用户以及设置其操作权限
  12. cpu风扇声音大_小米游戏本风扇声音大的处理方法
  13. talib python文档_GitHub - HuaRongSAO/talib-document: talib学习 talib中文翻译 talib中文文档...
  14. 第十三届蓝桥杯省赛模拟赛题解(2022年第四次模拟赛)C/C++
  15. vs2015安装msdn
  16. creo自定义调用零件库_creo国标零件库的建立
  17. java 逆矩阵_用Java实现求逆矩阵
  18. IDEA社区版详细安装2022最新版(保姆式)
  19. 自编一个从指定位置开始查找字符串的Python代码
  20. “ 一条路走到黑,不撞南墙不回头”——C++【DFS(深度优先搜索算法)】入门讲解

热门文章

  1. 特特特别详细的HTTP状态码总结
  2. python开源oa系统_「开源」目前见过的最好的开源OA产品
  3. [vscode]vetur扩展的安装问题
  4. 银联接口对接demo测试
  5. golang面试题(带答案)
  6. 误入歧途的蓝屏代码——STOP:c000021a Unknown Hard Error
  7. 互联网大会马化腾领衔发言,15个领先科技成果剑指互联网下半场
  8. 【原创】技术员 Ghost Win 10(x86/x64)企业版/专业版(附属Xp/Win7系统下载)
  9. Python3:Python+spark编程实战 总结
  10. 信息学奥赛初赛题目讲解