依据GCN和DGL完成的图上 node 分类, 值得一看!!!


书接上文,咱们在 GraphSage与DGL完成同构图 Link 猜测,通俗易懂好文强推 这篇文章中,初步解说了 图上机器使命 的大致分类以及图上 有无监督使命loss 的差异,并依据作者自己的了解分析了 图上音讯传达 的过程。在上文中,咱们运用 GraphSage与DGL技术完成了无监督情境下的 链接猜测 ,本文咱们将持续运用DGL和GCN网络来完成图上的有监督学习之一的 节点分类 ,下面让我咱们开端吧~


(1) 图上Node分类使命根底

从上篇文章文章中咱们知道:在图上去猜测两个节点之间的联系(或边)是否存在的使命,是 典型的链接(联系)猜测 使命。在图上 依据图上结构之间的联系对某个节点进行定性性质的分析 ,例如判断一个用户是否是反常用户等,这种使命则是典型的 节点(Node)分类 使命,对每个节点咱们是需求 打上label数据 的,二分类便是 0或1 。

从这儿咱们也能够 推断 出: 和 传统机器学习使命 相似,假如要进行回归使命的话,咱们只需求对节点的label数据赋予需求回归出的浮点数即可。

这儿咱们需求明确的一点便是:咱们无论是进行图上节点的 分类仍是回归 使命,咱们在最后一层DNN的前一层得到logit的时分,该 logit的数据其实携带了该节点周围的街坊节点的联系信息 ,咱们是融合了 该节点 以及其 街坊节点 的信息(通常以Embeding的方法存在) 来 对当时节点 进行的 定性方法 的判断。

并且这儿所说的节点以及街坊节点的信息,是指节点的 各个特点 ,比较简略的使命可能便是对每个节点便是一个综合的embeding ,而关于杂乱的使命,每个节点可能有很多个向量,则咱们就要对 各个向量 分别写 适合该特点数据特性 的聚合处理逻辑以及后边接入全链接DNN,也能够把得到各个特点的embeding用一个简略的网络 融合 之后在进行 信息传达 均是能够的。

这儿和传统机器学习的不同便是: 传统机器学习做决断用到的信息 都是 独立同分布 的,而这儿做决断用到的部分信息是 依赖图的空间结构上处于街坊方位的信息 的,非独立同分布 的。

历史图相关文章链接如下:

(1)一文揭开图机器学习的面纱,你确认不来看看吗

(2)graphSage仍是HAN ?吐血力作综述Graph Embeding 经典好文

(3) 看这儿,运用docker布置图深度学习结构GraphLearn运用阐明

(4) GraphSage与DGL完成同构图 Link 猜测,通俗易懂好文强推

本文这儿首要解说的是 依据GCN和DGL完成的图上 Node 分类使命 ,对图上音讯传达与各种使命不太熟悉的同学,能够先去看看上面列出的几篇历史文章,其间对图的常识有了大概的解说。

下面让咱们结合代码开端今天的学习吧~


(2) 采样与算子适用性阐明

从前一篇文章 GraphSage与DGL完成同构图 Link 猜测,通俗易懂好文强推 中,咱们能够知道:尽管咱们运用了dgl 官方完成的Graph Sage 算子,可是Sage算子的提出开端首要是为了解决GCN使命理论上每次均把所有节点以及他们的街坊节点均 load进内存 导致内存放不下或则 练习速度缓慢 的问题,以及对 没见过的节点进行猜测 的问题。

关于上面第二点,咱们要引入一个概念便是 直推式学习 (transductive learning)归纳式学习 (Inductive learning) 。传统的GCN便是归于直推式学习, 练习 节点embedding 的时分要看到全图的节点, 其根本原因是由于它运用了 拉普拉斯矩阵 。而 Sage算子 ,则是归纳式学习,由于它的根本逻辑是能够 用“你“的街坊信息归纳出没见过的”你“的信息 ,能够处理未见过你但知道你街坊这种相似的问题。 例如:你在北京的朋友都是程序员或则从事互联网的职业,那你在很大程度上也是程序员或从事互联网的职业…

在前一篇文章中,咱们尽管运用了 GraphSage算子,可是咱们并没有运用Sage算子与底层原理相契合的 街坊采样 的方法。由于链接猜测需求对结构没出现过的边作为负样本,文章中挑选了对每个种子节点进行 大局负采样 ,这儿的采样便是 必须 的。 而关于节点分类这种使命,归于有监督学习使命,节点的正负由外界赋予的label来确认,所以节点采样是 非必须 的,咱们 进行采样必定是为了解决某一类问题

