图谱特征工程基础:节点与边的定义、属性特征、结构特征、邻接矩阵与稀疏表示

做反欺诈图谱,第一步不是撸代码,而是想清楚你的图里装了什么。

说白了,图谱特征工程就是把现实世界的欺诈关系,翻译成机器能看懂的数字语言。我刚开始做这块的时候,觉得这步很简单——不就是把用户、设备、IP画个圈连个线吗?后来被坑了几次才发现,特征定义的好坏,直接决定模型的上限。

1. 节点与边的定义

图由两部分组成:节点(Vertex/Node)和边(Edge)。

节点:图中的基本实体。反欺诈场景下,常见的节点类型有:

  • 用户节点:账号、手机号、身份证
  • 设备节点:设备指纹、IMEI、MAC地址
  • 环境节点:IP地址、WiFi BSSID、GPS位置
  • 行为节点:订单、登录记录、支付交易

边:节点之间的关系。边可以有方向(有向图)或无方向(无向图)。

关键概念:同构与异构

如果所有节点是同一类(比如全是用户),叫同构图。如果节点类型超过一种(比如用户+设备+IP),叫异构图。反欺诈图谱99%都是异构的。

我在一个黑产团伙挖掘项目中,把「设备ID」也作为节点。结果发现一个设备绑定了上百个账号——这个特征直接揪出了一个机刷集群。

2. 属性特征

节点和边都可以携带属性信息。属性特征是图谱特征中最直观、最容易提取的一类。

特征类型 含义 反欺诈示例
节点属性 节点本身的描述信息 用户注册时间、设备型号、IP地理位置
边属性 节点之间关系的描述信息 交易金额、操作时间戳、关系强度(如通话次数)
环境属性 图谱上下文信息 图谱创建时间、数据源标记

实战建议:不要把所有属性都塞进去。我只选跟欺诈强相关的:比如设备首次活跃时间、同一IP下用户数量。属性噪声太多反而影响下游计算效率。

我记得第一次做图谱特征时,把用户注册时间直接作为一个属性传到GNN里。结果发现模型根本不收敛...后来才意识到,时间戳应该转换成「注册距今天数」或者「近7天活跃次数」这类归一化特征。

3. 结构特征

结构特征反映的是节点在图中的「位置」和「重要性」。这部分我当初花了最多功夫理解。

  • 度:节点的邻居数量。一个节点有100个邻居,说明它连接了很多其他节点——可能是团伙核心。
  • 聚类系数:衡量节点的邻居之间是否也相互连接。团伙内部通常呈现高聚类特征。
  • PageRank:节点的重要性排名。用于发现图中最「有影响力」的节点。
  • K-Core:节点所在的核层次。越内层的节点连接越稠密。
  • 最短路径长度:两个节点之间的距离。欺诈节点往往以短路径聚集。

举个例子:一个正常用户通常有3-5个好友,聚类系数在0.2左右。但一个养号团伙的节点,可能同时连接几百个账号,聚类系数接近0.8——因为都是机器批量互关。

避坑指南:结构特征不能独立使用。我曾经单纯用「度」来判断节点是否异常,结果把正常的大V用户误判成了欺诈节点。大V的度也高,但聚类系数低,组合使用才靠谱。

4. 邻接矩阵与稀疏表示

邻接矩阵是图的标准数学表示。它是一张 N × N 的矩阵(N 为节点数),矩阵元素 Aij 表示节点 i 和节点 j 之间是否有边。

有边则 Aij = 1(有权重时存权重),无边则 Aij = 0。

# 一个简单的邻接矩阵示例(3个节点)
# 节点A连接节点B和C,节点B只连接节点A
# 无向图

import numpy as np

A = np.array([
    [0, 1, 1],  # 节点A
    [1, 0, 0],  # 节点B
    [1, 0, 0]   # 节点C
])

print(A)
# 输出:
# [[0 1 1]
#  [1 0 0]
#  [1 0 0]]

但问题来了——真实图谱节点数动辄千万甚至上亿。邻接矩阵是 N × N 的,存储空间根本扛不住。一个1000万节点的邻接矩阵,需要 1014 个浮点数,约400TB内存——疯了吧?

所以实际工程中都用稀疏表示,只存非零元素的位置和值。

稀疏矩阵的三种常用格式

  • COO格式:用三个列表存(行索引、列索引、值)。简单直观,适合构建图。
  • CSR格式:行压缩存储。查邻居快,适合图算法。
  • CSC格式:列压缩存储。按列遍历快,用途相对少。
from scipy.sparse import coo_matrix, csr_matrix

# 用COO格式构建上面那个图的稀疏表示
rows = [0, 0, 1, 2]
cols = [1, 2, 0, 0]
data = [1, 1, 1, 1]

sparse_coo = coo_matrix((data, (rows, cols)), shape=(3, 3))
print(sparse_coo)
# 输出坐标和值,不存多余的0

# 转换成CSR格式查邻居
sparse_csr = sparse_coo.tocsr()
# 查节点0的邻居
neighbors = sparse_csr.getrow(0).indices
print(neighbors)  # [1, 2]

工程习惯:我建议在特征衍生阶段用COO格式,因为添加新边方便。模型训练阶段转CSR,查询效率高。别来回转——大量重复转换很耗内存。

5. 为何稀疏表示在反欺诈中如此重要?

反欺诈图谱有个特点:极度稀疏。一个用户在千万级用户的图中,通常只跟几十个节点有连接。稀疏度是 99.999% 级别的。

如果用稠密矩阵存储,大部分算力和内存都在处理0——浪费。稀疏表示只算有连接的部分,计算量和存储都是O(边数),这才是可行方案。

我曾经在处理一个5亿节点、200亿边的社交网络图时,用CSR格式存储,内存只用了30GB左右。换成稠密矩阵?想都不敢想。

图谱特征工程基础结构 节点定义 用户 设备 IP / 环境 行为(订单、登录) 边定义 有向 / 无向 同构 / 异构 带权 / 不带权 特征提取 属性特征 结构特征 邻接矩阵 稀疏表示 邻接矩阵 稀疏表示 COO / CSR / CSC 🔍 稀疏格式对比 COO格式:3个数组(row, col, data)→ 适合增量构建 CSR格式:压缩行索引 → 查询邻居快,适合图算法 CSC格式:压缩列索引 → 按列遍历快,用途相对少

说到这里,你应该能感觉到图谱特征工程的套路了:先确定节点和边是什么,再提取属性和结构特征,最后用稀疏表示把图存进计算机。这三步走扎实了,后面的特征衍生和模型训练才能吃得开。