Node2vec

news2025/1/20 5:46:38

引言

这篇论文可以说是对DeepWalk的扩展,按照LINE的说法,DeepWalk只捕捉了节点间的二阶相似性。LINE同时捕捉节点间的一阶相似性和二阶相似性。而Node2Vec同时也是同时捕捉一阶相似性和二阶相似性。和LINE不同的是,Node2Vec是基于Random Walk实现的。
首先介绍两个重要概念:

  • 一阶相似性:在Node2vec中也叫做同质性,一阶相似性捕捉的是图中实际存在的结构。比如两个节点由一条边相连。则这两个节点应该具有相似性表示,按照Node2vec中的设计**,高度互联且属于相似网络的集群或社区的节点表示应该比较相近的,一阶相似性往往可以通过节点的DFS遍历得到**。
    二阶相似性:在Node2vec中,也叫做结构的对等性。在网络中具有相似的结构节点表示应该相近。其并不强调两个节点是否在图中存在连接。即使两个节点离得很远,但是,由于结构上相似(连接得邻居结点相似)它们得表示也应该相似,所以二阶相似性可以发现不同得社区,二阶相似性可以通过结点得BFS遍历得到
    一阶相似性和二阶相似性的区别可以由下图看出:
    在这里插入图片描述
    其中结点 U U U和结点 S 6 S_6 S6就是属于二阶相似性,可由BFS遍历捕获到, U U U S 1 S_1 S1都属于一阶相似性。可以由DFS捕获到。
    注:这里的结论可能与我们的理解相反,即一戒嗔相似性不应该由BFS得到吗?下面介绍Node2Vec时候给出一些想法
    Node2Vec首先借鉴了自然语言处理的Skip-gram算法。给定一个节点,最大化周围邻近节点出现的条件概率
    m a x f ∑ u ∈ V l o g P r ( N s ( u ) ∣ f ( u ) ) max_f\sum_{u \in V}logPr(N_s(u)|f(u)) maxfuVlogPr(Ns(u)f(u))

注意:这里的 N s ( u ) 是 节 点 u 的 邻 居 节 点 N_s(u)是节点u的邻居节点 Ns(u)u,在Node2vec中,邻居节点有着不一样的定义,其不一定是由直接边相连的节点。而是根据采样策略确定的,后面会有详细的介绍。

为了方便优化上式,作者做了两个假设:

  • 条件独立性假设:即各个邻居节点是相互独立的,所以有::
    P r ( N s ( u ) ∣ f ( u ) ) = ∏ n i ∈ N s ( u ) P r ( n i ∣ f ( u ) ) Pr(N_s(u)|f(u)) = \prod_{n_i\in N_s(u)}Pr(n_i|f(u)) Pr(Ns(u)f(u))=niNs(u)Pr(nif(u))

特征空间对称性:即一个节点与它们之间的邻居节点影响是互相的,于是其可以对邻居节点进行嵌入表示。然后利用点乘形式刻画条件概率:
在这里插入图片描述
有了以上两个形式假设,可以把公式一改写为如下形式:
在这里插入图片描述
其中:
在这里插入图片描述
这里我们发现,最终导出的目标函数和LINE中的二阶相似性公式很想,实际上两者只相差了一个边的权重 w i j w_{ij} wij 和LINE中一样,计算 Z U Z_U ZU是耗时的,所以作者也采用了负采样的方法。

注意:LINE中对二阶相似性建模公式为:
在这里插入图片描述
写到这里可以发现,以上思想和Deepwalk非常相似,都是给定一个节点,最大化邻居节点(一次采样路径上的节点)出现的条件概率。只不过由于计算方式的不同,Node2vec捕捉了二阶相似性。Deepwalk捕捉了一阶相似性。Deepwalk到这里和新算法其实已经结束了。其接下来在介绍如何利用Hierarchical Softmax来优化条件概率的计算。而Node2vec到这里才刚刚开始,其和Deppwalk最大的不同是,如何采样节点,即采样邻居节点 N s ( u ) N_s(u) Ns(u)

采样算法

传统的采样算法

