跳转至
开发版采用单机部署,无需产品 License 文件。需要多机部署或大规模性能测试? 了解企业版

个性化 PageRank

本文介绍个性化 PageRank (Personalized Page Rank, PPR) 算法,并演示如何用 GQL 实现该算法。

算法介绍

PPR 算法是 PageRank 算法的一个变种,它基于个性化偏好计算页面的重要性。与传统的 PageRank 将所有页面一视同仁不同,PPR 允许用户指定一组他们感兴趣的页面,算法会优先考虑这些页面进行排名。

某页面的 PPR 分数由一个随机用户从个性化页面集出发,通过链接跳转到达该页面的可能性决定。计算页面 PPR 分数的公式如下:

\[ PPR(i) = (1 - d) \cdot v(i) + d \cdot \sum_{j \in In(i)} \frac{PPR(j)}{Out(j)} \]

其中:

  • PPR(i):页面i的 PPR 分数。
  • d:阻尼因子,表示用户从当前页面跳转到其他页面的概率。取值范围:[0, 1)。
  • v(i):代表对页面i的偏好程度的向量。取值越高表示偏好越高。通常,对个性化集合中的页面,取值为1;而对其他页面,则取值为0。
  • In(i):链接到页面i的页面集合。
  • Out(j):页面j的出链数量。

数据模型

为了用 GQL 实现 PPR 算法,将基于以下数据模型来创建图类型:

  • 页面被抽象为人,由类型为Person的点表示。
  • 页面之间的链接被抽象为人与人之间的关系,由类型为Knows的边表示。

该图类型的完整定义,请参见准备图数据。

过程实现

为了计算图中点的 PPR 分数,将创建一个过程来实现 PPR 算法。

该过程的完整定义,请参见创建过程。下文将介绍该过程的关键组成部分及其工作原理。

该过程接受以下参数:

  • source_pks:个性化集合中的点的 ID 列表。
  • damping:阻尼因子。
  • max_iter:最大迭代次数。
  • eps:收敛阈值。如果某次迭代中 PPR 分数的总变化小于该阈值,则停止迭代。

此外,在过程内部定义了以下变量:

变量名 变量类型 描述
start_set 活动集变量 每次迭代中正在处理的点的集合。
total_set 活动集变量 所有迭代中处理过的点的集合。
cur_iter 原始值变量 当前迭代次数。
delta 全局 SumAgg 聚合值变量 在每次迭代中点的 PPR 分数的总变化量。用于判断是否停止迭代。
topk_score 全局 TopKAgg 聚合值变量 PPR 分数最高的前 K 个点。
pk_set 全局 SetAgg 聚合值变量 个性化集合中点的 ID 集合。
current_score 点绑定 SumAgg 聚合值变量 每个点在当前迭代的 PPR 分数。
next_score 点绑定 SumAgg 聚合值变量 每个点在下一迭代的 PPR 分数。
out_edge 点绑定 SumAgg 聚合值变量 每个点的出边数量。
is_source 点绑定 OrAgg 聚合值变量 代表点是否在个性化集合中的标记。
result_file 文件变量 存储前 K 个点的 PPR 分数的文件。

以下步骤概述了该过程的逻辑:

  1. 将每个点的out_edge初始化为其出边数量。
  2. 将pk_set初始化为参数source_pks提供的个性化集合中的点的 ID。
  3. 将start_set初始化为个性化集合中的点,并将它们的current_score设置为1.0,is_source设置为true。同时,也将total_set初始化为这些点。
  4. 开始迭代过程以更新 PPR 分数。若当前迭代次数超过最大迭代次数(由max_iter指定)或达到收敛阈值(由eps指定),就停止迭代:
    1. 将delta重置为0.0。
    2. 对于start_set中的点的每条边,将目标点的next_score设置为起始点的current_score除以其out_edge。
    3. 将所有目标点添加到start_set中。
    4. 对start_set中的每个点,执行以下操作:
      1. 按照以下规则更新其next_score:
        • 若该点在个性化集合中,将其next_score设置为1.0 - damping + damping * next_score。
        • 否则,将其next_score设置为damping * next_score。
      2. 计算其next_score与current_score之间的绝对差值并累加至delta。
      3. 准备下一次迭代,将其current_score设置为next_score,并将next_score重置为0.0。
    5. 将start_set中的点添加到total_set中。
    6. 迭代次数cur_iter加1。
    7. 输出delta的值以便监控。
  5. 在最后一次迭代结束后,将topk_score初始化为total_set中current_score最高的前 20 个点。
  6. 把topk_score中的结果导出到result_file指定的文件中。
  7. 将以下结果作为输出参数返回:
    • node_id:topk_score中点的 ID。
    • score:该点的 PPR 分数,保留五位小数。

操作步骤

在悦数图管理的控制台中,按顺序执行以下代码块。首次使用时,先按准备算法示例数据确认操作位置和目录挂载。

1. 准备图数据

  1. 创建一个与数据模型匹配的图类型。

    '''
    CREATE GRAPH TYPE IF NOT EXISTS pagerank_graph_type AS {
        NODE Person (LABEL Person {id INT PRIMARY KEY, name STRING}),
        EDGE Knows (Person)-[LABEL Knows {MULTIEDGE KEY()}]->(Person)
    }
    '''
    
  2. 下载以下 CSV 文件,保留文件名,保存到部署目录下的examples/algorithms/ppr/:

    查看 CSV 内容
    pagerank_node.csv
    u_id,u_name
    1,Alice
    2,Bob
    3,Charlie
    4,David
    
    pagerank_edge.csv
    src_id,dst_id
    1,2
    2,3
    1,4
    3,4
    4,2
    
  3. 基于图类型创建一个临时图,并将数据文件加载到临时图中。

    CREATE TEMPORARY GRAPH IF NOT EXISTS #ppr_graph TYPED pagerank_graph_type
    
    '''
    USE #ppr_graph IMPORT INTO GRAPH {
          NODE (v@Person {id: u_id, name: u_name}) FROM DATAFILE {
              PATH: "file:///examples/algorithms/ppr/pagerank_node.csv",
              FORMAT: "CSV",
              DELIMITER: ','
          },
          EDGE (id: src_id)-[e@Knows{}]->(id: dst_id) FROM DATAFILE {
              PATH: "file:///examples/algorithms/ppr/pagerank_edge.csv",
              FORMAT: "CSV",
              DELIMITER: ','
          }
        }
    '''
    

    导入成功后,在控制台执行以下查询核对点数:

    '''
    USE #ppr_graph
    MATCH (n)
    RETURN count(n) AS node_count
    '''
    

    node_count应为4。点数不符时,先按文件问题排查检查文件和导入结果。

2. 创建过程

创建实现 PPR 算法的过程。关于该过程的更多信息,请参见过程实现。

