本教程是一个系列免费教程,如果喜欢请帮忙转发。
主要是基于图深度学习的入门内容。讲述最基本的基础知识,其中包括深度学习、数学、图神经网络等相关内容。该教程由代码医生工作室出版的全部书籍混编节选而成。偏重完整的知识体系和学习指南。
本教程主要针对的人群:
本篇主要介绍深度图互信息DGI模型,该模型需要用到熵、互信息等相关知识。在学习之前,可以先参考《想学好深度学习,你需要了解——熵!》一文,再学习本文中的DIM模型,最后学习DGI模型。
1 了解深度图互信息模型(DGI)
深度图互信息(Deep Graph Infomax 简称DGI)模型主要是使用无监督训练的方式去学习图中节点的嵌入向量,其做法借鉴了神经网络中的Deep Infomax(DIM)算法,即将目标函数设成最大化互信息。该方法可以理解为神经网络中Deep Infomax算法在图神经网络上的“迁移”。有关DGI的更多详细信息可以参考论文(arXiv: 1809.10341,2018)
2 了解最大化互信息Deep Infomax算法
好的编码器应该能够提取出样本的最独特、具体的信息,而不在于单纯的追求过小的重构误差。而样本的独特信息则可以使用“互信息”(Mutual Information,MI)来衡量。因此,在DIM(Deep Infomax)模型中,编码器的目标函数不是最小化输入与输出的MSE,而是最大化输入与输出的互信息。
2.1 DIM模型的原理
在DIM模型中,网络结构使用了自编码和对抗神经网络的结合;损失函数使用了MINE与f-GAN方法的结合;在此之上,又从全局损失、局部损失和先验损失三个损失出发对模型进行训练。具体的介绍如下。
1 DIM模型的主要思想
DIM模型中的互信息解决方案主要来自于MINE方法。即计算输入样本与编码器输出的特征向量之间的互信息。通过最大化互信息来实现模型的训练。
互信息神经估计(MINE)是一种基于神经网络估计互信息的方法。它通过bp算法来训练,对高维度连续随机变量间的互信息进行估计, 可以最大或者最小化互信息,提升生成模型的对抗训练,突破监督学习分类任务的瓶颈。(论文arXiv: 1801.04062,2018)
DIM模型在无监督训练中使用了2种约束来进行表示学习:
在实现时,DIM模型使用了3个判别器,分别从局部互信息最大化、全局互信息最大化和先验分布匹配最小化3个角度对编码器的输出结果进行约束。(论文arXiv: 1808.06670,2018)
2 用局部和全局互信息最大化约束的原理
许多表示学习只使用已探索过的数据空间(称为像素级别),当一小部分数据十分关心语义级别时,该表示学习将不利于训练。
因为对于图片,它的相关性更多体现在局部中,图片的识别、分类等应该是一个从局部到整体的过程。即,全局特征更适合用于重构,局部特征更适合用于下游的分类任务。
提示:
局部特征可以理解为卷积后得到的Feature Map;全局特征可以理解为对Feature Map进行编码得到的特征向量。
所以DIM模型从局部和全局两个角度出发对输入和输出做互信息计算。而先验匹配的目的是对编码器生成向量形式的一种约束,使其更接近高斯分布。
3 用验分布匹配最小化约束的原理
在变分自编码神经网络中,编码器部分的主要思想是:在对输入数据编码成特征向量的同时,还希望这个特征向量服从于标准的高斯分布。这种做法利于使得编码空间更加规整,甚至有利于解耦特征,便于后续学习。
DIM模型的编码器与变分自编码中编码器的使命是一样的。所以,在DIM模型中引入变分自编码神经网络的原理,将高斯分布当作先验分布,对编码器输出的向量进行约束。
2. 2 DIM模型的结构
DIM模型由4个子模型构成:一个编码器,3个判别器。其中编码器的作用主要是对图片进行特征提取。3个判别器分别从局部、全局、先验匹配3个角度对编码器的输出结果进行约束。总体结构如图所示。
在DIM模型的实际实现过程中,没有直接拿原始的输入数据与编码器输出的特征数据做最大化互信息计算,而是使用了编码器中间过程中的特征图(Feature Map)与最终的特征数据做互信息计算。
根据MINE方法,在利用神经网络计算互信息的方法可以换算成计算两个数据集合的联合分布和边缘分布间的散度。即,将判别器处理特征图和特征数据的结果当作联合分布,将乱序后的特征图和特征数据输入判别器,得到边缘分布。
注意:
DIM模型在处理边缘分布的部分是打乱特征图的批次顺序后与编码器输出的特征向量一起作为判别器的输入。
其本质是:令输入判别器的特征图与特征向量各自独立(破坏特征图与特征向量间的对应关系)。
1 全局判别器模型
全局判别器的输入值有两个:特征图m和特征数据y。在计算互信息的计算中,计算联合分布的特征图m和特征数据y都来自编码神经网络的输出;计算边缘分布的特征图是由改变特征图m的批次顺序得来,而特征数据y还是来自编码神经网络的输出。如图所示。
在全局判别器中,具体的处理步骤如下:
(1)使用卷积层对特征图m处理,得到其全局特征。
(2)将该全局特征与特征数据y用torch.cat函数连接起来。
(3)将连接后的结果输入全连接网络,最终输出判别结果(1维向量)。
其中,第(3)步的全连接网络作用是对两个全局特征进行判定。
2.局部判别器模型
如图8-30所示,局部判别器的输入值是一个特殊的合成向量:将编码器输出的特征数据y,按照特征图m的尺寸复制成mm份。令特征图m中的每个像素都与编码器输出的全局特征数据y相连。这样,判别器所做的事情就变成对每个像素与全局特征向量之间的互信息计算。所以该判别器被叫做局部判别器。
在局部判别器中,计算互信息的联合分布和边缘分布方式与全局判别器一致。如图所示。
如图8-32所示,在局部判别器中主要使用了1的卷积操作(步长也为1)。因为这种卷积操作不会改变特征图的尺寸(只是通道数的变换),所以判别器的最终输出也是m尺寸的向量。
局部判别器通过多层1的卷积操作,最终将通道数变成了1,作为最终的判别结果。该过程可以理解为,同时对每个像素与全局特征进行互信息的计算。
3. 先验判别器模型
在8.11.1中介绍过,先验判别器模型主要是辅助编码器生成的向量趋近于高斯分布。其做法与普通的对抗神经网络一致。先验判别器模型输出的结果只有0或1:令判别器对高斯分布采样的数据判定为真(1),对编码器输出的特征向量判定为假(0)。如图所示。
在先验判别器模型的输入只有特征向量一个。其结构主要使用了全连接神经网络,最终会输出“真”或“假”的判定结果。
4.损失函数
在DIM模型中,将MINE方法中的KL散度换成了JS散度来作为互信息的度量。这么做的原因是:JS散度是有上届(log2)的,而KL散度是没有上界的。相比之下,JS散度更适合在最大化任务中使用,因为它在计算时不会产生特别大的数。并且JS散度的梯度又是无偏的。
对这3个判别器各自损失函数的计算结果加权求和,便得到整个DIM模型的损失函数。
3 DGI模型的原理
好的编码器应该能够提取出样本的最独特、具体的信息,而不在于单纯的最求过小的重构误差。而样本的独特信息则可以使用“互信息”(Mutual Information,MI)来衡量。因此,在DIM模型中,编码器的目标函数不是最小化输入与输出的MSE,而是最大化输入与输出的互信息。
DGI模型的主要目的是用一个编码器来学习图中节点的高阶特征,该编码器输出的结果是一个带有高阶特征的图。其中,单个节点的特征H可以表示该节点的局部特征,而全部节点的特征组合到一起,则可以表示整个图的全局特征(summary vector),用S表示。
3.1.READOUT函数
在图神经网络中,主要有两种分类:基于节点分类,和对整个图进行分类。
(1)基于节点分类一般会先对图中邻居节点进行聚合,并更新到自身节点中。再对自身的节点特征进行分类。
(2)基于整个图的分类同样也是先对图中邻居节点进行聚合,并更新到自身节点中。不同的是,需要对所有节点聚合操作生成一个全局特征。最后再对这个全局特征做分类。其中的聚合操作的过程,便叫作READOUT函数。
在DGI模型实现时,使用具有加和功能的sum函数作为READOUT函数,即将所有节点特征加和在一起,所生成的新的向量summary vector当作整个图的全局特征。
3.2. DGI模型结构
在使用对抗神经网络训练编码器时,判别器的作用主要是令编码器输出的单个节点特征与整个图的特征互信息最大。同时令其它图中的节点特征与的该图的整体特征互信息最小。如图所示。
图中各个符号所代表的意义如下:
4 实现DGI模型
实例描述
使用非监督的方法从论文数据集中提取每篇论文的特征,并利用提取后的特征,对论文数据集中的论文样本进行分类。
利用深度图互信息的方法可以从更好的对图中的节点特征进行提取。被提取出来的节点可以用于分类、回归、特征转换等各种用途。下面就来使用深度图互信息的方法对论文数据集提取特征,并用使用提取后的特征进行论文分类。
4.1 代码实现:搭建多层SGC网络
定义MSGC类,搭建一个多层的SGC网络,该网络中包括输入层、隐藏层和输出层。具体代码如下:
import math
import time
import numpy as np#载入基础库
import torch
import torch.nn as nn
import torch.nn.functional as F#载入PyTorch库
from dgl.nn.pytorch.conv import SGConv #载入DGL库
from code_30_dglGAT import features,#载入本工程代码
g,n_classes,feats_dim,n_edges,trainmodel
class MSGC(nn.Module):#定义多次SGC网络
def __init__(self, in_feats,#输入特征的维度
n_hidden,#隐藏层的节点个数
n_classes,#输出层的维度
k,#每层SGC要计算的跳数
n_layers,#隐藏层个数
activation,#隐藏层的激活函数
dropout):#丢弃率
super(MSGC, self).__init__()
self.layers = nn.ModuleList()#定义列表
self.activation = activation
self.layers.append(SGConv(in_feats, n_hidden, k, #构建输入层
cached=False, bias=False))
for i in range(n_layers - 1):#构建隐藏层
self.layers.append(SGConv(n_hidden, n_hidden, k,
cached=False, bias=False))
self.layers.append(SGConv(n_hidden, n_classes, k,#构建输出层
cached=False, bias=False))
self.dropout = nn.Dropout(p=dropout)
def forward(self, g,features):#定义正向传播方法
h = features
for i, layer in enumerate(self.layers):#按照层列表依次处理
if i != 0:
h = self.dropout(h)#除输入层以外,剩下都使用Dropout处理
h = layer(g,h)
if i != len(self.layers)-1:
h = self.activation(h)#除输出层以外,剩下都使用激活函数处理
return h
在代码中调用SGConv构建SGC层时,将参数cached都设置成了False,表明不缓存输入节点特征经过邻接矩阵计算后的多跳数据。因为在多层SGC中,输入的节点特征不再来源于数据集,而是来源于上层的输出。这就意味着,每次的输入都会变化。所以,需要每次调用SGConv时,都需要重新对输入节点进行的多跳计算。
4.2 代码实现:搭建编码器和判别器
定义Encoder类用于DGI模型中的编码器。在对抗神经网络中,该Encoder类相当于生成器的角色,主要完成两部分功能:
(1)在原始的图节点中随机采样,生成新的图。
(2)计算输入图节点的高阶特征。
定义Discriminator类用于对抗神经网络的判别器。判别器的输入包括图节点特征和图的整体概要特征。其计算步骤如下:
(1)用全连接网络对图的整体概要特征做一次特征变换。
(2)将变换后的特征与输入的图节点特征进行矩阵相乘,计算二者的相似度。
具体代码如下:
class Encoder(nn.Module): #定义编码器类
def __init__(self, in_feats, n_hidden,k, n_layers, activation, dropout):
super(Encoder, self).__init__()
self.conv = MSGC( in_feats, n_hidden, n_hidden, k,#定义多层SGC
n_layers, activation, dropout)
def forward(self,g, features, corrupt=False):#正向传播
if corrupt: #对图节点随机采样,生成新的图
perm = torch.randperm(g.number_of_nodes())
features = features[perm]
features = self.conv(g,features)#用MSGC计算图节点的高阶特征
return features
class Discriminator(nn.Module):#定义判别器类
def __init__(self, n_hidden):
super(Discriminator, self).__init__()
self.FC = nn.Linear(n_hidden,n_hidden) #定义全连接层
def forward(self, features, summary):#定义正向传播方法
features = torch.matmul(features, self.FC(summary) )#计算相似度
return features
代码第51行,调用了torch.randperm函数得到一个索引序列。torch.randperm函数的作用是,根据输入值n,生成0~n个随机顺序的不重复整数。
代码第52行,根据索引序列在原有的图节点中取值,生成一个新图。原图与新图相比只是节点的特征发生了变化,节点间的关系(邻接矩阵)并没有变化。
注意:
在编码器中使用多层SGC网络的方法并不是唯一的。DGI模型的主要思想是在对抗神经网络中使用图节点的局部特征与图整体特征的互信息来做为判别器。计算图节点局部特征的方法可以使用任意图神经网络模型。比如,使用图卷积网络(GCN)、图注意力(GAT)网络都可以。
4.3 代码实现:搭建DGI模型并进行训练
定义DGI类将编码器与判别器联合起来,并构建损失函数,实现DGI模型的搭建。具体步骤如下:
(1)使用编码器分别对原图和新图的节点特征进行计算。生成正负样本特征。
(2)根据计算后的原图节点特征(正样本特征),生成图的整体摘要特征。
(3)分别将正负样本特征与图的整体摘要特征输入判别器,生成相似度特征。
(4)使用BCEWithLogitsLoss计算交叉熵损失。
提示:
BCEWithLogitsLoss函数会对判别器返回的相似度结果做Sigmoid非线性变换,使其值域转化在0~1之间。并让正向样本的相似度更接近最大值1,负向样本的相似度更接近最小值0。
class DGI(nn.Module): #定义DGI模型类
def __init__(self, in_feats, n_hidden,k, n_layers,
activation, dropout):
super(DGI, self).__init__()
self.encoder = Encoder( in_feats, n_hidden, k,n_layers,
activation, dropout)
self.discriminator = Discriminator(n_hidden)
self.loss = nn.BCEWithLogitsLoss() #带有Sigmoid激活函数的交叉熵损失
def forward(self, g,features):#正向传播
positive = self.encoder(g,features, corrupt=False)#计算原图节点特征
negative = self.encoder(g,features, corrupt=True) #计算新图的节点特征
summary = torch.sigmoid(positive.mean(dim=0)) #计算图的整体特征
#计算相似度
positive = self.discriminator(positive, summary)
negative = self.discriminator(negative, summary)
#分别对正负样本特征与图整体特征的相似度的损失进行计算
l1 = self.loss(positive, torch.ones_like(positive))
l2 = self.loss(negative, torch.zeros_like(negative))
return l1 + l2
dgi = DGI(feats_dim, n_hidden=512, k=2,n_layers=1, #实例化DGI模型
activation =nn.PReLU(512), dropout=0.1)
dgi.cuda()
#定义优化器
dgi_optimizer = torch.optim.Adam(dgi.parameters(),
lr=1e-3, weight_decay=5e-06)
该模型创建之后,便可以使用数据进行训练。训练后得到的模型可以实现对图节点的特征提取。
4.4 利用DGI模型提取特征并进行分类
DGI中的编码器只是有特征提取功能,如果用该特征进行分类,还需要额外定义一个分类模型。完成根据节点特征进行分类的功能。由于DGI中的编码器已经能够从节点中提取到有用特征,分类模型的结构不需要太复杂,直接使用一个全连接网络即可。