Auto Byte

专注未来出行及智能汽车科技

微信扫一扫获取更多资讯

Science AI

关注人工智能与其他前沿技术、基础学科的交叉研究与融合发展

微信扫一扫获取更多资讯

手把手教你用DGL框架进行批量图分类

图分类(预测图的标签)是图结构数据里一类重要的问题。它的应用广泛,可见于生物信息学、化学信息学、社交网络分析、城市计算以及网络安全。随着近来学界对于神经网络的热情持续高涨,出现了一批用神经网络做图分类的工作。比如训练神经网络来预测蛋白质结构的性质,根据社交网络结构来预测用户的所属社区等(Ying et al., 2018, Cangea et al., 2018, Knyazev et al., 2018, Bianchi et al., 2019, Liao et al., 2019, Gao et al., 2019)。

在这个教程里,我们将一起学习:

  • 如何使用 DGL 批量化处理大小各异的图数据

  • 训练神经网络完成一个简易的图分类任务

简易图分类任务

这里我们设计了一个简单的图分类任务。在 DGL 里我们实现了一个迷你图分类数据集(MiniGCDataset)。它由以下 8 类图结构数据组成。每一类图包含同样数量的随机样本。任务目标是训练神经网络模型对这些样本进行分类。

以下是使用 MiniGCDataset 的示例代码。我们先创建了一个拥有 80 个样本的数据集。数据集中每张图随机有 10 到 20 个节点。DGL 中所有的数据集类都符合 Sequence 的抽象结构——既可以使用 dataset[i] 来访问第 i 个样本。这里每个样本包含图结构以及它对应的标签。

from dgl.data import MiniGCDataset
import matplotlib.pyplot as plt
import networkx as nx
# 数据集包含了80张图。每张图有10-20个节点
dataset = MiniGCDataset(80, 10, 20)
graph, label = dataset[0]
fig, ax = plt.subplots()
nx.draw(graph.to_networkx(), ax=ax)
ax.set_title('Class: {:d}'.format(label))
plt.show()

运行以上代码后可以画出数据集中第一个样本的图结构以及它对应的标签:

打包一个图的小批量

为了更高效地训练神经网络,一个常见的做法是将多个样本打包成小批量(mini-batch)。打包尺寸相同的张量样本非常简单。比如说打包两个尺寸为 2828 的图片会得到一个 22828 的张量。相较之下,打包图面临两个挑战:

  • 图的边比较稀疏

  • 图的大小、形状各不相同

DGL 提供了名为 dgl.batch 的接口来实现打包一个图批量的功能。其核心思路非常简单。将 n 张小图打包在一起的操作可以看成是生成一张含 n 个不相连小图的大图。下图的可视化从直觉上解释了 dgl.batch 的功能。

可以看到通过 dgl.batch 操作,我们生成了一张大图,其中包含了一个环状和一个星状的连通分量。其邻接矩阵表示则对应为在对角线上把两张小图的邻接矩阵拼接在一起(其余部分都为 0)。

以下是使用 dgl.batch 的一个实际例子。我们定义了一个 collate 函数来将 MiniGCDataset 里多个样本打包成一个小批量。

import dgl

def collate(samples):
    # 输入`samples` 是一个列表
    # 每个元素都是一个二元组 (图, 标签)
    graphs, labels = map(list, zip(*samples))
    batched_graph = dgl.batch(graphs)
    return batched_graph, torch.tensor(labels)

正如打包 N 个张量得到的还是张量,dgl.batch 返回的也是一张图。这样的设计有两点好处。首先,任何用于操作一张小图的代码可以被直接使用在一个图批量上。其次,由于 DGL 能够并行处理图中节点和边上的计算,因此同一批量内的图样本都可以被并行计算。

图分类器

这里使用的图分类器和应用在图像或者语音上的分类器类似——先通过多层神经网络计算每个样本的表示(representation),再通过表示计算出每个类别的概率,最后通过向后传播计算梯度。一个常见的图分类器由以下几个步骤构成:

  1. 通过图卷积(Graph Convolution)层获得图中每个节点的表示。

  2. 使用「读出」操作(Readout)获得每张图的表示。

  3. 使用 Softmax 计算每个类别的概率,使用向后传播更新参数

下图展示了整个流程:

之后我们将分步讲解每一个步骤。

图卷积

我们的图卷积操作基本类似图卷积网络 GCN(具体可以参见我们的关于 GCN 的教程)。图卷积模型可以用以下公式表示:

