图学习【参考资料2】-知识补充与node2vec代码注解( 二 )

# 可增加轮次提高精度--epoch# 当前参数精度大概在95%左右!python my_deepwalk.py --use_my_random_walk --epoch 35 #35 用自己实现的random walk训练DeepWalk模型,可在 ./tmp/deepwalk/walks/ 中查看构造的节点路径[INFO] 2022-11-11 14:28:28,099 [my_deepwalk.py:250]: Step 1200 DeepWalk Loss: 0.1981060.242671 s/step.[INFO] 2022-11-11 14:28:30,539 [my_deepwalk.py:250]: Step 1210 DeepWalk Loss: 0.1871830.309996 s/step.[INFO] 2022-11-11 14:28:33,171 [my_deepwalk.py:250]: Step 1220 DeepWalk Loss: 0.1895330.244672 s/step.[INFO] 2022-11-11 14:28:35,537 [my_deepwalk.py:250]: Step 1230 DeepWalk Loss: 0.2022930.232859 s/step.[INFO] 2022-11-11 14:28:37,920 [my_deepwalk.py:250]: Step 1240 DeepWalk Loss: 0.1893660.244727 s/step.[INFO] 2022-11-11 14:28:40,450 [my_deepwalk.py:250]: Step 1250 DeepWalk Loss: 0.1886010.254400 s/step.[INFO] 2022-11-11 14:28:42,875 [my_deepwalk.py:250]: Step 1260 DeepWalk Loss: 0.1913430.247985 s/step.[INFO] 2022-11-11 14:28:45,286 [my_deepwalk.py:250]: Step 1270 DeepWalk Loss: 0.1865490.255688 s/step.[INFO] 2022-11-11 14:28:47,653 [my_deepwalk.py:250]: Step 1280 DeepWalk Loss: 0.1886380.240493 s/step.[INFO] 2022-11-11 14:29:45,898 [link_predict.py:199]: Step 180 Train Loss: 0.398023 Train AUC: 0.960870[INFO] 2022-11-11 14:29:46,023 [link_predict.py:223]:Step 180 Test Loss: 0.399052 Test AUC: 0.960234[INFO] 2022-11-11 14:29:48,816 [link_predict.py:199]: Step 190 Train Loss: 0.396805 Train AUC: 0.960916[INFO] 2022-11-11 14:29:48,951 [link_predict.py:223]:Step 190 Test Loss: 0.397910 Test AUC: 0.960275[INFO] 2022-11-11 14:29:51,783 [link_predict.py:199]: Step 200 Train Loss: 0.396290 Train AUC: 0.960936[INFO] 2022-11-11 14:29:51,913 [link_predict.py:223]:Step 200 Test Loss: 0.397469 Test AUC: 0.960292 2.0 SkipGram模型训练NOTE:在得到节点路径后,node2vec会使用SkipGram模型学习节点表示,给定中心节点,预测局部路径中还有哪些节点 。模型中用了negative sampling来降低计算量 。

图学习【参考资料2】-知识补充与node2vec代码注解

文章插图
参考 PGL/examples/node2vec/node2vec.py 中的 node2vec_model 函数
2.1 SkipGram模型实现代码参考--理解
  1. 这部分的话,官方代码已经给的很清晰了,这里主要是做一些解释补充--大都可以跟上边算法公式对应着看
  2. 这里采用组合损失--组合损失计算时,要注意在不必要的参数创建后,记得关闭梯度记录--否则会对他求梯度,这样不太好:如:ones_label,他只是一个中间量,用于存放结果的 , 不需要对他求梯度,因为不需要优化它
  3. 还有一点,静态图下 , 尽量使用layers下的运算方法,避免出现超出计算图的一些逻辑循环操作这一部分没什么好说的,大家理解就好--多看看源码哦!
