4. 数据采集与预处理:数据源、清洗、标准化与缺失值处理

数据采集是图谱工程的「地基」。地基没打牢,后面算法再花哨也白搭。我个人做了这么多年反欺诈项目,最深的体会就是:数据源选不对、洗不干净,变量衍生出来的全是噪音。这一章咱们就把「数据怎么来、怎么洗、怎么对齐、怎么补缺」这四件事捋清楚。

4.1 三大数据源:交易日志、社交关系、通话记录

团伙识别绕不开三个典型数据源。它们各有脾气,处理方式也不同。

  • 交易日志:银行核心系统、支付平台产生的转账、消费、提现等流水。字段通常包含交易时间、金额、对手方账号、设备指纹、IP 等。量大,时效性强。
  • 社交关系:微信、QQ 的聊天记录、群关系、好友列表、转发链。结构半开放,隐私脱敏要求高,字段偏文本和关系对。
  • 通话记录:通信网元的 CDR(话单),包含主叫、被叫、时长、基站位置。密度高,但原始信令数据很大,一个中等城市一天就有十几亿条。
个人习惯:我一般拿到项目的第一步是先拉一张「数据源可用性矩阵」,看看每个数据源能提什么字段、有多少历史存量、更新频率怎样。否则后面标准化时会发现 A 源有「手机号」,B 源只有「掩码手机号」,根本对不上。

4.2 数据源特点对比

维度交易日志社交关系通话记录
数据量级百万~亿级/天万~千万级/天千万~百亿级/天
核心字段时间、金额、账号、IP好友关系、群 ID、消息类型主叫、被叫、时长、基站
敏感等级高(资金安全)极高(隐私法)高(通信隐私)
常见噪声重复流水、抹零交易僵尸号、系统水军接通失败、漫游漂移

你看,这三个源的数据质量参差不齐。交易日志相对规整,但容易出现重复报文——我曾经遇到过一个项目,交易系统因为双写,同一条转账记录在 Kafka 里出现了三次,导致后续聚类图的边多了一倍。所以清洗的第一步就是去重。

4.3 数据清洗:去噪、去重、字段格式化

清洗不是「一刀切干净」,而是保留有价值的信息、剔除肯定有问题的记录。我通常会按这个流程来做:

  1. 字段完整性检查:必填字段(如交易ID、对方账号)为空则直接丢掉或少全量回溯。
  2. 格式校验:手机号位数、IP 合法性、金额范围。例如通话记录里时长字段如果出现负值,一定是设备异常,直接过滤。
  3. 语义去重:精确去重很容易,难的是「近似重复」。比如两笔交易时间差 1 秒、金额差 0.01 元、账号不同——多半是系统拆单,你得保留一份主单。
  4. 低价值清洗:僵尸号、测试账户、系统内部流转标记,这些对团伙分析无益,提前剔除。
# 伪代码示例 - 交易日志去重与格式校验
def clean_transaction(df):
    # 去重:基于交易ID + 时间窗口
    df = df.drop_duplicates(subset=['tx_id', 'tx_time'])
    # 格式校验:金额必须 > 0 且 < 1e8
    df = df[(df['amount'] > 0) & (df['amount'] < 1e8)]
    # 剔除测试账号(已知名单)
    df = df[~df['account'].isin(TEST_ACCOUNTS)]
    return df
曾经有一次:我在清洗社交关系数据时,发现大量「用户 A 添加了用户 B」的记录,但 B 的账号全是同一台设备注册的——这是典型的养号团伙。如果直接清洗掉这些关系,反而会丢失团伙特征。所以我在清洗阶段会保留一个「质量标记」,而不是直接删除。你想想看,有些噪声恰恰是团伙的指纹。

4.4 标准化:把「方言」翻译成「普通话」

三个数据源的字段名、时间格式、单位可能完全不同。标准化就是把它们统一到同一套字典里。我见过太多团队花 80% 时间在参数对齐上。

标准化的核心动作:

  • 时间对齐:所有时间戳转成 UTC+8,统一粒度到秒。交易日志用毫秒,通话记录用秒,那就截断取整。
  • 实体 ID 映射:手机号、设备指纹、身份证号,用统一的 hash 或脱敏标识符。例如手机号取前三位+后四位,但不能丢失关联性。
  • 金额单位统一:交易日志可能用「分」,社交关系里的红包金额用「元」,统一为「元」保留两位小数。
  • 类别编码:交易类型、通话状态等离散值,映射为统一编码表。
# 标准化示例 - 时间 & 金额
def standardize_transaction(df):
    df['tx_time'] = pd.to_datetime(df['tx_time'], unit='ms').tz_localize('UTC').tz_convert('Asia/Shanghai')
    df['amount'] = df['amount'] / 100.0  # 分转元
    df['phone_hash'] = df['phone'].apply(lambda x: hashlib.md5(x.encode()).hexdigest()[:16])
    return df

4.5 缺失值处理:不补、补均值、还是用图传播?

缺失值是团伙识别里的常客。比如通话记录里基站 ID 缺失,交易日志里 IP 缺失,社交关系里性别字段缺失。处理方法没有银弹,我按场景分级:

缺失程度举例我推荐的方法
< 5%少量 IP 字段缺直接删除该记录,或填充一个占位符(如 '0.0.0.0')
5%~30%部分基站的 GPS 缺使用同基站其他记录的众数填充
> 30%社交关系里的「职业」字段保留缺失标记,作为后续图特征的「未知」维度

核心原则:在团伙识别的图变量衍生中,缺失值本身可能就是一个强特征。例如某个账号的「设备型号」长期缺失,很可能是因为使用了模拟器或刷机工具——这在欺诈团伙中常见。

我不建议对图数据做针对字段的均值填充。因为图上的节点往往被多个边连接,一个缺失的 node attribute 可以用邻居的聚合值来估计,这就是图传播填充(Graph Propagation Imputation)。虽然复杂,但对团伙识别的精度提升明显。

4.6 本章小结:一张图看明白预处理流程

最后我把整个数据采集与预处理的步骤画成了一幅流程图,方便你在项目中直接参考。注意,这张图只画了最核心的环节——实际落地时你还要考虑数据分区、压缩存储、增量更新等工程细节。

数据采集与预处理流程 交易日志 社交关系 通话记录 数据采集(多源汇聚) 数据清洗(去重+去噪) 标准化(字段对齐) 缺失值处理(不删/填充/图传播) 干净、标准化的图数据 注意:清洗与标准化可并行,耗时约60%

嗯,数据预处理这一步虽然看起来「底层」,但它决定了图谱能否真实反映团伙结构。我每次带新人时都会让他先把这章的内容在真实数据上跑一遍——跑通了,后面的变量衍生自然顺。