在这个例子中,我们对这个公式进行了微调:

我们将求和替换成求平均可用来平衡度数不同的节点,在实验中这也带来了模型表现的提升。

此外,在构建数据集时,我们给每个图里所有的节点都加上了和自己的边(自环)。这保证节点在收集邻居节点表示进行更新时也能考虑到自己原有的表示。以下是定义图卷积模型的代码。这里我们使用 PyTorch 作为 DGL 的后端引擎(DGL 也支持 MXNet 作为后端)。

首先,我们使用 DGL 的内置函数定义消息传递:

import dgl.function as fn
import torch
import torch.nn as nn

# 将节点表示h作为信息发出
msg = fn.copy_src(src='h', out='m')

其次,我们定义消息累和函数。这里我们对收到的消息进行平均。

def reduce(nodes):
    """对所有邻节点节点特征求平均并覆盖原本的节点特征。"""
    accum = torch.mean(nodes.mailbox['m'], 1)
    return {'h': accum}

之后,我们对收到的消息应用线性变换和激活函数

class NodeApplyModule(nn.Module):
    """将节点特征 hv 更新为 ReLU(Whv+b)."""
    def __init__(self, in_feats, out_feats, activation):
        super(NodeApplyModule, self).__init__()
        self.linear = nn.Linear(in_feats, out_feats)
        self.activation = activation

    def forward(self, node):
        h = self.linear(node.data['h'])
        h = self.activation(h)
        return {'h' : h}

最后,我们把所有的小模块串联起来成为 GCNLayer。

class GCNLayer(nn.Module):
    def __init__(self, in_feats, out_feats, activation):
        super(GCNLayer, self).__init__()
        self.apply_mod = NodeApplyModule(in_feats, out_feats, activation)

    def forward(self, g, feature):
        # 使用 h 初始化节点特征。
        g.ndata['h'] = feature
        # 使用 update_all接口和自定义的消息传递及累和函数更新节点表示。
        g.update_all(msg, reduce)
        g.apply_nodes(func=self.apply_mod)
        return g.ndata.pop('h')

读出和分类

读出(Readout)操作的输入是图中所有节点的表示,输出则是整张图的表示。在 Google 的 Neural Message Passing for Quantum Chemistry(Gilmer et al. 2017) 论文中总结过许多不同种类的读出函数。在这个示例里,我们对图中所有节点表示取平均以作为图的表示:

DGL 提供了许多读出函数接口,以上公式可以很方便地用 dgl.mean(g) 完成。最后我们将图的表示输入分类器。分类器对图表示先做了一个线性变换然后得到每一类在 softmax 之前的 logits。具体代码如下:

import torch.nn.functional as F

class Classifier(nn.Module):
    def __init__(self, in_dim, hidden_dim, n_classes):
        super(Classifier, self).__init__()
        # 两层图卷积层。
        self.layers = nn.ModuleList([
            GCNLayer(in_dim, hidden_dim, F.relu),
            GCNLayer(hidden_dim, hidden_dim, F.relu)])
        # 分类层。
        self.classify = nn.Linear(hidden_dim, n_classes)

    def forward(self, g):
        # 使用节点度数作为初始节点表示。
        h = g.in_degrees().view(-1, 1).float()
        # 图卷积层。
        for conv in self.layers:
            h = conv(g, h)
        g.ndata['h'] = h
        # 读出函数。
        graph_repr = dgl.mean_nodes(g, 'h')
        # 分类层。
        return self.classify(graph_repr)

准备和训练

阅读到这边的读者可以长舒一口气了。因为之后的训练过程和其他经典的图像,语音分类问题基本一致。首先我们创建了一个包含 400 张节点数量为 10~20 的合成数据集。其中 320 张图作为训练数据集,80 张图作为测试集。

import torch.optim as optim
from torch.utils.data import DataLoader

# 创建一个训练数据集和测试数据集
trainset = MiniGCDataset(320, 10, 20)
testset = MiniGCDataset(80, 10, 20)
# 使用 PyTorch 的 DataLoader 和之前定义的 collate 函数。
data_loader = DataLoader(trainset, batch_size=32, shuffle=True,
                         collate_fn=collate)

其次我们创建一个刚刚定义的神经网络模型对象。

# 创建模型
model = Classifier(1, 256, trainset.num_classes)
loss_func = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
model.train()

训练过程则是经典的反向传播和梯度下降

# 创建模型

