2. 数据准备:交易数据的特征工程(时间、金额、地点、设备指纹等)

好,咱们进入实战的第二关。上一章我们把原始交易日志捞出来了,但说实话,那些日志就像一堆没洗的菜,直接下锅肯定不行。这一章,咱们得好好「洗菜切菜」——做特征工程。

我个人习惯把特征工程看作「翻译官」的工作。把机器看不懂的原始字段,翻译成模型能理解的数字语言。你想想看,模型它不认识「北京」和「上海」,它只认 0 和 1。所以,这一步做得好不好,直接决定了模型的天花板。

2.1 时间特征:从时间戳里挖出「行为密码」

时间戳是最容易被忽视的金矿。很多新手直接把它当字符串扔掉,太可惜了。我在项目中遇到过,光靠时间特征就能干掉 30% 的明显盗刷。

我们一般从三个维度拆解:

  • 周期性特征:交易发生在几点?周几?是月初还是月末?
  • 间隔特征:距离上一笔交易过了多久?距离注册时间过了多久?
  • 行为模式特征:用户通常在这个点交易吗?

举个例子,一个用户平时都在晚上 8-10 点网购,突然凌晨 3 点在境外刷了一笔。这个「凌晨 3 点」就是强信号。怎么提取?看代码:

import pandas as pd
import numpy as np

# 假设 df 包含 'trans_time' 列,格式为 datetime
df['hour'] = df['trans_time'].dt.hour
df['day_of_week'] = df['trans_time'].dt.dayofweek  # 0=周一
df['is_weekend'] = df['day_of_week'].isin([5, 6]).astype(int)
df['is_night'] = ((df['hour'] >= 23) | (df['hour'] <= 5)).astype(int)

# 计算距离上一笔交易的时间间隔(秒)
df = df.sort_values(['user_id', 'trans_time'])
df['time_since_last_txn'] = df.groupby('user_id')['trans_time'].diff().dt.total_seconds()
df['time_since_last_txn'] = df['time_since_last_txn'].fillna(999999)  # 首笔交易填充大值
我的小技巧: 对于「时间间隔」特征,我通常还会做一个对数变换(np.log1p)。因为正常用户间隔可能几小时,盗刷可能几秒,数值跨度太大,对数变换能让分布更平滑。

2.2 金额特征:不只是看数字大小

金额特征,说白了就是看「钱对不对」。但光看绝对值是不够的。一个富哥刷 10 万可能正常,一个学生党刷 10 万就是异常。

我一般会构建三类金额特征:

  1. 统计偏离特征:这笔金额偏离用户历史均值多少个标准差?
  2. 金额取整特征:金额是不是整数?比如 10000.00 比 9999.87 更可疑。
  3. 金额与余额关系:交易后余额是否接近 0?

这里有个坑,我曾经踩过。统计偏离特征如果直接用全局均值,效果很差。一定要按用户分组计算。看代码:

# 按用户计算历史均值和标准差
user_stats = df.groupby('user_id')['amount'].agg(['mean', 'std']).rename(
    columns={'mean': 'user_amount_mean', 'std': 'user_amount_std'}
)
df = df.merge(user_stats, on='user_id', how='left')

# 计算 Z-score:当前金额偏离用户均值的程度
df['amount_zscore'] = (df['amount'] - df['user_amount_mean']) / (df['user_amount_std'] + 1e-8)

# 金额取整检测:判断金额是否为整数
df['is_round_amount'] = (df['amount'] == np.floor(df['amount'])).astype(int)

# 大额交易标记(超过用户均值 3 倍)
df['is_large_txn'] = (df['amount'] > df['user_amount_mean'] * 3).astype(int)
注意: 计算用户历史统计量时,一定要用「历史数据」而非「全部数据」。否则会造成数据泄露(Data Leakage),模型在训练时看到了未来的信息,上线后效果会崩盘。我一般用 expanding 窗口或 rolling 窗口来计算。

2.3 地点特征:距离与异常地理位置的博弈

地点特征,核心就两件事:距离风险区域

距离特征很好理解。用户上一笔在北京,10 分钟后在纽约又刷了一笔。物理上不可能,这就是强异常。我们通常计算交易地点与常用地点的球面距离。

风险区域呢?有些 IP 地址或城市本身就是高风险区,比如某些诈骗高发地。我们可以构建一个「历史黑名单」特征。

# 假设有经纬度数据
from math import radians, sin, cos, sqrt, asin

