数据采集与预处理:打好反欺诈的第一仗

说实话,做反欺诈算法这几年,我最大的体会就是——数据质量决定了模型的天花板。模型再花哨,喂进去的是脏数据,出来的就是垃圾。今天咱们就聊聊数据采集与预处理这块硬骨头。

我个人习惯把这一步叫做「地基工程」。地基没打好,后面盖的楼再漂亮也得塌。你想想看,欺诈检测里,一个缺失值可能让你漏掉一笔欺诈交易,一个异常值可能触发一堆误报。嗯,咱们一步步来拆解。

数据采集与预处理 数据源分类 数据清洗 标准化与归一化 内部日志 第三方数据 设备指纹 缺失值处理 异常值检测 重复值去重 Z-score标准化 Min-Max归一化 Robust缩放 输出:干净、标准化的特征矩阵

一、数据源分类:你的数据从哪来?

做反欺诈,数据源通常分三大类。我刚开始入行时,以为数据越多越好,结果被一堆冗余字段坑惨了。后来才明白,选对数据源比堆数据量重要得多

1. 内部日志

说白了就是自家系统产生的数据。比如用户登录日志、交易流水、操作行为记录。这些数据最可靠,因为完全可控。我在项目中遇到过,内部日志里时间戳格式不统一,有的用毫秒,有的用秒,差点导致特征计算全错。

常见内部日志字段:
  • 用户ID、IP地址、User-Agent
  • 登录时间、登出时间、会话时长
  • 交易金额、交易类型、商户ID
  • 操作序列(点击、页面跳转、表单填写)

2. 第三方数据

征信报告、黑名单库、社交网络数据这些。我建议你注意一点:第三方数据往往有延迟。我记得有一次,我们用的黑名单库更新周期是24小时,结果当天新增的欺诈分子完全没被拦截。后来我们加了实时接口才解决。

数据源类型 优点 缺点
内部日志 实时、可控、成本低 维度有限、可能有偏差
第三方数据 维度丰富、权威性强 有延迟、成本高、依赖接口
设备指纹 唯一性强、难以伪造 隐私合规风险、采集复杂

3. 设备指纹

这个很有意思。设备指纹不是简单的设备ID,而是通过浏览器或APP采集的多维特征组合。比如屏幕分辨率、操作系统版本、字体列表、时区、甚至显卡型号。为什么需要这么多?因为欺诈分子可以伪造单个特征,但很难同时伪造几十个特征的组合。

我的经验:设备指纹采集时,一定要做「特征稳定性」评估。有些特征(比如电池电量)变化太快,不适合做指纹。我曾经因为用了MAC地址,结果iOS14之后隐私策略一变,全崩了。

二、数据清洗:脏数据是欺诈模型的毒药

数据清洗占了我整个项目时间的60%以上。别嫌烦,这一步省了,后面模型上线你会哭的。

1. 缺失值处理

缺失值在反欺诈里特别常见。比如用户没填手机号、设备指纹没采集到、第三方接口超时。处理方法有三种:

  • 直接删除:缺失比例超过70%的字段,我一般直接砍掉。留着也是噪音。
  • 填充:数值型用中位数或均值,类别型用众数。但要注意——欺诈场景下,缺失本身可能就是一个特征。比如「手机号缺失」这个字段,在欺诈样本中出现的概率远高于正常用户。
  • 模型预测填充:用其他字段预测缺失值。这个方法效果好,但计算量大。我一般只在关键字段上使用。
# 缺失值处理示例
import pandas as pd
import numpy as np

# 读取数据
df = pd.read_csv('transaction_data.csv')

# 查看缺失情况
print(df.isnull().sum())

# 数值型字段:用中位数填充
df['amount'].fillna(df['amount'].median(), inplace=True)

# 类别型字段:用众数填充,并新增缺失标记
df['phone_missing'] = df['phone'].isnull().astype(int)
df['phone'].fillna('UNKNOWN', inplace=True)

# 删除缺失率超过70%的字段
threshold = 0.7
df = df.loc[:, df.isnull().mean() < threshold]

2. 异常值检测

异常值在反欺诈里分两种:一种是数据录入错误(比如年龄填了200岁),另一种是真正的欺诈行为(比如单笔交易金额是平时的100倍)。怎么区分?我教你一个笨办法:先做统计分布,再结合业务规则。

注意:千万不要一刀切地删除所有异常值!在反欺诈场景下,异常值往往就是我们要找的欺诈样本。我曾经犯过这个错,把一批高金额交易当异常删了,结果模型完全学不到大额欺诈的模式。

常用的异常值检测方法:

  • 3σ原则:适用于正态分布的数据
  • 箱线图(IQR):更稳健,不受极端值影响
  • 孤立森林:适合高维数据,我在设备指纹异常检测中常用
# 使用IQR检测异常值
Q1 = df['amount'].quantile(0.25)
Q3 = df['amount'].quantile(0.75)
IQR = Q3 - Q1

lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

# 标记异常值,但不删除
df['amount_outlier'] = ((df['amount'] < lower_bound) | (df['amount'] > upper_bound)).astype(int)

3. 重复值去重

重复数据在反欺诈里是个大坑。你以为同一个用户操作了两次,其实可能是系统重试导致的重复记录。我建议你:先区分「业务重复」和「数据重复」

  • 数据重复:所有字段完全一样,直接删除
  • 业务重复:关键字段一样但时间戳不同,需要结合业务判断。比如同一笔交易在1秒内重复提交,大概率是系统问题,保留第一条即可
# 去重示例
# 完全重复:删除
df.drop_duplicates(inplace=True)

# 业务重复:按用户ID和交易ID去重,保留最早的一条
df.sort_values('timestamp', inplace=True)
df.drop_duplicates(subset=['user_id', 'transaction_id'], keep='first', inplace=True)

三、数据标准化与归一化:让特征站在同一起跑线

为什么需要标准化?你想想看,交易金额可能是几万块,而登录次数只有个位数。如果不做处理,模型会天然认为金额更重要——但实际可能恰恰相反。我见过太多新手直接拿原始数据训练,结果模型全被量纲大的特征带偏了。

1. Z-score标准化

把数据变成均值为0、标准差为1的分布。适合数据本身近似正态分布的场景。我在做信用评分卡时特别喜欢用这个。

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
df['amount_scaled'] = scaler.fit_transform(df[['amount']])

2. Min-Max归一化

把数据缩放到[0,1]区间。适合有明确上下界的特征,比如年龄、评分。但要注意——如果有异常值,Min-Max会被严重拉偏

from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()
df['age_scaled'] = scaler.fit_transform(df[['age']])

3. Robust缩放

这个是我在反欺诈场景下的首选。它使用中位数和四分位数,对异常值不敏感。欺诈数据里异常值太多了,用这个最稳。

from sklearn.preprocessing import RobustScaler

scaler = RobustScaler()
df['amount_robust'] = scaler.fit_transform(df[['amount']])
我的选择建议:
  • 数据近似正态分布 → Z-score
  • 数据有明确边界、无异常值 → Min-Max
  • 数据含大量异常值(反欺诈常见) → Robust缩放
  • 树模型(XGBoost、LightGBM)→ 其实可以不做标准化,但为了特征可比性,我习惯做一下

好了,数据采集与预处理这块就聊到这儿。记住一句话:干净的数据是反欺诈模型的灵魂。下一章咱们会深入特征工程,到时候这些预处理过的数据就能派上大用场了。


专注资料整理