epoch_losses = []
for epoch in range(80):
    epoch_loss = 0
    for iter, (bg, label) in enumerate(data_loader):
        prediction = model(bg)
        loss = loss_func(prediction, label)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        epoch_loss += loss.detach().item()
    epoch_loss /= (iter + 1)
    print('Epoch {}, loss {:.4f}'.format(epoch, epoch_loss))
    epoch_losses.append(epoch_loss)

下图是以上模型训练的学习曲线

在训练完成后,我们在测试集上验证模型的表现。出于部署教程的考量,我们限制了模型训练的时间。如果你花更多时间训练模型,应该能得到更好的表现(80%-90%)。

我们还制作了一个动画来展示训练好的模型预测每张图真实标签的概率。可以看到我们刚刚定义的神经网络能够较为准确地预测出图样本的对应标签:

为了更好地理解模型学到的节点和图的表示,我们使用了 t-SNE 来进行降维和可视化。

顶部的两张小图分别可视化了做完 1 层和 2 层图卷积后的节点表示。不同颜色代表属于不同类别的图的节点。可以看到,经过训练后,属于同一类别的节点表示更加接近。并且,经过两层图卷积后这一聚类效果更明显。其原因是因为两层卷积后每个节点能接收到 2 度范围内的邻居信息。

底部的大图可视化了每张图在做 softmax 前的 logits,也就是图表示。可以看到通过读出函数后,图表示能非常好地各自区分开来。这一区分度比节点表示更加明显。

拓展阅读

使用神经网络作图分类还是一个崭新的领域。这个任务并不简单,需要模型能将不同的图结构数据映射到不同的表示,同时要求图表示可以保存它们结构和内容上的异同。欲知更多内容,ICLR 2019 上新鲜出炉的 oral paper How Powerful Are Graph Neural Networks? 可能是一个不错的出发点。

以上代码都可以在我们的文档网站下载:https://docs.dgl.ai/tutorials/basics/4_batch.html

对于想要更进一步学习用 DGL 进行批量化图处理,以下内容可能会有所帮助:

  • Tree LSTM 教程。该模型需要批处理多个句子的语法书结构。

  • Deep Generative Models of Graphs 的 DGL 教程。

  • Junction Tree VAE 的 DGL 示例代码。该模型根据分子结构图来预测分子性质。

关于 DGL 专栏: DGL 是一款全新的面向神经网络的开源框架。通过该专栏,我们 DGL 团队希望和大家一起学习神经网络的最新进展。同时展示 DGL 的灵活性和高效性。通过系统学习算法,通过算法理解系统。

DGL专栏
DGL专栏

DGL是一款全新的面向图神经网络的开源框架。通过该专栏,我们DGL团队希望和大家一起学习图神经网络的最新进展。同时展示DGL的灵活性和高效性。通过系统学习算法,通过算法理解系统。

工程DGL图神经网络框架
91
相关数据
激活函数技术

在 计算网络中, 一个节点的激活函数定义了该节点在给定的输入或输入的集合下的输出。标准的计算机芯片电路可以看作是根据输入得到"开"(1)或"关"(0)输出的数字网络激活函数。这与神经网络中的线性感知机的行为类似。 一种函数(例如 ReLU 或 S 型函数),用于对上一层的所有输入求加权和,然后生成一个输出值(通常为非线性值),并将其传递给下一层。

参数技术

在数学和统计学裡,参数(英语:parameter)是使用通用变量来建立函数和变量之间关系(当这种关系很难用方程来阐述时)的一个数量。

分类数据技术

一种特征,拥有一组离散的可能值。以某个名为 house style 的分类特征为例,该特征拥有一组离散的可能值(共三个),即 Tudor, ranch, colonial。通过将 house style 表示成分类数据,相应模型可以学习 Tudor、ranch 和 colonial 分别对房价的影响。 有时,离散集中的值是互斥的,只能将其中一个值应用于指定样本。例如,car maker 分类特征可能只允许一个样本有一个值 (Toyota)。在其他情况下,则可以应用多个值。一辆车可能会被喷涂多种不同的颜色,因此,car color 分类特征可能会允许单个样本具有多个值(例如 red 和 white)。

学习曲线技术

在机器学习领域,学习曲线通常是表现学习准确率随着训练次数/时长/数据量的增长而变化的曲线

张量技术

