所有文章 > AI驱动 > 全面!图神经网络(GNN)系统介绍及实践(Pytorch)

全面!图神经网络(GNN)系统介绍及实践(Pytorch)

本文介绍了有关图神经网络所有内容,包括 GNN 是什么、不同类型的图神经网络以及它们的用途。此外,还展示了如何使用 Pytorch 构建图神经网络

什么是图形?

图形是包含节点和边的数据结构类型。节点可以是人、地点或事物,边定义节点之间的关系。边可以基于方向依赖关系进行定向和无向。

在下面的示例中,蓝色圆圈是节点,箭头是边。边的方向定义了两个节点之间的依赖关系。

让我们来了解一下复杂的 Graph 数据集:Jazz Musicians Network。它包含 198 个节点和 2742 条边。在下面的社区图中,不同颜色的节点代表爵士音乐家的各种社区以及连接它们的边缘。有一个合作网络,一个音乐家在社区内外都有关系。

Jazz Musicians Network 的社区图

图形在处理关系和交互的复杂问题方面非常出色。它们用于模式识别、社交网络分析、推荐系统和语义分析。创建基于图形的解决方案是一个全新的领域,它提供了对复杂和相互关联的数据集的丰富见解。

 使用 NetworkX 绘制图形

在本节中,我们将学习如何使用 NetworkX 创建图形。

下面的代码受到 Daniel Holmberg 关于 Python 中的图神经网络的博客的影响。

  1. 创建 networkx 的 DiGraph 对象 “H”
  2. 添加包含不同标签、颜色和大小的节点
  3. 添加边以在两个节点之间创建关系。例如,“(0,1)” 表示 0 对 1 具有方向依赖关系。我们将通过添加“(1,0)”来创建双向关系
  4. 以列表的形式提取颜色和大小
  5. 使用 networkx 的 draw 函数绘制图形
import networkx as nx
H = nx.DiGraph()

#adding nodes
H.add_nodes_from([
(0, {"color": "blue", "size": 250}),

(1, {"color": "yellow", "size": 400}),

(2, {"color": "orange", "size": 150}),

(3, {"color": "red", "size": 600})


])

#adding edges
H.add_edges_from([
(0, 1),

(1, 2),

(1, 0),

(1, 3),

(2, 3),

(3,0)


])

node_colors = nx.get_node_attributes(H, "color").values()
colors = list(node_colors)
node_sizes = nx.get_node_attributes(H, "size").values()
sizes = list(node_sizes)

#Plotting Graph
nx.draw(H, with_labels=True, node_color=colors, node_size=sizes)

在下一步中,我们将使用 to_undirected() 函数将数据结构从定向图转换为无向图。

#converting to undirected graph
G = H.to_undirected()
nx.draw(G, with_labels=True, node_color=colors, node_size=sizes)

为什么图很难分析?

基于图的数据结构有缺点,数据科学家在开发基于图的解决方案之前必须了解这些缺点。

  1. 图存在于非欧几里得空间中。它不存在于 2D 或 3D 空间中,这使得解释数据变得更加困难。要在 2D 空间中可视化结构,必须使用各种降维工具。
  2. 图形是动态的;他们没有固定的形式。可以有两个视觉上不同的图,但它们可能具有相似的邻接矩阵表示。这使得我们很难使用传统的统计工具来分析数据。
  3. 大尺寸和大维将增加图形对人类解释的复杂性。具有多个节点和数千条边的密集结构更难理解和提取见解。

什么是图神经网络(GNN)?

图神经网络是能够处理图数据结构的特殊类型的神经网络。它们受到卷积神经网络 (CNN) 和图嵌入的高度影响。GNN 用于预测节点、边和基于图的任务。

  • CNN 用于图像分类。同样,GNN 被应用于图结构(像素网格)来预测一个类。
  • 递归神经网络用于文本分类。同样,GNN 应用于图结构,其中每个单词都是句子中的一个节点。

当卷积神经网络由于图的任意大小和复杂的结构而未能达到最佳结果时,引入了GNN。

 图片由 Purvanshi Mehta 提供

输入图通过一系列神经网络传递。输入图结构被转换为图嵌入,使我们能够维护有关节点、边缘和全局上下文的信息。

然后,节点 A 和 C 的特征向量通过神经网络层。它聚合这些特征并将它们传递到下一层 – neptune.ai