'''
CREATE OR REPLACE PROCEDURE pers_pagerank(source_pks LIST<INT>, damping DOUBLE, max_iter INT, eps DOUBLE) RETURNS (node_id INT, score DOUBLE) AS {
    VALUE      start_set     ACTIVE_SET
    VALUE      total_set     ACTIVE_SET
    VALUE      cur_iter      = 0
    VALUE      delta         SumAgg<DOUBLE> = 1.0
    VALUE      topk_score    TopKAgg<20, score DOUBLE DESC, node_id INT DESC>
    VALUE      pk_set        SetAgg<INT>
    NODE VALUE current_score SumAgg<DOUBLE> = 0.0
    NODE VALUE next_score    SumAgg<DOUBLE> = 0.0
    NODE VALUE out_edge      SumAgg<INT64> = 0
    NODE VALUE is_source     OrAgg = false

    FILE result_file {vid INT64, score DOUBLE} = DATAFILE {PATH:"file:///examples/algorithms/ppr/output", FORMAT:"CSV", INCLUDE_HEADER: true}

    MATCH (s)-[e]->(t)
    PER PATH {
      SET s.@out_edge += 1
    }

    SET @pk_set = source_pks

    MATCH (s@Person)
    WHERE @pk_set.contains_key(s.id)
    FINALLY {
      SET start_set = s
    }

    MATCH (s)
    WHERE s IN start_set
    PER NODE (s) {
      SET s.@current_score = 1.0
      SET s.@is_source = true
    }
    FINALLY {
      SET total_set = s
    }

    WHILE cur_iter < max_iter AND @delta > eps THEN {
      SET @delta = 0.0
      MATCH (s)-[e]->(t)
      WHERE s IN start_set
      PER PATH {
        SET t.@next_score += s.@current_score / s.@out_edge
      }
      FINALLY {
        SET start_set |= t
      }

      MATCH (s)
      WHERE s IN start_set
      PER NODE (s) {
          IF s.@is_source = true THEN {
            SET s.@next_score = 1.0 - damping + damping * s.@next_score
            } ELSE {
              SET s.@next_score = damping * s.@next_score
            }
          SET @delta += abs(s.@next_score - s.@current_score)
          SET s.@current_score = s.@next_score
          SET s.@next_score = 0.0
      }
      FINALLY {
        SET total_set |= s
      }
      SET cur_iter = cur_iter + 1
      LOG_INFO("delta == ", @delta)
    }

    MATCH (a)
    WHERE a IN total_set
    PER NODE (a) {
      SET @topk_score += RECORD{score:a.@current_score, node_id:a.id}
    }

    FOR r IN @topk_score
      EXPORT r.node_id AS vid, round(r.score, 5) AS score INTO result_file

    FOR r IN @topk_score
      RETURN r.node_id, round(r.score, 5)
}
'''

3. 调用过程

调用过程来计算 PPR 分数。以下示例将个性化集合中的点设置为 ID 为1和2的点,阻尼因子设置为0.85、最大迭代次数设置为10,收敛阈值设置为0.001。

USE #ppr_graph CALL pers_pagerank([1,2],0.85, 10, 0.001) RETURN *

此过程在控制台返回结果表,同时将结果导出为 CSV 文件。

返回:

+---------+---------+
| node_id | score   |
+---------+---------+
| 2       | 0.66672 |
| 3       | 0.65038 |
| 4       | 0.5329  |
| 1       | 0.15    |
+---------+---------+
执行日志示例
Log:
2025-10-11T09:17:37.900499 INFO  - delta == 2.55
2025-10-11T09:17:37.909048 INFO  - delta == 0.7225
2025-10-11T09:17:37.918101 INFO  - delta == 0.614125
2025-10-11T09:17:37.927305 INFO  - delta == 0.522006
2025-10-11T09:17:37.936807 INFO  - delta == 0.443705
2025-10-11T09:17:37.944857 INFO  - delta == 0.37715
2025-10-11T09:17:37.953506 INFO  - delta == 0.320577
2025-10-11T09:17:37.962312 INFO  - delta == 0.272491
2025-10-11T09:17:37.970650 INFO  - delta == 0.231617
2025-10-11T09:17:37.978231 INFO  - delta == 0.196874

在宿主机的部署目录下,打开examples/algorithms/ppr/output/中的 CSV 文件。示例内容如下:

"vid","score"
2,0.66672
3,0.65038
4,0.5329
1,0.15
将结果写入图中

如果希望将个性化 PageRank 结果持久化到图,请参见 EXPORT:

  • 先将 PPR 输出字段导出到表变量(通常为vid、score)。
  • 若写入远程图,先table_split(...)分批,再通过nebula_exec执行批量USE + INSERT。

目标图类型需与输出模型匹配,例如为点增加ppr_score属性。