本项目参考:

https://aistudio.baidu.com/aistudio/projectdetail/5012408?contributionType=1

*一、正题篇:DeepWalk、word2vec、node2vec

其它相关项目:

关于图计算&图学习的基础知识概览:前置知识点学习(PGL)[系列一] https://aistudio.baidu.com/aistudio/projectdetail/4982973?contributionType=1

图机器学习(GML)&图神经网络(GNN)原理和代码实现(前置学习系列二):https://aistudio.baidu.com/aistudio/projectdetail/4990947?contributionType=1

1.1 DeepWalk算法流程

【图来源:网络,笔记由笔者添上】

算法流程:

【其中使用skip-gram模型是为了利用梯度的方法对参数进行更新训练】

1.2 Skip Gram算法流程

【图来源:网络,笔记由笔者添上】

算法流程:

【其中使用skip-gram模型是为了利用梯度的方法对参数进行更新训练】

此外,关于以上两个算法的定义和概念就不一一展示说明了,后边等有空了补上。下边说一下我学习的一些参考资料,希望对大家有帮助。

参考资料

1.【论文笔记】DeepWalk——陌上疏影凉

1.【网络图模型综述

1.【异构图神经网络简介--机器之心

1.【Graph Embedding之metapath2vec--圈圈_Master

1.【从Random Walk谈到Bacterial foraging optimization algorithm(BFOA),再谈到Ramdom Walk Graph Segmentation图分割算法

二 程序注解

【注解展示,是为了方便自己理解,同时也希望能帮到和自己一样在学习这块知识的小伙伴】

1.0 DeepWalk随机游走的实现



实现Graph类的random_walk函数--可参考

1.1 实现的参考代码--DeepWalk的随机游走算法实现

思路

  1. 理清successor,outdegree函数的输入输出
  2. 查看补全函数的返回类型--分析可能的结果--我最初的猜测:这walks应该是多个向量的集合,最后确实也是【[[],..]这样的结构多用于扩充,然后联想需要学习向量,所以想到向量递推的那种向量集合】
  3. 生成随机采样索引序列集 -- 这个需要匹配采样形状,以及索引阈值--我是参考一个示例修改的,感悟是: 利用随机数0~1,给rand传入指定shape,再乘以一个相同shape的数据,可以得到一个>=0, <=最大出度-1的值,这样就可以用来索引采样了
  4. 理清上下文变量关系,进行zip打包遍历到一起,然后进行聚合操作即可。

具体注解在代码中,可查阅,基本都是按照一行行注解的

from pgl.graph import Graph

import numpy as np

