我的项目链接:[PGL图学习之基于UniMP算法的论文援用网络节点分类工作[系列九]](https://aistudio.baidu.com/ai...)
1.常规赛:图神经网络入门节点分类介绍
(1)赛题介绍
图神经网络(Graph Neural Network)是一种专门解决图构造数据的神经网络,目前被广泛应用于举荐零碎、金融风控、生物计算中。图神经网络的经典问题次要有三种,包含节点分类、连贯预测和图分类三种,本次较量是次要让同学们相熟如何图神经网络解决节点分类问题。
在过来的一个世纪里,迷信出版物的数量每12年减少近一倍,对每一种出版物的主题及畛域进行主动分类已成为当下非常重要的工作。本次工作的指标是预测未知论文的主题类别,如软件工程,人工智能,语言计算和操作系统等。较量所选35个畛域标签已失去论文作者和arXiv版主确认并标记。
本次较量选用的数据集为arXiv论文援用网络——ogbn-arixv数据集的子集。ogbn-arixv数据集由大量的学术论文组成,论文之间的援用关系造成一张微小的有向图,每一条有向边示意一篇论文援用另一篇论文,每一个节点提供100维简略的词向量作为节点特色。在论文援用网络中,咱们已对训练集对应节点做了论文类别标注解决。本次工作心愿参赛者通过已有的节点类别以及论文之间的援用关系,预测未知节点的论文类别。
(2)数据形容
本次赛题数据集由学术网络图形成,该图会给出每个节点的特色,以及节点与节点间关系(训练集节点的标注后果已给出)。
1.1 数据集简介
1.学术网络图数据:
该图蕴含164,7958条有向边,13,0644个节点,参赛者报名胜利后即可通过较量数据集页面提供edges.csv以及feat.npy下载并读取数据。图上的每个节点代表一篇论文,论文从0开始编号;图上的每一条边蕴含两个编号,例如 3,4代表第3篇论文援用了第4篇论文。图结构能够参照AiStudio上提供的基线零碎我的项目理解数据读取办法。
2.训练集与测试集:
训练集的标注数据有70235条,测试集的标注数据有37311条。训练数据给定了论文编号与类别,如3,15 代表编号为3的论文类别为15。测试集数据只提供论文编号,不提供论文类别,须要参赛者预测其类别。
具体数据介绍:
| 数据集 | 简介|
| -------- | -------- |
| edges.csv | 边数据:用于标记论文间援用关系 |
| feat.npy | 节点数据:每个节点含100维特色 |
| train.csv | 训练集数据 |
| test.csv | 测试集数据 |
1.图数据:
feat.npy为Numpy格局存储的节点特色矩阵,Shape为(130644, 100),即130644个节点,每个节点含100维特色。其中 feat.npy能够用numpy.load(“feat.npy”)读取。edges.csv用于标记论文援用关系,为无向图,且由两列组成,没有表头。
字段 | 阐明 |
---|---|
第一列 | 边的初始节点 |
第二列 | 边的终止节点 |
2.训练集: train.csv
字段 | 阐明 |
---|---|
nid | 训练节点在图上的Id |
label | 训练节点的标签(类别编号从0开始,共35个类别) |
3.测试集: test.csv
字段 | 阐明 |
---|---|
nid | 测试节点在图上的Id |
相干材料
飞桨PGL图学习框架飞桨PGL图学习框架根本可能笼罩大部分的图网络应用,包含图示意学习以及图神经网络。
节点分类实例分享蕴含多种援用网络模型,如GCN、GAT、GCNII、APPNP等。
UniMP模型对立消息传递模型UniMP,同时实现了节点的特色与标签传递,显著晋升了模型的泛化成果。
提交内容与格局
最终提交的submission.csv 格局如下:
字段 | 阐明 |
---|---|
nid | 测试集节点在图上的id |
label | 测试集的节点类别 |
特地提醒: 提交的后果必须是类别的id,不能是概率。
提交样例:
nid | label |
---|---|
2 | 34 |
3 | 1 |
4 | 15 |
… | … |
留神:选手提交的csv文件的第一行为”nid,label”,程序调转将视为有效答案。接下来每一行的两个数字顺次示意测试集提供的节点id以及对应的预测论文类别,留神给出的预测类别id要属于给定范畴值且必须是整数。提交的数据行数要与test.csv统一。
2.BaseLine代码解说
2.1 代码整体逻辑
- 读取提供的数据集,蕴含构图以及读取节点特色(用户可本人改变边的结构形式)
- 配置化生成模型,用户也能够依据教程进行图神经网络的实现。
- 开始训练
- 执行预测并产生后果文件
环境配置
该我的项目依赖飞桨paddlepaddle==1.8.4, 以及pgl==1.2.0。请依照版本号下载对应版本就可运行。
2.1.1 Config局部注解
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", #模型名称,能够参考model.py、modelfied.py文件外面有 gcn gat appnp unimp等模型 "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便当字典的读取
2.1.2 数据读取与解决局部
创立一个命名索引元组
from collections import namedtuple'''模块阐明: collections是一个python根底数据结构的裁减库: 蕴含以下几种扩大: 1. namedtuple(): 生成能够应用名字来拜访元素内容的tuple子类 2. deque: 双端队列,能够疾速的从另外一侧追加和推出对象 3. Counter: 计数器,次要用来计数 4. OrderedDict: 有序字典 5. defaultdict: 带有默认值的字典 而当初用到的namedtuple(): 则是生成能够应用名字来拜访元素内容的tuple子类 eg: websites = [ ('math', 98, '张三'), ('physics', 74, '李四'), ('english', 85, '王二') ] Website = namedtuple('Website', ['class', 'score', 'name']) for website in websites: website = Website._make(website) print website show: Website(class='math', score=98, name='张三') Website(class='physics', score=74, name='李四') Website(class='english', score=85, name='王二')'''#创立一个能够应用名字来拜访元素内容的tuple--作为Dataset数据集#蕴含图、类别数、训练索引集、训练标签、验证索引集、验证标签、测试索引集Dataset = namedtuple("Dataset", ["graph", "num_classes", "train_index", "train_label", "valid_index", "valid_label", "test_index"])
2.1.3 边数据的加载与解决
#加载边数据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
2.1.4 数据的残缺加载与解决
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数据
2.1.5 数据读取与宰割
这一部分的宰割和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创立一个空白矩阵,并指明数据类型
2.1.6 模型加载局部
为了防止合成太多,这里也蕴含了执行器相干的局部代码正文!
import pglimport model # model.pyimport paddle.fluid as fluidimport numpy as npimport timefrom build_model import build_model # build_model.pyuse_gpu = Trueplace = fluid.CUDAPlace(0) if use_gpu else fluid.CPUPlace() # 工作环境--这里有批改--须要用cpu只须要把use_gpu设置为False即可train_program = fluid.default_main_program() # 创立主program -- paddle动态图都是在相应的program中运行的 -- 通常为startstartup_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_programtest_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_programexe = fluid.Executor(place) # 创立一个解释器
2.1.7. 模型训练过程
这个局部就绝对比较简单,就是利用执行器喂参数和算子就能够啦!
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]) # 打印训练数据
2.1.8 模型进阶
- 本人入手实现图神经网络
这里以想本人实现一个CustomGCN为例子,首先,咱们在model.py 创立一个类CustomGCN
import paddle.fluid.layers as Lclass CustomGCN(object): """实现本人的CustomGCN""" def __init__(self, config, num_class): # 分类的数目 self.num_class = num_class # 分类的层数,默认为1 self.num_layers = config.get("num_layers", 1) # 中间层 hidden size 默认64 self.hidden_size = config.get("hidden_size", 64) # 默认dropout率0.5 self.dropout = config.get("dropout", 0.5) def forward(self, graph_wrapper, feature, phase): """定义前向图神经网络 graph_wrapper: 图的数据的容器 feature: 节点特色 phase: 标记训练或者测试阶段 """ # 通过Send Recv来定义GCN def send_func(src_feat, dst_feat, edge_feat): # 发送函数简略将输出src发送到输入节点dst。 return { "output": src_feat["h"] } def recv_func(msg): # 对音讯函数进行简略求均值 return L.sequence_pool(msg["output"], "average") # 调用发送 message = graph_wrapper.send(send_func, nfeat_list=[ ("h", feature)]) # 调用接管 output = graph_wrapper.recv(message, recv_func) # 对输入过一层MLP output = L.fc(output, size=self.num_class, name="final_output")
而后,咱们只有改一下本notebook脚本的config,把model_type改成CustomGCN就能跑起来了。
config = { "model_name": "CustomGCN",}
- 残差-GAT改写
class ResGAT(object): """Implement of ResGAT""" 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) self.hidden_size = config.get("hidden_size", 8) self.feat_dropout = config.get("feat_drop", 0.6) self.attn_dropout = config.get("attn_drop", 0.6) self.edge_dropout = config.get("edge_dropout", 0.0) def forward(self, graph_wrapper, feature, phase): # feature [num_nodes, 100] if phase == "train": edge_dropout = self.edge_dropout else: edge_dropout = 0 feature = L.fc(feature, size=self.hidden_size * self.num_heads, name="init_feature") for i in range(self.num_layers): ngw = pgl.sample.edge_drop(graph_wrapper, edge_dropout) res_feature = feature # res_feature [num_nodes, hidden_size * n_heads] feature = conv.gat(ngw, feature, self.hidden_size, activation=None, name="gat_layer_%s" % i, num_heads=self.num_heads, feat_drop=self.feat_dropout, attn_drop=self.attn_dropout) # feature [num_nodes, num_heads * hidden_size] feature = res_feature + feature # [num_nodes, num_heads * hidden_size] + [ num_nodes, hidden_size * n_heads] feature = L.relu(feature) feature = L.layer_norm(feature, name="ln_%s" % i) ngw = pgl.sample.edge_drop(graph_wrapper, edge_dropout) feature = conv.gat(ngw, feature, self.num_class, num_heads=1, activation=None, feat_drop=self.feat_dropout, attn_drop=self.attn_dropout, name="output") return feature
更多进阶模型就在2.2节
2.2模型代码解说:model.py的内容解析
2.2.1 GCN模型代码解说
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
2.2.2 GAT模型代码解说
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 # 返回预测后果
2.2.3 APPNP模型代码解说
#新网络学习-APPNPclass 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
2.2.4 SGC模型代码解说——(APPNP模型补充)
#单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
2.2.5 GCNII模型代码解说
#新网络模型学习——GCNIIclass 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
2.3 build_model.py的内容解析——能够学习的重点
这样的形式搭建加载模型和算子,能使后边的工作轻松不少,值得学习哦!
import pglimport modelfrom pgl import data_loaderimport paddle.fluid as fluidimport numpy as npimport 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
3.Res_Unimp_Large算法实现
#导入相干包!pip install --upgrade python-dateutil!pip install easydict!pip install pgl==1.2.1 !pip install pandas>=0.25# !pip install pyarrow==0.13.0!pip install chardet==3.0.4import sys import pglimport paddle.fluid as fluidimport numpy as npimport timeimport pandas as pd
3.1图网络配置
这里曾经有很多弱小的模型配置了,你能够尝试简略的改一下config的字段。
例如,换成GAT的配置
config = { "model_name": "GAT", "num_layers": 1, "dropout": 0.5, "learning_rate": 0.01, "weight_decay": 0.0005, "edge_dropout": 0.00,}
from easydict import EasyDict as edictconfig = { "model_name": "res_unimp_large", "num_layers": 3, "hidden_size": 64, "heads": 2, "learning_rate": 0.001, "dropout": 0.3, "weight_decay": 0.0005, "edge_dropout": 0.3, "use_label_e": True}config = edict(config)
3.2数据加载模块
这里次要是用于读取数据集,包含读取图数据构图,以及训练集的划分。
from collections import namedtupleDataset = namedtuple("Dataset", ["graph", "num_classes", "train_index", "train_label", "valid_index", "valid_label", "test_index"])def load_edges(num_nodes, self_loop=True, add_inverse_edge=True): # 从数据中读取边 edges = pd.read_csv("work/edges.csv", header=None, names=["src", "dst"]).values if add_inverse_edge: edges = np.vstack([edges, edges[:, ::-1]]) if self_loop: src = np.arange(0, num_nodes) dst = np.arange(0, num_nodes) self_loop = np.vstack([src, dst]).T edges = np.vstack([edges, self_loop]) return edgesdef load(): # 从数据中读取点特色和边,以及数据划分 node_feat = np.load("work/feat.npy") num_nodes = node_feat.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() norm = np.maximum(indegree.astype("float32"), 1) norm = np.power(norm, -0.5) graph.node_feat["norm"] = np.expand_dims(norm, -1) df = pd.read_csv("work/train.csv") # 打乱程序 df.sample(frac=1.0) node_index = df["nid"].values node_label = df["label"].values train_part = int(len(node_index) * 0.8) train_index = node_index[:train_part] train_label = node_label[:train_part] valid_index = node_index[train_part:] valid_label = node_label[train_part:] test_index = pd.read_csv("work/test.csv")["nid"].values 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 datasetdataset = load()train_index = dataset.train_indextrain_label = np.reshape(dataset.train_label, [-1 , 1])train_index = np.expand_dims(train_index, -1)val_index = dataset.valid_indexval_label = np.reshape(dataset.valid_label, [-1, 1])val_index = np.expand_dims(val_index, -1)test_index = dataset.test_indextest_index = np.expand_dims(test_index, -1)test_label = np.zeros((len(test_index), 1), dtype="int64")
3.3 组网模块
这里是组网模块,目前曾经提供了一些预约义的模型,包含GCN, GAT, APPNP等。能够通过简略的配置,设定模型的层数,hidden_size等。你也能够深刻到model.py外面,去奇思妙想,写本人的图神经网络。
import pglimport modelimport paddle.fluid as fluidimport numpy as npimport timefrom build_model import build_modelimport paddle# # 应用CPU# place = fluid.CPUPlace()# 应用GPUplace = fluid.CUDAPlace(0)train_program = fluid.default_main_program()startup_program = fluid.default_startup_program()with fluid.program_guard(train_program, startup_program): with fluid.unique_name.guard(): gw, loss, acc, pred = build_model(dataset, config=config, phase="train", main_prog=train_program)test_program = fluid.Program()with fluid.program_guard(test_program, startup_program): with fluid.unique_name.guard(): _gw, v_loss, v_acc, v_pred = build_model(dataset, config=config, phase="test", main_prog=test_program)test_program = test_program.clone(for_test=True)exe = fluid.Executor(place)
3.4 开始训练过程
图神经网络采纳FullBatch的训练形式,每一步训练就会把所有整张图训练样本全副训练一遍。
import osuse_label_e = Truelabel_rate = 0.625epoch = 1000exe.run(startup_program)max_val_acc = 0# 这里能够复原训练pretrained = Falseif pretrained: def name_filter(var): res = var.name in os.listdir('./output') return res fluid.io.load_vars(exe, './output',predicate=name_filter) max_val_acc = 0.756earlystop = 0# 将图数据变成 feed_dict 用于传入Paddle Excecutorfeed_dict = gw.to_feed(dataset.graph)for epoch in range(epoch): # Full Batch 训练 # 设定图下面那些节点要获取 # node_index: 未知label节点的nid # node_label: 未知label # label_idx: 已知label节点的nid # label: 已知label if use_label_e: # 在训练集中抽取局部数据,其Label已知,并能够输出网络训练 train_idx_temp = np.array(train_index, dtype="int64") train_lab_temp = np.array(train_label, dtype="int64") state = np.random.get_state() np.random.shuffle(train_idx_temp) np.random.set_state(state) np.random.shuffle(train_lab_temp) label_idx=train_idx_temp[:int(label_rate*len(train_idx_temp))] unlabel_idx=train_idx_temp[int(label_rate*len(train_idx_temp)):] label=train_lab_temp[:int(label_rate*len(train_idx_temp))] unlabel=train_lab_temp[int(label_rate*len(train_idx_temp)):] feed_dict["node_index"] = unlabel_idx feed_dict["node_label"] = unlabel feed_dict['label_idx']= label_idx feed_dict['label']= label else: feed_dict["node_label"] = np.array(train_label, dtype="int64") feed_dict["node_index"] = np.array(train_index, dtype="int64") train_loss, train_acc = exe.run(train_program, feed=feed_dict, fetch_list=[loss, acc], return_numpy=True) # Full Batch 验证 # 设定图下面那些节点要获取 # node_index: 未知label节点的nid # node_label: 未知label # label_idx: 已知label节点的nid # label: 已知label feed_dict["node_index"] = np.array(val_index, dtype="int64") feed_dict["node_label"] = np.array(val_label, dtype="int64") if use_label_e: feed_dict['label_idx'] = np.array(train_index, dtype="int64") feed_dict['label'] = np.array(train_label, dtype="int64") val_loss, val_acc = exe.run(test_program, feed=feed_dict, fetch_list=[v_loss, v_acc], return_numpy=True) print("Epoch", epoch, "Train Acc", train_acc[0], "Valid Acc", val_acc[0]) # 保留历史最优验证精度对应的模型 if val_acc[0] > max_val_acc: max_val_acc = val_acc[0] print(val_acc[0]) fluid.io.save_persistables(exe, './output', train_program) # 训练精度继续大于验证精度,完结训练 if train_acc[0] > val_acc[0]: earlystop += 1 if earlystop == 40: break else: earlystop = 0
局部后果展现:
Epoch 987 Train Acc 0.7554459 Valid Acc 0.7546095Epoch 988 Train Acc 0.7537374 Valid Acc 0.75717235Epoch 989 Train Acc 0.75497127 Valid Acc 0.7573859Epoch 990 Train Acc 0.7611409 Valid Acc 0.75653166Epoch 991 Train Acc 0.75316787 Valid Acc 0.75489426Epoch 992 Train Acc 0.749561 Valid Acc 0.7547519Epoch 993 Train Acc 0.7571544 Valid Acc 0.7551079Epoch 994 Train Acc 0.7516492 Valid Acc 0.75581974Epoch 995 Train Acc 0.7563476 Valid Acc 0.7563181Epoch 996 Train Acc 0.7504627 Valid Acc 0.7538976Epoch 997 Train Acc 0.7476152 Valid Acc 0.75439596Epoch 998 Train Acc 0.7539272 Valid Acc 0.7528298Epoch 999 Train Acc 0.7532153 Valid Acc 0.75396883
3.5对测试集进行预测-生成提交文件
训练实现后,咱们对测试集进行预测。预测的时候,因为不晓得测试汇合的标签,咱们随便给一些测试label。最终咱们取得测试数据的预测后果。
feed_dict["node_index"] = np.array(test_index, dtype="int64")feed_dict["node_label"] = np.array(test_label, dtype="int64") #假标签test_prediction = exe.run(test_program, feed=feed_dict, fetch_list=[v_pred], return_numpy=True)[0]submission = pd.DataFrame(data={ "nid": test_index.reshape(-1), "label": test_prediction.reshape(-1) })submission.to_csv("submission.csv", index=False)
4.计划优化
次要改良思路嘛是在对预测后果进行简略投票和绝对多数投票下了点功夫,我只用了10个预测样本进行交融,预测的样本更多的话,提交后果的分数可能也越高,具体流程如下图所示。
留神图中简略投票和绝对多数投票的样本是任意数目
优良计划参考:
翻新点:
1.对node_feat进行循环解决;
2.减少标签节点
https://aistudio.baidu.com/ai...
https://aistudio.baidu.com/ai...
https://aistudio.baidu.com/ai...
4.2 简略投票(本地)
这里将训练进去的文件进行简略投票import csvfrom collections import Counterdef vote_merge(filelst): result = {} fw = open('D:/subexl/76/merge.csv', encoding='utf-8', mode='w', newline='') csv_writer = csv.writer(fw) csv_writer.writerow(['nid', 'label']) for filepath in filelst: cr = open(filepath, encoding='utf-8', mode='r') csv_reader = csv.reader(cr) for i, row in enumerate(csv_reader): if i == 0: continue idx, cls = row if idx not in result: result[idx] = [] result[idx].append(cls) for nid, clss in result.items(): counter = Counter(clss) true_cls = counter.most_common(1) csv_writer.writerow([nid, true_cls[0][0]])if __name__ == '__main__': vote_merge([ # "D:/subexl/75/0.76436.csv", # "D:/subexl/75/0.7635.csv", # "D:/subexl/75/0.75666.csv", # "D:/subexl/75/0.75736.csv", # "D:/subexl/75/0.75755.csv", # "D:/subexl/75/0.75801.csv", # "D:/subexl/75/0.75868.csv", # "D:/subexl/75/0.75978.csv", # "D:/subexl/75/0.76171.csv", # "D:/subexl/75/0.76288.csv", # "D:/subexl/75/0.76412.csv", # "D:/subexl/75/0.759664.csv", # "D:/subexl/75/0.75973517.csv", # "D:/subexl/75/0.75980633.csv", # "D:/subexl/75/0.76322347.csv", # "D:/subexl/75/0.763223471.csv", "D:/subexl/76/0.75736.csv", "D:/subexl/76/0.75755.csv", "D:/subexl/76/0.75801.csv", "D:/subexl/76/0.75868.csv", "D:/subexl/76/0.75978.csv", "D:/subexl/76/0.76436.csv", "D:/subexl/76/0.759664.csv", "D:/subexl/76/0.75973517.csv", "D:/subexl/76/0.75980633.csv", "D:/subexl/76/0.76322347.csv", "D:/subexl/76/0.763223471.csv", "D:/subexl/76/submission.csv", ])
4.3 绝对多数投票(本地)
绝对多数投票法很简略,分类器的投票数超过半数便认可预测后果,否则回绝。将简略投票后的后果再与新训练进去的文件进行绝对多数投票。
在这里,我将所有提交文件的名称改为“测试精度.csv”,例如0.76087.csv;而后依照精度大小排序,首先应用绝对多数投票法进行投票,若某一投票不过半数,间接取精度最高csv的预测后果。
import osimport numpy as npfrom scipy import statsimport pandas as pd#path放的是你所有的提交文件path = 'D:/subexl/75'filelist = os.listdir(path)#上面这行代码依照测试精度进行排序filelist = sorted(filelist, key= lambda x:float(x[:-4]), reverse=True)print(filelist)#n为测试集条目数n = 37311num_files = len(filelist)a = np.zeros([num_files, n], dtype='i8')for i, filename in enumerate(filelist): filepath = os.path.join(path, filename) a[i, :] = np.array(np.loadtxt(filepath, dtype=int, delimiter=',', skiprows=1, usecols=1, encoding='utf-8'))res = np.zeros([n, 1])for j in range(n): counts = np.bincount(a[:,j]) maxnum = np.argmax(counts) # 判读最大投票数是否过半数 if counts[maxnum] > num_files//2: res[j] = maxnum else: res[j] = a[0,j]#写入文件data=pd.read_csv('D:/subexl/75/0.75897.csv')data['label'] = resdata.to_csv('E:/submission.csv',index=False,header=True)
5.总结
APPNP这个网络,貌似间接应用体现不是很好--我这边的话是这样;当然改一改可能会有奇观
对于后边两个model中最初两个模型,k_hop这个参数,设计的大的化可能会导致显存溢出哦?尽管对精度有些晋升然而也要看本身想要什么后果啦!
【GCNII——理论两层, "k_hop", 64, "hidden_size", 64; 就会爆显存哦--notebook:16G——GPU】
不如先跑最简略的那个模型,把学习率调小一点,层数2~3,epoch的话4000左右吧,最初还是要看学习率理论大小。
【学习率太小可能在某些地位迭代不不上来,就是loss可能在一个陷阱里彷徨,下不去!】
- 学习了easydict库和collections库!
- 从官网数据处理局部,学习到利用np的vstack实现自环边以及晓得有向边如何增加反向边的数据——这样的一种代码实现边数据转换的形式!
- 从模型加载局部,学习了多program执行的操作,理清了program与命名空间之间的分割!
- 从模型训练局部,强化了执行器执行时,须要传入正确的program以及feed_dict,在pgl中能够应用图Graph自带的to_feed办法返回一个feed_dict数据字典作为初始数据,后边再按需增加新数据!
- 从model.py学习了模型的组网,以及pgl中conv类下的网络模型办法的调用,不便组网!
- 重点来了:从build_model.py学习了模型的参数的加载组合,实现对立的解决和返回对立的算子以及参数!