传统的采样算法主要分为以下两部分:

  • 基于BFS:基于广度优先遍历采样,那么通常 N s ( u ) N_s(u) Ns(u)为节点u的直接邻居。即与 u u u直接相连接的节点。比如在图1中,我们设置采样大小 K = 3 K = 3 K=3,那么 N s ( u ) N_s(u) Ns(u) s 1 , s 2 , s 3 s_1,s_2,s_3 s1,s2,s3.
  • 基于DFS:基于深度优先遍历的采样,那么采样节点会离原节点越来越来越元。比如在图1中,我们设置采样大小 K = 3 K = 3 K=3,那么 N s ( u ) N_s(u) Ns(u) S 4 , S 5 . S 6 S_4,S_5.S_6 S4,S5.S6.

实际上这两种比较极端的采样方法,DFS和BFS对于前面所说二阶相似性和二阶相似性。也叫同质和结构对等性
node2vevv都是想通过设计一种采样算法,来融合一阶相似性和二阶相似性

Node2vec中的采样算法

node2vec中的采样算法基于random walk的,给定源节点 u u u,采样长度为1.假设当前节点在 C i − 1 C_{i - 1} Ci1个采样的节点,那么下一个采样节点为 x x x的概率为:
在这里插入图片描述
其中 π v x \pi_{vx} πvx为从节点 v v v到节点 x x x的转移概率。Z为归一化常数。

传统的random walk 采样算法是完全随机的,这样就很难让采样过程自动一阶和二阶相似性。为此,作者提出了二阶随机游走
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

使用基于random walk采样的好处

在这里插入图片描述

node2vec的算法流程

在这里插入图片描述

特征学习算法

在这里插入图片描述
在这里插入图片描述
注意:node2vecwalk算法的第5步:采用了alas采样算法。可以在 O ( 1 ) O(1) O(1)的时间内完成。

实验

作者首先利用《悲惨世界》里的人物,共现关系验证了node2vec里的有效性,如下图所示:
在这里插入图片描述
在这里插入图片描述

总结

node2vec可以说是deepwalk的扩展,两个参数 p 和 q p和q pq来控制, b f s 和 d f s bfs和dfs bfsdfs两种方式的随机游走,而deepwalk是一个不加制约,漫无目的的游走。不能显式的建模节点之间的结构信息

Alias sample采样算法

import numpy as np
def alias_setup(probs):
	'''
	Compute utility lists for non-uniform sampling from discrete distributions.
	Refer to https://hips.seas.harvard.edu/blog/2013/03/03/the-alias-method-efficient-sampling-with-many-discrete-outcomes/
	for details
	'''
	K = len(probs)
	q = np.zeros(K)    #保存样本概率
	J = np.zeros(K, dtype=np.int)  #保存补1的事件

	smaller = []
	larger = []
	for kk, prob in enumerate(probs):
	    q[kk] = K*prob
	    if q[kk] < 1.0:
	        smaller.append(kk)
	    else:
	        larger.append(kk)

	while len(smaller) > 0 and len(larger) > 0:
	    small = smaller.pop()
	    large = larger.pop()

	    J[small] = large
	    q[large] = q[large] + q[small] - 1.0  #q[large]-(1-q[small])
	    if q[large] < 1.0:
	        smaller.append(large)
	    else:
	        larger.append(large)

	return J, q    #(alias,prab)

def alias_draw(J, q):
	'''
	Draw sample from a non-uniform discrete distribution using alias sampling.
	'''
	K = len(J)

	kk = int(np.floor(np.random.rand()*K))
	if np.random.rand() < q[kk]:
	    return kk
	else:
	    return J[kk]

这部分为真正的源吗

import numpy as np
import networkx as nx
import random
from gensim.models import word2vec


