4. 特征工程进阶:聚合特征、统计特征、时间窗口特征、行为序列特征
大家好,我是你们的老朋友。今天咱们聊聊特征工程里比较硬核的部分——交易异常检测中的高级特征构造。说实话,很多同学做风控模型,模型选得再好,特征没做对,效果就是上不去。我见过太多人把时间全花在调参上,结果特征工程一塌糊涂。
嗯,咱们直接进入正题。交易异常检测里,原始字段往往不够用。你想想看,单看一笔交易金额是1000块,能判断异常吗?不能。但如果这个用户过去1小时刷了50笔,每笔都是1000块,那就有问题了。这就是特征工程要干的事。
核心思想:特征工程不是堆砌变量,而是把业务理解转化成数学表达。说白了,就是让模型能看懂「人的行为模式」。
4.1 聚合特征:从个体到群体的视角
聚合特征,我个人习惯叫它「群体画像特征」。什么意思呢?就是不看单个用户,而是看一群人的行为规律。
举个例子。某个IP地址下,突然有50个不同的用户账号在登录。这正常吗?大概率是撞库攻击。这时候,ip_user_count 这个聚合特征就派上用场了。
我在项目中遇到过最典型的场景:一个电商平台的风控系统,每天几千万笔交易。我们做了个简单的聚合特征——device_id 下绑定的银行卡数量。结果发现,正常用户一般绑1-3张卡,但某个设备绑了200多张卡。一查,果然是黑产团伙在批量操作。
常用的聚合方式有这些:
- 计数聚合:某个维度下的记录数(如:IP下用户数、设备下订单数)
- 去重计数:某个维度下的唯一值数量(如:手机号去重数、银行卡去重数)
- 比率聚合:两个计数的比值(如:失败次数/总次数)
# 聚合特征示例代码
import pandas as pd
# 假设df是交易数据
# 计算每个IP下的用户数量
ip_user_count = df.groupby('ip')['user_id'].nunique().reset_index()
ip_user_count.columns = ['ip', 'ip_user_count']
# 计算每个设备下的银行卡数量
device_card_count = df.groupby('device_id')['card_no'].nunique().reset_index()
device_card_count.columns = ['device_id', 'device_card_count']
# 合并回原数据
df = df.merge(ip_user_count, on='ip', how='left')
df = df.merge(device_card_count, on='device_id', how='left')
小技巧:聚合特征要注意时效性。我建议用「近7天」或「近30天」的数据做聚合,别用全量历史。因为用户的设备、IP会变,全量聚合反而会引入噪声。
4.2 统计特征:描述数据的「脾气」
统计特征,说白了就是给数据算算「平均值、标准差、最大值、最小值」这些。听起来简单,但用好了威力很大。
我记得有一次做信用卡盗刷检测。正常用户消费金额波动不大,但盗刷者往往一上来就刷大额。这时候,近30天消费金额的均值 和 当前金额与均值的比值 就非常敏感。
常用的统计特征包括:
| 统计量 | 含义 | 异常检测场景 |
|---|---|---|
| 均值 | 中心趋势 | 偏离均值过大可能异常 |
| 标准差 | 波动程度 | 标准差突然变大,行为异常 |
| 分位数 | 分布位置 | 超过99分位数的交易需警惕 |
| 偏度 | 分布对称性 | 偏度异常可能数据被篡改 |
# 统计特征示例
# 计算每个用户近7天的交易金额统计
user_stats = df.groupby('user_id')['amount'].agg(
['mean', 'std', 'max', 'min', 'median', 'skew']
).reset_index()
user_stats.columns = ['user_id', 'amt_mean_7d', 'amt_std_7d',
'amt_max_7d', 'amt_min_7d', 'amt_median_7d', 'amt_skew_7d']
# 当前金额与均值的比值
df['amt_vs_mean_ratio'] = df['amount'] / (df['amt_mean_7d'] + 1e-8)
避坑指南:我曾经犯过一个错误——直接用全量历史数据算统计量。结果新用户的统计特征全是NaN,模型直接崩了。后来我改成「至少要有3笔交易才计算统计量」,不足3笔的用全局均值填充。嗯,这个坑你们别踩。
4.3 时间窗口特征:捕捉行为的节奏感
时间窗口特征,是我认为交易异常检测里最核心的特征类型。为什么?因为人的行为是有节奏的。你早上9点上班,中午12点吃饭,晚上6点下班。盗刷者呢?他们可能凌晨3点疯狂操作。
时间窗口特征的核心是「滑动窗口」。比如:
- 过去1小时内的交易次数:检测短时间内高频交易
- 过去24小时内的交易金额总和:检测日累计异常
- 过去7天内的交易天数:检测活跃度变化
我建议你们把时间窗口分成三类:
- 短窗口(5分钟-1小时):检测实时攻击,如撞库、秒杀
- 中窗口(1小时-24小时):检测日行为模式,如盗刷
- 长窗口(3天-30天):检测账户异动,如休眠账户突然活跃
# 时间窗口特征示例
# 计算每个用户过去1小时内的交易次数
df = df.sort_values(['user_id', 'trans_time'])
# 使用rolling窗口(注意:这里需要按用户分组)
df['trans_count_1h'] = df.groupby('user_id')['trans_id'].transform(
lambda x: x.rolling(window='1h', min_periods=1).count()
)
# 计算过去24小时内的交易金额总和
df['amt_sum_24h'] = df.groupby('user_id')['amount'].transform(
lambda x: x.rolling(window='24h', min_periods=1).sum()
)
个人经验:时间窗口的「粒度」很重要。我一般会同时做多个窗口,比如1小时、6小时、24小时、7天。然后让模型自己去学哪个窗口更重要。你想想看,不同场景下敏感的时间窗口是不一样的。
4.4 行为序列特征:还原「作案」全过程
行为序列特征,这是进阶中的进阶。它不再是简单的统计,而是把用户的一系列操作当成一个「序列」来建模。
举个例子。正常用户登录后,一般是:浏览商品 → 加入购物车 → 支付。但盗刷者呢?可能是:登录 → 直接支付 → 支付 → 支付。这个操作顺序完全不同。
常用的序列特征构造方法:
- 操作类型编码:把每个操作映射成一个数字(如:登录=1,支付=2,修改密码=3)
- 序列长度:一段时间内的操作总数
- 序列熵:操作类型的多样性(熵越低,行为越单一,越可疑)
- 转移概率:从操作A到操作B的概率(如:支付后立即修改密码,异常概率高)
# 行为序列特征示例
# 假设有用户操作日志:user_id, action_type, timestamp
# 1. 计算序列长度(近1小时内的操作数)
seq_length = df.groupby('user_id')['action_type'].rolling('1h').count()
# 2. 计算序列熵(操作类型的多样性)
from scipy.stats import entropy
def calc_entropy(actions):
value_counts = actions.value_counts(normalize=True)
return entropy(value_counts)
seq_entropy = df.groupby('user_id')['action_type'].rolling('1h').apply(calc_entropy)
# 3. 计算转移概率(从支付到修改密码的概率)
# 这个比较复杂,需要构建转移矩阵,这里只展示思路
# 先对每个用户的序列做shift,然后统计转移对
df['prev_action'] = df.groupby('user_id')['action_type'].shift(1)
transfer_prob = df.groupby(['prev_action', 'action_type']).size() / df.groupby('prev_action').size()
核心要点:行为序列特征的本质是「把时间维度上的行为模式量化」。我做过一个实验,只加一个「序列熵」特征,模型AUC提升了3个百分点。为什么?因为盗刷者的行为模式太单一了,熵值极低。
4.5 知识体系总览
说了这么多,咱们用一张图来总结一下这四种特征的关系和适用场景。
这张图把四种特征的关系理清楚了。你看,聚合特征和统计特征解决的是「是什么」的问题,时间窗口特征解决的是「什么时候」的问题,行为序列特征解决的是「怎么做的」问题。三者结合,才能全面刻画一个交易行为是否异常。
最后说一句:特征工程没有银弹。我做了这么多年风控,最大的体会就是——多试、多验证、多跟业务聊。有时候一个简单的聚合特征,比复杂的深度学习模型还管用。嗯,今天就到这里,你们回去把代码跑一跑,有问题随时交流。