class UserDefGraph(Graph):
def random_walk(self, nodes, walk_len):
"""
输入:nodes - 当前节点id list (batch_size,)
walk_len - 最大路径长度 int
输出:以当前节点为起点得到的路径 list (batch_size, walk_len) 用到的函数
1. self.successor(nodes)
描述:获取当前节点的下一个相邻节点id列表
输入:nodes - list (batch_size,)
输出:succ_nodes - list of list ((num_successors_i,) for i in range(batch_size))
2. self.outdegree(nodes)
描述:获取当前节点的出度
输入:nodes - list (batch_size,)
输出:out_degrees - list (batch_size,)
"""
walks = [[node] for node in nodes] # 首先获得当前节点列表对应的一个向量 walks_ids = np.arange(0, len(nodes)) # 游走路径中节点对应id号
cur_nodes = np.array(nodes) # 当前节点情况
for l in range(walk_len): # 根据游走长度进行遍历--破出条件:1. range结束;2. outdegree==0【出度为零,没有可继续的节点】
"""选取有下一个节点的路径继续采样,否则结束"""
outdegree = self.outdegree(cur_nodes) # 计算当前节点的出度--也就是对应有哪些位置的邻近节点
walk_mask = (outdegree != 0) # 根据出度来确定掩码--True, False--将出度为0的部分复制为False,反之True
if not np.any(walk_mask): # 判断是否没有可继续的节点情况--出度为0
break
cur_nodes = cur_nodes[walk_mask] # 根据掩码获取可继续前进的节点,作为后边讨论的当前可前行节点
walks_ids = walks_ids[walk_mask] # 获取掩码下,原节点id,组成新的work_ids用于后边讨论,但本身还是作为一个节点的标记,对应这是第几个节点
outdegree = outdegree[walk_mask] # 根据掩码获取相应的不为0的出度--用于后边计算前行的路径 ######################################
# 请在此补充代码采样出下一个节点
'''
[注解有点多,所以放外边了]
PS:
1. successor 可获取当前节点的下一个相邻节点id列表,
那么successor 计算出下一节点的集合后,我们需要从中随机取出一个节点--所以我们要创建随机采样的index_list(索引序列集)
2. 创建index_list=>为了才到合适的index信息,采用np.floor与np.random,rand()实现:
eg: np.floor(np.random.rand(outdegree.shape[0]) * outdegree).astype('int64')
np.random.rand(outdegree.shape[0]): 根据出度集的形状来取得相应形状的随机数--这里体现游走的随机性
np.random.rand(outdegree.shape[0]) * outdegree:利用生成的随机数与出度集对应元素相乘——这里得到一些列的随机数,随机数范围在0~最大出度值--保证路径有效
np.floor(np.random.rand(outdegree.shape[0]) * outdegree)——实现向下取整,这样就得到了相应游走路径中接下来那个点的索引
具体实例:
np.floor(np.random.rand(20) * 3).astype('int64')
result: array([0, 1, 2, 1, 0, 0, 0, 0, 1, 1, 1, 2, 0, 2, 2, 2, 2, 1, 2, 0])
3. 既然知道了随机采样的序列集了,那么接下就是分配新的游走路径了
next_nodes = [] # 用于后边存放—— 装配有下一个节点的新路径
# 参数说明:
succ_nodes:相邻节点id列表
sample_index:对应出度生成的随即索引集
walks_ids:游走路径中节点对应id号
# 接下来的循环指的是,将节点列表、随机采样序列、游走路径中节点对应id号一一对应进行填充--得到一个游走情况
for s, ind, walk_id in zip(succ_nodes, sample_index, walks_ids):
walks[walk_id].append(s[ind]) # 注意: 从开始已经知道walks=>[[], [], []]是这种形式的,这样这里的append,就很容易理解成为相应节点添加可以继续前行的节点,形成一条路径
next_nodes.append(s[ind]) # 同时获取接下来要重新讨论游走时所需的新节点--即:如:从1走到了2,从3走到了7: [[1], [3]]=>[[1, 2], [3, 7]]
# 接下来自然就该考虑把新的2, 7 作为下一次游走时讨论出度的节点啦
'''
succ_nodes = self.successor(cur_nodes) # 返回可继续的节点集合
# next_nodes = ...
sample_index = np.floor(np.random.rand(outdegree.shape[0]) * outdegree).astype('int64')
next_nodes = []
for s, ind, walk_id in zip(succ_nodes, sample_index, walks_ids):
walks[walk_id].append(s[ind])
next_nodes.append(s[ind])
######################################
cur_nodes = np.array(next_nodes) # 将节点转换为np类型,方便一些操作运算--同时保证前后数据类型 # 遍历完游走长度的次数,就可以返回得到的随机游走路径啦
return walks
# 可增加轮次提高精度--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.198106  0.242671 s/step.
[INFO] 2022-11-11 14:28:30,539 [my_deepwalk.py: 250]: Step 1210 DeepWalk Loss: 0.187183 0.309996 s/step.
[INFO] 2022-11-11 14:28:33,171 [my_deepwalk.py: 250]: Step 1220 DeepWalk Loss: 0.189533 0.244672 s/step.
[INFO] 2022-11-11 14:28:35,537 [my_deepwalk.py: 250]: Step 1230 DeepWalk Loss: 0.202293 0.232859 s/step.
[INFO] 2022-11-11 14:28:37,920 [my_deepwalk.py: 250]: Step 1240 DeepWalk Loss: 0.189366 0.244727 s/step.
[INFO] 2022-11-11 14:28:40,450 [my_deepwalk.py: 250]: Step 1250 DeepWalk Loss: 0.188601 0.254400 s/step.
[INFO] 2022-11-11 14:28:42,875 [my_deepwalk.py: 250]: Step 1260 DeepWalk Loss: 0.191343 0.247985 s/step.
[INFO] 2022-11-11 14:28:45,286 [my_deepwalk.py: 250]: Step 1270 DeepWalk Loss: 0.186549 0.255688 s/step.
[INFO] 2022-11-11 14:28:47,653 [my_deepwalk.py: 250]: Step 1280 DeepWalk Loss: 0.188638 0.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来降低计算量。



参考 PGL/examples/node2vec/node2vec.py 中的 node2vec_model 函数

