比赛地址:常规赛:论文引用网络节点分类
文章转载自:图网络笔记–论文节点比赛baseline代码注解,感谢红白黑大佬的笔记
注意,每一次调整使用的模型或者更管模型后,要记得重启notebook,避免运行失败或者运行上一次的模型等
【ps: 重启运行时,可以跳过依赖下载,但请记得运行从import sys; sys.path.append(’/home/aistudio/external-libraries’)开始运行】
自定义模型时,要注意参考baseline重点例子,如何创建一个可用的模型【后边会说一下,大家不用担心,很简单的】
from easydict import EasyDict as edict
'''模块说明:
easydict这个模块下的EasyDict,可以使得创建的字典像访问属性一样
eg:
dicts = {'A': 1}
print(dicts['A']) => 1
使用EasyDict之后:
dicts = EasyDict(dicts)
print(dicts.A) => 1
'''
# 模型参数字典
config = {
"model_name": "GCNII",
"num_layers": 1, # 网络层数--这个实现在模型类的forward里边,通过循环实现
"dropout": 0.5, # 训练时,参数drop概率
"learning_rate": 0.0002, # 训练优化的学习率
"weight_decay": 0.0005, # 权重正则化率
"edge_dropout": 0.00, # 边drop概率
}
config = edict(config) # 利用EasyDict便利字典的读取
# 加载边数据
def load_edges(num_nodes, self_loop=True, add_inverse_edge=True):
'''
input:
num_nodes: 节点数
self_loop: 是否加载自环边
add_inverse_edge: 是否添加反转的边--我的理解是正反都添加--即对应无向图的情况
'''
# 从数据中读取边
edges = pd.read_csv("work/edges.csv", header=None, names=["src", "dst"]).values
# 反转边添加
if add_inverse_edge:
edges = np.vstack([edges, edges[:, ::-1]]) # vstack沿竖直方向拼接--如:A =[1, 2] , B = [2, 3]; vstack([A, B]) => [[1, 2], [2, 3]]
# eg: edges=[[1, 3], [2, 5], [6, 7]] => edges[:, ::-1]=[[3, 1], [5, 2], [7, 6]]
# 再拼接就得到了正反边的一个集合了
# 自环边添加
if self_loop:
src = np.arange(0, num_nodes) # 定义n和节点作为起点
dst = np.arange(0, num_nodes) # 定义n个节点作为终点--且与src一一对应
self_loop = np.vstack([src, dst]).T # 再将两个行向量拼接(此时shape:[2, num_nodes]), 然后再转置T=>得到shape:[num_node, 2]这是的数据0->0, 1->1 ...就得到了自环边的数据
edges = np.vstack([edges, self_loop]) # 将自环边数据添加到本身的边数据中
return edges
def load():
# 从数据中读取点特征和边,以及数据划分
node_feat = np.load("work/feat.npy") # 读取节点特征--每个节点100个特征
num_nodes = node_feat.shape[0] # shape[0] 正好对应节点个数
edges = load_edges(num_nodes=num_nodes, self_loop=True, add_inverse_edge=True) # 根据实际传入的节点数,返回合理的边--这里包含自环边以及正向和反向的边
graph = pgl.graph.Graph(num_nodes=num_nodes, edges=edges, node_feat={"feat": node_feat}) # 创建图:节点数、边数据、以及节点特征的字典
indegree = graph.indegree() # 计算当前图的所有节点的入度--返回一个list==>等价于graph.indegree(nodes=None),nodes指定,返回指定的入度
norm = np.maximum(indegree.astype("float32"), 1) # 取最大入度中的一个然后返回
norm = np.power(norm, -0.5) # 利用这个最大入读计算一个归一化参数
graph.node_feat["norm"] = np.expand_dims(norm, -1) # 将归一化参数添加到节点的norm特征中, shape[1], 只含有一个元素的序列,但不算标量:如,a 和 [a]
df = pd.read_csv("work/train.csv") # 读取总的训练数据
node_index = df["nid"].values # 读取总的节点的索引序列(集)
node_label = df["label"].values # 读取总的节点的label序列
train_part = int(len(node_index) * 0.8) # 划分训练数据集--80%--这里是计算一个训练集数目值
train_index = node_index[:train_part] # 利用训练集数目进行划分--0:train_part
train_label = node_label[:train_part] # 训练label划分
valid_index = node_index[train_part:] # 验证数据valid_index划分
valid_label = node_label[train_part:] # 验证valid_label划分
test_index = pd.read_csv("work/test.csv")["nid"].values # 读取测试集--也就是赛题提交数据--指定读取['nid']列数据
# 这是一个可以使用名字来访问元素内容的tuple子类
# 所以直接对应传入数据即可
dataset = Dataset(graph=graph,
train_label=train_label,
train_index=train_index,
valid_index=valid_index,
valid_label=valid_label,
test_index=test_index, num_classes=35)
return dataset # 最后返回dataset数据
这一部分的分割和load中的命名索引元组有关!
dataset = load() # 执行load函数获取完整的dataset(可命名索引的tuple)数据
# 从dataset中读取出相应数据
train_index = dataset.train_index # 读取训练索引序列
train_label = np.reshape(dataset.train_label, [-1 , 1]) # 读取训练label序列
train_index = np.expand_dims(train_index, -1) # 在最后一位添加一个维度,保证数据向量化[[a]]
val_index = dataset.valid_index # 读取验证索引序列
val_label = np.reshape(dataset.valid_label, [-1, 1]) # 读取训练label序列
val_index = np.expand_dims(val_index, -1) # 在最后一位添加一个维度,保证数据向量化[[a]]
test_index = dataset.test_index # 读取训练索引序列
test_index = np.expand_dims(test_index, -1) # 在最后一位添加一个维度,保证数据向量化[[a]]
test_label = np.zeros((len(test_index), 1), dtype="int64") # 用于保存最终结果--提前用zeros创建一个空白矩阵,并指明数据类型
import pgl
import model # model.py
import paddle.fluid as fluid
import numpy as np
import time
from build_model import build_model # build_model.py
use_gpu = True
place = fluid.CUDAPlace(0) if use_gpu else fluid.CPUPlace() # 工作环境--这里有修改--需要用cpu只需要把use_gpu设置为False即可
train_program = fluid.default_main_program() # 创建主program -- paddle静态图都是在相应的program中运行的 -- 通常为start
startup_program = fluid.default_startup_program() # 创建start_program -- 是我们运行的开始
# 以下是配置执行器执行空间(block)的操作部分--个人觉得,如果只是使用,记住使用规范即可
# program_guard接口配合使用python的 with 语句来将 with block 里的算子和变量添加进指定的全局主程序(main program)和启动程序(startup program)。
with fluid.program_guard(train_program, startup_program): # 以下执行的算子等都会放入到train_program->startup_program的block中
with fluid.unique_name.guard(): # 开启一个命名空间--常用program_guard一起使用
# 这里使用到build_model.py中的函数,执行模型和参数的配置,并返回相关的data变量
# 这个过程的算子都会被记录到(train_program, startup_program)对应的工作空间中
gw, loss, acc, pred = build_model(dataset,
config=config,
phase="train",
main_prog=train_program)
# 创建一个新的Program作为test_program
test_program = fluid.Program()
with fluid.program_guard(test_program, startup_program): # 含义如上,这里是开启(test_program, startup_program)的工作空间,并记录相应的算子、变量
with fluid.unique_name.guard(): # 开启一个命名空间
# 返回test的模型参数等
_gw, v_loss, v_acc, v_pred = build_model(dataset,
config=config,
phase="test",
main_prog=test_program)
# 总结——program_guard确定工作环境--unique_name开启一个相应的命名空间,相辅相成。
test_program = test_program.clone(for_test=True) # 克隆test_program
exe = fluid.Executor(place) # 创建一个解释器
epoch = 4000 # 训练轮次
exe.run(startup_program) # 执行器运行-->优先执行
# 将图数据变成 feed_dict 用于传入Paddle Excecutor
# 图数据原型:graph = pgl.graph.Graph(num_nodes=num_nodes, edges=edges, node_feat={"feat": node_feat}) # 创建图:节点数、边数据、以及节点特征的字典
feed_dict = gw.to_feed(dataset.graph) # 调用to_feed方法,将图数据转换为feed_dict,用于执行器的输入参数
# 训练开始
for epoch in range(epoch):
# Full Batch 训练 == 单batch_size训练--全数据一次投入
# 设定图上面那些节点要获取
# node_index: 训练节点的nid
# node_label: 训练节点对应的标签
feed_dict["node_index"] = np.array(train_index, dtype="int64") # 往feed_dict中添加键值对数据--每一个轮次数据都会重新赋值更新
feed_dict["node_label"] = np.array(train_label, dtype="int64")
train_loss, train_acc = exe.run(train_program, # 执行器执行--执行train_program这个program空间内的算子和参数
feed=feed_dict, # 传入的数据:graph..., node_index, node_label
fetch_list=[loss, acc], # 需要计算返回的数据
return_numpy=True) # 返回numpy数据
# Full Batch 验证 == 单batch_size验证--全数据一次投入
# 设定图上面那些节点要获取
# node_index: 训练节点的nid
# node_label: 训练节点对应的标签
feed_dict["node_index"] = np.array(val_index, dtype="int64") # 往feed_dict中添加键值对数据--每一个轮次数据都会重新赋值更新
feed_dict["node_label"] = np.array(val_label, dtype="int64")
val_loss, val_acc = exe.run(test_program, # 执行器执行--执行test_program这个program空间内的算子和参数
feed=feed_dict, # 传入的数据:graph..., node_index, node_label
fetch_list=[v_loss, v_acc], # 需要计算返回的数据
return_numpy=True) # 返回numpy数据
print("Epoch", epoch, "Train Acc", train_acc[0], "Valid Acc", val_acc[0]) # 打印训练数据
GCN需要用到的一个计算归一参数的方法
def get_norm(indegree):
'''
入度归一化函数: 返回一个浮点数类型的最小值为1.0的入度值序列
入度值:可以表示无向图中当前节点的邻边,而对于有向图则是指向当前节点的边数
'''
float_degree = L.cast(indegree, dtype="float32") # data的类型转换后的值返回给float_degree,值返回
float_degree = L.clamp(float_degree, min=1.0) # 值裁剪--将其中小于1的值赋值为1.0 -->个人的考虑是,添加自环边的入度
norm = L.pow(float_degree, factor=-0.5) # 倒数开根号,获取归一化的入度
# TODO: 度为float类型?
# CALL: 为了后边方便用于计算,float数据更适合后边所需的运算
return norm # 返回一个归一化的度,用于公式计算
GCN模型代码注解
class GCN(object):
"""Implement of GCN
"""
def __init__(self, config, num_class):
self.num_class = num_class # 节点种类
self.num_layers = config.get("num_layers", 1) # 模型层数
self.hidden_size = config.get("hidden_size", 64) # 中间层输出大小--不一定只有一层中间层哈--跟num_layers有关
self.dropout = config.get("dropout", 0.5) # fc层的drop率
self.edge_dropout = config.get("edge_dropout", 0.0) # 边drop率--为了获取一个忽略指定数目的随机子图(忽略部分边属性,然后生成一个新的子图用于训练--仅仅用于训练而已)
def forward(self, graph_wrapper, feature, phase):
'''
graph_wrapper: 一个图容器,用于存储图信息,并可以迭代训练与预测
feature:图的节点特征
phase:指令--train or eval or test等
功能:
实现将输入的图进行一个简单的处理--通过n层图卷积实现特征提取,然后经过一个dropout层抑制过拟合;
最后经过第二个图卷积获取类别数的输出,根据相应的处理得到需要的预测结果。
如:softmax进行一个类别处理,利用argmax的到分类的类别【具体过程详见build_model.py】
'
注意: 在GCN中,需要计算一个norm值,用于GCN的推断训练,以及后期的预测【详见GCN推导的公式】
'
'''
# GCN这个layer的返回值:张量shape:(num_nodes,hidden_size)
for i in range(self.num_layers): # 根据层数进行迭代
if phase == "train": # 训练模式--才有边drop
# 每次调用edge_drop(graph_wrapper, self.edge_dropout)结果可能不同
ngw = pgl.sample.edge_drop(graph_wrapper, self.edge_dropout) # 传入输入图,然后根据edge_dropout随机生成忽略某些边属性的新子图
norm = get_norm(ngw.indegree()) # 归一化出度--得到计算参数
else: # eval/test模式
ngw = graph_wrapper # 新子图就是原始图
norm = graph_wrapper.node_feat["norm"] #
# 利用pgl自带的网络进行配置
feature = pgl.layers.gcn(ngw, # 传入图
feature, # 相应的特征--训练过程中,最多只是对边有修改,并不涉及节点变化
self.hidden_size, # 输出大小
activation="relu", # 激活函数
norm=norm, # 归一化值--用于gcn公式计算
name="layer_%s" % i) # 层名称
# 在此后紧跟dropout进行,防止过拟合
# 根据给定的丢弃概率,dropout操作符按丢弃概率随机将一些神经元输出设置为0,其他的仍保持不变。
feature = L.dropout(
feature, # 上一级的输出
self.dropout, # drop率
dropout_implementation='upscale_in_train') # drop配置upscale_in_train表示,仅在训练时drop,评估预测不实现drop
# 将以上迭代部分做完后,再通过下边这个部分输出结果
if phase == "train": # 训练模式下
ngw = pgl.sample.edge_drop(graph_wrapper, self.edge_dropout) # 同前边一样的过滤一些边--基本效果同普通的dropout,这里作用于边而已
norm = get_norm(ngw.indegree())
else:
ngw = graph_wrapper
norm = graph_wrapper.node_feat["norm"]
# 再通过一层图卷积层
feature = conv.gcn(ngw,
feature,
self.num_class, # 输出结果就是我们实际节点训练或预测输出的类别情况:详见PS1
activation=None,
norm=norm,
name="output") # 最后返回一个shape[-1]=num_class的数据,然后我们对数据处理只需要经过一个softmax,再argmax就得到了预测得到了节点的类别了
return feature
class GAT(object):
"""Implement of GAT"""
def __init__(self, config, num_class):
self.num_class = num_class # 类别数
self.num_layers = config.get("num_layers", 1) # 层数
self.num_heads = config.get("num_heads", 8) # 多头注意力 -- 8*8尽量别改
self.hidden_size = config.get("hidden_size", 8) # 中间层输出大小--不一定只有一层中间层哈--跟num_layers有关
self.feat_dropout = config.get("feat_drop", 0.6) # 特征drop率
self.attn_dropout = config.get("attn_drop", 0.6) # 参数drop率
self.edge_dropout = config.get("edge_dropout", 0.0) # 边drop率
def forward(self, graph_wrapper, feature, phase):
'''
graph_wrapper: 一个图容器,用于存储图信息,并可以迭代训练与预测
feature:图的节点特征
phase:指令--train or eval or test等
功能:
首先根据运行模式,确定edge_drop率
然后进入网络叠加的循环中,进行pgl.sample.edge_drop后的子图获取,接着通过一个gat的layer--头尾8,输出大小为8,得到可叠加的特征输出
循环结束后,再通过一个头为1,输出大小为num_class的gat,得到输出结果
根据相应的处理得到需要的预测结果。
如:softmax进行一个类别处理,利用argmax的到分类的类别 【具体过程详见build_model.py】
'''
if phase == "train": # 训练模式才会进行边drop
edge_dropout = self.edge_dropout
else:
edge_dropout = 0
# 在GAT中,只需要简单进行遍历层叠加即可
# GAT这个layer的返回值:张量shape:(num_nodes,hidden_size * num_heads)
for i in range(self.num_layers): # 遍历num_layers层网络
ngw = pgl.sample.edge_drop(graph_wrapper, edge_dropout) # 随机边drop
# gat网络layer
feature = conv.gat(ngw, # 传入图容器--传入模型中的都不是简单的图,而是经过pgl中对应的图容器(不是pgl.Graph哦)
feature, # 特征参数--节点特征
self.hidden_size, # 输出大小
activation="elu", # 激活函数
name="gat_layer_%s" % i, # nameed
num_heads=self.num_heads, # 头数
feat_drop=self.feat_dropout, # 特征drop率
attn_drop=self.attn_dropout) # 参数drop率
# 最后再通过一层实现结果输出
ngw = pgl.sample.edge_drop(graph_wrapper, edge_dropout)
feature = conv.gat(ngw, # 图
feature, # 特征参数--节点特征
self.num_class, # 输出大小为类别数--用于预测
num_heads=1, # 头数变为1
activation=None, # 不需要激活函数
feat_drop=self.feat_dropout, # 特征drop率
attn_drop=self.attn_dropout, # 参数drop率
name="output")
return feature # 返回预测结果
# 新网络学习-APPNP
class APPNP(object):
"""Implement of APPNP"""
def __init__(self, config, num_class):
self.num_class = num_class # 类别数
self.num_layers = config.get("num_layers", 1) # 层数
self.hidden_size = config.get("hidden_size", 64) # 中间层输出大小--不一定只有一层中间层哈--跟num_layers有关
self.dropout = config.get("dropout", 0.5) # drop率——指的是fc层中用到的dopr率
self.alpha = config.get("alpha", 0.1) # alpha值---用于公式计算_论文中的超参数
self.k_hop = config.get("k_hop", 10) # k_hop值---网络传播次数
self.edge_dropout = config.get("edge_dropout", 0.0) # 边drop率
def forward(self, graph_wrapper, feature, phase):
'''
graph_wrapper: 一个图容器,用于存储图信息,并可以迭代训练与预测
feature:图的节点特征
phase:指令--train or eval or test等
功能:
首先根据运行模式,确定edge_drop率
然后进入循环遍历叠加网络层,这里不同于之前的网络---这里叠加的是fc层和drop操作--先drop,后fc
APPNP仅仅一个--并且由于APPNP层无法改变中间层大小,所以在传入前要把对应的feature转换为跟num_class相关的大小
根据相应的处理得到需要的预测结果。
如:softmax进行一个类别处理,利用argmax的到分类的类别 【具体过程详见build_model.py】
'''
if phase == "train": # 训练模式才会进行边drop
edge_dropout = self.edge_dropout
else:
edge_dropout = 0
# APPNP比较特殊,这里的num_layers层数是指前层网络fc的深度,而不是直接叠加APPNP层
for i in range(self.num_layers):
feature = L.dropout(
feature, # 需要drop的特征
self.dropout, # drop率
dropout_implementation='upscale_in_train') # 训练时drop,非训练不drop
feature = L.fc(feature, self.hidden_size, act="relu", name="lin%s" % i)
# 完成上述操作后,再重复一次相同的操作,最后调整输出为num_class
feature = L.dropout(
feature,
self.dropout,
dropout_implementation='upscale_in_train')
feature = L.fc(feature, self.num_class, act=None, name="output") # 为appnp做好准备
# APPNP这个layer的返回值:张量:shape(num_nodes,hidden_size)
# 不能修改输出的new_hidden_size,只能使用传入的feature的数据形状hidden_size(num_class)
feature = conv.appnp(graph_wrapper, # 传入图容器
feature=feature, # 特征--节点特征
edge_dropout=edge_dropout, # 边drop率
alpha=self.alpha, # alpha值_论文中的超参数
k_hop=self.k_hop) # 传播次数————这个部分太大,会显存爆炸哈
return feature
# 单APPNP网络
class SGC(object):
"""Implement of SGC"""
def __init__(self, config, num_class):
self.num_class = num_class # 类别数
self.num_layers = config.get("num_layers", 1) # 层数
def forward(self, graph_wrapper, feature, phase):
'''
graph_wrapper: 一个图容器,用于存储图信息,并可以迭代训练与预测
feature:图的节点特征
phase:指令--train or eval or test等
功能:
直接将图容器传入appnp层中,不经过任何处理,也不进行任何drop
然后再经过fc层得到合适形状的输出
根据相应的处理得到需要的预测结果。
如:softmax进行一个类别处理,利用argmax的到分类的类别 【具体过程详见build_model.py】
'''
# APPNP这个layer的返回值:张量:shape(num_nodes,hidden_size)
# 这里的hidden_size是输入feature的最低维度大小
feature = conv.appnp(graph_wrapper,
feature=feature,
edge_dropout=0, # drop为零
alpha=0, # 论文中的超参数
k_hop=self.num_layers)
feature.stop_gradient=True # 在这里停止梯度计算--也就是之后的运算计算相应的梯度,用于优化
feature = L.fc(feature, self.num_class, act=None, bias_attr=False, name="output") # 转换形状输出即可
return feature
# 新网络模型学习——GCNII
class GCNII(object):
"""Implement of GCNII"""
def __init__(self, config, num_class):
self.num_class = num_class # 类别数
self.num_layers = config.get("num_layers", 1) # 层数
self.hidden_size = config.get("hidden_size", 64) # 中间层输出大小--不一定只有一层中间层哈--跟num_layers有关
self.dropout = config.get("dropout", 0.6) # drop率——既是fc的,也是GCNII的drop
self.alpha = config.get("alpha", 0.1) # alpha值——论文中的超参数
self.lambda_l = config.get("lambda_l", 0.5) # labda_l值——论文中的超参数
self.k_hop = config.get("k_hop", 64) # 传播次数
self.edge_dropout = config.get("edge_dropout", 0.0) # 边drop率
def forward(self, graph_wrapper, feature, phase):
'''
graph_wrapper: 一个图容器,用于存储图信息,并可以迭代训练与预测
feature:图的节点特征
phase:指令--train or eval or test等
功能:
首先根据运行模式,确定edge_drop率
然后进入循环遍历叠加网络层,这里不同于之前的网络---这里叠加的是fc层和drop操作--先fc,再drop
GCNII仅仅一个--并且由于GCNII层无法改变中间层大小,所以计算后要把对应的feature转换为跟num_class相关的大小--又要再次利用fc来完成
根据相应的处理得到需要的预测结果。
如:softmax进行一个类别处理,利用argmax的到分类的类别 【具体过程详见build_model.py】
'''
if phase == "train": # 训练模式才会进行边drop
edge_dropout = self.edge_dropout
else:
edge_dropout = 0
# GCNII也比较特殊,这里的num_layers层数指的是前层网络fc的深度,而不是直接叠加GCNII层
for i in range(self.num_layers):
feature = L.fc(feature, self.hidden_size, act="relu", name="lin%s" % i) # 跟APPNP相比--GCNII先经过fc再通过dropout
feature = L.dropout(
feature,
self.dropout,
dropout_implementation='upscale_in_train') # 训练时drop,否则不drop
# GCNII这个layer的返回值:张量shape: (num_nodes, hidden_size)
# GCNII也不能改变特征输出的大小
feature = conv.gcnii(graph_wrapper, # 图容器
feature=feature, # 特征数据
name="gcnii", # named
activation="relu", # 激活函数
lambda_l=self.lambda_l, # 论文中的超参数--用于内部公式计算
alpha=self.alpha, # 论文中的超参数
dropout=self.dropout, # drop率
k_hop=self.k_hop) # 传播次数
feature = L.fc(feature, self.num_class, act=None, name="output") # 再经过fc获得指定大小的输出
return feature
import pgl
import model
from pgl import data_loader
import paddle.fluid as fluid
import numpy as np
import time
'''build_model整个流程的说明:
1. 首先明确传入参数(dataset, config, phase, main_prog)
1. dataset: 一个简单的图
2. config: 配置参数
1. 包括模型名称,以及相关的初始化参数--根据自己的模型配置就好
3. phase: 工作指令--train-训练模式,其它为非训练模式
2. 主要工作流程
1. 首先将传入的图放入一个图容器,此时传入图和节点特征即可
2. 利用python自带的getattr读取model.py中的类,并返回这个类
3. 利用返回的类实例一个模型
这后边就涉及静态的参数创建了:
4. 将图容器传入以及其它对于模型的forward必须的参数--得到一个返回值--logits,这个输出信息用于预测等 -- logits是经过模型层返回的,也是一个data
5. 创建一些训练和预测所必须的参数--node_index: 节点索引集【需回到notebook中对照理解】;node_label,用于计算acc,loss等
【注意,这里涉及到模型返回的参数也好,其它的loss以及node_index、node_label都是一个静态图下的data,要通过build_model返回之后,经过执行器运行时才会有实际的值】
6. 接着创建loss方法以及返回loss_data----以及添加acc方法,计算acc_data
7. 添加一个softmax获取实际类别
8. 接着平均化损失
9. 如果是训练模式,还会单独添加优化器,返回优化器对象,并优化参数
**: 切记,这里使用方法创建的变量都是静态图中的data,需要放入执行器中运行才有实际的意义
'''
def build_model(dataset, config, phase, main_prog):
'''
dataset: 就是一个图
config: 来自以下代码
from easydict import EasyDict as edict
config = {
"model_name": "APPNP",
"num_layers": 3,
"dropout": 0.5,
"learning_rate": 0.0002,
"weight_decay": 0.0005,
"edge_dropout": 0.00,
}
config = edict(config)
main_prog:执行器对象(program)
'''
gw = pgl.graph_wrapper.GraphWrapper(
name="graph",
node_feat=dataset.graph.node_feat_info()) # 创建图容器
GraphModel = getattr(model, config.model_name) # 获取model中关于config.model_name指定的模型配置--即在model.py中,getattr获取的对象属性就是相应的模型类
m = GraphModel(config=config, num_class=dataset.num_classes) # 利用返回的模型类,实例一个对象--传入配置信息,以及节点类别数(用于预测分类)
logits = m.forward(gw, gw.node_feat["feat"], phase) # 调用模型对象,进行前向计算--传入图,节点特征,执行指令--phase为train或者false
# 补充说明:m.forward得到的是一个shape为[batch, num_class]的序列--后边用于softmax处理再进行类别获取
# Take the last
# 创建节点data
node_index = fluid.layers.data(
"node_index",
shape=[None, 1],
dtype="int64",
append_batch_size=False)
# 创建节点标签data
node_label = fluid.layers.data(
"node_label",
shape=[None, 1], # 【batch,1】
dtype="int64",
append_batch_size=False)
# 根据索引 node_index 获取输入logits的最外层维度的条目,并将它们拼接在一起
# 即: eg: node_index=[1, 2], logits=[[1, 2], [2, 3], [3, 4]]
# 那么拼接的对象就是——[1, 2], [2, 3], [3, 4]
# 然后根据node_index索引拼接,选择[2, 3], [3, 4]进行拼接,然后维数不变的返回[[2, 3], [3, 4]]
pred = fluid.layers.gather(logits, node_index) # node_index是一个data,暂时是不作用的,要等exe执行器运行时才会传入信息---这里最后作用的结果就是根据index索引相应的值
loss, pred = fluid.layers.softmax_with_cross_entropy(
logits=pred, label=node_label, return_softmax=True) # 输入pred, 与node_label,进行交叉熵计算--并返回通过sortmax的pred数据和loss
acc = fluid.layers.accuracy(input=pred, label=node_label, k=1) # 准确率计算
pred = fluid.layers.argmax(pred, -1) # 利用argmax确定具体的类别
loss = fluid.layers.mean(loss) # 计算平均损失
if phase == "train": # 训练模式才进行优化
# Adam优化器:利用梯度的一阶矩估计和二阶矩估计动态调整每个参数的学习率
adam = fluid.optimizer.Adam(
learning_rate=config.learning_rate, # 学习率
regularization=fluid.regularizer.L2DecayRegularizer( # L2权重衰减正则化
regularization_coeff=config.weight_decay)) # 正则化系数--就是我们在notbook中config设置的weight_decay
adam.minimize(loss) # 通过minimize获取实际损失--即loss_all / batchsize
return gw, loss, acc, pred # 返回训练后的图、损失、精度、预测data
训练少许经验: