图神经网络-图游走算法核心代码SkipGram、Node2Vec实现

图神经网络-图游走算法核心代码SkipGram、Node2Vec实现

图神经网络-图游走算法核心代码SkipGram、Node2Vec实现

1. DeepWalk采样算法

对于给定的节点,DeepWalk会等概率的选取下一个相邻节点加入路径,直至达到最大路径长度,或者没有下一个节点可选。

www.zeeklog.com  - 图神经网络-图游走算法核心代码SkipGram、Node2Vec实现

2. SkipGram模型训练

在得到节点路径后,node2vec会使用SkipGram模型学习节点表示,给定中心节点,预测局部路径中还有哪些节点。模型中用了negative sampling来降低计算量。

www.zeeklog.com  - 图神经网络-图游走算法核心代码SkipGram、Node2Vec实现

loss的计算过程可参考 PGL/examples/node2vec/node2vec.py中的 node2vec_model 函数。

%%writefile userdef_model.py
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

运行脚本,可以查看在ArXiv数据集上的效果:

!python my_node2vec.py  --use_my_model --epoch 5 # 使用自己定义的loss函数
!python link_predict.py --ckpt_path ./tmp/node2vec/paddle_model --epoch 100


pgl的官网node2vec_model源代码:

def node2vec_model(graph, hidden_size=16, neg_num=5):


    pyreader = l.py_reader(
        capacity=70,
        shapes=[[-1, 1, 1], [-1, 1, 1], [-1, neg_num, 1]],
        dtypes=['int64', 'int64', 'int64'],
        lod_levels=[0, 0, 0],
        name='train',
        use_double_buffer=True)


    embed_init = fluid.initializer.UniformInitializer(low=-1.0, high=1.0)
    weight_init = fluid.initializer.TruncatedNormal(scale=1.0 /
                                                    math.sqrt(hidden_size))


    src, pos, negs = l.read_file(pyreader)


    embed_src = l.embedding(
        input=src,
        size=[graph.num_nodes, hidden_size],
        param_attr=fluid.ParamAttr(
            name='content', initializer=embed_init))


    weight_pos = l.embedding(
        input=pos,
        size=[graph.num_nodes, hidden_size],
        param_attr=fluid.ParamAttr(
            name='weight', initializer=weight_init))
    weight_negs = l.embedding(
        input=negs,
        size=[graph.num_nodes, hidden_size],
        param_attr=fluid.ParamAttr(
            name='weight', initializer=weight_init))


    pos_logits = l.matmul(
        embed_src, weight_pos, transpose_y=True)  # [batch_size, 1, 1]
    neg_logits = l.matmul(
        embed_src, weight_negs, transpose_y=True)  # [batch_size, 1, neg_num]


    ones_label = pos_logits * 0. + 1.
    ones_label.stop_gradient = True
    pos_loss = l.sigmoid_cross_entropy_with_logits(pos_logits, ones_label)


    zeros_label = neg_logits * 0.
    zeros_label.stop_gradient = True
    neg_loss = l.sigmoid_cross_entropy_with_logits(neg_logits, zeros_label)
    loss = (l.reduce_mean(pos_loss) + l.reduce_mean(neg_loss)) / 2


    return pyreader, loss

3. Node2Vec采样算法

Node2Vec会根据与上个节点的距离按不同概率采样得到当前节点的下一个节点。

www.zeeklog.com  - 图神经网络-图游走算法核心代码SkipGram、Node2Vec实现
www.zeeklog.com  - 图神经网络-图游走算法核心代码SkipGram、Node2Vec实现

PGL/pgl/graph_kernel.pyx 中用Cython语言实现了节点采样函数node2vec_sample,以下使用numpy实现自己的node2vec_sample函数 ,用于随机游走后得到的路径,然后对这些路径进行吸收学习,训练图结构

%%writefile userdef_sample.py
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的节点采样函数
    #sampled_succ = 
    ################################## 
    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])   
    
    # 概率参数信息
    probs = []     # 保存每一个待前往的概率
    prob = 0   # 记录当前讨论的节点概率
    prob_sum = 0.  # 所有待前往的节点的概率之和


    # 遍历当前节点的相邻节点
    for i in range(succ_len):    # 遍历每一个当前节点前往的概率
        if succ[i] == prev_node:  # case 1 :采样节点与前一节点一致,那么概率为--1/q(原地)
            prob = 1. / p
        elif np.where(prev_succ_set==succ[i]):   # case 2  :采样节点在前一节点list内,那么概率为--1  
            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   # 这是一个随机数的最值 
    rand_num = float(np.random.randint(0, RAND_MAX+1)) / RAND_MAX * prob_sum  # 计算一个随机概率 


    sampled_succ = 0.   # 当前节点的相邻节点中确定的采样点


 
    for i in range(succ_len):   # 遍历当前节点的所有相邻节点
        rand_num -= probs[i]    # 利用rand_num这个随机获得的概率值 ,进行一个循环概率检验
        if rand_num <= 0:   # 停止检验条件 
            sampled_succ = succ[i]   # 并把当前节点作为确定的节点
    return sampled_succ




运行脚本

!python my_node2vec.py  --use_my_sample --epoch 5 # 用自己实现的采样函数训练模型
!python link_predict.py --ckpt_path ./tmp/node2vec/paddle_model --epoch 100 # 测试

pgl官网node2vec_sample源代码