class Graph():
    def __init__(self, nx_G, is_directed, p, q):
        self.G = nx_G
        self.is_directed = is_directed
        self.p = p
        self.q = q

    def node2vec_walk(self, walk_length, start_node):
        '''
        Simulate a random walk starting from start node.
        '''
        G = self.G
        alias_nodes = self.alias_nodes
        alias_edges = self.alias_edges

        walk = [start_node]

        while len(walk) < walk_length:
            cur = walk[-1]
            cur_nbrs = sorted(G.neighbors(cur))
            if len(cur_nbrs) > 0:
                # 如果序列中仅有一个结点,即第一次游走
                # alias_nodes中保存了alias_setup的[alias, accept],通过alias_draw返回采样的下一个索引号
                if len(walk) == 1:
                    walk.append(cur_nbrs[alias_draw(alias_nodes[cur][0], alias_nodes[cur][1])])
                else:
                    # 当前游走结点的前一个结点和下一个节点
                    prev = walk[-2]
                    # 使用alias_edges中记录的[alias, accept],来采样邻居中的下一个节点
                    next = cur_nbrs[alias_draw(alias_edges[(prev, cur)][0], 
                                                alias_edges[(prev, cur)][1])]
                    walk.append(next)
            else:
                break

        return walk

    def simulate_walks(self, num_walks, walk_length):
        '''
        Repeatedly simulate random walks from each node.
        '''
        G = self.G
        walks = []
        nodes = list(G.nodes())
		# nodes采样一次为一个epoch,此处就是num_walks个epoch
        print('Walk iteration:')
        for walk_iter in range(num_walks):
            print(str(walk_iter+1), '/', str(num_walks))
            random.shuffle(nodes)
            for node in nodes:
                walks.append(self.node2vec_walk(walk_length=walk_length, start_node=node))

        return walks

    def get_alias_edge(self, src, dst):
        '''
        Get the alias edge setup lists for a given edge.
        :return alias_setup(): 在上一次访问顶点 t ,当前访问顶点为 v 时到下一个顶点 x 的未归一化转移概率。
		:param src:  随机游走序列种的上一个结点
		:param dst:  当前结点
        参数p控制重复访问刚刚访问过的顶点的概率。若p较大,则访问刚刚访问过的顶点的概率会变低。
        参数q控制着游走是向外还是向内:
        若q>1,随机游走倾向于访问和上一次的t接近的顶点(偏向BFS);若q<1,倾向于访问远离t的顶点(偏向DFS)
        '''
        G = self.G
        p = self.p
        q = self.q

        unnormalized_probs = []
        for dst_nbr in sorted(G.neighbors(dst)):
            if dst_nbr == src:   # 如果是要返回上一个节点
                unnormalized_probs.append(G[dst][dst_nbr]['weight']/p)
            elif G.has_edge(dst_nbr, src):   # 如果接下来访问的节点与src的距离与当前节点相等
                unnormalized_probs.append(G[dst][dst_nbr]['weight'])
            else:
                unnormalized_probs.append(G[dst][dst_nbr]['weight']/q)
        norm_const = sum(unnormalized_probs)
        normalized_probs =  [float(u_prob)/norm_const for u_prob in unnormalized_probs]

        return alias_setup(normalized_probs)

    def preprocess_transition_probs(self):
        '''
        Preprocessing of transition probabilities for guiding the random walks.
        用于引导随机游走的预处理,得到马尔可夫转移概率矩阵。
        '''
        G = self.G
        is_directed = self.is_directed

        alias_nodes = {}
        # G.neighbors(node) 与顶点相邻的所有顶点,更方便更快的访问adjacency字典用: G[cur]
        for node in G.nodes():
            # 根据邻居节点的权重,计算转移概率
            unnormalized_probs = [G[node][nbr]['weight'] for nbr in sorted(G.neighbors(node))]
            norm_const = sum(unnormalized_probs)
            # 计算当前节点到邻居节点的转移概率,其实就是权重归一化
            normalized_probs =  [float(u_prob)/norm_const for u_prob in unnormalized_probs]
            # 设置alias table,保存每个节点的accept[i]和alias[i],为后面alias采样做准备。
            alias_nodes[node] = alias_setup(normalized_probs)

        alias_edges = {}
        triads = {}

        # 保存每条边的accept[i]和alias[i]
        if is_directed:
            for edge in G.edges():
                alias_edges[edge] = self.get_alias_edge(edge[0], edge[1])
        else:
            for edge in G.edges():
                alias_edges[edge] = self.get_alias_edge(edge[0], edge[1])   # 随机游走序列种的上一个结点 当前节点
                alias_edges[(edge[1], edge[0])] = self.get_alias_edge(edge[1], edge[0])

        self.alias_nodes = alias_nodes
        self.alias_edges = alias_edges
        
        print(self.alias_nodes)
        print(self.alias_edges)
        return