阅读我们的深度学习教程或参加我们的深度学习入门课程,了解有关深度学习算法和应用的更多信息。

图神经网络的类型

有几种类型的神经网络,其中大多数都有一些卷积神经网络的变体。在本节中,我们将了解最流行的 GNN。

  • 图卷积网络 (GCN) 类似于传统的 CNN。它通过检查相邻节点来学习特征。GNN 聚合节点向量,将结果传递到密集层,并使用激活函数应用非线性。简而言之,它由图卷积、线性层和非学习器激活函数组成。GCN有两种主要类型:空间卷积网络和频谱卷积网络。
  • 图自动编码器网络使用编码器学习图表示,并尝试使用解码器重建输入图。编码器和解码器由瓶颈层连接。它们通常用于链路预测,因为自动编码器擅长处理类平衡。
  • 递归图神经网络(RGNNs)学习最佳扩散模式,它们可以处理单个节点具有多个关系的多关系图。这种类型的图神经网络使用正则化器来提高平滑度并消除过度参数化。RGNN 使用更少的计算能力来产生更好的结果。它们用于生成文本机器翻译语音识别、生成图像描述、视频标记和文本摘要
  • 门控图神经网络 (GGNN) 在执行具有长期依赖性的任务方面优于 RGNN。门控图神经网络通过在长期依赖关系上添加节点、边和时间门来改进递归图神经网络。与门控循环单元 (GRU) 类似,门用于记住和忘记不同状态下的信息。

如果您有兴趣了解有关递归神经网络 (RNN) 的更多信息,请查看 DataCamp 的课程。它将向您介绍各种 RNN 模型架构、Keras 框架和 RNN 应用程序。

图神经网络任务的类型

下面,我们通过示例概述了一些类型的 GNN 任务:

  • 图分类:我们用它来将图分类为各种类别。它的应用是社交网络分析和文本分类。
  • 节点分类:此任务使用相邻节点标签来预测图中缺失的节点标签。
  • 链接预测:预测具有不完整邻接矩阵的图中一对节点之间的链接。它通常用于社交网络。
  • 社区检测:根据边缘结构将节点划分为不同的集群。它从边缘权重、距离和图形对象中学习。
  • 图嵌入:将图映射到向量中,保留节点、边和结构的相关信息。
  • 图生成:从样本图分布中学习,生成新的但相似的图结构。

 图片由作者提供

图神经网络的缺点

了解它们将有助于我们确定何时使用 GNNa 以及如何优化机器学习模型的性能。

  1. 大多数神经网络可以深入以获得更好的性能,而 GNN 是浅层网络,主要有三层。它限制了我们在大型数据集上实现最先进的性能。
  2. 图形结构在不断变化,这使得在其上训练模型变得更加困难。
  3. 将模型部署到生产环境面临可伸缩性问题,因为这些网络的计算成本很高。如果你有一个庞大而复杂的图结构,你将很难在生产环境中扩展 GNN。

什么是图卷积网络(GCN)?

大多数 GNN 都是图卷积网络,在进入节点分类教程之前了解它们很重要。

GCN 中的卷积与卷积神经网络中的卷积相同。它用权重(过滤器)将神经元相乘,以从数据特征中学习。

它充当整个图像的滑动窗口,以从相邻单元格中学习特征。该过滤器使用权重共享来学习图像识别系统中的各种面部特征 – 迈向数据科学。

现在将相同的功能转移到图卷积网络,其中模型从相邻节点学习特征。GCN 和 CNN 之间的主要区别在于,它是为处理节点和边的顺序可能变化的非欧几里得数据结构而开发的。

CNN 与 GCN |图片来源

通过阅读卷积神经网络 (CNN) 和 TensorFlow 教程,了解有关基本 CNN 的更多信息。

有两种类型的 GCN:

  • 空间图卷积网络使用空间特征从位于空间空间中的图中学习。
  • 谱图卷积网络使用图拉普拉斯矩阵的特征分解沿节点进行信息传播。这些网络的灵感来自信号和系统中的波传播。

GNN 如何工作?使用 Pytorch 构建图神经网络

我们将为节点分类模型构建和训练 Spectral Graph Convolution。Workspace 上提供了代码源,供你体验和运行第一个基于图形的机器学习模型。

