第1章:特征工程基础
大家好,我是你们这堂课的主讲人。今天咱们聊聊特征工程,说白了就是怎么把原始数据变成模型能听懂的语言。我做了这么多年风控,见过太多模型因为特征没做好而翻车的案例。嗯,这一章咱们就从头捋一遍。
1.1 特征理解——先别急着动手
拿到数据第一件事是什么?不是跑代码,是理解数据。我习惯先问自己三个问题:
- 数据从哪来? 是用户填的申请表,还是埋点采集的行为日志?
- 字段含义是什么? 比如“age”是年龄还是注册天数?
- 数据质量如何? 有没有空值、异常值、重复值?
我在项目中遇到过,某次合作方给了一个“income”字段,结果发现单位是“元/月”,但文档写的是“万元/年”。你想想看,这种错误如果不提前发现,模型训练出来就是废的。
df.info() 和 df.describe() 快速扫一眼,心里有个底。
1.2 特征清洗——脏数据是模型的天敌
特征清洗,说白了就是给数据“洗澡”。常见的脏数据有:
- 缺失值: 直接删掉?还是填充均值/中位数?我个人习惯先看缺失比例。如果超过50%,这个字段基本可以放弃了。
- 异常值: 比如年龄填了200岁,或者收入是负数。这种要么截断,要么标记为缺失。
- 重复值: 同一个用户出现多次,记得去重。
我曾经接手过一个信贷模型,发现“负债率”字段有30%的缺失值。当时团队想直接填充0,我拦住了。为什么?因为缺失本身可能就是一个信号——用户没填负债,也许是因为负债太高不敢填。后来我们单独建了一个“是否缺失”的哑变量,模型效果反而提升了。
1.3 特征构造——从原始数据中挖金子
特征构造,就是创造新特征。风控领域常用的方法有:
- 统计特征: 比如近30天交易次数、平均金额、最大金额。
- 时间特征: 注册时间、首次交易时间、最近一次交易时间。
- 交叉特征: 比如“年龄×收入”,或者“交易金额/交易次数”。
- 聚合特征: 按用户ID分组,计算交易金额的均值、方差、分位数。
举个例子,假设你有用户的交易流水数据。原始字段只有“用户ID”、“交易金额”、“交易时间”。你可以构造出:
# 伪代码示例
df['交易次数_近7天'] = df.groupby('user_id')['交易时间'].rolling('7D').count()
df['平均金额_近30天'] = df.groupby('user_id')['交易金额'].rolling('30D').mean()
df['金额波动率'] = df.groupby('user_id')['交易金额'].rolling('30D').std() / df.groupby('user_id')['交易金额'].rolling('30D').mean()
你想想看,原始数据只有3个字段,经过构造后,一下子多了几十个特征。模型能学到的信息量完全不一样。
1.4 特征选择——别让模型吃太多
特征不是越多越好。我见过有人一口气造了500个特征,结果模型训练了三天三夜,效果还不如50个特征的版本。特征选择的核心是:保留有用的,去掉冗余的。
常用的方法有:
- 过滤法: 计算每个特征与目标变量的相关系数,或者用卡方检验、互信息。保留排名靠前的特征。
- 包裹法: 比如递归特征消除(RFE),每次去掉一个最不重要的特征,直到达到指定数量。
- 嵌入法: 用L1正则化(Lasso)或者树模型的特征重要性,自动筛选特征。
我个人习惯先用过滤法快速筛一遍,再用树模型的特征重要性做二次确认。这样既快又稳。
1.5 特征存储——好记性不如烂笔头
特征工程做完后,怎么存?我建议用特征存储系统(Feature Store)。简单说,就是把所有特征集中管理起来,方便后续模型训练和在线推理。
常见的存储方式有:
| 存储方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| Hive/Parquet | 离线批量计算 | 存储成本低,适合大规模数据 | 查询延迟高 |
| Redis | 在线实时推理 | 查询速度快,毫秒级响应 | 存储容量有限 |
| MySQL/PostgreSQL | 小规模特征管理 | 易于维护,支持SQL查询 | 扩展性差 |
我在项目中常用的是“离线存Hive,在线存Redis”的双层架构。离线特征每天批量计算一次,存入Hive;在线特征实时计算,存入Redis。这样既保证了离线训练的完整性,又满足了在线推理的实时性。
本章知识体系
下面这张图总结了特征工程的完整流程,从原始数据到最终存储,每一步都环环相扣。
好了,这一章的内容就到这里。特征工程是风控模型的基石,地基没打好,上面盖的房子再漂亮也白搭。下一章咱们聊聊特征存储的具体实现,到时候我会带大家手写一个简单的特征存储系统。