张量是一个可用来表示在一些矢量、标量和其他张量之间的线性关系的多线性函数,这些线性关系的基本例子有内积、外积、线性映射以及笛卡儿积。其坐标在 维空间内,有 个分量的一种量,其中每个分量都是坐标的函数,而在坐标变换时,这些分量也依照某些规则作线性变换。称为该张量的秩或阶(与矩阵的秩和阶均无关系)。 在数学里,张量是一种几何实体,或者说广义上的“数量”。张量概念包括标量、矢量和线性算子。张量可以用坐标系统来表达,记作标量的数组,但它是定义为“不依赖于参照系的选择的”。张量在物理和工程学中很重要。例如在扩散张量成像中,表达器官对于水的在各个方向的微分透性的张量可以用来产生大脑的扫描图。工程上最重要的例子可能就是应力张量和应变张量了,它们都是二阶张量,对于一般线性材料他们之间的关系由一个四阶弹性张量来决定。

神经网络技术

(人工)神经网络是一种起源于 20 世纪 50 年代的监督式机器学习模型,那时候研究者构想了「感知器(perceptron)」的想法。这一领域的研究者通常被称为「联结主义者(Connectionist)」,因为这种模型模拟了人脑的功能。神经网络模型通常是通过反向传播算法应用梯度下降训练的。目前神经网络有两大主要类型,它们都是前馈神经网络:卷积神经网络(CNN)和循环神经网络(RNN),其中 RNN 又包含长短期记忆(LSTM)、门控循环单元(GRU)等等。深度学习是一种主要应用于神经网络帮助其取得更好结果的技术。尽管神经网络主要用于监督学习,但也有一些为无监督学习设计的变体,比如自动编码器和生成对抗网络(GAN)。

梯度下降技术

梯度下降是用于查找函数最小值的一阶迭代优化算法。 要使用梯度下降找到函数的局部最小值,可以采用与当前点的函数梯度(或近似梯度)的负值成比例的步骤。 如果采取的步骤与梯度的正值成比例,则接近该函数的局部最大值,被称为梯度上升。

映射技术

映射指的是具有某种特殊结构的函数,或泛指类函数思想的范畴论中的态射。 逻辑和图论中也有一些不太常规的用法。其数学定义为:两个非空集合A与B间存在着对应关系f,而且对于A中的每一个元素x,B中总有有唯一的一个元素y与它对应,就这种对应为从A到B的映射,记作f:A→B。其中,y称为元素x在映射f下的象,记作:y=f(x)。x称为y关于映射f的原象*。*集合A中所有元素的象的集合称为映射f的值域,记作f(A)。同样的,在机器学习中,映射就是输入与输出之间的对应关系。

降维技术

降维算法是将 p+1 个系数的问题简化为 M+1 个系数的问题,其中 M<p。算法执行包括计算变量的 M 个不同线性组合或投射(projection)。然后这 M 个投射作为预测器通过最小二乘法拟合一个线性回归模型。两个主要的方法是主成分回归(principal component regression)和偏最小二乘法(partial least squares)。

图神经网络技术

图网络即可以在社交网络或其它基于图形数据上运行的一般深度学习架构,它是一种基于图结构的广义神经网络。图网络一般是将底层图形作为计算图,并通过在整张图上传递、转换和聚合节点特征信息,从而学习神经网络基元以生成单节点嵌入向量。生成的节点嵌入向量可作为任何可微预测层的输入,并用于节点分类或预测节点之间的连接,完整的模型可以通过端到端的方式训练。

MXNet技术

MXNet是开源的,用来训练部署深层神经网络的深度学习框架。它是可扩展的,允许快速模型训练,并灵活支持多种语言(C ++,Python,Julia,Matlab,JavaScript, Go,R,Scala,Perl,Wolfram语言)

聚类技术

将物理或抽象对象的集合分成由类似的对象组成的多个类的过程被称为聚类。由聚类所生成的簇是一组数据对象的集合,这些对象与同一个簇中的对象彼此相似,与其他簇中的对象相异。“物以类聚,人以群分”,在自然科学和社会科学中,存在着大量的分类问题。聚类分析又称群分析,它是研究(样品或指标)分类问题的一种统计分析方法。聚类分析起源于分类学,但是聚类不等于分类。聚类与分类的不同在于,聚类所要求划分的类是未知的。聚类分析内容非常丰富,有系统聚类法、有序样品聚类法、动态聚类法、模糊聚类法、图论聚类法、聚类预报法等。

小白来请教,我执行到最后一段,出现错误 RuntimeError: expected scalar type Long but found Int 具体出在这一行 loss = loss_func(prediction, label) 请问如何修改?