@cython.boundscheck(False)
@cython.wraparound(False)
def node2vec_sample(np.ndarray[np.int64_t, ndim=1] succ,
        np.ndarray[np.int64_t, ndim=1] prev_succ, long long prev_node,
        float p, float q):
    """Fast implement of node2vec sampling
    """
    cdef long long i
    cdef succ_len = len(succ)
    cdef prev_succ_len = len(prev_succ)


    cdef vector[float] probs
    cdef float prob_sum = 0


    cdef unordered_set[long long] prev_succ_set
    for i in xrange(prev_succ_len):
        prev_succ_set.insert(prev_succ[i])


    cdef float prob
    for i in xrange(succ_len):
        if succ[i] == prev_node:
            prob = 1. / p
        elif prev_succ_set.find(succ[i]) != prev_succ_set.end():
            prob = 1.
        else:
            prob = 1. / q
        probs.push_back(prob)
        prob_sum += prob


    cdef float rand_num = float(rand())/RAND_MAX * prob_sum


    cdef long long sample_succ = 0
    for i in xrange(succ_len):
        rand_num -= probs[i]
        if rand_num <= 0:
            sample_succ = succ[i]
            return sample_succ

Read more

科普文:软件架构数据库系列之【MySQL:innodb刷脏页之Checkpoint机制详解】

科普文:软件架构数据库系列之【MySQL:innodb刷脏页之Checkpoint机制详解】

概叙 CheckPoint是MySQL的WAL和Redolog的一个优化技术。 ‌MySQL的CHECKPOINT机制‌是一种优化技术,主要用于将缓存池中的脏页刷新到磁盘,确保数据的持久性和一致性。 CHECKPOINT机制通过记录一个特定的时间点(称为检查点),在该时间点上,所有在此之前的修改都会被刷新到磁盘上,从而在系统崩溃时能够快速恢复数据。 CHECKPOINT的作用 1. ‌缩短数据库的恢复时间‌:当数据库宕机时,由于CHECKPOINT之前的页已经被刷新到磁盘,因此只需要对CHECKPOINT之后的重做日志进行恢复,从而缩短恢复时间‌。 2. ‌缓冲池不够用时刷新脏页‌(buffer pool大小有限):当缓冲池空间不足时,系统会强制执行CHECKPOINT,将脏页刷新到磁盘,以释放空间‌。 3. ‌重做日志不可用时刷新脏页‌(redolog file组中日志文件大小有限制):当重做日志不可用时(例如重做日志循环使用),系统会强制执行CHECKPOINT,确保脏页至少刷新到当前重做日志的位置‌。 CHECKPOINT的触发时机 1. ‌定期执行‌:

By Ne0inhk
科普文:软件架构Linux系列之【搞懂计算机架构NUMA(Non-Uniform Memory Access)非一致性内存访问】

科普文:软件架构Linux系列之【搞懂计算机架构NUMA(Non-Uniform Memory Access)非一致性内存访问】

概叙 前面了解了cpu、内存、磁盘、磁盘阵列,我们再接着看看NUMA。 NUMA到底指的是什么?我们怎么可以感受到它的存在?以及NUMA的存在对于我们编程会有什么影响?今天我们一起来看一下。 计算机应用和计算机模式 NUMA 非一致内存访问结构 ( Non Uniform Memory Access ) 系统架构 , 可以 集成多个处理器 , 使得系统在 " 处理事务 " 方面 , 有着 很高的性能 ; NUMA 架构中 , 处理器 访问 自己的本地内存速度很快 , 但是 访问 其它处理器的内存速度慢 , 这样为了 保证事物的执行性能 , 需要 减少 CPU 处理器之间的数据交互 , NUMA 架构 只适合OLTP ( On-Line Transaction Processing 联机事务处理过程 ) 事务处理场景 ; 使用

By Ne0inhk
科普文:软件架构数据库系列之【MySQL:innodb对numa的支持】

科普文:软件架构数据库系列之【MySQL:innodb对numa的支持】

概叙 关于numa和其他cpu架构,可以看看上面的文章。 在数据库系统中,OLTP(在线事务处理)数据库通常面临着高并发和高吞吐量的要求。 在某些情况下,可以通过启用 NUMA(非统一内存访问)来改善 OLTP 数据库的性能。 NUMA 系统的内存分配策略与传统的对称多处理 (SMP) 系统不同。在 SMP 系统中,所有 CPU 核心都可以访问同一个共享的内存。而在 NUMA 系统中,内存访问被限制在特定的节点(或节点集合),这取决于 CPU 核心的物理位置。 为了在 OLTP 数据库中使用 NUMA 技术来提升性能,可以考虑以下策略: 1. 内存分配:确保数据库进程被调度到具有足够内存的 NUMA 节点上。 1. 文件系统和磁盘:使用本地磁盘和文件系统来减少跨节点的通信。 1. 数据库配置:调整数据库的内存配置,使其尽可能利用本地内存。

By Ne0inhk
科普文:微服务之容器化【8种JDK性能测试:K8s上应该用哪种JDK?】

科普文:微服务之容器化【8种JDK性能测试:K8s上应该用哪种JDK?】

概叙 这次我将通过多次重复进行非常准确的比较以获得可重现的结果。 我将测试以下 JVM 实现: * Adoptium Eclipse Temurin * Alibaba Dragonwell * Amazon Corretto * Azul Zulu * BellSoft Liberica * IBM Semeru OpenJ9 * Oracle JDK * Microsoft OpenJDK 对于所有测试,我将使用 Paketo Java buildpack。我们可以使用 Paketo 轻松地在多个 JVM 实现之间切换。我将测试一个简单的 Spring Boot 3 应用程序,它使用 Spring Data 与 Mongo 数据库进行交互。 如果您已经使用 Dockerfile 构建了镜像,那么您可能使用的是来自 Docker Hub

By Ne0inhk