直链图 Link 预估 方法论与DGL 源代码两栖作战
书断开文,首段 备受瞩目好文有条理认知,直链图上 Node 展开分类方法论与DGL源代码两栖作战 中,他们讲了 直链图结点展开分类重回 各项任务,而在从前的系列产品该文中,他们也相继如是说了 同画法上的结点展开分类重回各项任务、边展开分类重回各项任务和镜像预估 各项任务。接著从前的诗歌创作烙印,这一则就罢了 直链图上镜像预估各项任务 了,一起上看一看吧 ~ go go go
(1) 直链图上镜像预估此基础认知
镜像预估,简而言之,就是图中边与否存有 的预估,其本质上是把可视化白苞展开分类各项任务,来预估边存有的机率。但 前述存有的边与乱数取样的边 构筑成了差值样品,外间不须要输出条码,自学的是 图内部结构另一方面 的重要信息,这儿他们把归因于 无监督管理机器自学。在GraphSage与DGL同时实现同画法 Link 预估,易懂好文G360A 中,他们详尽如是说了 同画法上的镜像预估,把以此类推到直链图上方可。在直链图上展开镜像预估,他们采用考量边两边的结点的Embeding重要信息,依照其关联性与关联性等不利因素 ,来对 边的存有与否 展开推论。
这儿 须要特别注意 的是:尽管是用的 边两边的2个结点 的重要信息,但从 体能训练多轮数 上看,依照 他们从前该文 如是说的科学知识 来认知,这2个结点也是 均紧密结合的周遭结点的局部性内部结构与自上而下物理性质 的并紧密结合图上空间内部结构做出的推论。
从 GraphSage与DGL同时实现同画法 Link 预估,易懂好文G360A 中,他们也了解到 图上镜像预估属于 无监督管理机器自学,这和上一则该文如是说的直链图上结点展开分类重回预估各项任务的不同非常相似,不同仅仅是在他们须要对镜像预估展开负边的取样。特别注意这儿是 边取样, 而上文用的是结点取样,接口是不一样的,同时这两个各项任务的 损失与预估打分 函数也是不同的。
上文 他们已经说过镜像预估 是无监督管理机器自学,外间不用输出条码,模型自学的其实是基于图的另一方面内部结构与数据特性来推论边与否存有的机器自学各项任务。基于此,他们应该明白: 既然自学的是图上的某边与否存有,则他们仅仅 建图的时候提供各类结点的关系来建图 方可,可以依照用户历史行为日志来构筑直链图的边,例如用户购买了某件商品就有用户-》购买-〉商品的关系存有,就可以构筑一条边。
依照前述存有的结点关系建边构成正样品,而图上乱数取样的边组成负样品,基于 间隔比较近的结点特性也相似的 同源偏好假设 来构筑损失展开模型体能训练 ,可以说是 图上镜像(关系)预估的精髓 了。而镜像预估也是现在的很多互联网大厂 采用的最多的一种 可视化方式,不须要显示的 条码 就可以自学到 须要 的 各类结点 的 Embeding, 非常 nice !!!
从前的该文对 同构/直链图 的各种机器自学各项任务均展开了 详尽的阐述,本文这儿就不在继续赘述了。
感兴趣的同学可以去 上去 阅读历史该文。这儿他们直接开始本节 基于DGL和RGCN同时实现的直链图上 镜像预估 机器自学各项任务的代码如是说吧~
(2) 代码时光
为了提高该文的 可读性与降低认知难度 ,也保持每一则 该文的 相对对立性 ,让读者从任何一则该文进来都是一则完整的该文,本文和上文重复的代码,这儿依然会 赘述 着展开如是说。阅读过上一则该文的同学可以自行跳过哈,下面,就让他们开始coding 吧~
开篇先吼一嗓子 , talk is cheap , show me the code !!!
本文的代码讲的是 基于DGL和RGCN同时实现的直链图上镜像预估 各项任务,整个源代码流程是一个 小型的工业可用的工程 ,基于dgl同时实现,觉得有用赶紧 收藏转发 吧~
(2.1) 数据准备 (和上文相同)
他们 假设 可以输出类似于这样的数据, 其中每2列对应这一种关系,例如 用户2352193 购买了商品CEEC9EBF7,用户用了IP 174.74.201.9登录了账号,用户用IP 174.74.201.9 购买了商品 CEEC9EBF7, 最终的镜像各项任务预估是预估用户的购买意愿,用户到该商品之间,与否会有边存有
更 常规的一种用法是,基于无监督管理的体能训练,得到图上各个结点的 Embeding ,这是非常有价值的中间数据产出,可以为他们其他的机器自学各项任务提供有力辅助。
他们可以把这样一份数据存入 source_data.csv 文件中,用 pandas 接口把数据读入:graph_features_pdf = pd.read_csv(./source_data.csv)
因为对于直链图模型,结点和边的类型均有多种,为了处理方便,他们可以把各种类型的结点展开编码,再到后期对其展开解码,对pandas 的 dataframe 数据内部结构的编解码,他们可以采用下面的代码:
#编码方法def encode_map(input_array): p_map={} length=len(input_array)for index, ele in zip(range(length),input_array):# print(ele,index)p_map[str(ele)] = indexreturn p_map#解码方法def decode_map(encode_map): de_map={}for k,v inencode_map.items():# index,ele de_map[v]=kreturn de_map然后用其中的各列node 展开 编码
userid_encode_map=encode_map(set(graph_features_pdf[user_id].values))# 解码map userid_decode_map=decode_map(userid_encode_map)graph_features_pdf[user_id_encoded] = graph_features_pdf[user_id].apply(lambda e: userid_encode_map.get(str(e),-1))# print unique值的个数 userid_count=len(set(graph_features_pdf[user_id_encoded].values))print(userid_count)# user login ipu_e_ip_src = final_graph_features_pdf[user_id_encoded].valuesu_e_ip_dst = final_graph_features_pdf[ip_encoded].valuesu_e_ip_count = len(u_e_ip_dst)print(“u_e_ip_count”, u_e_ip_count)# user buy itemu_e_item_src = final_graph_features_pdf[user_id_encoded].valuesu_e_item_dst = final_graph_features_pdf[item_id_encoded].valuesu_e_item_count = len(u_e_item_dst)print(“u_e_item_count”, u_e_item_count)这儿仅仅以 用户结点编码 为例,itemId和 IP 同理编解码方可。特别注意: 这儿的 u_e_ip_count,u_e_item_count 在下文有用到。
最后他们可以把图数据保存,供以后的直链图代码 demo采用。
final_graph_pdf=graph_features_pdf[[user_id_encoded,ip_encoded,item_id_encoded,label]].sort_values(by=user_id_encoded, ascending=True)final_graph_pdf.to_csv(result_label.csv,index=False)`
基于此,直链图的此基础准备数据就结束了,下面开始正式的coding了。
(2.2) 导包 (和上文相同)
老规矩,先导包,基于DGL和RGCN同时实现的直链图上镜像预估各项任务只须要这些包就可以了。
import argparse
import torch
import torch.nn as nn
import dgl
import torch.optim as optim
from dgl.dataloading import MultiLayerFullNeighborSampler, EdgeDataLoader
from dgl.dataloading.negative_sampler import Uniform
import numpy as np
import pandas as pd
import itertools
import os
import tqdm
from dgl import save_graphs, load_graphs
import dgl.function as fn
import torch
import dgl
import torch.nn.functional as F
from dgl.nn.pytorch import GraphConv, SAGEConv, HeteroGraphConv
from dgl.utils import expand_as_pair
import tqdm
from collections import defaultdict
import torch as th
import dgl.nn as dglnn
from dgl.data.utils import makedirs, save_info, load_info
from sklearn.metrics import roc_auc_score
import gc
gc.collect()
推荐一个工具,tqdm很好用哦,紧密结合 dataloading接口, 可以看到模型体能训练和数据处理执行的进度,赶紧用起来吧~
各种模型工具无所谓展开分类,能解决问题的是好工具,混用又有何不可呢? 实用就行!
(2.3) 画法
数据有了,接下来是画法了,他们构筑的是包含三种结点的直链图。
算法全栈之路# user 登录 ipu_e_ip_src = final_graph_pdf[user_id_encoded].valuesu_e_ip_dst = final_graph_pdf[ip_encoded].values# user 购买 item u_e_item_src = final_graph_pdf[user_id_encoded].valuesu_e_item_dst = final_graph_pdf[item_id_encoded].values# item和ip 共同出现 ip_e_item_src = final_graph_pdf[ip_encoded].valuesip_e_item_dst = final_graph_pdf[item_id_encoded].values# user 购买 labeluser_node_buy_label = final_graph_pdf[label].valueshetero_graph = dgl.heterograph({ (user, u_e_ip, ip): (u_e_ip_src, u_e_ip_dst),(ip, u_eby_ip, user): (u_e_ip_dst, u_e_ip_src), (user, u_e_item, item): (u_e_item_src, u_e_item_dst), (item, u_eby_item, user): (u_e_item_dst, u_e_item_src), (ip, ip_e_item, item): (ip_e_item_src, ip_e_item_dst), (item, item_eby_ip, ip): (ip_e_item_dst, ip_e_item_src)})# 特别注意:这儿的代码本文是不须要的# 这儿是镜像预估各项任务,是无监督管理机器自学,不须要条码,这里没有删除,仅仅注释起来,方便对比上一则该文的代码# 给 user node 添加条码# hetero_graph.nodes[user].data[label] = torch.tensor(user_node_buy_label)print(hetero_graph)这儿直链图是无向图,因为无向,所以双向 ,画法的时候就 须要构筑双向的边 ,代码很好认知,就不再赘述了哈。这儿和上文不同的是,这儿是无监督管理机器自学各项任务,不须要对用户结点的边展开 label 赋值 。我这儿仅仅是把注释起来哈。这儿是不须要的。
(2.4) 模型的自定义函数
这儿定义了 直链图上RGCN会用到的模型的一系列产品自定义函数,终点看代码注释,紧密结合上文第一小节的抽象认知,希望你能看明白哦。
class RelGraphConvLayer(nn.Module): def __init__(self,in_feat, out_feat, rel_names, num_bases, *, weight=True, bias=True, activation=None, self_loop=False,dropout=0.0): super(RelGraphConvLayer, self).__init__() self.in_feat = in_feat self.out_feat = out_featself.rel_names = rel_names self.num_bases = num_bases self.bias = bias self.activation = activationself.self_loop = self_loop# 那个地方只是起到计算的作用, 不保存数据 self.conv = HeteroGraphConv({# graph conv 里面有模型参数weight,如果外边不传进去的话,里面新建# 相当于模型加了一层全镜像, 对每一种类型的边计算卷积 rel: GraphConv(in_feat, out_feat, norm=right, weight=False, bias=False)for rel in rel_names }) self.use_weight = weightself.use_basis = num_bases < len(self.rel_names) and weightif self.use_weight:if self.use_basis:self.basis = dglnn.WeightBasis((in_feat, out_feat), num_bases, len(self.rel_names))else:# 每个关系,又一个weight,全连接层self.weight = nn.Parameter(th.Tensor(len(self.rel_names), in_feat, out_feat)) nn.init.xavier_uniform_(self.weight, gain=nn.init.calculate_gain(relu))# biasif bias: self.h_bias = nn.Parameter(th.Tensor(out_feat)) nn.init.zeros_(self.h_bias)# weight for self loopif self.self_loop: self.loop_weight = nn.Parameter(th.Tensor(in_feat, out_feat))nn.init.xavier_uniform_(self.loop_weight, gain=nn.init.calculate_gain(relu)) self.dropout = nn.Dropout(dropout)def forward(self, g, inputs): g = g.local_var()if self.use_weight: weight = self.basis() if self.use_basis elseself.weight# 这每个关系对应一个权重矩阵对应输出维度和输出维度 wdict = {self.rel_names[i]: {weight: w.squeeze(0)}for i, w inenumerate(th.split(weight, 1, dim=0))}else: wdict = {}if g.is_block: inputs_src = inputsinputs_dst = {k: v[:g.number_of_dst_nodes(k)]for k, v in inputs.items()}else: inputs_src = inputs_dst = inputs# 多类型的边结点卷积完成后的输出# 输出的是blocks 和 embeding hs = self.conv(g, inputs, mod_kwargs=wdict) def _apply(ntype, h):ifself.self_loop: h = h + th.matmul(inputs_dst[ntype], self.loop_weight)if self.bias: h = h + self.h_biasifself.activation: h = self.activation(h)return self.dropout(h)#return {ntype: _apply(ntype, h) for ntype, h inhs.items()}class RelGraphEmbed(nn.Module): r“”“Embedding layer for featureless heterograph.”“”def __init__(self, g, embed_size, embed_name=embed, activation=None, dropout=0.0):super(RelGraphEmbed, self).__init__() self.g = g self.embed_size = embed_size self.embed_name = embed_name self.activation = activationself.dropout = nn.Dropout(dropout)# create weight embeddings for each node for each relationself.embeds = nn.ParameterDict()for ntype in g.ntypes:embed = nn.Parameter(torch.Tensor(g.number_of_nodes(ntype), self.embed_size)) nn.init.xavier_uniform_(embed, gain=nn.init.calculate_gain(relu))self.embeds[ntype] = embed def forward(self, block=None):return self.embedsclass EntityClassify(nn.Module):def __init__(self, g, h_dim, out_dim, num_bases=-1, num_hidden_layers=1, dropout=0, use_self_loop=False):super(EntityClassify, self).__init__() self.g = g self.h_dim = h_dim self.out_dim = out_dim self.rel_names = list(set(g.etypes)) self.rel_names.sort()if num_bases < 0 or num_bases > len(self.rel_names):self.num_bases = len(self.rel_names)else: self.num_bases = num_bases self.num_hidden_layers = num_hidden_layersself.dropout = dropout self.use_self_loop = use_self_loop self.embed_layer = RelGraphEmbed(g, self.h_dim)self.layers = nn.ModuleList()# i2h self.layers.append(RelGraphConvLayer(self.h_dim, self.h_dim, self.rel_names, self.num_bases, activation=F.relu, self_loop=self.use_self_loop,dropout=self.dropout, weight=False))# h2h , 这儿不添加隐层,只用2层卷积# for i in range(self.num_hidden_layers):# self.layers.append(RelGraphConvLayer(# self.h_dim, self.h_dim, self.rel_names,# self.num_bases, activation=F.relu, self_loop=self.use_self_loop,# dropout=self.dropout))# h2o self.layers.append(RelGraphConvLayer(self.h_dim, self.out_dim, self.rel_names, self.num_bases, activation=None, self_loop=self.use_self_loop))# 输出 blocks,embeding def forward(self, h=None, blocks=None):if h is None:# full graph training h = self.embed_layer()ifblocks is None:# full graph trainingfor layer in self.layers: h = layer(self.g, h)else:# minibatch training# 输出 blocks,embedingfor layer, block in zip(self.layers, blocks): h = layer(block, h)return hdef inference(self, g, batch_size, device=“cpu”, num_workers=0, x=None):if x is None: x = self.embed_layer()forl, layerin enumerate(self.layers): y = { k: th.zeros( g.number_of_nodes(k), self.h_dim ifl != len(self.layers) – 1else self.out_dim)for k in g.ntypes} sampler = dgl.dataloading.MultiLayerFullNeighborSampler(1)dataloader = dgl.dataloading.NodeDataLoader( g, {k: th.arange(g.number_of_nodes(k)) for k in g.ntypes},sampler, batch_size=batch_size, shuffle=True, drop_last=False, num_workers=num_workers)forinput_nodes, output_nodes, blocksin tqdm.tqdm(dataloader):# print(input_nodes) block = blocks[0].to(device)h = {k: x[k][input_nodes[k]].to(device)for k in input_nodes.keys()} h = layer(block, h)for k in h.keys():y[k][output_nodes[k]] = h[k].cpu() x = yreturn y上面的代码主要分为三大块:分别是 RelGraphConvLayer、 RelGraphEmbed 和 EntityClassify。
首先是:RelGraphConvLayer 。他们可以看到 RelGraphConvLayer 是他们的 直链图卷积层layer, 其主要是调用了DGL同时实现的 HeteroGraphConv算子,从上面第一小节他们也详尽阐述了 直链图卷积算子其实是对各种关系分别展开卷积然后展开同类型的结点的紧密结合。
这儿他们须要 的是:RelGraphConvLayer层的返回,从代码中,他们可以看到,对于每种结点类型是返回了一个Embeding, 维度是 out_feat。如果是带了激活函数的,则是返回激活后的一定维度的一个tensor。
过来是 RelGraphEmbed。 从代码中可以看到: 那个python类仅仅返回了一个字典,但那个字典里却包括了 多个Embeding Variable, 特别注意这儿的 Variable 均是可以 随着网络体能训练变化更新的。他们可以根据结点类型,结点ID取得对应元素的 Embeding 。 这种同时实现方法是不是解决了前文GraphSage与DGL同时实现同画法 Link 预估,易懂好文G360A 和 基于GCN和DGL同时实现的图上 node 展开分类, 值得一看!!!所提到的 动态更新的Embeding 的问题呢。
最后是 EntityClassify类 了,他们可以看到 那个是最终的模型RGCN内部结构了,包括了模型体能训练的 forward 和用于推断的inference方法。这儿的 inference 可以用于 各个结点的embedding的导出, 他们在后文有实例代码,接著看下去吧~
特别注意看 forword 方法里的 for layer, block in zip(self.layers, blocks) 那个位置, 这儿是他们前一小节所说的 取样层数和模型的卷积层数目是相同的说法 的由来,可以紧密结合上文说明认知源代码哦。
(2.5) 模型取样超参与边取样如是说
特别注意: 这儿的取样是边取样,和上一则该文不同 ,具体看代码。
# 根据结点类型和结点ID抽取embeding 参与模型体能训练更新 def extract_embed(node_embed, input_nodes): emb = {}for ntype, nid in input_nodes.items(): nid = input_nodes[ntype]emb[ntype] = node_embed[ntype][nid]return emb# 取样定义neg_sample_count = 1batch_size=20480# 取样2层全部结点sampler = MultiLayerFullNeighborSampler(2)# 边的条数,数目比顶点个数多很多.# 这是 EdgeDataLoader 数据加载器 hetero_graph.edges[u_e_ip].data[train_mask] = torch.zeros(u_e_ip_count, dtype=torch.bool).bernoulli(1.0)train_ip_eids = hetero_graph.edges[u_e_ip].data[train_mask].nonzero(as_tuple=True)[0]ip_dataloader = EdgeDataLoader( hetero_graph, {u_e_ip: train_ip_eids}, sampler, negative_sampler=Uniform(neg_sample_count), batch_size=batch_size)hetero_graph.edges[u_e_item].data[train_mask] = torch.zeros(u_e_item_count, dtype=torch.bool).bernoulli(1.0)train_item_eids = hetero_graph.edges[u_e_item].data[train_mask].nonzero(as_tuple=True)[0]item_dataloader = EdgeDataLoader( hetero_graph, {u_e_item: train_item_eids}, sampler, negative_sampler=Uniform(neg_sample_count), batch_size=batch_size)这儿的代码作者花了大量时间展开优化,注释和组织形式 尽量写的非常清晰,非常容易认知。
他们这儿选择了 EdgeDataLoader来展开体能训练数据的读入,这其实是一种分batch体能训练 的方法,而不是一次性把图全读入内存展开体能训练,而是每次选择batch的种子结点 真实边构成的pos graph 和 种子结点和他们乱数取样的自上而下结点组成的neg graph 读入内存参与体能训练,这也让大的图神经网络体能训练成为了可能,是 DGL图深度框架 非常优秀的同时实现 !!! 大赞 !
这儿 EdgeDataLoader取样算法也和 negative_sampler 与 sampler 紧密结合采用,其中sampler 取样了2层全部邻居作为正样品,而 negative_sampler 则是对不存有的边展开构筑,起点也是种子结点,而终点则是 自上而下乱数取样得到的 。
特别注意读者在里的边取样可以上一则该文备受瞩目好文有条理认知,直链图上 Node 展开分类方法论与DGL源代码两栖作战 中的结点取样对比查看,可以加深认知哦~
(2.6) 模型内部结构定义与 损失函数说明
三个类的方法定义,和结点展开分类各项任务有差异的地方,可以看一看~
# Define a Heterograph Conv modelclass Model(nn.Module):def __init__(self, graph, hidden_feat_dim, out_feat_dim): super().__init__() self.rgcn = EntityClassify(graph,hidden_feat_dim, out_feat_dim) self.pred = HeteroDotProductPredictor()def forward(self, h, pos_g, neg_g, blocks, etype): h = self.rgcn(h, blocks)return self.pred(pos_g, h, etype), self.pred(neg_g, h, etype)class MarginLoss(nn.Module): def forward(self, pos_score, neg_score):# 求损失的平均值 , view 改变tensor 的形状# 1- pos_score + neg_score ,应罢了 -pos 符号越大变成越小 +neg_score 越小越好return(1 – pos_score + neg_score.view(pos_score.shape[0], -1)).clamp(min=0).mean()class HeteroDotProductPredictor(nn.Module):def forward(self, graph, h, etype):# 在计算之外更新h,保存为自上而下可用# h contains the node representations for each edge type computed from node_clf_hetero.py with graph.local_scope(): graph.ndata[h] = h # assigns h of all node types in one shot graph.apply_edges(fn.u_dot_v(h, h, score), etype=etype)return graph.edges[etype].data[score]这儿的三个类函数 Model、MarginLoss、HeteroDotProductPredictor 均是非常重要的。
首先是 model , 他们可以看到 这儿的model 分别引入了 EntityClassify 和 HeteroDotProductPredictor ,这两个函数分别定义了 模型的内部结构与损失 。EntityClassify 和 上一文 如是说的一模一样,这儿不在赘述了。
接著是 MarginLoss,可以看到 MarginLoss 是他们前文讲过的基于同源性假设 设计的损失,HeteroDotProductPredictor 则是基于两端结点重要信息 计算边与否存有 的函数,可以 从同画法推断到直链图 中去,和 GraphSage与DGL同时实现同画法 Link 预估,易懂好文G360A 中一样,本文也不在展开赘述 了。
(2.7) 模型体能训练超参与单epoch体能训练
代码是表达程序员思想的最好语言,直接看代码吧!
算法全栈之路# in_feats = hetero_graph.nodes[user].data[feature].shape[1]hidden_feat_dim = n_hetero_featuresout_feat_dim = n_hetero_featuresembed_layer = RelGraphEmbed(hetero_graph, hidden_feat_dim)all_node_embed = embed_layer()model = Model(hetero_graph, hidden_feat_dim, out_feat_dim)# 优化模型所有参数,主要是weight以及输出的embeding参数all_params = itertools.chain(model.parameters(), embed_layer.parameters())optimizer = torch.optim.Adam(all_params, lr=0.01, weight_decay=0)loss_func = MarginLoss()def train_etype_one_epoch(etype, spec_dataloader): losses = []# input nodes 为 取样的subgraph中的所有的结点的集合forinput_nodes, pos_g, neg_g, blocksin tqdm.tqdm(spec_dataloader): emb = extract_embed(all_node_embed, input_nodes)pos_score, neg_score = model(emb, pos_g, neg_g, blocks, etype) loss = loss_func(pos_score, neg_score)losses.append(loss.item()) optimizer.zero_grad() loss.backward() optimizer.step()print({:s} Epoch {:d} | Loss {:.4f}.format(etype, epoch, sum(losses) / len(losses)))这儿他们定义了模型内部结构,损失采用的是 上文定义的 MarginLoss , 这儿须要特别注意的是 spec_dataloader 返回值,这儿是 边取样 ,返回和结点取样的dataloader是 不一样的。
(2.8) 模型多种结点体能训练
之路# 开始train 模型for epoch in range(1):print(“start epoch:”, epoch) model.train() train_etype_one_epoch(u_e_ip, ip_dataloader) train_etype_one_epoch(u_e_item, item_dataloader)从代码中他们可以知道:
对于直链图,其实他们也是以 各种类型的结点作为种子结点, 然后展开图上的负边取样,分别展开体能训练然后更新整个模型内部结构 的。
(2.7) 模型保存与结点Embeding导出 (和上文相同)
# 图数据和模型保存save_graphs(“graph.bin”, [hetero_graph])torch.save(model.state_dict(), “model.bin”)# 每个结点的embeding,自己初始化,因为参与了体能训练,那个是最后每个结点输出的embedingprint(“node_embed:”, all_node_embed[user][0])# 模型预估的结果,最后应该采用 inference,这里得到的是logit# 特别注意,这儿传入 all_node_embed,选择0,选1可能会死锁,最终程序不执行inference_out = model.inference(hetero_graph, batch_size,cpu, num_workers=0, all_node_embed)print(inference_out[“user”].shape)print(inference_out[user][0])这儿他们可以看到, 他们采用了 model.inference 接口展开模型的 结点 Embeding导出 。
这儿须要特别注意的是: 那个地方 num_workers应该设置0,即为不用多线程, 不然会互锁,导致预估各项任务不执行 。
这儿是深坑啊,反正经过很长时间的纠结和查找,最终发现是那个原因,希望读者可以避免遇到相似的问题 ~
到这儿,直链图 Link 预估 方法论与DGL 源代码两栖作战的全文就写完了。这一则该文是为了图系列产品该文 的 完整性 而写的一则该文。相信认真看过作者该文的人,每一则都不错过的话,到这儿修改下网络对他们来说是非常容易的事情。
但事实上,也确有同学卡在了 直链图镜像预估的一些自定义函数上,不知道如何去同时实现来展开镜像预估 各项任务,那就紧密结合本文与上一则该文和从前的一则同画法镜像预估的该文一起看一看吧,相信你会有很有收获的 ~
上面的代码demo 在环境没问题的情况下,全部 复制到一个python文件 里,就可以完美运行起来。本文的代码是一个 小型的商业可以用 的工程项目,希望可以对你有参考作用 ~
码字不易,觉得有收获就动动小手转载一下吧,你的支持是我写下去的最大动力 ~
更多更全更新内容 : 算法全栈之路
– END –