Sage算子 具有 采样聚合 两种的特性,咱们分别在采样和聚合的阶段去利用这种特性,当然这两种都用也是能够的。咱们假如不采样,则仅仅利用来它多种的聚合方法的特性,去聚合所有的街坊节点。当然咱们也能够用采样的方法,对每个节点去采样一定数目的街坊,参与Sage算子的聚合阶段。

当然,除了对每个节点的 街坊进行采样 ,咱们通常也会对大数据集合进行 分batch练习 ,这两种方法都能够 减小数据对内存 的压力。在DGL中,咱们能够选用 dgl.dataloading.NodeDataLoader 接口进行针对 节点 的采样操作,而对 边采样 则能够运用 dgl.dataloading.EdgeDataLoader 接口,能够同时对上述说的两个方面进行工程上的完成,dgl是我心目中处理图机器学习的 yyds !!! 这两个采样算子的接口阐明,作者将在后续的文章中将持续写一篇介绍比较杂乱的使命的文章,在其间将对算子的运用进行阐明~

这儿首要拿Sage算子来论述,首要是想阐明:针对不同的使命,例如节点和边的使命,选用不同的算子,用这个算子的什么特性咱们都是能够依据自己的数据流程灵敏定制,咱们在拿到代码的时分,能够去深化思考一下,看运用了什么特性以及怎么调配能更好的处理咱们的使命。这儿不但针对Sage算子的采样与聚合,以及后边的同异构图的Attention也是如此。


(3) 代码时光

留意:这儿的代码是 依据 gcn 和 dgl 完成的痛构图上有监督的节点分类使命 .

开篇吼一嗓子 , talk is cheap , show me the code !!! 下面,就让咱们开端coding 吧~

(3.1) 导包

@欢迎重视微信大众号:算法全栈之路
importargparse
importtorch
importtorch.nnasnn
importtorch.nn.functionalasF
importdgl.nnasdglnn
fromdglimportAddSelfLoop
fromdgl.dataimportCiteseerGraphDataset,CoraGraphDataset,PubmedGraphDataset

老规矩,先导包。跑起来这个使命,仅仅需求这些包就能够。


(3.2) 模型结构定义

这儿咱们选用 dgl 官方完成的 graphConv 算子进行街坊节点信息的聚合,不进行街坊节点的采样。

@欢迎重视微信大众号:算法全栈之路
classGCN(nn.Module):
def__init__(self,in_size,hid_size,out_size):
super().__init__()
self.layers=nn.ModuleList()
#two-layerGCN
self.layers.append(
dglnn.GraphConv(in_size,hid_size,activation=F.relu)
)
self.layers.append(dglnn.GraphConv(hid_size,out_size))
self.dropout=nn.Dropout(0.5)
defforward(self,g,features):
h=features
fori,layerinenumerate(self.layers):
ifi!=0:
h=self.dropout(h)
h=layer(g,h)
returnh

咱们能够看到:这儿的网络结构挑选的是gcn 。在上面的网络结构中,nn.ModuleList中放有2层的GraphConv卷积层,并在其间加入了dropout层。图卷基层之间也是能够加入dropout层的,和传统的深度学习DNN无任何差异

为了 加深了解 ,咱们能够要点重视下gcn模型的初始化参数以及输入输出参数。能够看到:初始化参数包含了模型的输入参数,这儿便是节点初始embeding的维度,隐藏维度以及输出维度,h的维度和out_size相同 。在本文,h维度也是和 logit维度相同,等于输出类别数


(3.3) 模型评价代码

@欢迎重视微信大众号:算法全栈之路
#模型评价,返回精确率
defevaluate(g,features,labels,mask,model):
model.eval()
withtorch.no_grad():
logits=model(g,features)
logits=logits[mask]
labels=labels[mask]
_,indices=torch.max(logits,dim=1)
correct=torch.sum(indices==labels)
returncorrect.item()*1.0/len(labels)

这儿的评价指标是 精确率,衡量的正负样本猜测的精确与否。函数的输入是 G图, feature 是节点的原始特点feature,通过gcn后得到logit, 和标签进行比较。

咱们能够看到其间用到了 model.eval() 代码。 它的作用是:在评价过程中不启用 Batch Normalization 和 Dropout,一般在测验的代码之前增加,使得在测验时确保BN层能够用全部练习数据的均值和方差,即测验过程中要确保BN层的均值和方差不变。 关于Dropout则是用到了所有网络连接,即不进行随机舍弃神经元