def alias_setup(probs):
    '''
    Compute utility lists for non-uniform sampling from discrete distributions.
    Refer to https://hips.seas.harvard.edu/blog/2013/03/03/the-alias-method-efficient-sampling-with-many-discrete-outcomes/
    for details
    :param probs: 指定的采样结果概率分布列表。期望按这个概率列表来采样每个随机变量X。
    :return J: alias[i]表示第i列中不是事件i的另一个事件的编号。
    :return p: accept[i]表示事件i占第i列矩形的面积的比例。
    '''
    K = len(probs)
    # q表示:accept数组
    q = np.zeros(K)
    # J表示:alias数组
    J = np.zeros(K, dtype=np.int)

    # Alias方法将整个概率分布压成一个 1*N 的矩形,每个事件转换为矩形中的面积。
    # 将面积大于1的事件多出的面积补充到面积小于1对应的事件中,以确保每一个小方格的面积为1,
    # 同时,保证每一方格至多存储两个事件。
    smaller = [] # 面积小于1的事件
    larger = []  # 面积大于1的事件
    
    for kk, prob in enumerate(probs):
        q[kk] = K*prob
        if q[kk] < 1.0:
            smaller.append(kk)
        else:
            larger.append(kk)

    while len(smaller) > 0 and len(larger) > 0:
        small = smaller.pop()
        large = larger.pop()

        J[small] = large
        # 其实是 q[large] - (1.0 - q[small]),把大的削去(1.0 - q[small])填充到小的上
        q[large] = q[large] + q[small] - 1.0
		# 大的剩下的面积,放到下一轮继续倒腾
        if q[large] < 1.0:
            smaller.append(large)
        else:
            larger.append(large)

    return J, q

def alias_draw(J, q):
    '''
    Draw sample from a non-uniform discrete distribution using alias sampling.
    参考:https://zhuanlan.zhihu.com/p/54867139

    :param q: accept数组,表示事件i占第i列矩形的面积的比例;
    :param J: alias数组,表示alias矩形的第i列中不是事件i的另一个事件的编号,也就是填充的那一列的序号;
    生成一个随机数 kk in [0, K],另一个随机数 x in [0,1],
    如果 x < accept[kk],表示接受事件kk,返回kk,否则拒绝事件kk,返回alias[kk]
    '''
    K = len(J)

    kk = int(np.floor(np.random.rand()*K))
    if np.random.rand() < q[kk]:
        return kk
    else:
        return J[kk]
      
def read_graph(input_file, directed):
    '''
    Reads the input network in networkx.
    '''
    if directed:
        G = nx.read_edgelist(input_file, delimiter=",", nodetype=int, data=(('weight',float),), create_using=nx.DiGraph())
    else:
        G = nx.read_edgelist(input_file, delimiter=",", nodetype=int, create_using=nx.DiGraph())
        for edge in G.edges():
            G[edge[0]][edge[1]]['weight'] = 1

    if not directed:
        G = G.to_undirected()

    return G

def learn_embeddings(walks):
    '''
    Learn embeddings by optimizing the Skipgram objective using SGD.
    '''
    walks = [list(map(str, walk)) for walk in walks]
    print(walks)
#     model = word2vec.Word2Vec(walks, vector_size=64, window=3, min_count=0, sg=1, workers=1, epochs=5)
    # model.save_word2vec_format(args.output)
    #model.wv.save_word2vec_format(args.output, binary=False)
    
    return

def main(directed):
    '''
    Pipeline for representational learning for all nodes in a graph.
    '''
    nx_G = read_graph(r"C:\Users\Administrator\TensorFlow\game.csv", directed)
    print(list(nx_G.edges(data=True)), list(nx_G))
    for node in nx_G.neighbors(2):
        print(node)
    G = Graph(nx_G, False, 1, 2)
    
    G.preprocess_transition_probs()
    walks = G.simulate_walks(5, 3)
    learn_embeddings(walks)

if __name__ == "__main__":
    main(directed = False)

总结

