2. 图数据基础:节点、边、属性

做图特征工程,得先搞清楚图长什么样。你可能会想:「不就是点和线吗?」没错,但具体到团伙识别场景,每个「点」和每条「线」背后都藏着业务含义。我在项目里最常见的问题就是:大家把节点和属性混为一谈,最后特征全乱套了。

先給一个直观的框架:

图数据基础结构:节点、边、属性 图数据基础三元组:节点 → 边 → 属性 嫌疑人A 年龄:32 嫌疑人B 年龄:28 嫌疑人C 年龄:35 团伙头目X 角色:组织者 关联账户D 余额:12万 通话 转账 见面 资金 核心概念 节点 = 主体(人/账户/设备) 边 = 关系(通话/转账/见面) 属性 = 节点或边的附加信息

2.1 节点:谁是「谁」

节点就是图里的一个主体。在团伙识别里,节点可以是人、手机号、银行卡、IP地址、设备ID……任何一个能作为实体的东西。我一开始犯过个错误——把「通话记录」也放进节点里。记住,行为是边,不是节点。

节点通常用唯一ID标记,比如身份证号、设备指纹。每个节点可以挂一堆属性,像年龄、性别、注册时间、历史风险分。这些属性后面会成为特征工程的核心原料。

技巧: 设计节点时,先想清楚「这个实体有没有独立参与团伙行为的能力」。一个人、一张卡可以,但一条短信内容不行——短信内容应该作为边的属性。

2.2 边:关系说了算

边就是节点之间的连接。无向边表示「双方对等」,比如互为好友;有向边表示「方向性」,比如转账(A→B),或资金归集(C→D)。团伙识别里,方向特别重要——钱流向上游还是下游,往往藏着组织层级。我曾经遇到一个案子,只看双向通话没发现异常,加上方向后发现所有通话都指向同一个人,头目立刻浮现。

边也可以有属性:通话时长、转账金额、见面地点经纬度、同设备登录次数……这些属性直接决定了边的「强度」或「风险权重」。

元素 团伙识别示例 常用属性
节点人员、银行卡、手机、设备年龄、性别、注册时间、风险分
边通话、转账、见面、同设备登录金额、次数、时长、IP段

2.3 属性:给数据和关系「贴标签」

属性分两类:节点属性和边属性。节点属性描述「谁是谁」,边属性描述「发生了什么」。做特征工程时,这两类属性要分开处理。我见过有人把通话金额加在节点上,结果多人共用一张卡时,金额归属全乱套了。

举个代码例子,用 NetworkX 快速构建一个团伙图:

import networkx as nx

G = nx.Graph()

# 添加节点,带上属性
G.add_node('A', age=32, risk_score=0.85)
G.add_node('B', age=28, risk_score=0.72)
G.add_node('头目X', role='组织者')

# 添加边,带上属性
G.add_edge('A', 'B', relation='通话', duration=120)
G.add_edge('A', '头目X', relation='见面', location='咖啡馆')
G.add_edge('B', '头目X', relation='转账', amount=5000)

print("节点数量:", G.number_of_nodes())
print("边数量:", G.number_of_edges())
print("节点A的属性:", G.nodes['A'])
避坑: 我曾经把节点ID写成中文姓名,结果数据量一大,重名导致图结构分裂。节点ID必须全局唯一,推荐使用哈希值或业务编码。

2.4 图的几种「变种」

图不是只有一种。根据业务需要,可能遇到:

  • 有向图 vs 无向图:转账、上下级关系必须用有向图;好友、同行关系用无向图。
  • 加权图 vs 无权图:通话次数、转账金额作为权重,比单纯的有无权更有信息量。
  • 多重边:两个人既有通话又有转账,应该保留两条边,而不是合并成一条。合并会损失行为多样性。
  • 异构图:节点类型不同(人、卡、设备),边类型也不同。团伙识别几乎都是异构图,同一张图里既有「人-人」边,也有「人-卡」边。

总结一下: 节点是主体,边是关系,属性是细节。把这个三元组弄扎实了,后面做邻居聚合、子图提取才有底气。我在项目复盘时发现,80%的特征工程问题都出在最基础的这三样东西没定义清楚。搞清楚再动手,别急着堆代码。