编码示例受 Pytorch 几何文档的影响。

 开始

我们将安装 Pytorch 包,因为 pytorch_geometric 是建立在它之上的。

!pip install -q torch

然后我们将安装 torch-scatter 和 torch-sparse。之后,我们将从 GitHub 安装 pytorch_geometric 的最新版本。

%%capture
import os
import torch
os.environ['TORCH'] = torch.__version__
os.environ['PYTHONWARNINGS'] = "ignore"
!pip install torch-scatter -f https://data.pyg.org/whl/torch-${TORCH}.html
!pip install torch-sparse -f https://data.pyg.org/whl/torch-${TORCH}.html
!pip install git+https://github.com/pyg-team/pytorch_geometric.git

Planetoid Cora 数据集

Planetoid 是来自 Cora、CiteSeer 和 PubMed 的引文网络数据集。节点是具有 1433 维词袋特征向量的文档,边缘是研究论文之间的引文链接。有 7 个类,我们将训练模型来预测缺失的标签。

我们将引入 Planetoid Cora 数据集,并对词袋输入要素进行行规范化。之后,我们将分析数据集和第一个图形对象。

from torch_geometric.datasets import Planetoid
from torch_geometric.transforms import NormalizeFeatures

dataset = Planetoid(root='data/Planetoid', name='Cora', transform=NormalizeFeatures())

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(data)

Cora 数据集有 2708 个节点、10,556 条边、1433 个要素和 7 个类。第一个对象有 2708 个训练掩码、验证掩码和测试掩码。我们将使用这些掩码来训练和评估模型。

Dataset: Cora():
======================
Number of graphs: 1
Number of features: 1433
Number of classes: 7
Data(x=[2708, 1433], edge_index=[2, 10556], y=[2708], train_mask=[2708], val_mask=[2708], test_mask=[2708])

使用 GNN 进行节点分类

我们将创建一个 GCN 模型结构,其中包含两个 GCNConv 层、relu 激活和 0.5 的辍学率。该模型由 16 个隐藏通道组成。

 GCN层:

W(l+1) 是上式中的可移动权重矩阵,Cw,v 对每条边都具有固定的归一化系数。

from torch_geometric.nn import GCNConv
import torch.nn.functional as F

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)

>>> GCN(
(conv1): GCNConv(1433, 16)
(conv2): GCNConv(16, 7)
)

可视化未经训练的 GCN 网络

让我们使用 sklearn.manifold.TSNE 和 matplotlib.pyplot 可视化未经训练的 GCN 网络的节点嵌入。它将绘制一个嵌入 2D 散点图的 7 维节点。

%matplotlib inline
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE

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()

我们将评估模型,然后将训练数据添加到未训练的模型中,以可视化各种节点和类别。

model.eval()

out = model(data.x, data.edge_index)
visualize(out, color=data.y)

训练 GNN

我们将使用 Adam 优化和交叉熵损失函数在 100 个 Epoch 上训练我们的模型

在 train 函数中,我们有:

  1.  清除渐变
  2. 执行了一次前向传递
  3. 使用训练节点计算损失
  4. 计算梯度并更新参数

在测试函数中,我们有:

  1.  预测的节点类
  2. 概率最高的提取类标签
  3. 已检查正确预测的值数
  4. 使用正确预测的总和除以节点总数来创建准确率。
model = GCN(hidden_channels=16)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4)
criterion = torch.nn.CrossEntropyLoss()

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 test():
model.eval()
out = model(data.x, data.edge_index)
pred = out.argmax(dim=1)
test_correct = pred[data.test_mask] == data.y[data.test_mask]
test_acc = int(test_correct.sum()) / int(data.test_mask.sum())
return test_acc


for epoch in range(1, 101):
loss = train()
print(f'Epoch: {epoch:03d}, Loss: {loss:.4f}')
GAT(
(conv1): GATConv(1433, 8, heads=8)
(conv2): GATConv(64, 7, heads=8)
)

.. .. .. ..
.. .. .. ..
Epoch: 098, Loss: 0.5989
Epoch: 099, Loss: 0.6021
Epoch: 100, Loss: 0.5799

 模型评估

现在,我们将使用测试函数在看不见的数据集上评估模型,正如您所看到的,我们以 81.5% 的准确率获得了相当不错的结果。

