初步接触图神经网络代码
环境配置
对于在多目标跟踪中应用图匹配网络,需要学习使用GNN图神经网络,对于图神经网络的实现需要学习使用一下库和项目来进行实践。
PyG(PyTorch Geometric)是一个建立在 PyTorch 基础上的库,用于轻松编写和训练图神经网络(GNN),用于与结构化数据相关的广泛应用。
官网地址:https://www.pyg.org/
这里自己之前学习GMtracker的时候曾经踩过坑,按照这个图神经网络相关的库,用pip直接去安装的话大概率是安装失败的。需要去手动的进行下载和使用。
在安装的时候为了使用全部的功能和相关的一些扩展库,需要尽量的去使用PYG的编译后的版本同时也需要去安装GMtracker中使用过的额外的一些拓展库。
这里为了以后的方便我们提供一下安装的相关地址:https://data.pyg.org/whl/
在里面找到对应的版本号,cu是我们的GPU的 cpu是cpu版的torch,同时我们要学着cpxx(指的是pytorch的版本)
额外拓展包主要就包括了下面的四个扩展包
pip install torch_scatter-2.0.5-cp38-cp38-win_amd64.whl
pip install torch_sparse-0.6.7-cp38-cp38-win_amd64.whl
pip install torch_cluster-1.5.7-cp38-cp38-win_amd64.whl
pip install torch_spline_conv-1.2.0-cp38-cp38-win_amd64.whl
装完这些所有的依赖之后我们在执行pip install torch_geometric即可安装完成所有的图神经网络相关的环境信息。
图神经网络的基本使用
- Graph Neural Networks
-
致力于解决不规则数据结构(图像和文本相对格式都固定,但是社交网络与化学分子等格式肯定不是固定的)
-
GNN模型迭代更新主要基于图中每个节点及其邻居的信息,基本表示如下:
x v ( ℓ + 1 ) = f θ ( ℓ + 1 ) ( x v ( ℓ ) , { x w ( ℓ ) : w ∈ N ( v ) } ) \mathbf{x}{v}^{(\ell+1)}=f{\theta}^{(\ell+1)}\left(\mathbf{x}{v}^{(\ell)},\left\{\mathbf{x}{w}^{(\ell)}: w \in \mathcal{N}(v)\right\}\right) xv(ℓ+1)=fθ(ℓ+1)(xv(ℓ),{xw(ℓ):w∈N(v)})
使用的数据集介绍:Zachary's karate club network.
该图描述了一个空手道俱乐部会员的社交关系,以34名会员作为节点 ,如果两位会员在俱乐部之外仍保持社交关系,则在节点间增加一条边。 每个节点具有一个34维的特征向量 ,一共有78条边
。 在收集数据的过程中,管理人员 John A 和 教练 Mr. Hi(化名)之间产生了冲突,会员们选择了站队,一半会员跟随 Mr. Hi 成立了新俱乐部,剩下一半会员找了新教练或退出了俱乐部。
我们学习的尝试就是:通过使用官方提供的API来学习各种图神经网络的应用,对于图神经网络有一定的了解,为之后用GNN进行图匹配铺垫。
节点与边的结构
我们使用这个数据集的任务是对这个一个图上的点来做分类任务。
python
from torch_geometric.datasets import KarateClub
dataset = KarateClub()
print(len(dataset)) # 只有一个图
print(dataset.num_features) # 每一个点有34个特征
print(dataset.num_classes) # 对于每一个点做一个二分类的任务
print(dataset[0]) # 当前只有一个图打印这个图的一个结果
1
34
2
Data(edge_index=[2, 156], x=[34, 34], y=[34])
Data(edge_index=[2, 156], x=[34, 34], y=[34]):对其输出的一个数据结构我们进行简单的解释和说明:
X=[F x M] : F=34代表我们有34个节点,M=34每一个节点有34个特征。后面的edge[2,156]代表的是有156个相连的边。y代表的是标签。应该是通过标签来计算损失的。
python
data = dataset[0]
edge_index = data.edge_index
print(edge_index.t()) # 转置成ex2的形状来进行输出
使用networkx可视化展示
这里是可视化图得到的一个结果,也就是将其分成了两类。
python
def visualize_graph(G, color):
plt.figure(figsize=(7,7))
plt.xticks([])
plt.yticks([])
nx.draw_networkx(G, pos=nx.spring_layout(G, seed=42), with_labels=False,
node_color=color, cmap="Set2")
plt.show()
from torch_geometric.utils import to_networkx
G = to_networkx(data, to_undirected=True)
visualize_graph(G, color=data.y)
图神经网络的定义
Graph Neural Networks 网络定义:GCN layer (Kipf et al. (2017)) 定义如下:https://arxiv.org/pdf/1609.02907
x v ( ℓ + 1 ) = W ( ℓ + 1 ) ∑ w ∈ N ( v ) ∪ { v } 1 c w , v ⋅ x w ( ℓ ) \mathbf{x}{v}^{(\ell+1)}=\mathbf{W}^{(\ell+1)} \sum{w \in \mathcal{N}(v) \cup\{v\}} \frac{1}{c_{w, v}} \cdot \mathbf{x}_{w}^{(\ell)} xv(ℓ+1)=W(ℓ+1)w∈N(v)∪{v}∑cw,v1⋅xw(ℓ)
下面我们给出图神经网络的参考文档:https://pytorch-geometric.readthedocs.io/en/latest/modules/nn.html#torch_geometric.nn.conv.GCNConv
下面我们编写代码学习一下图神经网络的网络结构是如何进行定义的:
python
# 导入全连接层和一个图卷积层
import torch
from torch.nn import Linear
from torch_geometric.nn import GCNConv
from torch_geometric.datasets import KarateClub
dataset = KarateClub()
class GCN(torch.nn.Module):
def __init__(self):
super().__init__()
torch.manual_seed(1234)
self.conv1 = GCNConv(dataset.num_features,4) # 只需要定义好输入特征和输出特征即可
self.conv2 = GCNConv(4,4)
self.conv3 = GCNConv(4,2)
self.classifier = Linear(2,dataset.num_features) # 最后一层的分类结构
def forward(self, x, edge_index):
h = self.conv1(x,edge_index) # 输入特征与邻接矩阵
h = h.tanh()
h = self.conv2(h,edge_index)
h = h.tanh()
h = self.conv3(h,edge_index)
h = h.tanh()
# 分类层
out = self.classifier(h)
# 返回最后的结果
return out,h
model = GCN()
print(model)
GCN(
(conv1): GCNConv(34, 4)
(conv2): GCNConv(4, 4)
(conv3): GCNConv(4, 2)
(classifier): Linear(in_features=2, out_features=34, bias=True)
)
输出特征展示
最后不是输出了两维特征嘛,画出来看看长啥样
但是,模型还木有开始训练,训练完成之后会在展示训练完成之后的样子。
模型训练
这里的图神经网络的训练是一个半监督的任务。通过半监督的方法来进行执行。
python
import time
import torch.nn
import GCN2
model = GCN2.GCN()
criterion = torch.nn.CrossEntropyLoss() # 定义交叉熵损失函数
optimizer = torch.optim.Adam(model.parameters(),lr=0.01) # 定义Adam优化器
def train(data):
optimizer.zero_grad()
out,h = model(data.x,data.edge_index) # h是两维度的向量便于画图
# print(h)
loss = criterion(out,data.y)
loss.backward()
optimizer.step()
return loss ,h
from torch_geometric.datasets import KarateClub
dataset = KarateClub()
data = dataset[0]
for epoch in range(401):
loss,h = train(data)
if epoch % 10 == 0:
GCN2.visualize_embedding(h,color=data.y,epoch=epoch,loss=loss)
time.sleep(0.3)
图神经网络点的分类任务
Cora dataset(数据集描述:Yang et al. (2016)):论文引用数据集,每一个点有1433维向量最终要对每个点进行7分类任务(每个类别只有20个点有标注)
Cora数据集是由McCallum等人于2000年创建的,用于研究文本分类和信息检索它在2008年由Sen等人进行了更新,增加了更多的文档和类别Cora数据集包含2708篇科学论文,这些论文被分类到7个不同的领域。每个论文都由一个1433维的二进制词袋模型特征向量表示,其中每个维度对应一个词汇,1表示词汇在论文中出现,0表示未出现。此外,每篇论文至少引用了一篇其他论文,或者被其他论文引用,形成了一个连通的图结构,没有任何孤立点。
- Cora数据集的具体特点如下:样本特征与标签:Cora数据集包含2708个样本点,每个样本点都是一篇科学论文,被分为7个类别,分别是神经网络、强化学习、规则学习、概率方法、遗传算法、理论研究、案例相关。每篇论文都由一个1433维的词向量表示,词向量的每个元素对应一个词,取值只有0或1。
- 邻接矩阵:Cora数据集的论文之间存在引用关系,这些引用关系构成了图的边。数据集中共有5429条边,表示论文之间的引用链接。
- 文件格式:Cora数据集包含三个文件,分别是cora.cites、cora.content和README。cora.content包含了所有论文的独立信息,包括论文的编号、词向量和类别标签;cora.cites包含了论文之间的引用记录。
- Cora数据集因其结构化的特点和丰富的引用关系,被广泛用于图神经网络(GNN)和半监督学习领域的研究,特别是在图卷积网络(GCN)的研究中,Cora数据集被用作基准数据集。它不仅用于节点分类任务,还涉及到图结构数据的特征提取和模式发掘,满足聚类、分类、预测等多种图学习任务的需求.
这里的学习和传统的CNN的步骤相同,我们对上面介绍的数据集进行一个分类的任务。这里加入一个对比实验与传统的MLP进行分类进行对比。
数据集下载
python
from torch_geometric.datasets import Planetoid #下载数据集用的
from torch_geometric.transforms import NormalizeFeatures
dataset = Planetoid(root='data/Planetoid', name='Cora', transform=NormalizeFeatures())#transform预处理
print()
print(f'Dataset: {dataset}:')
print('======================')
print(f'Number of graphs: {len(dataset)}')
print(f'Number of features: {dataset.num_features}')
print(f'Number of classes: {dataset.num_classes}')
data = dataset[0] # Get the first graph object.
print()
print(data)
print('===========================================================================================================')
# Gather some statistics about the graph.
print(f'Number of nodes: {data.num_nodes}')
print(f'Number of edges: {data.num_edges}')
print(f'Average node degree: {data.num_edges / data.num_nodes:.2f}')
print(f'Number of training nodes: {data.train_mask.sum()}')
print(f'Training node label rate: {int(data.train_mask.sum()) / data.num_nodes:.2f}')
# print(f'Has isolated nodes: {data.has_isolated_nodes()}')
# print(f'Has self-loops: {data.has_self_loops()}')
print(f'Is undirected: {data.is_undirected()}')
这里会产生报错,我们参考下面的解决方案解决:https://blog.csdn.net/takedachia/article/details/140211194
下面是打印出来的数据集的基本结构。2708个节点 10556个边。等等。
Dataset: Cora():
======================
Number of graphs: 1
Number of features: 1433
Number of classes: 7
Data(edge_index=[2, 10556], test_mask=[2708], train_mask=[2708], val_mask=[2708], x=[2708, 1433], y=[2708])
===========================================================================================================
Number of nodes: 2708
Number of edges: 10556
Average node degree: 3.90
Number of training nodes: 140
Training node label rate: 0.05
Is undirected: True
编写可视化相关的函数。
python
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE # 降维的库降到2维画图
def visualize(h, color):
z = TSNE(n_components=2).fit_transform(h.detach().cpu().numpy())
plt.figure(figsize=(10,10))
plt.xticks([])
plt.yticks([])
plt.scatter(z[:, 0], z[:, 1], s=70, c=color, cmap="Set2")
plt.show()
使用MLP进行分类和训练
两层的MLP结构定义:
python
import torch
from torch.nn import Linear
import torch.nn.functional as F
from torch_geometric.datasets import Planetoid #下载数据集用的
from torch_geometric.transforms import NormalizeFeatures
dataset = Planetoid(root='data/Planetoid', name='Cora', transform=NormalizeFeatures())
class MLP(torch.nn.Module):
def __init__(self, hidden_channels):
super().__init__()
torch.manual_seed(12345)
self.lin1 = Linear(dataset.num_features, hidden_channels)
self.lin2 = Linear(hidden_channels, dataset.num_classes)
def forward(self, x):
x = self.lin1(x)
x = x.relu()
x = F.dropout(x, p=0.5, training=self.training)
x = self.lin2(x)
return x
model = MLP(hidden_channels=16)
print(model)
MLP(
(lin1): Linear(in_features=1433, out_features=16, bias=True)
(lin2): Linear(in_features=16, out_features=7, bias=True)
)
模型训练函数的编写:
python
import torch
from torch_geometric.nn import GCNConv
from torch.nn import Linear
import visual
import torch.nn.functional as F
from torch_geometric.datasets import Planetoid #下载数据集用的
from torch_geometric.transforms import NormalizeFeatures
import MLP
dataset = Planetoid(root='data/Planetoid', name='Cora', transform=NormalizeFeatures())
data = dataset[0]
def train():
model.train()
optimizer.zero_grad() # Clear gradients.
out = model(data.x) # Perform a single forward pass.
loss = criterion(out[data.train_mask], data.y[data.train_mask]) # Compute the loss solely based on the training nodes.
loss.backward() # Derive gradients.
optimizer.step() # Update parameters based on gradients.
return loss
def tes():
model.eval()
out = model(data.x)
pred = out.argmax(dim=1) # Use the class with probability.
test_correct = pred[data.test_mask] == data.y[data.test_mask] # Check against ground-truth labels.
test_acc = int(test_correct.sum()) / int(data.test_mask.sum()) # Derive ratio of correct predictions.
return test_acc
model = MLP.MLP(hidden_channels=16)
criterion = torch.nn.CrossEntropyLoss() # Define loss criterion.
optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4) # Define optimizer.
for epoch in range(1, 201):
loss = train()
print(f'Epoch: {epoch:03d}, Loss: {loss:.4f}')
test_acc = tes()
print(f'Test Accuracy: {test_acc:.4f}')
使用图神经网络进行分类和训练
图神经网络的结构定义:
python
from torch_geometric.nn import GCNConv
import torch
from torch.nn import Linear
import visual
import torch.nn.functional as F
from torch_geometric.datasets import Planetoid #下载数据集用的
from torch_geometric.transforms import NormalizeFeatures
dataset = Planetoid(root='data/Planetoid', name='Cora', transform=NormalizeFeatures())
data = dataset[0]
class GCN(torch.nn.Module):
def __init__(self, hidden_channels):
super().__init__()
torch.manual_seed(1234567)
self.conv1 = GCNConv(dataset.num_features, hidden_channels)
self.conv2 = GCNConv(hidden_channels, dataset.num_classes)
def forward(self, x, edge_index):
x = self.conv1(x, edge_index)
x = x.relu()
x = F.dropout(x, p=0.5, training=self.training)
x = self.conv2(x, edge_index)
return x
model = GCN(hidden_channels=16)
print(model)
model.eval()
out = model(data.x, data.edge_index)
visual.visualize(out, color=data.y)
训练与最后的结果可视化
python
import torch
from torch_geometric.nn import GCNConv
from torch.nn import Linear
import visual
import torch.nn.functional as F
from torch_geometric.datasets import Planetoid #下载数据集用的
from torch_geometric.transforms import NormalizeFeatures
import GCN_class
dataset = Planetoid(root='data/Planetoid', name='Cora', transform=NormalizeFeatures())
data = dataset[0]
def train():
model.train()
optimizer.zero_grad()
out = model(data.x, data.edge_index)
loss = criterion(out[data.train_mask], data.y[data.train_mask])
loss.backward()
optimizer.step()
return loss
def tes():
model.eval()
out = model(data.x,data.edge_index)
pred = out.argmax(dim=1) # Use the class with probability.
test_correct = pred[data.test_mask] == data.y[data.test_mask] # Check against ground-truth labels.
test_acc = int(test_correct.sum()) / int(data.test_mask.sum()) # Derive ratio of correct predictions.
return test_acc
model = GCN_class.GCN(hidden_channels=16)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4)
criterion = torch.nn.CrossEntropyLoss()
for epoch in range(1, 101):
loss = train()
print(f'Epoch: {epoch:03d}, Loss: {loss:.4f}')
test_acc = tes()
print(f'Test Accuracy: {test_acc:.4f}')
准确率的提升从50%到80%由此可见提升还是非常大的。