转载请注明出处:小锋学长日子大爆炸[xfxuezhang.cn]
Colab Notebook
安装必备的库
# Install required packages.
import os
import torch
os.environ['TORCH'] = torch.__version__
print(torch.__version__)
!pip install -q torch-scatter -f https://data.pyg.org/whl/torch-${TORCH}.html
!pip install -q torch-sparse -f https://data.pyg.org/whl/torch-${TORCH}.html
!pip install -q git+https://github.com/pyg-team/pytorch_geometric.git
# Helper function for visualization.
%matplotlib inline
import networkx as nx
import matplotlib.pyplot as plt
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()
def visualize_embedding(h, color, epoch=None, loss=None):
plt.figure(figsize=(7,7))
plt.xticks([])
plt.yticks([])
h = h.detach().cpu().numpy()
plt.scatter(h[:, 0], h[:, 1], s=140, c=color, cmap="Set2")
if epoch is not None and loss is not None:
plt.xlabel(f'Epoch: {epoch}, Loss: {loss.item():.4f}', fontsize=16)
plt.show()
介绍:图神经网络的实践
最近,图的深度学习现已成为深度学习界最热门的研究范畴之一。在这里,图神经网络(GNNs)旨在将经典的深度学习概念推广到不规则的结构化数据(与图画或文本相反),并使神经网络能够推理目标及其联系。
这是经过一个简略的神经信息传递计划来完成的,图G=(V,E)中一切节点v∈V的节点特征x(ℓ)v经过聚合来自其街坊N(v)的局部信息而重复更新。
本教程将向你介绍一些关于经过基于PyTorch Geometric(PyG)库的图神经网络进行图上深度学习的基本概念。PyTorch Geometric是盛行的深度学习框架PyTorch的一个扩展库,由各种方法和工具组成,以简化图神经网络的完成。
继Kipf等人(2017)之后,让咱们经过调查一个简略的图结构的比如来深入了解GNN的国际,即著名的Zachary的空手道沙龙网络。这个图描绘了一个空手道沙龙的34名成员的社会网络,并记录了在沙龙外互动的成员之间的联系。在这里,咱们感兴趣的是检测由成员互动发生的社区。
PyTorch Geometric经过torch_geometric.datasets子包供给了对这个数据集的简略访问。
from torch_geometric.datasets import KarateClub
dataset = KarateClub()
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}')
Dataset: KarateClub():
======================
Number of graphs: 1
Number of features: 34
Number of classes: 4
在初始化KarateClub数据集后,咱们首先能够查看它的一些特点。例如,咱们能够看到这个数据集正好有一个图,并且这个数据集的每个节点都被分配了一个34维的特征向量(它唯一地描绘了空手道沙龙的成员)。此外,该图正好有4个类,代表每个节点所属的社区。
现在让咱们更具体地看一下底层图。
data = dataset[0] # Get the first graph object.
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()}')
Data(x=[34, 34], edge_index=[2, 156], y=[34], train_mask=[34])
==============================================================
Number of nodes: 34
Number of edges: 156
Average node degree: 4.59
Number of training nodes: 4
Training node label rate: 0.12
Has isolated nodes: False
Has self-loops: False
Is undirected: True
PyTorch Geometric中的每个图形都由一个数据目标表明,该目标具有描绘其图形表明的一切信息。咱们能够经过print(data) 随时打印数据目标,以获得关于其特点及其形状的简略摘要:
Data(edge_index=[2, 156], x=[34, 34], y=[34], train_mask=[34])
咱们能够看到,这个数据目标具有4个特点:
(1) edge_index特点具有关于图衔接性的信息,即每条边的源节点和意图节点索引的一个元组;
(2) 节点特征称为 x(34个节点中的每一个都被分配了一个34维的特征向量);
(3) 节点标签称为 y(每个节点都被精确地分配到一个类别);
(4) 还存在一个额外的特点,叫做train_mask,它描绘了咱们现已知道哪些节点的社区分配。
****总的来说,咱们只知道4个节点的实在标签(每个社区一个),咱们的任务是揣度其余节点的社区分配。
数据目标还供给了一些有用函数来揣度基础图的一些基本特点。例如,咱们能够很容易地揣度出图中是否存在孤立的节点(即不存在通往任何节点的边),图中是否包括自循环(即(v,v)∈E),或者图是否是无向的(即关于每条边(v,w)∈E,也存在边(w,v)∈E)。
现在让咱们更具体地查看edge_index特点。
from IPython.display import Javascript # Restrict height of output cell.
display(Javascript('''google.colab.output.setIframeHeight(0, true, {maxHeight: 300})'''))
edge_index = data.edge_index
print(edge_index.t())
[0 1], [0 2], [0 3], [0 4], [0 5], [0 6], [0 7], [0 8], [0 10], [0 11],
[0 12], [0 13], [0 17], [0 19], [0 21], [0 31], [1 0], [1 2], [1 3], [1 7],
[1 13], [1 17], [1 19], [1 21], [1 30], [2 0], [2 1], [2 3], [2 7], [2 8],
[2 9], [2 13], [2 27], [2 28], [2 32], [3 0], [3 1], [3 2], [3 7], [3 12],
[3 13], [4 0], [4 6], [4 10], [5 0], [5 6], [5 10], [5 16], [6 0], [6 4],
[6 5], [6 16], [7 0], [7 1], [7 2], [7 3], [8 0], [8 2], [8 30], [8 32],
[8 33], [9 2], [9 33], [10 0], [10 4], [10 5], [11 0], [12 0], [12 3], [13 0],
[13 1], [13 2], [13 3], [13 33], [14 32], [14 33], [15 32], [15 33], [16 5], [16 6],
[17 0], [17 1], [18 32], [18 33], [19 0], [19 1], [19 33], [20 32], [20 33], [21 0],
[21 1], [22 32], [22 33], [23 25], [23 27], [23 29], [23 32], [23 33], [24 25], [24 27],
[24 31], [25 23], [25 24], [25 31], [26 29], [26 33], [27 2], [27 23], [27 24], [27 33],
[28 2], [28 31], [28 33], [29 23], [29 26], [29 32], [29 33], [30 1], [30 8], [30 32],
[30 33], [31 0], [31 24], [31 25], [31 28], [31 32], [31 33], [32 2], [32 8], [32 14],
[32 15], [32 18], [32 20], [32 22], [32 23], [32 29], [32 30], [32 31], [32 33], [33 8],
[33 9], [33 13], [33 14], [33 15], [33 18], [33 19], [33 20], [33 22], [33 23], [33 26],
[33 27], [33 28], [33 29], [33 30], [33 31], [33 32],
经过打印edge_index,咱们能够了解PyG在内部怎么表明图的衔接性。咱们能够看到,关于每一条边,edge_index持有两个节点索引的元组,其间第一个值描绘了源节点的节点索引,第二个值描绘了一条边的意图节点的节点索引。
这种表明法被称为COO格局(坐标格局),一般用于表明稀少矩阵。PyG不是用密集表明法A∈{0,1}|V||V|来保存邻接信息,而是稀少地表明图,这指的是只保存A中条目为非零的坐标/值。
重要的是,PyG不区分有向图和无向图,并将无向图视为有向图的一个特例,其间edge_index中的每个条目都存在反向的边。
咱们能够经过将其转换为networkx库的格局来进一步完成图形的可视化,该库除了完成图形操作的功用外,还完成了强壮的可视化工具。
from torch_geometric.utils import to_networkx
G = to_networkx(data, to_undirected=True)
visualize_graph(G, color=data.y)
完成图神经网络
在学习了PyG的数据处理之后,是时候完成咱们的第一个图谱神经网络了!为此,咱们将运用最简略的GNN运算符,即GCN层(Kipf等人2017)。为此,咱们将运用最简略的GNN操作,GCN层,其界说为:
其间W(ℓ+1)表明形状为 [num_output_features, num_input_features] 的可练习权重矩阵,cw,v是指每条边的固定归一化系数。
PyG经过GCNConv完成这一层,能够经过传入节点特征表明x和COO图衔接表明edge_index来执行。
有了这些,咱们就能够经过在torch.nn.Module类中界说咱们的网络架构来创建咱们的第一个图形神经网络。
import torch
from torch.nn import Linear
from torch_geometric.nn import GCNConv
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_classes)
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() # Final GNN embedding space.
# Apply a final (linear) classifier.
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=4, bias=True)
)
在这里,咱们首先在 __init__ 中初始化咱们一切的构建模块,并在forward中界说咱们网络的核算流程。咱们首先界说并堆叠了三个图卷积层,这相当于集合了每个节点周围的3跳邻域信息(3 “跳 “以内的一切节点) 。此外,GCNConv层将节点特征维度降低到2,即34→4→4→2。每个GCNConv层都被一个tanh非线性增强。
之后,咱们应用一个单一的线性变换(torch.nn.Linear),作为一个分类器,将咱们的节点映射到4个类别/社区中的一个。
咱们同时返回终究分类器的输出以及由GNN发生的终究节点嵌入。咱们持续经过GCN()初始化咱们的终究模型,并打印咱们的模型,发生其一切运用的子模块的摘要。
嵌入Karate Club Network
让咱们来看看由咱们的GNN发生的节点嵌入。在这里,咱们将初始节点特征x和图的衔接信息edge_index传递给模型,并将其二维嵌入可视化。
model = GCN()
_, h = model(data.x, data.edge_index)
print(f'Embedding shape: {list(h.shape)}')
visualize_embedding(h, color=data.y)
Embedding shape: [34, 2]
值得注意的是,即使在练习咱们模型的权重之前,该模型发生的节点嵌入与图的社区结构十分类似。尽管咱们模型的权重是彻底随机初始化的,并且到目前为止咱们还没有进行任何练习,但相同颜色(社区)的节点现已在嵌入空间中严密地集合在一起了!这导致的结论是,GNNs引入了强烈的归纳偏置,导致输入图中彼此挨近的节点的嵌入类似。
在Karate Club Network上练习
让咱们看一个比如,怎么根据图中4种节点(每个社区一种)的社区分配常识来练习咱们的网络参数。
因为咱们模型中的一切东西都是可分的和参数化的,咱们能够增加一些标签,练习模型并调查嵌入的反应。在这里,咱们运用了一个半监督或过渡性的学习程序。咱们只是针对每一类的一个节点进行练习,但允许咱们运用完整的输入图数据。
练习咱们的模型与任何其他PyTorch模型十分类似。除了界说咱们的网络结构,咱们还界说了一个丢失原则(这里是CrossEntropyLoss)并初始化了一个随机梯度优化器(这里是Adam)。之后,咱们进行多轮优化,每轮包括一个前向和后向传达,以核算咱们模型参数的梯度,即前向传达得出的丢失。假如你不是PyTorch的新手,这个计划对你来说应该很熟悉。否则,PyTorch文档供给了一个关于怎么在PyTorch中练习神经网络的良好介绍。
请注意,咱们的半监督学习计划是由以下一行完成的。
loss = criterion(out[data.train_mask], data.y[data.train_mask])
虽然咱们为一切的节点核算节点嵌入,但咱们只使用练习节点来核算丢失。在这里,这是经过过滤分类器的输出和实在标签data.y来完成的,只包括train_mask中的节点。
现在让咱们开始练习,看看咱们的节点嵌入是怎么随时间演化的(最好是经过明确地运行代码来体会)。
import time
from IPython.display import Javascript # Restrict height of output cell.
display(Javascript('''google.colab.output.setIframeHeight(0, true, {maxHeight: 430})'''))
model = GCN()
criterion = torch.nn.CrossEntropyLoss() # Define loss criterion.
optimizer = torch.optim.Adam(model.parameters(), lr=0.01) # Define optimizer.
def train(data):
optimizer.zero_grad() # Clear gradients.
out, h = model(data.x, data.edge_index) # 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, h
for epoch in range(401):
loss, h = train(data)
if epoch % 10 == 0:
visualize_embedding(h, color=data.y, epoch=epoch, loss=loss)
time.sleep(0.3)
正如人们所看到的,咱们的3层GCN模型成功地线性分离了社区,并对大多数节点进行了正确分类。
此外,咱们只用了几行代码就完成了这一切,这要感谢PyTorch的PyG库,它在数据处理和GNN的完成方面协助了咱们。
总结
对GNN和PyTorch Geometric国际的第一次介绍到此结束。在后续课程中,你将学习怎么在一些实在国际的图数据集上完成最先进的分类结果。
下一篇:用图谱神经网络进行节点分类