会先大致看一下,代码,然后慢慢的开始研究代码结构,全部将其搞定都行啦的样子与打算,慢慢的将代码全部都将其搞定都行啦的样子与打算,用到啥,后续将各种的代码都将其搞完整都行啦的样子与打算。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/44041.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringCloudGateWay个人笔记

核心概念&#xff1a; Route&#xff08;路由&#xff09;&#xff1a; 路由是构建⽹关的基本模块&#xff0c;它由ID&#xff0c;⽬标URI&#xff0c;⼀系列的断⾔和过滤器组成&#xff0c;如果断⾔为true就 匹配该路由。Predicate&#xff08;断⾔、谓词&#xff09;&#xf…

D-018 LED硬件电路设计

LED硬件电路设计1 简介2 LED的参数3 驱动方式3.1 定电压驱动3.2 定电流驱动4 应用场景5 设计要点1 简介 发光二极管&#xff08;简称LED&#xff09;,是一种发光的电子器件&#xff0c;能将电能转换为光能。这种器件不仅发光效率高&#xff0c;而且节能、寿命长。LED会议波长来…

SpringMVC入门到实战------七、RESTful的详细介绍和使用 具体代码案例分析(一)

代码资源位置&#xff1a;F:\workspace\SpringMVC代码\springMVC-demo4 1、RESTful简介 REST&#xff1a;Representational State Transfer&#xff0c;表现层资源状态转移。 a>资源 资源是一种看待服务器的方式&#xff0c;即&#xff0c;将服务器看作是由很多离散的资源组…

C语言 指针与数组

C语言 指针与数组引言1. 指针与数组之间的联系2. 指针与字符串之间的联系一、指针与数组1. 对比两者的区别2. 指针数组的用法3. 数组指针的用法二、数组参数、指针参数1. 一维数组传参2. 二维数组传参三、指针与函数 (了解)1. 其实函数也有地址2. 函数指针3. 函数指针数组4. qs…

03_SpringBoot项目配置

文章目录SpringBoot项目配置0x01_properties格式配置文件0x02_yml格式配置文件配置对象类型数据配置集合类型配置数组类型0x03_properties和yml的区别优先级区别0x04_配置文件在项目中的位置0x05_bootstrap配置文件0x06_springboot项目结构SpringBoot项目配置 SpringBoot默认读…

Mysql数据库和SQL语句

一、数据库介绍&安装 1. 什么数据库软件 在前期学习的过程中&#xff0c;对于数据的保存方式有两种体现&#xff1a; 一种是将数据保存到本地的文件中&#xff0c;优点是可以持久保存&#xff0c;但是数据管理查询等相当麻烦。 一种是将数据保存到本地的内存中&#xff0c…

Linux学习记录——삼 基本指令(3)及了解权限

接着上一篇把一些基本命令写完 unzip解压默认解压到当前目录&#xff0c;加上-d后面跟路径就可以解压到指定目的地 tar指令 不同文件格式的压缩指令&#xff0c;可以直接看内容&#xff0c;不需要打开。tar后面有几个指令选项。-c表示创建压缩文件&#xff0c;-z打包并压缩&am…

E. Matrix and Shifts(思维+遍历正对角线)

Problem - 1660E - Codeforces 你会得到一个大小为nn的二进制矩阵A。行从上到下从1到n编号&#xff0c;列从左到右从1到n编号&#xff0c;位于第i行和第j列交点的元素称为Aij。考虑一组4个操作。 循环地将所有行向上移动。索引为i的行将被写在i-1行的位置上&#xff08;2≤i≤…

文件权限概念,相关操作

一&#xff0c;文件权限的基本概念 权限&#xff1a;操作系统限制对资源访问的一种机制。 文件权限的信息展示&#xff0c;使用ls -l 命令即可查看&#xff1a; 整个文件信息可以分为以下几部分&#xff1a; &#xff08;一&#xff09;第一个字段表示文件类型 和 文件权限。…

Socket套接字编程

文章目录1、网络字节序列2、socket编程接口<1>socket常见ARI<2>sockaddr结构 VS sockaddr_in结构3、UDP套接字4、TCP套接字5、总结1、网络字节序列 内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏 移地址也有大端小端…

