图数据基础:节点、边、属性、有向图与无向图、图的存储格式
在构建欺诈评分系统之前,必须理解图数据的基本构成。图(Graph)是描述实体之间关系的数学结构,在欺诈检测中,实体(如用户、设备、IP地址)被抽象为节点,它们之间的交互(如登录、转账、共享设备)被抽象为边。本章将系统讲解图的核心概念与存储格式,为后续使用 Spark 处理图谱数据奠定基础。
2.1 图的核心要素
一个图由三个基本要素组成:节点、边和属性。下表总结了每个要素在欺诈场景中的典型含义:
| 要素 | 定义 | 欺诈评分示例 |
|---|---|---|
| 节点(Vertex) | 图中的实体对象,也称为顶点 | 用户 ID、设备指纹、IP 地址、银行卡号 |
| 边(Edge) | 节点之间的关联或交互 | 登录行为、转账交易、共享 WiFi |
| 属性(Property) | 节点或边的附加描述信息 | 用户注册时间、交易金额、设备型号 |
实战提示:在欺诈图谱中,属性是评分模型的重要特征来源。例如,边的“交易金额”属性可以直接作为异常检测的输入,节点的“注册天数”属性可用于评估账户成熟度。
2.2 有向图与无向图
根据边是否具有方向性,图分为有向图和无向图。这一区别在欺诈分析中至关重要,因为许多欺诈行为具有明确的方向性。
- 有向图(Directed Graph):边具有方向,表示从源节点指向目标节点。例如,转账交易从付款人指向收款人,登录行为从用户指向设备。
- 无向图(Undirected Graph):边没有方向,仅表示两个节点之间存在关联。例如,两个用户共享同一台设备,这种关系是对称的。
下图展示了有向图与无向图的结构差异:
欺诈场景选择原则:
- 资金流向类(转账、支付)必须使用有向图,方向代表资金流动路径。
- 共享资源类(同设备、同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 中的转换关系:
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)接口统一处理两种存储格式,开发者可根据算法需求灵活选择。