def haversine(lon1, lat1, lon2, lat2):
    """计算两点间球面距离(单位:公里)"""
    lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2])
    dlon = lon2 - lon1
    dlat = lat2 - lat1
    a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
    c = 2 * asin(sqrt(a))
    r = 6371  # 地球半径
    return c * r

# 计算与上一笔交易的距离
df = df.sort_values(['user_id', 'trans_time'])
df['prev_lon'] = df.groupby('user_id')['lon'].shift(1)
df['prev_lat'] = df.groupby('user_id')['lat'].shift(1)
df['distance_km'] = df.apply(
    lambda row: haversine(row['prev_lon'], row['prev_lat'], row['lon'], row['lat']) 
    if pd.notna(row['prev_lon']) else 0, 
    axis=1
)

# 标记高风险城市(示例)
high_risk_cities = ['CityX', 'CityY']
df['is_high_risk_city'] = df['city'].isin(high_risk_cities).astype(int)

2.4 设备指纹特征:识别「隐身」的坏人

设备指纹,这是反欺诈的杀手锏。坏人可以换账号、换 IP,但很难换设备。设备指纹通常包括:设备 ID、操作系统、浏览器版本、屏幕分辨率、时区、语言设置等。

我常用的特征思路:

  • 设备聚集性:一个设备关联了多少个用户?如果超过 3 个,大概率是团伙作案。
  • 设备历史风险:这个设备之前有没有触发过风控规则?
  • 设备信息一致性:比如操作系统是 iOS,但浏览器是 Chrome Android,明显矛盾。
# 设备关联用户数
device_user_count = df.groupby('device_id')['user_id'].nunique().reset_index()
device_user_count.columns = ['device_id', 'device_user_count']
df = df.merge(device_user_count, on='device_id', how='left')

# 设备历史风险标记(假设有历史黑名单)
blacklist_devices = ['dev_12345', 'dev_67890']
df['is_blacklist_device'] = df['device_id'].isin(blacklist_devices).astype(int)

# 设备信息一致性检测:操作系统与浏览器是否匹配
def check_os_browser_consistency(row):
    os = row['os']
    browser = row['browser']
    if os == 'iOS' and browser not in ['Safari', 'Chrome iOS']:
        return 1  # 不一致
    if os == 'Android' and browser not in ['Chrome', 'Firefox', 'Samsung Internet']:
        return 1
    return 0

df['os_browser_mismatch'] = df.apply(check_os_browser_consistency, axis=1)

2.5 特征工程全景图

说了这么多,咱们用一张图把整个特征工程体系串起来。这样你脑子里就有个框架了。

交易异常检测特征工程全景图 原始交易数据 ⏰ 时间特征 • 交易小时 / 星期几 • 距离上一笔时间间隔 • 是否深夜交易 • 距离注册时间 💰 金额特征 • 金额 Z-score 偏离度 • 是否整数金额 • 是否超过均值 3 倍 • 交易后余额特征 📍 地点特征 • 与上一笔距离(km) • 是否高风险城市 • 常用地点偏离度 📱 设备指纹 • 设备关联用户数 • 是否黑名单设备 • OS/浏览器一致性 🎯 最终特征向量 → 输入模型

2.6 特征工程避坑指南

最后,分享几个我这些年踩过的坑,希望能帮你省点时间:

坑1:特征膨胀——别一股脑把所有特征都扔进去。我见过有人做了 500 个特征,结果模型过拟合得一塌糊涂。记住:特征不是越多越好,有用才重要

坑2:缺失值处理——设备指纹经常缺失。我曾经直接填充 -1,结果模型学出了一个「缺失就是异常」的假规律。后来我改用「是否缺失」作为一个独立特征,效果好了很多。

坑3:时间穿越——这个前面提过,但值得再说一遍。计算用户历史统计量时,一定要用「截止到当前时间」的数据。用 pandas 的 expanding 或 shift 操作可以避免。

嗯,特征工程这部分内容确实不少,但它是整个模型的地基。地基打牢了,后面盖楼才稳。你把这些特征构建好,模型就已经成功了一半。

我的建议: 刚开始做特征工程时,先构建 10-15 个核心特征,跑一个基线模型。然后根据模型的特征重要性(Feature Importance)逐步添加新特征。这样既不会漏掉重要信息,也不会一开始就被特征工程搞晕。

好了,这一章就到这里。下一章我们会把这些特征喂给模型,看看它们到底能发挥多大威力。


专注资料整理