test_acc = test()
print(f'Test Accuracy: {test_acc:.4f}')


>>> Test Accuracy: 0.8150

现在,我们将可视化经过训练的模型的输出嵌入以验证结果。

model.eval()
out = model(data.x, data.edge_index)
visualize(out, color=data.y)

正如我们所看到的,经过训练的模型为同一类别的节点生成了更好的聚类。

 训练 GATConv 模型

在第二种情况下,我们将用 GATConv 层替换 GCNConv。图注意力网络使用屏蔽的自我注意力层来解决 GCNConv 的缺点并实现最先进的结果。

您还可以尝试其他 GNN 层,并尝试优化、丢弃和一些隐藏通道,以实现更好的性能。

在下面的代码中,我们刚刚将 GCNConv 替换为 GATConv,第一层有 8 个注意力头,第二层有 1 个注意力头。

 我们还将设置:

  • dropout达 0.6
  • 隐藏通道到 8
  •  学习率 0.005

我们修改了测试函数,以查找特定掩码的准确性(有效,测试)。它将帮助我们在模型训练期间打印出验证和测试分数。稍后,我们还会将验证和测试结果存储到绘图折线图中。

from torch_geometric.nn import GATConv

class GAT(torch.nn.Module):
def __init__(self, hidden_channels, heads):
super().__init__()
torch.manual_seed(1234567)
self.conv1 = GATConv(dataset.num_features, hidden_channels,heads)
self.conv2 = GATConv(heads*hidden_channels, dataset.num_classes,heads)

def forward(self, x, edge_index):
x = F.dropout(x, p=0.6, training=self.training)
x = self.conv1(x, edge_index)
x = F.elu(x)
x = F.dropout(x, p=0.6, training=self.training)
x = self.conv2(x, edge_index)
return x

model = GAT(hidden_channels=8, heads=8)
print(model)

optimizer = torch.optim.Adam(model.parameters(), lr=0.005, weight_decay=5e-4)
criterion = torch.nn.CrossEntropyLoss()

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 test(mask):
model.eval()
out = model(data.x, data.edge_index)
pred = out.argmax(dim=1)
correct = pred[mask] == data.y[mask]
acc = int(correct.sum()) / int(mask.sum())
return acc

val_acc_all = []
test_acc_all = []

for epoch in range(1, 101):
loss = train()
val_acc = test(data.val_mask)
test_acc = test(data.test_mask)
val_acc_all.append(val_acc)
test_acc_all.append(test_acc)
print(f'Epoch: {epoch:03d}, Loss: {loss:.4f}, Val: {val_acc:.4f}, Test: {test_acc:.4f}')

.. .. .. ..
.. .. .. ..
Epoch: 098, Loss: 1.1283, Val: 0.7960, Test: 0.8030

Epoch: 099, Loss: 1.1352, Val: 0.7940, Test: 0.8050

Epoch: 100, Loss: 1.1053, Val: 0.7960, Test: 0.8040

正如我们所观察到的,我们的模型并不比 GCNConv 表现得更好。它需要超参数优化或更多的 Epoch 才能获得最先进的结果。

 模型评估

在评估部分,我们使用 matplotlib.pyplot 的折线图可视化验证和测试分数。

import numpy as np

plt.figure(figsize=(12,8))
plt.plot(np.arange(1, len(val_acc_all) + 1), val_acc_all, label='Validation accuracy', c='blue')
plt.plot(np.arange(1, len(test_acc_all) + 1), test_acc_all, label='Testing accuracy', c='red')
plt.xlabel('Epochs')
plt.ylabel('Accurarcy')
plt.title('GATConv')
plt.legend(loc='lower right', fontsize='x-large')
plt.savefig('gat_loss.png')
plt.show()

在60个周期后,验证和测试精度达到了0.8+/-0.02的稳定值。

同样,让我们可视化 GATConv 模型的节点聚类。

model.eval()

out = model(data.x, data.edge_index)
visualize(out, color=data.y)

正如我们所看到的,GATConv 层在同一类别的节点上聚类时产生了相同的结果。

我们可以通过添加验证数据集来减少过拟合,并通过试验pytoch_geometric中的各种 GCN 层来提高模型性能。

本文章转载微信公众号@Python人工智能前沿