特征工程入门:时间特征提取、金额特征标准化、类别特征编码

大家好,我是你们这堂课的主讲人。今天咱们聊聊特征工程。

说实话,在异常交易检测这个领域摸爬滚打这么多年,我最大的体会就是:模型再牛,也怕烂特征。你想想看,原始数据就像一块璞玉,不经过打磨,很难直接派上用场。特征工程,就是那个打磨的过程。

这一章,我带你入门三个最基础、也最常用的特征处理手段:时间特征提取金额特征标准化类别特征编码。别小看这三板斧,很多实际项目里,光靠它们就能把模型效果提升一大截。

核心观点:特征工程不是花架子,它是连接原始数据和机器学习模型的桥梁。好的特征,能让简单的模型发挥出惊人的效果。

特征工程三大核心 时间特征提取 金额特征标准化 类别特征编码 • 小时/星期/月份 • 是否节假日 • 交易间隔 • Z-score 标准化 • Min-Max 缩放 • 对数变换 • One-Hot 编码 • Label 编码 • 频次编码 目标:让原始数据变成模型能理解的“语言”

一、时间特征提取:从时间戳里挖出宝藏

原始数据里的时间戳,通常是一串数字,比如 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()

二、金额特征标准化:让数字站在同一起跑线

金额特征有个特点:分布极度偏斜。你想想看,大部分交易是几十几百块,但偶尔会有几万甚至几十万的大额交易。这种分布,对很多模型来说是个灾难。

我记得有一次做信用卡欺诈检测,原始金额特征直接喂进模型,结果模型学到的全是“金额越大越异常”。这显然不对——大额交易也可能是正常的。问题出在哪?模型被极端值带偏了。

常用的标准化方法有三种:

  1. Z-score 标准化:减去均值,除以标准差。适合数据近似正态分布的情况。
  2. Min-Max 缩放:缩放到 [0, 1] 区间。简单粗暴,但容易受异常值影响。
  3. 对数变换:取 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 适合少类别,频次编码适合多类别。

嗯,这些方法看起来简单,但我在实际项目中反复验证过——它们真的能打。下一章我们会把这些特征组合起来,构建一个完整的特征工程流水线。到时候你会发现,原来数据预处理也可以这么优雅。


专注资料整理