最后这儿,咱们能够看到并没有进行穿插熵核算的代码,是由于:加上了穿插熵并不会影响核算出的max logit 的 indeces ,也就不会影响模型评价的精确性了。


(3.4) 练习模块

@欢迎重视微信大众号:算法全栈之路
deftrain(g,features,labels,masks,model):
#definetrain/valsamples,lossfunctionandoptimizer
train_mask=masks[0]
val_mask=masks[1]
loss_fcn=nn.CrossEntropyLoss()
optimizer=torch.optim.Adam(model.parameters(),lr=1e-2,weight_decay=5e-4)
#trainingloop
forepochinrange(20):
model.train()
logits=model(g,features)
loss=loss_fcn(logits[train_mask],labels[train_mask])
optimizer.zero_grad()
loss.backward()
optimizer.step()
acc=evaluate(g,features,labels,val_mask,model)
print(
"Epoch{:05d}|Loss{:.4f}|Accuracy{:.4f}".format(
epoch,loss.item(),acc
)
)

留意: 这儿的丢失函数选用的是穿插熵丢失,而其实 CrossEntropyLoss相当于softmax + log + nllloss , 这也就和咱们上面的代码是相互映证的。

优化器挑选的是 adam, 当咱们不知道挑选什么优化器好的时分,无脑挑选adam 总是能给咱们一个不错的成果。

中心train 模块的输入输出参数,能够在下面的主函数中看到,这儿就不再多做阐明了。 model.train()和 model.eval() 同理,只不过作用相反。


(3.5) 模型输入参数等主函数

咱们这儿的测验数据,直接选用了dgl官方供给的数据集, 这儿供给了三个数据集。

闲言少叙,看代码吧~

@欢迎重视微信大众号:算法全栈之路
if__name__=="__main__":
parser=argparse.ArgumentParser()
parser.add_argument(
"--dataset",
type=str,
default="cora",
help="Datasetname('cora','citeseer','pubmed').",
)
args=parser.parse_args()
print(f"TrainingwithDGLbuilt-inGraphConvmodule.")
#loadandpreprocessdataset
#增加自环
transform=(
AddSelfLoop()
)#bydefault,itwillfirstremoveself-loopstopreventduplication
ifargs.dataset=="cora":
data=CoraGraphDataset(transform=transform)
elifargs.dataset=="citeseer":
data=CiteseerGraphDataset(transform=transform)
elifargs.dataset=="pubmed":
data=PubmedGraphDataset(transform=transform)
else:
raiseValueError("Unknowndataset:{}".format(args.dataset))
g=data[0]

device=torch.device("cuda"iftorch.cuda.is_available()else"cpu")

g=g.int().to(device)
features=g.ndata["feat"]
labels=g.ndata["label"]
masks=g.ndata["train_mask"],g.ndata["val_mask"],g.ndata["test_mask"]
#createGCNmodel
in_size=features.shape[1]
out_size=data.num_classes
model=GCN(in_size,16,out_size).to(device)
#modeltraining
print("Training...")
train(g,features,labels,masks,model)
#testthemodel
print("Testing...")
acc=evaluate(g,features,labels,masks[2],model)
print("Testaccuracy{:.4f}".format(acc))

咱们能够看到: 上述的代码中,有给 图增加自环 的过程 AddSelfLoop ,是由于增加自环能够 有用缓解图的稀疏性 ,能够提高模型的练习作用。

中心的 g = g.int().to(device) 是把图数据转入到device 中,假如练习model的机器有gpu的话,是能够把 数据复制到gpu的显存 里去的。

把上面的代码复制到一个python文件中是能够完美运行的,我这儿的 dgl版本挑选的是0.9 。代码本身是非常通俗易懂的,望文能够知其意 ,我就不在过多赘述了哈。假如有任何疑问,欢迎重视大众号讨论~

最后在强调一点: 这儿的代码和上一篇文章中的代码用了相同的处理方法:节点 feature 不随着网络的更新而更新,假如要随着网络更新,能够去看 GraphSage与DGL完成同构图 Link 猜测,通俗易懂好文强推 文章最后介绍的处理方法,或则去重视作者的后边一篇文章,会挑选让特征随着网络更新的方法进行更新的代码完成。


宅男民工码字不易,你的重视是我持续输出的最大动力。

接下来作者会持续共享学习与工作中一些有用的、有意思的内容,点点手指头支撑一下吧~

了解更多更全内容,欢迎重视作者的大众号: 算法全栈之路

  • END –