2.1 SkipGram模型实现代码参考--理解

  1. 这部分的话,官方代码已经给的很清晰了,这里主要是做一些解释补充--大都可以跟上边算法公式对应着看
  2. 这里采用组合损失--组合损失计算时,要注意在不必要的参数创建后,记得关闭梯度记录--否则会对他求梯度,这样不太好:

    如:ones_label,他只是一个中间量,用于存放结果的,不需要对他求梯度,因为不需要优化它
  3. 还有一点,静态图下,尽量使用layers下的运算方法,避免出现超出计算图的一些逻辑循环操作

    这一部分没什么好说的,大家理解就好--多看看源码哦!
import paddle.fluid.layers as l

def 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] # 设置正样本标签,并计算正样本loss
ones_label = pos_logits * 0. + 1.
ones_label.stop_gradient = True # 关闭梯度记录
pos_loss = l.sigmoid_cross_entropy_with_logits(pos_logits, ones_label) # 交叉熵计算==对应公式2 # 设置负样本标签,并计算负样本loss
zeros_label = neg_logits * 0.
zeros_label.stop_gradient = True
neg_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 loss

NOTE:Node2Vec会根据与上个节点的距离按不同概率采样得到当前节点的下一个节点。 <img src="https://ai-studio-static-online.cdn.bcebos.com/09001163a1064101a8dd2892eb559cf2006aa93d7fe84c70b2ad47b810f4c86a" width="85%" height="85%" /><br> 参考; PGL/pgl/graph_kernel.pyx 中用Cython语言实现了节点采样函数node2vec_sample

3.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 int
p - 控制回到上一节点的概率 float
q - 控制偏向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/q
prob = 1. / q
else:
prob = 0. # case 4 : other probs.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_sum sampled_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中
[INFO] 2022-11-11 14:38:49,133 [link_predict.py:  199]:	Step 170 Train Loss: 0.454199 Train AUC: 0.954936
[INFO] 2022-11-11 14:38:49,260 [link_predict.py: 223]: Step 170 Test Loss: 0.454974 Test AUC: 0.954118
[INFO] 2022-11-11 14:38:51,997 [link_predict.py: 199]: Step 180 Train Loss: 0.452219 Train AUC: 0.955133
[INFO] 2022-11-11 14:38:52,122 [link_predict.py: 223]: Step 180 Test Loss: 0.453069 Test AUC: 0.954312
[INFO] 2022-11-11 14:38:54,851 [link_predict.py: 199]: Step 190 Train Loss: 0.450969 Train AUC: 0.955254
[INFO] 2022-11-11 14:38:54,978 [link_predict.py: 223]: Step 190 Test Loss: 0.451892 Test AUC: 0.954428
[INFO] 2022-11-11 14:38:57,714 [link_predict.py: 199]: Step 200 Train Loss: 0.450440 Train AUC: 0.955305
[INFO] 2022-11-11 14:38:57,842 [link_predict.py: 223]: Step 200 Test Loss: 0.451436 Test AUC: 0.954473

1. 回顾并总结了图的基本概念。<br>
2. 学习思考算法实现的代码思路--Node2Vec的实现以及RandomWalk的实现。<br>
3. 对源码阅读能力的提升。<br> 其它相关笔记:<br> 关于图计算&图学习的基础知识概览:前置知识点学习(PGL)[系列一] https://aistudio.baidu.com/aistudio/projectdetail/4982973?contributionType=1 图机器学习(GML)&图神经网络(GNN)原理和代码实现(前置学习系列二):https://aistudio.baidu.com/aistudio/projectdetail/4990947?contributionType=1 <br>
* 如果我的项目对你有帮助不如点一个心,fork一下,以备可以常复习哦!

