本文以dgl中examples的基于pytorch的gcn的训练过程为例进行学习总结。
官方github地址:https://github.com/dmlc/dgl
输入参数分析
-
dropout probability:丢弃率 在神经网络训练过程中,对于一次迭代中的某一层神经网络,先随机选择中的一些神经元并将其临时隐藏(丢弃),然后再进行本次训练和优化,目的是防止过拟合现象出现;
-
learning rate:学习率 决定目标函数能否收敛到局部最小值以及何时收敛到最小值,过小收敛过程慢,过大精度低,可能无法收敛;
-
epochs:纪元 每个epoch指数据集中的所有样本都跑过一遍,根据样本的容量确定每个epoch的迭代次数;
-
hidden gcn units/layers:隐藏的gcn个数/层数;
-
weight decay:权重衰减 让权重衰减到更小的值,在一定程度上减少模型过拟合的问题,权重衰减也叫L2正则化。
-
self loop:自循环处理,使度数为0的节点添加一条自己指向自己的边
(同时还可以选择其余其他的参数,比如默认数据集、GPU训练等,但与GCN基本过程原理关系较小)
gcn训练过程分析
-
加载数据集:以ACM.mat为例,通过其自身的数据处理方法,创建一个DGL异构/同构图对象,并根据数据提取特征集、标签集、类型数、训练集序列及张量、验证集序列及张量、测试集序列及张量,这里通过han下的utils中的load_data方法为例子;
-
如果加载的DGLgraph为异构图,则执行异构图转同构图的操作(使用dgl.metapath_reachable_graph方法对异构图进行提取操作,提取对象为给定元路径到达的节点,比如"pa-ap"即为经author节点连接的paper节点;使用dgl.to_homogeneous方法将提取节点后的异构图进行向同构图的转换);
g, features, labels, num_classes, train_idx, val_idx, test_idx, train_mask, val_mask,test_mask = load_data('ACMRaw') g = dgl.metapath_reachable_graph(g,['pa','ap']) g = dgl.to_homogeneous(g)
-
对图各个节点度数进行归一化处理(将有量纲的表达式,经过变换,化为无量纲的表达式,成为纯量);
degs = g.in_degrees().float() norm = torch.pow(degs, -0.5) norm[torch.isinf(norm)] = 0
-
以之前输入参数与读取处理的数据建立GCN对象,过程在细节部分详述;
model = GCN(g, in_feats, args.n_hidden, n_classes, args.n_layers, F.relu, args.dropout)
-
以模型参数为输入使用Adam算法(我们介绍亚当(Adam),一种基于一阶梯度的随机目标函数优化算法,该算法基于低阶矩的自适应估计。该方法易于实现,计算效率高,对存储器的要求很少,对于梯度的对角线重缩放不变,并且非常适合于数据和/或参数较大的问题。该方法还适用于非平稳目标和梯度非常嘈杂和/或稀疏的问题)进行优化处理(https://arxiv.org/abs/1412.6980);
# 交叉熵损失 loss_fcn = torch.nn.CrossEntropyLoss() # use optimizer 使用优化器 optimizer = torch.optim.Adam(model.parameters(), lr=args.lr, weight_decay=args.weight_decay)
6.进行每次epoch的训练及处理,过程在细节部分详述,最后进行最终的指标评估,输出模型评估值。
细节分析
GCN模型建立
-
模块构建,使用GraphConv对象将图卷积应用于输入信号,分别对应输入层、隐藏层、输出层,GraphConv部分参数分析(1.norm 应用规范化器,前面使用Adam方法 2.weight 是否使用权重矩阵 3.activation 将激活功能应用于更新的节点功能),GCN中forward方法用于计算图卷积,获取到图卷积的计算结果向量后添加入训练层中用以训练;
-
GraphConv细节解释,使用forward方法计算图卷积,重点在于对图的节点进行信息发送、信息聚集操作,并最后更新节点数据,涉及方法为graph.update_all(),其以方法为参数,包括发送消息方法、聚合消息方法、更新功能方法,对与异构图来说拥有边类型这个参数。发送消息方法以传送值方法copy_src为例进行传送值,在给定的示例中聚集方法以求和聚集方法sum为例。
def update_all(self, message_func, reduce_func, apply_node_func=None, etype=None): Parameters ---------- 发送消息 message_func : dgl.function.BuiltinFunction or callable The message function to generate messages along the edges. It must be either a :ref:`api-built-in` or a :ref:`apiudf`. 聚合消息 reduce_func : dgl.function.BuiltinFunction or callable The reduce function to aggregate the messages. It must be either a :ref:`api-built-in` or a :ref:`apiudf`. 更新功能 apply_node_func : callable, optional An optional apply function to further update the node features after the message reduction. It must be a :ref:`apiudf`. 边类型 etype : str or (str, str, str), optional The type name of the edges.
graph.update_all(fn.copy_src(src='h', out='m'), fn.sum(msg='m', out='h')) # 更新节点数据
每次epoch的训练及处理
-
在每次epoch中对模型进行训练,之后进行交叉熵损失的计算并根据交叉熵损失损失函数用梯度下降算法以对优化器与进行更新,最后对参数格式、训练序号、训练时间、失败率、准确率进行输出,在全部epoch执行完毕之后最后对模型、特征、标签、测试集进行评估,得出最终的评估指标。
for epoch in range(args.n_epochs): model.train() #模型训练 if epoch >= 3: t0 = time.time() # 获取当前时间戳 # forward logits = model(features) # 模型输入特征 loss = loss_fcn(logits[train_mask], labels[train_mask]) optimizer.zero_grad() # 梯度置0 loss.backward() # 反向传播求梯度 optimizer.step() # 更新所有参数 if epoch >= 3: dur.append(time.time() - t0) # 时间差 acc = evaluate(model, features, labels, val_mask) print("Epoch {:05d} | Time(s) {:.4f} | Loss {:.4f} | Accuracy {:.4f} | " "ETputs(KTEPS) {:.2f}". format(epoch, np.mean(dur), loss.item(), acc, n_edges / np.mean(dur) / 1000)) # 训练输出参数格式 训练序号、训练时间、失败率、准确率、输出值