腾讯T9纯手写基于Mycat中间件的分布式数据库架构笔记

随着移动互联网的兴起和大数据的蓬勃发展&#xff0c;系统的数据量正呈几何倍数增长&#xff0c;系统的压力也越来越大&#xff0c;这时最容易出现的问题就是服务器繁忙&#xff0c;我们可以通过增加服务器及改造系统来缓解压力&#xff0c;然后采用负载均衡、动静分离、缓存系…

流媒体传输 - RTSP Over HTTP

RTSP 的标准端口是 554&#xff0c;但是由于各种不同的防火墙等安全策略配置的原因&#xff0c;客户端在访问 554 端口时可能存在限制&#xff0c;从而无法正常传输 RTSP 报文。 但是 HTTP 端口&#xff08;80 端口&#xff09;是普遍开放的&#xff0c;于是就有了让 RTSP 报文…

【Android App】给三维的地球仪贴上动物贴纸实战(附源码和演示 超详细必看)

需要源码和图片集请点赞关注收藏后评论区留言~~~ 一、纹理贴图 给三维物体穿衣服的动作&#xff0c;通常叫做给三维图形贴图&#xff0c;更专业地说叫纹理渲染。 渲染纹理的过程主要由三大项操作组成&#xff0c;分别说明如下&#xff1a; &#xff08;1&#xff09;启用纹理…

STL的常用遍历算法(20221128)

STL的常用算法 概述&#xff1a; 算法主要是由头文件<algorithm> <functional> <numeric> 组成。 <algorithm>是所有STL头文件中最大的一个&#xff0c;涉及比较、交换、查找、遍历等等&#xff1b; <functional>定义了一些模板类&#xff0…

2022VR高级研修班总结

本人有幸参加2022VR高级研修班&#xff0c;此次高级研修班由赵沁平院士和丁文华院士领衔&#xff0c;全国知名专家及长期在相关领域从事产业、管理、科研工作的专家参与&#xff0c;带来了18个专题讲座&#xff0c;内容涵盖虚拟现实技术与系统现状与发展、产学研合作与产业协同…

都什么年代了,你居然还连不上GitHub?

前言 众所周知&#xff0c;GitHub是我们程序员在上班或者学习的时候经常会逛的一个地方[手动狗头]&#xff0c;而且如果我们想参与开源项目的话&#xff0c;GitHub也是一个很好的平台。 可问题是&#xff0c;GitHub网页总是进不去&#xff0c;提交代码到GitHub也总是超时&…

在Navicat上如何停止正在运行的MYSQL语句

目录 &#xff08;一&#xff09;前言 &#xff08;二&#xff09;正文 1. 图形化停止SQL 2. 用SQL方式停止运行的SQL &#xff08;1&#xff09;找到运行的SQL的ID &#xff08;2&#xff09;运行kill命令杀掉SQL &#xff08;一&#xff09;前言 众所周知&#xff0c;…

BI数据分析软件有哪些?为什么说奥威BI很特别?

经过十几年的发展&#xff0c;以及近几年国家的大力鼓励发展大数据智能产业等原因&#xff0c;BI数据分析软件开始越来越被大家所熟知&#xff0c;那么BI数据分析软件都有哪些&#xff0c;为什么说奥威BI数据分析软件很特别&#xff1f;它对企业的大数据智能数据可视化分析起到…

什么样的人最适合做软件测试---喜欢找人帮忙办事的人

今天和大家说下什么样类型的人适合做软件测试。 经验干货&#xff0c;可仔细品 很多测试工程师面试中也可能会遇到问题&#xff0c;说怎么做一名优秀合格的测试工程师&#xff0c;需要有哪些品质&#xff0c;很多人会回答说要仔细&#xff0c;要承受压力&#xff0c;要有责任感…

2013-2020年全国31省数字经济数据集

1、时间&#xff1a;2013-2020年 2、来源&#xff1a;整理自国家统计J和统计NJ 3、指标包括&#xff1a; 信息化基础&#xff1a;"光缆线路长度(公里)、移动电话基站&#xff08;万个&#xff09;、信息传输、软件和信息技术服务业城镇单位就业人员(万人)、年末常住人口…