第三章 数据准备:风控图数据构造与特征工程
各位同学,欢迎来到第三章。这一章咱们要动真格的了——把原始数据变成图神经网络能吃的“饲料”。
说实话,很多同学在学GCN时,模型原理背得滚瓜烂熟,一上手就懵了:数据怎么组织?特征怎么算?邻接矩阵太大怎么办?
嗯,这些坑我都踩过。今天咱们一个一个填平。
3.1 风控多模图的构造逻辑
先问个问题:为什么风控要用图?
传统风控看的是单点特征——你借过多少钱、逾期过几次。但黑产团伙是“组团作案”的。一个设备挂几十个账号,一个IP关联几百个手机号。这种关联关系,表格根本表达不了。
所以我们要建图。而且是多模图——多种节点类型、多种边关系。
核心思路:把用户、设备、IP、手机号都作为节点,它们之间的关联作为边。
我习惯用这种四类节点:
- 用户节点(U):每个借款用户是一个节点
- 设备节点(D):手机IMEI、设备指纹等
- IP节点(I):登录IP、交易IP
- 手机号节点(P):注册手机号、联系人手机号
边怎么定义?很简单:谁用过谁,就连一条边。
| 边类型 | 含义 | 示例 |
|---|---|---|
| U-D | 用户使用过某设备 | 用户A登录过设备X |
| U-I | 用户从某IP登录 | 用户A的IP是192.168.1.1 |
| U-P | 用户使用某手机号 | 用户A绑定手机号138xxxx |
| D-I | 设备连接过某IP | 设备X通过IP 10.0.0.1上网 |
我在项目中遇到过一个问题:数据量太大,全量构图内存直接爆了。后来我学乖了——按时间窗口切分,只取近30天的关联数据。既保证时效性,又控制图规模。
3.2 特征工程:给节点和边“贴标签”
图建好了,但节点和边都是“裸”的。我们需要给它们赋予特征。
3.2.1 节点特征
节点特征说白了就是“这个节点本身长什么样”。
我一般分三类:
- 基础属性:用户年龄、性别、注册时长;设备型号、系统版本;IP所属地区、运营商
- 统计特征:用户历史借款次数、逾期次数;设备关联用户数;IP出现频率
- 时序特征:最近一次活跃时间、活跃时段分布、行为间隔规律
举个例子,用户节点的特征向量可能是这样:
# 用户节点特征示例
user_features = {
'age': 28, # 年龄
'gender': 1, # 性别编码
'reg_days': 365, # 注册天数
'loan_count': 12, # 借款次数
'overdue_rate': 0.08, # 逾期率
'device_count': 2, # 关联设备数
'ip_change_freq': 0.3, # IP更换频率
'night_active_ratio': 0.15 # 夜间活跃比例
}
你想想看,如果一个用户关联了50个设备,IP每天换3次,这正常吗?大概率是黑产在批量操作。这些特征就是GCN判断的依据。
3.2.2 边特征
边特征容易被忽略,但它非常关键。同样是“用户-设备”边,有的用户是正常登录,有的可能是盗号登录。
我常用的边特征:
- 交互频次:该用户通过该设备操作了多少次
- 交互时长:平均每次使用时长
- 交互时段:主要在白天还是深夜
- 行为一致性:该用户的操作行为是否与设备历史行为一致
3.3 邻接矩阵与稀疏矩阵处理
图在数学上怎么表示?邻接矩阵。但风控图的节点数动辄百万级,全量邻接矩阵存不下。
举个例子:100万个节点,邻接矩阵就是100万×100万,需要约4TB内存。这谁顶得住?
所以必须用稀疏矩阵。
3.3.1 稀疏矩阵的三种格式
| 格式 | 适用场景 | 存储方式 |
|---|---|---|
| COO | 构建阶段,方便添加边 | 三个数组:行索引、列索引、值 |
| CSR | 矩阵运算,GCN训练 | 行偏移、列索引、值 |
| CSC | 列操作多的场景 | 列偏移、行索引、值 |
我建议这样用:构建图时用COO格式,方便动态添加边。训练前转成CSR格式,计算效率最高。
import scipy.sparse as sp
import numpy as np
# 1. 先用COO格式构建
row = np.array([0, 0, 1, 2, 2, 3])
col = np.array([1, 2, 0, 1, 3, 2])
data = np.array([1, 1, 1, 1, 1, 1]) # 无权图用1
coo_matrix = sp.coo_matrix((data, (row, col)), shape=(4, 4))
# 2. 转成CSR用于训练
csr_matrix = coo_matrix.tocsr()
# 3. 添加归一化(GCN必备)
# 计算度矩阵的逆平方根
row_sum = np.array(csr_matrix.sum(axis=1)).flatten()
d_inv_sqrt = np.power(row_sum, -0.5)
d_inv_sqrt[np.isinf(d_inv_sqrt)] = 0
# 对称归一化:D^{-1/2} * A * D^{-1/2}
d_mat_inv_sqrt = sp.diags(d_inv_sqrt)
norm_adj = d_mat_inv_sqrt.dot(csr_matrix).dot(d_mat_inv_sqrt)
3.3.2 大规模图的处理技巧
当节点数超过100万时,连稀疏矩阵都可能装不下。怎么办?
我常用的三种策略:
- 子图采样:每次训练只取一个子图,比如以目标节点为中心,采样2跳邻居
- Mini-batch训练:用GraphSAFE或ClusterGCN的思路,分批训练
- 特征压缩:把高维特征用PCA或AutoEncoder降到64维以下
说实话,在工业级风控场景,我很少见到全图训练的。基本都是采样+分批。你想想看,一个用户的风险只和他直接或间接关联的节点有关,没必要把全图都加载进来。
3.4 本章知识体系总览
下面这张图,是我自己总结的本章核心流程。建议你保存下来,做项目时对照着看。
这张图把本章的核心逻辑串起来了。从原始数据到最终的稀疏矩阵,每一步都有对应的处理技巧。我个人建议你按照这个流程,先用小规模数据跑通,再上生产环境。
公众号:蓝海资料掘金营,微信deep3321