特征工程入门:时间特征提取、金额特征标准化、类别特征编码
大家好,我是你们这堂课的主讲人。今天咱们聊聊特征工程。
说实话,在异常交易检测这个领域摸爬滚打这么多年,我最大的体会就是:模型再牛,也怕烂特征。你想想看,原始数据就像一块璞玉,不经过打磨,很难直接派上用场。特征工程,就是那个打磨的过程。
这一章,我带你入门三个最基础、也最常用的特征处理手段:时间特征提取、金额特征标准化、类别特征编码。别小看这三板斧,很多实际项目里,光靠它们就能把模型效果提升一大截。
核心观点:特征工程不是花架子,它是连接原始数据和机器学习模型的桥梁。好的特征,能让简单的模型发挥出惊人的效果。
一、时间特征提取:从时间戳里挖出宝藏
原始数据里的时间戳,通常是一串数字,比如 2024-03-15 14:30:00。模型看不懂这个,它需要的是数字化的特征。
我个人习惯,拿到时间戳后,先问自己三个问题:
- 周期性:交易有没有明显的日周期、周周期?比如深夜交易是不是更可疑?
- 趋势性:交易频率是不是在逐渐升高?这可能是异常的前兆。
- 间隔性:两次交易之间隔了多久?秒级间隔的密集交易,很可能是机器在刷单。
具体怎么做?我一般会提取以下几类特征:
| 特征类别 | 提取方法 | 示例 |
|---|---|---|
| 基础时间分量 | 拆解为小时、星期几、月份 | 14点、星期三、3月 |
| 周期编码 | 用 sin/cos 映射到圆上 | 小时:sin(2π*h/24) |
| 特殊标记 | 是否周末、是否节假日 | 1(是)或 0(否) |
| 时间间隔 | 与上一笔交易的时间差 | 120秒 |
避坑指南:我曾经犯过一个错误——直接把小时数(0-23)扔进模型。结果模型学到的规律是“14点比15点更异常”,这显然不合理。后来我改用 sin/cos 编码,把时间映射到连续的圆上,效果立马好了很多。记住:时间特征是循环的,不是线性的。
# 时间特征提取示例
import pandas as pd
import numpy as np
# 假设 df 包含 'trans_time' 列
df['hour'] = df['trans_time'].dt.hour
df['weekday'] = df['trans_time'].dt.weekday
df['is_weekend'] = df['weekday'].isin([5, 6]).astype(int)
# 周期编码:小时
df['hour_sin'] = np.sin(2 * np.pi * df['hour'] / 24)
df['hour_cos'] = np.cos(2 * np.pi * df['hour'] / 24)
# 交易间隔(按用户分组)
df = df.sort_values(['user_id', 'trans_time'])
df['time_diff'] = df.groupby('user_id')['trans_time'].diff().dt.total_seconds()
二、金额特征标准化:让数字站在同一起跑线
金额特征有个特点:分布极度偏斜。你想想看,大部分交易是几十几百块,但偶尔会有几万甚至几十万的大额交易。这种分布,对很多模型来说是个灾难。
我记得有一次做信用卡欺诈检测,原始金额特征直接喂进模型,结果模型学到的全是“金额越大越异常”。这显然不对——大额交易也可能是正常的。问题出在哪?模型被极端值带偏了。
常用的标准化方法有三种:
- Z-score 标准化:减去均值,除以标准差。适合数据近似正态分布的情况。
- Min-Max 缩放:缩放到 [0, 1] 区间。简单粗暴,但容易受异常值影响。
- 对数变换:取 log(金额+1)。对付长尾分布的神器,我强烈推荐。
我的经验:在异常交易场景下,我首选对数变换。原因很简单——它能把大额和小额之间的差距“压缩”到一个合理的范围。比如 10 元和 10000 元,原始差距是 1000 倍,取 log 后差距只有 3 倍左右。模型更容易学到相对关系,而不是被绝对数值迷惑。
# 金额特征标准化示例
from sklearn.preprocessing import StandardScaler, MinMaxScaler
import numpy as np
# 1. 对数变换(推荐)
df['amount_log'] = np.log1p(df['amount'])
# 2. Z-score 标准化(对对数变换后的数据使用)
scaler = StandardScaler()
df['amount_scaled'] = scaler.fit_transform(df[['amount_log']])
# 3. Min-Max 缩放(直接对原始金额使用)
minmax = MinMaxScaler()
df['amount_minmax'] = minmax.fit_transform(df[['amount']])
注意:标准化参数(均值和标准差)一定要用训练集计算,然后应用到测试集上。千万别用全量数据计算,否则会造成数据泄露,模型评估结果会虚高。我见过不少新手在这里翻车。
三、类别特征编码:把文字变成数字
类别特征,比如交易类型(转账、消费、提现)、商户类别(餐饮、零售、娱乐),模型是看不懂的。我们需要把它们变成数字。
常用的编码方式有三种:
| 编码方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| One-Hot 编码 | 类别少(<10种) | 无顺序假设,模型友好 | 维度爆炸 |
| Label 编码 | 类别有顺序关系 | 节省空间 | 引入虚假顺序 |
| 频次编码 | 类别多(>10种) | 保留分布信息 | 可能过拟合 |
我个人在异常交易项目里,最常用的是 One-Hot 编码。为什么?因为很多类别特征(比如交易类型)之间没有大小关系,Label 编码会误导模型。比如“转账”编码为 1,“消费”编码为 2,模型可能错误地认为“消费”比“转账”大。
但 One-Hot 也有问题——如果类别太多(比如商户 ID 有几千种),维度会爆炸。这时候我会用 频次编码:用每个类别出现的次数来代替原始类别。你想想看,高频出现的商户通常是正常的,低频出现的反而更可疑。
# 类别特征编码示例
import pandas as pd
from sklearn.preprocessing import OneHotEncoder
# 1. One-Hot 编码(适合类别少的特征)
encoder = OneHotEncoder(sparse_output=False)
trans_type_encoded = encoder.fit_transform(df[['trans_type']])
# 将结果转回 DataFrame
trans_type_df = pd.DataFrame(trans_type_encoded,
columns=encoder.get_feature_names_out(['trans_type']))
df = pd.concat([df, trans_type_df], axis=1)
# 2. 频次编码(适合类别多的特征)
freq_map = df['merchant_id'].value_counts().to_dict()
df['merchant_freq'] = df['merchant_id'].map(freq_map)
小技巧:对于高基数类别特征(比如用户 ID、设备 ID),我建议先做一次聚合统计,比如“该用户过去24小时交易次数”,然后再编码。这样既保留了信息,又避免了维度灾难。说白了,就是把原始 ID 转化为有业务含义的统计量。
小结
这一章我们聊了三个基础但极其重要的特征工程方法:
- 时间特征:别只拆小时,记得用 sin/cos 做周期编码,还要算交易间隔。
- 金额特征:对数变换是首选,能有效处理长尾分布。
- 类别特征:One-Hot 适合少类别,频次编码适合多类别。
嗯,这些方法看起来简单,但我在实际项目中反复验证过——它们真的能打。下一章我们会把这些特征组合起来,构建一个完整的特征工程流水线。到时候你会发现,原来数据预处理也可以这么优雅。