图学习【参考资料2】-知识补充与node2vec代码注解的更多相关文章

  1. 关于图计算&图学习的基础知识概览:前置知识点学习(Paddle Graph Learning (PGL))

    关于图计算&图学习的基础知识概览:前置知识点学习(Paddle Graph Learning (PGL)) 欢迎fork本项目原始链接:关于图计算&图学习的基础知识概览:前置知识点学习 ...

  2. 词向量word2vec(图学习参考资料)

    介绍词向量word2evc概念,及CBOW和Skip-gram的算法实现. 项目链接: https://aistudio.baidu.com/aistudio/projectdetail/500940 ...

  3. Paddle Graph Learning (PGL)图学习之图游走类模型[系列四]

    Paddle Graph Learning (PGL)图学习之图游走类模型[系列四] 更多详情参考:Paddle Graph Learning 图学习之图游走类模型[系列四] https://aist ...

  4. 大数据学习day23-----spark06--------1. Spark执行流程(知识补充:RDD的依赖关系)2. Repartition和coalesce算子的区别 3.触发多次actions时,速度不一样 4. RDD的深入理解(错误例子,RDD数据是如何获取的)5 购物的相关计算

    1. Spark执行流程 知识补充:RDD的依赖关系 RDD的依赖关系分为两类:窄依赖(Narrow Dependency)和宽依赖(Shuffle Dependency) (1)窄依赖 窄依赖指的是 ...

  5. Android知识补充(Android学习笔记)

    Android知识补充 ●国际化 所谓的国际化,就是指软件在开发时就应该具备支持多种语言和地区的功能,也就是说开发的软件能同时应对不同国家和地区的用户访问,并针对不同国家和地区的用户,提供相应的.符合 ...

  6. (C/C++学习笔记) 二十四. 知识补充

    二十四. 知识补充 ● 子类调用父类构造函数 ※ 为什么子类要调用父类的构造函数? 因为子类继承父类,会继承到父类中的数据,所以子类在进行对象初始化时,先调用父类的构造函数,这就是子类的实例化过程. ...

  7. Python学习---django知识补充之CBV

    Django知识补充之CBV Django: url    -->  def函数      FBV[function based view]  用函数和URL进行匹配 url    --> ...

  8. (1)写给Web初学者的教案-----学习Web的知识架构

    1:学习Web的知识架构 前文中我们简单的介绍了一些关于Web的基本知识,这里任老师再次强调一下凡是用浏览器打开的网站我们就称之为Web应用程序(B/S结构).除此之外其它需要下载安装的软件或是手机  ...

  9. 【转载】跟着9张思维导图学习JavaScript

    原文:跟着9张思维导图学习JavaScript 学习的道路就是要不断的总结归纳,好记性不如烂笔头,so,下面将 po 出我收集的 9 张 JavaScript相关的思维导图(非原创). 思维导图小ti ...

随机推荐

  1. Java控制台打印三角形

    for (int i = 1; i <= 5; i++) { //最上面先是五个往下一次4.3.2.1 for (int j = 5; j >= i; j--) { System.out. ...

  2. 【多服务场景化解决方案】智能家居(UrbanHome)

    ​ 介绍 UrbanHome是一款提供房屋维修服务的移动应用.如有维修需求,用户可通过该应用联系所在城市的管道工,电工,保洁,漆匠,木匠,修理工等,或是搜寻导航附近的维修商店. 通过构建UrbanHo ...

  3. KingbaseES R6 集群手工配置VIP案例

    经常有用户问,V8R6集群搭建时没有配置VIP,搭建完成后,如何添加VIP?以下向大家介绍下手动添加VIP 的过程. 一.操作系统环境 操作系统(UOS): root@uos01:~# cat /et ...

  4. 字符类数据类型和oracle字符类型的区别

    为兼容Oracle的数据类型,KingbaseES扩展了Oracle的NUMBER.VARCHAR2.CHAR(n)和DATE类型.该措施使得移植Oracle的Create Table等DDL语句时, ...

  5. MyBatis标签之Select resultType和resultMap

    摘要:介绍MyBatis 中Select标签的两个属性resultType和resultMap及其区别. 1 MyBatis动态SQL之if 语句 2 MyBatis动态sql之where标签|转 3 ...

  6. 8.X 的安装配置简化体现

    文章转载自:https://mp.weixin.qq.com/s/CEYYKUub81mk59B3lqAcBA 节点无需任何安全配置,即可实现 TLS 加密通信.Https 加密通信. TLS 应用于 ...

  7. 使用prometheus + granafa 监控mysql主从

    若主从同步数据库未同步默认的mysql表,则也需要在从库上创建mysql用户mysqld_exporter用来收集监控数据 mysqld_exporter安装部署 这里采取的是mysqld_expor ...

  8. 第四章:Django表单 - 5:模型表单ModelForm

    如果你正在构建一个数据库驱动的应用,那么你可能会有与Django的模型紧密映射的表单.比如,你有个BlogComment模型,并且你还想创建一个表单让大家提交评论到这个模型中.在这种情况下,写一个fo ...

  9. 初试 Centos7 上 Ceph 存储集群搭建

    转载自:https://cloud.tencent.com/developer/article/1010539 1.Ceph 介绍 Ceph 是一个开源的分布式存储系统,包括对象存储.块设备.文件系统 ...

  10. EasyExcel实现文件导入

    导入 准备工作 见:https://www.cnblogs.com/wywblogs/p/16095576.html 异步导入 接口代码 public Map importMemberList(@Re ...