import paddle.fluid.layers as ldef userdef_loss(embed_src, weight_pos, weight_negs):"""输入:embed_src- 中心节点向量 list (batch_size, 1, embed_size)weight_pos- 标签节点向量 list (batch_size, 1, embed_size)weight_negs - 负样本节点向量 list (batch_size, neg_num, embed_size)输出:loss - 正负样本的交叉熵 float"""################################### 请在这里实现SkipGram的loss计算过程### 负采样计算部分——Multi Sigmoids# 分别计算正样本和负样本的 logits(概率)pos_logits = l.matmul(embed_src, weight_pos, transpose_y=True)# [batch_size, 1, 1] -- matmul:矩阵相乘neg_logits = l.matmul(embed_src, weight_negs, transpose_y=True)# [batch_size, 1, neg_num]# 设置正样本标签,并计算正样本lossones_label = pos_logits * 0. + 1.ones_label.stop_gradient = True# 关闭梯度记录pos_loss = l.sigmoid_cross_entropy_with_logits(pos_logits, ones_label)# 交叉熵计算==对应公式2# 设置负样本标签,并计算负样本losszeros_label = neg_logits * 0.zeros_label.stop_gradient = Trueneg_loss = l.sigmoid_cross_entropy_with_logits(neg_logits, zeros_label)# 交叉熵计算==对应公式3# 总的Loss计算为正样本与负样本loss之和loss = (l.reduce_mean(pos_loss) + l.reduce_mean(neg_loss)) / 2# 得到总的loss##################################return lossNOTE:Node2Vec会根据与上个节点的距离按不同概率采样得到当前节点的下一个节点 。<img src="http://img.zhejianglong.com/231019/2254304628-4.jpg" width="85%" height="85%" /><br>参考; PGL/pgl/graph_kernel.pyx 中用Cython语言实现了节点采样函数node2vec_sample3.1 Node2Vec采样算法转换代码注解
  1. 这部分代码,用于随机游走后得到的路径,然后对这些路径进行吸收学习,训练图结构
import numpy as np# 随机节点的获取def node2vec_sample(succ, prev_succ, prev_node, p, q):"""输入:succ - 当前节点的下一个相邻节点id列表 list (num_neighbors,)prev_succ - 前一个节点的下一个相邻节点id列表 list (num_neighbors,)prev_node - 前一个节点id intp - 控制回到上一节点的概率 floatq - 控制偏向DFS还是BFS float输出:下一个节点id int"""################################### 请在此实现node2vec的节点采样函数# 节点参数信息succ_len = len(succ)# 获取相邻节点id列表节点长度(相对当前)prev_succ_len = len(prev_succ)# 获取相邻节点id列表节点长度(相对前一个节点)prev_succ_set = np.asarray([])# 前一节点的相邻节点id列表for i in range(prev_succ_len):# 遍历得到前一节点的相邻节点id列表的新list——prev_succ_set,用于后边概率的讨论# 将前一节点list,依次押入新的list中prev_succ_set = np.append(prev_succ_set,prev_succ[i])# ? prev_succ_set.insert(prev_succ[i])# 概率参数信息probs = []# 保存每一个待前往的概率prob = 0# 记录当前讨论的节点概率prob_sum = 0.# 所有待前往的节点的概率之和# 遍历当前节点的相邻节点for i in range(succ_len):# 遍历每一个当前节点前往的概率if succ[i] == prev_node:# case 1 : 采样节点与前一节点一致,那么概率为--1/q(原地)prob = 1. / p# case 2 完整的应该是: np.where(prev_succ_set==succ[i]) and np.where(succ==succ[i])# 但是因为succ本身就是采样集,所以np.where(succ==succ[i])总成立,故而忽略,不考虑elif np.where(prev_succ_set==succ[i]):# case 2: 采样节点在前一节点list内,那么概率为--1?cpython中的代码: prev_succ_set.find(succ[i]) != prev_succ_set.end()prob = 1.elif np.where(prev_succ_set!=succ[i]):# case 3: 采样节点不在前一节点list内 , 那么概率为--1/qprob = 1. / qelse:prob = 0.# case 4 : otherprobs.append(prob)# 将待前往的每一个节点的概率押入保存prob_sum += prob# 计算所有节点的概率之和RAND_MAX = 65535# 这是一个随机数的最值,用于计算随机值的--根据C/C++标准,最小在30000+,这里取2^16次方rand_num = float(np.random.randint(0, RAND_MAX+1)) / RAND_MAX * prob_sum# 计算一个随机概率:0~prob_sum. ?cpython中的代码: float(rand())/RAND_MAX * prob_sumsampled_succ = 0.# 当前节点的相邻节点中确定的采样点# rand_num => 是0~prob_num的一个值,表示我们的截取概率阈值--即当遍历了n个节点时 , 若已遍历的节点的概率之和已经超过了rand_num# 我们取刚好满足已遍历的节点的概率之和已经超过了rand_num的最近一个节点作为我们的采样节点# 比如: 遍历到第5个节点时,权重概率和大于等于rand_num,此时第5个节点就是对应的采样的节点了# 为了方便实现:这里利用循环递减--判断条件就变成了————当rand_num减到<=0时 , 开始采样节点for i in range(succ_len):# 遍历当前节点的所有相邻节点rand_num -= probs[i]# 利用rand_num这个随机获得的概率值作为依据,进行一个循环概率检验if rand_num <= 0:# 当遇到第一次使得rand_num减到<=0后,说明到这个节点为止, 遍历应该终止了,此时的节点即未所求的节点,【停止检验条件】sampled_succ = succ[i]# 并把当前节点作为确定的节点return sampled_succ# 返回待采样的节点--节点一定在succ中

推荐阅读