图数据基础:节点、边、属性、有向图与无向图、图的存储格式

在构建欺诈评分系统之前,必须理解图数据的基本构成。图(Graph)是描述实体之间关系的数学结构,在欺诈检测中,实体(如用户、设备、IP地址)被抽象为节点,它们之间的交互(如登录、转账、共享设备)被抽象为边。本章将系统讲解图的核心概念与存储格式,为后续使用 Spark 处理图谱数据奠定基础。

2.1 图的核心要素

一个图由三个基本要素组成:节点、边和属性。下表总结了每个要素在欺诈场景中的典型含义:

要素 定义 欺诈评分示例
节点(Vertex) 图中的实体对象,也称为顶点 用户 ID、设备指纹、IP 地址、银行卡号
边(Edge) 节点之间的关联或交互 登录行为、转账交易、共享 WiFi
属性(Property) 节点或边的附加描述信息 用户注册时间、交易金额、设备型号
实战提示:在欺诈图谱中,属性是评分模型的重要特征来源。例如,边的“交易金额”属性可以直接作为异常检测的输入,节点的“注册天数”属性可用于评估账户成熟度。

2.2 有向图与无向图

根据边是否具有方向性,图分为有向图和无向图。这一区别在欺诈分析中至关重要,因为许多欺诈行为具有明确的方向性。

  • 有向图(Directed Graph):边具有方向,表示从源节点指向目标节点。例如,转账交易从付款人指向收款人,登录行为从用户指向设备。
  • 无向图(Undirected Graph):边没有方向,仅表示两个节点之间存在关联。例如,两个用户共享同一台设备,这种关系是对称的。

下图展示了有向图与无向图的结构差异:

有向图与无向图对比 有向图 用户A 设备X 用户B 设备Y 登录 转账 无向图 用户C 用户D 设备Z 设备W 共享设备 共享IP
欺诈场景选择原则:
  • 资金流向类(转账、支付)必须使用有向图,方向代表资金流动路径。
  • 共享资源类(同设备、同IP、同手机号)使用无向图,关系对称且无方向性。
  • 混合场景中,可将无向边拆分为两条有向边,便于统一处理。

2.3 图的存储格式

在大数据环境下,图数据通常以文本或列式格式存储。最常用的两种格式是边列表(Edge List)和邻接表(Adjacency List)。下表对比了它们的结构与适用场景:

存储格式 结构描述 优点 缺点 Spark 适用性
边列表 每行一条边,包含源节点和目标节点,可选属性列 简单直观,易于分区和并行读取 冗余存储节点信息,查询邻居需全表扫描 适合大规模图构建,GraphLoader 原生支持
邻接表 每行一个节点及其所有邻居节点列表 邻居查询高效,适合迭代计算 写入成本高,节点度分布不均时易倾斜 适合 PageRank 等迭代算法

2.3.1 边列表(Edge List)

边列表是最简单的图存储格式,每一行代表一条边。在欺诈评分中,通常还会附加边的属性(如交易金额、时间戳)。以下是一个典型的边列表示例:

# 边列表格式:src_id, dst_id, weight, timestamp
1001,2001,5000.00,2025-01-15 10:30:00
1001,2002,120.50,2025-01-15 11:00:00
1002,2001,9999.99,2025-01-15 11:05:00
1003,2003,0.01,2025-01-15 11:10:00
注意:边列表不直接存储节点属性。节点属性通常需要单独维护一个节点表(Vertex Table),通过节点 ID 进行关联。在 Spark 中,可以使用 DataFrame 的 join 操作将节点属性与边数据合并。

2.3.2 邻接表(Adjacency List)

邻接表将每个节点的邻居组织在一起,适合需要频繁访问邻居信息的图算法。以下是对应上述边列表的邻接表表示:

# 邻接表格式:src_id, neighbor1:weight, neighbor2:weight, ...
1001,2001:5000.00,2002:120.50
1002,2001:9999.99
1003,2003:0.01

下图展示了边列表与邻接表在 Spark 中的转换关系:

边列表 → 邻接表转换 边列表(Edge List) 1001, 2001, 5000.00 1001, 2002, 120.50 1002, 2001, 9999.99 1003, 2003, 0.01 每行一条边,包含 src, dst, weight groupBy 邻接表(Adjacency List) 1001 → (2001:5000.00, 2002:120.50) 1002 → (2001:9999.99) 1003 → (2003:0.01) 每行一个节点,聚合所有邻居及边属性

2.4 在 Spark 中加载图数据

Spark 的 GraphX 库提供了从边列表和邻接表加载图数据的标准方法。以下代码展示了如何使用 Spark 读取边列表并构建图:

import org.apache.spark.graphx._
import org.apache.spark.rdd.RDD

// 1. 从边列表文件加载边数据
val edgeRDD: RDD[Edge[Double]] = spark.sparkContext
  .textFile("hdfs://data/edges.csv")
  .map { line =>
    val parts = line.split(",")
    Edge(parts(0).toLong, parts(1).toLong, parts(2).toDouble)
  }

// 2. 加载节点属性(可选)
val vertexRDD: RDD[(Long, String)] = spark.sparkContext
  .textFile("hdfs://data/vertices.csv")
  .map { line =>
    val parts = line.split(",")
    (parts(0).toLong, parts(1))
  }

// 3. 构建图
val graph: Graph[String, Double] = Graph(vertexRDD, edgeRDD)

// 4. 查看图基本信息
println(s"节点数: ${graph.vertices.count()}")
println(s"边数: ${graph.edges.count()}")
性能建议:在欺诈评分场景中,图数据通常达到亿级边规模。建议使用 Parquet 或 ORC 列式格式存储边列表,并按照源节点 ID 进行分区,以提升 Spark 读取和 Shuffle 效率。

2.5 本章小结

  • 图数据由节点、边和属性三要素构成,在欺诈评分中分别对应实体、关系和特征。
  • 有向图适用于资金流向等方向性明确的场景,无向图适用于共享资源等对称关系。
  • 边列表是最通用的图存储格式,适合 Spark 分布式加载;邻接表在迭代算法中性能更优。
  • Spark GraphX 通过 Graph(vertexRDD, edgeRDD) 接口统一处理两种存储格式,开发者可根据算法需求灵活选择。