特征工程入门——从原始数据到特征矩阵
特征工程入门——从原始数据到特征矩阵
深入理解特征工程的重要性,掌握使用 Featuretools 进行自动特征生成的方法。从第一性原理出发,理解为什么需要特征工程,以及各种特征方法为什么有效。
目录
1. 什么是特征工程?
1.1 特征的本质
特征的定义:对原始数据的抽象表示。
这句话包含了三个关键词:
- 原始数据:未经处理的原始信息
- 抽象:提取关键信息,去除细节
- 表示:转化为模型可理解的形式
例子:理解特征的本质
原始数据(活动记录表):
1
2
3
4
5
6
7
人员ID | 时间 | 地点 | 类型 | 交通工具
-------|-----------|--------|--------|----------
001 | 20XX-01-15| A市 | 旅游 | 飞机
001 | 20XX-02-20| B市 | 商务 | 高铁
001 | 20XX-03-10| A市 | 旅游 | 飞机
001 | 20XX-04-05| C市 | 旅游 | 飞机
001 | 20XX-05-12| A市 | 探亲 | 火车
特征工程后(特征向量):
1
2
3
人员ID | 活动次数 | 不同城市数 | 最常见类型 | 最常见交通工具 | 平均间隔天数
-------|-----------|-----------|-----------|--------------|------------
001 | 5 | 3 | 旅游 | 飞机 | 28
关键理解:
- 原始数据:5条记录(详细信息)
- 特征:5个数值(抽象概括)
- 信息压缩:从多条记录压缩为单个特征向量
- 模式提取:提取了”频率”、”多样性”、”模式”等信息
1.2 为什么不能直接用原始数据?
问题1:数据格式不统一
原始数据示例:
1
2
3
4
5
样本一:
- 姓名:"某甲"(文本)
- 年龄:30(数值)
- 出生日期:"19XX-05-15"(日期)
- 是否高风险:True(布尔)
问题:
- 模型只能处理数值型输入
- 文本、日期、布尔值需要转换
特征工程后:
1
2
3
4
5
样本一:
- 年龄:30(数值,可直接使用)
- 年龄组:3(编码:20-30岁=3)
- 出生年份:1993(从日期提取)
- 是否高风险:1(布尔转数值:True=1)
问题2:数据包含噪声和冗余
原始数据示例:
1
2
3
4
样本二的活动记录:
- 记录1:20XX-01-15, A市, 旅游, 飞机, 经济舱, 窗口座位, ...
- 记录2:20XX-02-20, B市, 商务, 高铁, 一等座, 过道座位, ...
...
问题:
- 包含大量细节(座位号、舱位等级等)
- 这些细节可能与目标变量(风险类型)无关
- 噪声会干扰模型学习
特征工程后:
1
2
3
4
5
样本二的特征:
- 活动次数:5(关键信息)
- 不同城市数:3(关键信息)
- 最常见类型:旅游(关键信息)
- (去除了座位号、舱位等无关细节)
问题3:数据粒度不匹配
原始数据:多条记录(一人多记录)
模型需求:每个样本一个特征向量(一人一向量)
特征工程的作用:将多条记录聚合为单个特征向量
流程图:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
原始数据(多表结构)
↓
┌─────────────────────────────────┐
│ 人员信息表(1条记录/人) │
│ - 姓名、年龄、标识号 │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ 活动记录表(多条记录/人) │
│ - 记录1:时间、地点、类型 │
│ - 记录2:时间、地点、类型 │
│ - 记录3:时间、地点、类型 │
│ ... │
└─────────────────────────────────┘
↓
特征工程(聚合)
↓
┌─────────────────────────────────┐
│ 特征矩阵(1条记录/人) │
│ - COUNT(活动记录) = 5 │
│ - NUM_UNIQUE(地点) = 3 │
│ - MODE(类型) = "旅游" │
│ ... │
└─────────────────────────────────┘
1.3 特征工程的重要性
名言:”数据决定上限,模型决定逼近上限的能力”
理解:
- 数据(特征)决定上限:如果特征不好,模型性能的上限就很低
- 模型决定逼近上限的能力:好的模型能更好地逼近这个上限
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
场景1:特征质量差
- 特征:随机数(与目标无关)
- 模型:XGBoost(最好的模型)
- 结果:准确率 ≈ 50%(随机猜测)
→ 特征差,再好的模型也无法弥补 ❌
场景2:特征质量好
- 特征:与目标高度相关
- 模型:简单决策树
- 结果:准确率 = 85%
→ 特征好,简单模型也能表现好 ✅
场景3:特征质量好 + 模型好
- 特征:与目标高度相关
- 模型:XGBoost
- 结果:准确率 = 92%
→ 特征好 + 模型好 = 最佳性能 ✅
关键洞察:特征工程通常是提升模型性能最有效的方法。
1.4 特征工程的目标
目标1:提取有用信息
- 保留与目标变量相关的信息
- 去除无关信息和噪声
目标2:降低模型学习难度
- 让模式更容易被模型发现
- 减少模型需要学习的复杂度
例子:时间特征工程
原始数据:
1
日期:"20XX-01-15"
特征工程后:
1
2
3
4
5
6
7
- 年份:2023
- 月份:1
- 日期:15
- 星期几:星期日
- 是否节假日:是
- 季度:Q1
- 是否周末:是
为什么这样更好?
- 模型更容易学习”周末”、”节假日”等模式
- 比直接使用日期字符串更容易理解
- 提取了与目标变量可能相关的信息
2. 特征工程的常见方法
2.1 统计特征的本质
为什么统计特征有效?
核心原理:用少量数值概括大量数据的分布特征。
例子:活动次数(COUNT)
原始数据:
1
2
3
4
5
6
样本一的活动记录:
- 记录1:20XX-01-15
- 记录2:20XX-02-20
- 记录3:20XX-03-10
- 记录4:20XX-04-05
- 记录5:20XX-05-12
特征:
1
COUNT(活动记录) = 5
为什么有效?
- 信息压缩:5条记录 → 1个数值
- 模式提取:反映了”活跃度”(次数多 = 活跃)
- 与目标相关:活跃度可能与风险相关
常见统计特征及其含义
COUNT(计数)
- 定义:记录的数量
- 含义:频率、活跃度
- 例子:
COUNT(活动记录) = 5→ 活动5次
SUM(求和)
- 定义:数值的总和
- 含义:总量
- 例子:
SUM(交易金额) = 50000→ 总交易金额5万
MEAN(平均值)
- 定义:数值的平均值
- 含义:平均水平
- 例子:
MEAN(间隔天数) = 28→ 平均28天一次
MAX/MIN(最大值/最小值)
- 定义:数值的最大值/最小值
- 含义:极值、范围
- 例子:
MAX(间隔天数) = 60→ 最长间隔60天
STD(标准差)
- 定义:数值的离散程度
- 含义:稳定性、规律性
- 例子:
STD(间隔天数) = 10→ 间隔比较规律(标准差小)
可视化理解:
1
2
3
4
5
6
7
8
9
10
11
12
13
原始数据分布:
间隔天数:[20, 25, 30, 28, 32]
统计特征:
- MEAN = 27(平均水平)
- STD = 4.5(离散程度小,规律)
- MAX = 32(最大值)
- MIN = 20(最小值)
含义:
- 平均27天一次(频率)
- 标准差小(规律性强)
- 范围20-32天(稳定性)
2.2 类别特征的本质
MODE(众数)
定义:出现最频繁的值。
为什么有效?
- 反映了”典型情况”、”最常见模式”
- 从无序的类别数据中提取有序信息
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
样本一的活动类型:
- 记录1:旅游
- 记录2:商务
- 记录3:旅游
- 记录4:旅游
- 记录5:探亲
MODE(类型) = "旅游"(出现3次,最多)
含义:
- 最常见的类型是"旅游"
- 反映了人员的行为模式
- 可能与风险类型相关(旅游 vs 商务)
NUM_UNIQUE(唯一值数量)
定义:不同取值的数量。
为什么有效?
- 反映了”多样性”、”离散程度”
- 多样性可能与目标变量相关
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
样本一去过的城市:
- A市、B市、C市、A市、A市
NUM_UNIQUE(城市) = 3(去过3个不同城市)
样本二去过的城市:
- A市、A市、A市、A市、A市
NUM_UNIQUE(城市) = 1(只去过1个城市)
对比:
- 样本一:多样性高(去过多个城市)
- 样本二:多样性低(只去一个城市)
- 多样性可能与风险相关
2.3 时间特征的本质
DIFF(差值)
定义:相邻时间点的差值。
为什么有效?
- 反映了”变化量”、”趋势”、”稳定性”
- 时间间隔的模式可能包含重要信息
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
样本一的活动时间:
- 20XX-01-15
- 20XX-02-20(间隔36天)
- 20XX-03-10(间隔18天)
- 20XX-04-05(间隔26天)
- 20XX-05-12(间隔37天)
DIFF特征:
- MEAN(DIFF) = 29.25天(平均间隔)
- STD(DIFF) = 8.5天(标准差,反映规律性)
- MIN(DIFF) = 18天(最短间隔)
- MAX(DIFF) = 37天(最长间隔)
含义:
- 平均约一个月一次(频率)
- 间隔比较规律(标准差小)
- 反映了行为模式
TREND(趋势)
定义:时间序列的方向性(增长/下降/稳定)。
为什么有效?
- 反映了”动态变化”、”发展趋势”
- 趋势可能与目标变量相关
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
样本一的每月活动次数:
- 1月:1次
- 2月:1次
- 3月:1次
- 4月:2次(增加)
- 5月:3次(增加)
TREND = 上升趋势
含义:
- 活动频率在增加
- 可能反映了行为变化
- 可能与风险相关(频率增加 = 风险增加?)
2.4 组合特征的本质
为什么需要组合特征?
核心原理:特征的交互可能包含非线性的重要信息。
例子:年龄 × 收入
单独特征:
1
2
年龄:30岁
收入:50000元/月
组合特征:
1
年龄 × 收入 = 1500000
为什么组合特征可能更有用?
- 非线性关系:年龄和收入的交互可能比单独特征更有预测力
- 例子:
- 30岁 + 50000元 = 正常(年轻高收入)
- 50岁 + 50000元 = 可能异常(年龄大但收入不高)
- 组合特征能捕捉这种交互
可视化理解:
1
2
3
4
5
6
单独特征(线性):
年龄 → 风险(线性关系)
收入 → 风险(线性关系)
组合特征(非线性):
年龄 × 收入 → 风险(非线性关系,更复杂)
常见的组合方式
乘积特征:A × B
- 适用于:两个数值特征的交互
条件特征:IF A THEN B
- 适用于:条件依赖关系
字符串拼接:A + "_" + B
- 适用于:类别特征的组合
例子:
1
2
3
4
5
6
特征A:最常去城市 = "A市"
特征B:最常见类型 = "旅游"
组合特征:
- 城市_类型 = "A市_旅游"
- 可能比单独特征更有预测力
2.5 特征工程的理论基础
信息论视角
好特征的标准:
- 高信息量:特征与目标变量的相关性高
- 低冗余:特征之间的相关性低(信息不重复)
互信息(Mutual Information):
- 量化特征与目标的信息关联
- 值越大,特征越有用
例子:
1
2
3
4
5
6
7
8
9
10
特征1:年龄
- 与目标变量的互信息:0.3(中等相关)
特征2:随机数
- 与目标变量的互信息:0.001(几乎无关)
→ 应该删除
特征3:活动次数
- 与目标变量的互信息:0.6(高度相关)
→ 应该保留
维度灾难
问题:为什么不能无限制地增加特征?
原因:
- 特征维度增加 → 需要的样本数指数级增长
- 例子:
- 1个特征:需要10个样本
- 2个特征:需要100个样本(10²)
- 10个特征:需要10¹⁰个样本(不可行)
解决:特征选择
- 在信息量和维度之间平衡
- 只保留最重要的特征
偏差-方差权衡在特征工程中的体现
特征太少:
- 信息不足 → 高偏差(欠拟合)
- 例子:只用1个特征,无法捕捉复杂模式
特征太多:
- 噪声干扰 → 高方差(过拟合)
- 例子:用1000个特征,但只有100个样本
平衡点:
- 通过交叉验证找到最优特征数量
- 例子:20-50个特征可能是最优的
3. Featuretools 自动特征生成
3.1 什么是 Featuretools?
Featuretools 的设计理念
核心思想:自动化特征工程,减少人工工作量。
传统方法 vs Featuretools:
传统方法(手工特征工程):
流程:
1
2
3
4
5
6
7
8
9
10
11
手工编写代码
↓
为每个特征编写计算逻辑
- 活动次数:统计记录数
- 平均间隔:计算时间差平均值
- 最常去地点:统计众数
- 不同城市数:统计唯一值数量
↓
需要写大量代码
↓
容易遗漏重要特征组合
Featuretools(自动特征生成):
流程:
1
2
3
4
5
6
7
8
9
10
11
定义实体集(EntitySet)
↓
设置目标实体(persons)
↓
设置特征深度(max_depth=2)
↓
Featuretools自动生成
↓
输出:特征矩阵 + 特征定义
- 自动生成所有可能的特征组合
- 自动发现隐藏模式
为什么需要自动特征生成?
问题1:手工特征工程工作量大
- 需要领域专家知识
- 容易遗漏重要特征
- 难以维护和更新
问题2:特征组合爆炸
- 2个特征的组合:A, B, A×B
- 10个特征的组合:2¹⁰ = 1024种可能
- 手工无法穷尽所有组合
问题3:特征发现困难
- 人类可能想不到某些特征组合
- 自动生成可以发现隐藏模式
3.2 实体集(EntitySet)构建
理解实体和关系
实体(Entity):数据表
关系(Relationship):表之间的关联
例子:多表数据结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌─────────────────────┐
│ 人员信息表 │
│ (persons) │
│ - 标识号(主键) │
│ - 姓名 │
│ - 年龄 │
└─────────────────────┘
│
│ 1对多关系
↓
┌─────────────────────┐
│ 活动记录表 │
│ (activity_records) │
│ - 记录ID(主键) │
│ - 标识号(外键) │
│ - 时间 │
│ - 地点 │
│ - 类型 │
└─────────────────────┘
实体集构建流程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
步骤1:创建实体集
创建空的EntitySet(标识:'人员数据')
↓
步骤2:添加实体(表)
添加人员实体(persons)
- 主键:标识号
- 数据:人员信息表
↓
添加活动记录实体(activity_records)
- 主键:记录ID
- 数据:活动记录表
↓
步骤3:添加关系
建立关系:persons.标识号 ← → activity_records.标识号
(一个人员对应多条活动记录)
↓
步骤4:完成实体集构建
实体集包含:2个实体 + 1个关系
关键理解:
- 实体:数据表
- 关系:表之间的外键关联
- 目标实体:最终要生成特征的实体(通常是主表,如persons)
3.3 深度特征合成(DFS)
max_depth 参数的含义
depth = 1(深度1):
- 只对目标实体本身应用特征函数
- 例子:
1 2 3
目标实体:persons 特征:AGE(年龄)、GENDER(性别) → 直接特征
depth = 2(深度2):
- 对目标实体 + 相关实体应用特征函数
- 例子:
1 2 3 4 5 6
目标实体:persons 相关实体:activity_records 特征: - COUNT(activity_records) = 5(深度2:聚合特征) - MODE(activity_records.地点) = "A市"(深度2:聚合特征)
depth = 3(深度3):
- 对更深层的实体应用特征函数
- 例子:
1 2 3 4 5 6 7 8
目标实体:persons 相关实体1:activity_records 相关实体2:visa_records 特征: - COUNT(activity_records) = 5(深度2) - COUNT(visa_records) = 3(深度2) - COUNT(activity_records) × COUNT(visa_records) = 15(深度3:交叉特征)
特征爆炸问题
问题:深度增加 → 特征数量指数级增长
例子:
1
2
3
depth = 1:10个特征
depth = 2:100个特征(10²)
depth = 3:1000个特征(10³)
解决方案:
- 控制深度:通常 depth = 2 就够用了
- 特征选择:训练后选择重要特征
- 特征过滤:根据字段配置过滤不需要的特征
4. Featuretools 的工作机制
4.1 特征函数类型
聚合函数(Aggregation Primitives)
定义:对相关实体的多条记录进行聚合。
例子:
1
2
3
4
5
6
7
目标实体:persons(人员)
相关实体:activity_records(活动记录)
聚合函数:
- COUNT(activity_records) → 活动次数
- MEAN(activity_records.间隔天数) → 平均间隔
- MODE(activity_records.地点) → 最常去地点
工作原理:
1
2
3
4
5
6
7
8
9
10
11
12
样本一的活动记录:
- 记录1:A市
- 记录2:B市
- 记录3:A市
- 记录4:A市
MODE(activity_records.地点):
1. 统计每个地点的出现次数
- A市:3次
- B市:1次
2. 返回出现次数最多的地点
→ "A市"
变换函数(Transform Primitives)
定义:对单个实体的字段进行变换。
例子:
1
2
3
4
5
6
目标实体:persons
变换函数:
- YEAR(出生日期) → 出生年份
- MONTH(出生日期) → 出生月份
- DIFF(时间1, 时间2) → 时间差
4.2 特征生成过程:”先收集再过滤”
为什么需要”先收集再过滤”?
Featuretools 的限制:
- API不支持字段级别的特征函数配置
- 只能传入全局的特征函数列表
- 会为所有字段生成所有类型的特征
例子:
Featuretools API调用:
1
2
3
4
5
6
7
8
9
10
调用深度特征合成(DFS)
↓
输入参数:
- 实体集:es
- 目标实体:persons
- 聚合函数:['mean', 'mode'](全局配置)
↓
限制:不能指定字段级别的配置
- 不能指定:字段A只用mean,字段B只用mode
- 只能全局配置:所有字段都用mean和mode
结果:
- Featuretools会为所有字段生成mean和mode特征
- 例如:MEAN(地点)、MODE(地点)、MEAN(时间)、MODE(时间)…
- 但有些特征可能没有意义(如MEAN(地点))
解决方案:”先收集再过滤”
流程:
1
2
3
4
5
6
7
8
9
10
11
12
13
步骤1:传入所有可能的特征函数
agg_primitives = ['count', 'sum', 'mean', 'max', 'min', 'mode', ...]
↓
步骤2:Featuretools生成所有可能的特征
生成:MEAN(地点)、MODE(地点)、MEAN(时间)、MODE(时间)、...
↓
步骤3:根据字段配置过滤
配置:地点只用mode,时间只用mean
过滤:保留MODE(地点)、MEAN(时间)
删除:MEAN(地点)、MODE(时间)
↓
步骤4:得到最终特征矩阵
只包含需要的特征
为什么这样做?
- Featuretools的设计就是这样的(不支持字段级配置)
- 过滤阶段很高效(只是过滤DataFrame的列)
- 代码简单,充分利用Featuretools的能力
5. 特征工程实战示例
5.1 多表数据的特征提取流程
完整流程图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
原始数据(Excel文件)
↓
┌─────────────────────────────────┐
│ 数据加载 │
│ - 读取4个sheet页 │
│ - 解析为DataFrame │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ 数据预处理 │
│ - 清洗(去除空值、异常值) │
│ - 类型转换(文本→类别) │
│ - 格式规范化 │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ 实体集构建 │
│ - 创建EntitySet │
│ - 添加实体(表) │
│ - 添加关系(外键) │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ 特征生成(DFS) │
│ - 设置max_depth=2 │
│ - 传入特征函数列表 │
│ - 自动生成特征 │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ 特征过滤 │
│ - 根据字段配置过滤 │
│ - 去除不需要的特征 │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ 特征矩阵 │
│ - 每个人员一行 │
│ - 每个特征一列 │
│ - 保存特征定义 │
└─────────────────────────────────┘
具体例子
输入数据:
人员信息表(persons):
1
2
3
4
标识号 | 姓名 | 年龄
-----------|------|------
ID_A | 某甲 | 30
ID_B | 某乙 | 25
活动记录表(activity_records):
1
2
3
4
5
6
7
记录ID | 标识号 | 时间 | 地点 | 类型
-------|-----------|-----------|------|------
1 | ID_A | 20XX-01-15| A市 | 旅游
2 | ID_A | 20XX-02-20| B市 | 商务
3 | ID_A | 20XX-03-10| A市 | 旅游
4 | ID_B | 20XX-01-10| C市 | 旅游
5 | ID_B | 20XX-02-15| C市 | 旅游
特征生成过程:
步骤1:实体集构建
流程:
1
2
3
4
5
6
7
8
9
10
11
12
13
创建实体集(EntitySet)
↓
添加人员实体(persons)
- 主键:标识号
- 数据:人员信息表
↓
添加活动记录实体(activity_records)
- 主键:记录ID
- 数据:活动记录表
↓
添加关系
- persons.标识号 ← → activity_records.标识号
- 关系类型:一对多(一个人员对应多条记录)
步骤2:深度特征合成
流程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
调用深度特征合成(DFS)
↓
输入参数:
- 实体集:es
- 目标实体:persons(人员)
- 最大深度:2(包含相关实体特征)
- 聚合函数:['count', 'mean', 'mode', 'num_unique']
- 变换函数:['diff']
↓
自动生成特征
↓
输出:
- 特征矩阵(feature_matrix)
- 特征定义(feature_defs)
步骤3:生成的特征矩阵
输出特征矩阵:
1
2
3
4
标识号 | COUNT(activity_records) | NUM_UNIQUE(activity_records.地点) | MODE(activity_records.类型)
-----------|----------------------|------------------------------|------------------------
ID_A | 3 | 2 | 旅游
ID_B | 2 | 1 | 旅游
特征解释:
COUNT(activity_records) = 3:某甲有3条活动记录NUM_UNIQUE(activity_records.地点) = 2:去过2个不同城市MODE(activity_records.类型) = "旅游":最常见类型是旅游
5.2 特征命名规则的理解
Featuretools 的特征命名规则
格式:FUNCTION(ENTITY.FIELD)
例子:
COUNT(activity_records):对activity_records实体计数MODE(activity_records.地点):对activity_records实体的”地点”字段取众数MEAN(activity_records.间隔天数):对activity_records实体的”间隔天数”字段取平均值
深度特征命名
深度2特征:
COUNT(activity_records):直接聚合
深度3特征:
COUNT(activity_records) × COUNT(visa_records):交叉特征
5.3 特征定义文件的保存与使用
为什么需要保存特征定义?
问题:训练时和预测时特征必须一致
场景:
1
2
3
4
5
6
7
8
训练时:
特征1:COUNT(activity_records)
特征2:MODE(activity_records.地点)
→ 训练模型
预测时:
如果特征顺序或名称不一致
→ 模型无法正确预测 ❌
解决方案:保存特征定义文件
保存流程:
1
2
3
4
5
6
训练时:
生成特征定义(feature_defs)
↓
保存到文件(feature_definitions.pkl)
↓
文件包含:特征名称、特征函数、特征结构等信息
加载流程:
1
2
3
4
5
6
预测时:
加载特征定义文件(feature_definitions.pkl)
↓
使用相同的特征定义生成特征
↓
确保特征与训练时完全一致
关键:确保训练和预测时特征完全一致。
6. 数据预处理完整流程
6.1 数据预处理的本质
数据预处理的定义:在特征工程之前,对原始数据进行清洗、转换、分割等操作,使其适合模型训练。
为什么需要数据预处理?
问题:原始数据通常存在各种问题,不能直接用于模型训练。
例子:
1
2
3
4
5
6
原始数据的问题:
- 缺失值:某些字段为空
- 异常值:年龄=200岁(不合理)
- 重复值:同一条记录出现多次
- 格式不一致:日期格式"20XX-01-15" vs "2023/1/15"
- 量纲不同:年龄(20-80)vs 收入(1000-50000)
数据预处理的作用:
- 提高数据质量:去除噪声、异常值、重复值
- 统一数据格式:确保数据格式一致
- 准备训练数据:将数据分割为训练集和测试集
- 处理不平衡数据:通过采样解决类别不平衡问题
6.2 数据清洗
异常值处理
异常值的定义:明显偏离正常范围的值。
识别方法:
1
2
3
4
5
6
7
8
9
方法1:3σ原则(正态分布)
超出均值±3σ的数据可能是异常值
方法2:IQR方法(四分位距)
超出Q1-1.5×IQR或Q3+1.5×IQR的数据可能是异常值
方法3:业务规则
年龄>150岁 → 异常值
收入<0 → 异常值
处理方法:
1
2
3
4
5
6
7
8
方法1:删除异常值
直接删除异常样本
方法2:替换异常值
用中位数、均值或边界值替换
方法3:保留异常值
如果异常值有业务意义(如真实的高收入),保留
重复值处理
问题:同一条记录出现多次。
识别方法:
1
2
检查主键是否重复
如果主键重复 → 重复记录
处理方法:
1
2
3
4
5
方法1:删除重复值
保留第一条,删除后续重复记录
方法2:合并重复值
如果重复记录有不同信息,合并
错误值处理
问题:数据格式错误、类型错误。
例子:
1
2
3
4
错误值示例:
- 日期格式:"20XX-01-15" vs "2023/1/15"(格式不一致)
- 类型错误:年龄="30岁"(应该是数值30)
- 范围错误:年龄=-5(负数不合理)
处理方法:
1
2
3
4
5
6
7
8
方法1:格式规范化
统一日期格式、统一单位等
方法2:类型转换
文本转数值、日期字符串转日期对象
方法3:范围检查
检查数值是否在合理范围内
6.3 数据转换
对数变换
适用场景:数据分布严重偏斜(右偏)。
作用:将偏斜分布转换为近似正态分布。
例子:
1
2
3
4
5
6
7
8
9
原始数据(收入):
[1000, 2000, 5000, 10000, 50000]
↓
分布:严重右偏(少数大值)
对数变换后:
[log(1000), log(2000), ...] = [6.9, 7.6, 8.5, 9.2, 10.8]
↓
分布:近似正态分布 ✅
标准化与归一化
标准化(Z-score):
1
2
3
公式:标准化值 = (原始值 - 均值) / 标准差
作用:将数据转换为均值0、标准差1的分布
适用:梯度下降算法、正态分布数据
归一化(MinMax):
1
2
3
公式:归一化值 = (原始值 - 最小值) / (最大值 - 最小值)
作用:将数据映射到[0, 1]范围
适用:相似度计算、神经网络
为什么需要?
- 消除量纲影响:不同特征的量纲不同(年龄vs收入)
- 提高训练效率:标准化后的数据训练更快
- 提高模型性能:归一化后的数据模型性能更好
注意:不是所有模型都需要归一化
- 需要归一化的模型:神经网络、线性回归、KNN、SVM等(基于梯度下降或距离计算)
- 不需要归一化的模型:决策树、随机森林、XGBoost(树模型基于排序分裂,不受尺度影响)
训练前归一化的作用:
即使树模型不强制要求归一化,但归一化可能带来好处:
1. 提升计算效率
- 直方图加速:某些树模型实现(如LightGBM)使用直方图加速,归一化可能提升效率
- 数值稳定性:归一化可以提高数值计算的稳定性
2. 特征公平性
- 权重分配:虽然树模型基于排序,但归一化后所有特征在同一尺度,权重分配更公平
- 特征重要性:归一化后的特征重要性评估更准确
3. 混合模型场景
- 特征选择:如果后续需要特征选择或特征融合,归一化后的特征更容易比较
- 模型组合:如果使用多个模型(树模型+线性模型),归一化可以统一特征尺度
建议:
- 树模型:归一化不是必需的,但可以尝试(可能提升效率或公平性)
- 梯度下降类模型:归一化是必需的
- 距离计算类模型:归一化是必需的
6.4 数据分割
训练集与测试集
为什么需要分割?
问题:不能在同一份数据上训练和评估。
原因:
- 过拟合风险:如果在训练数据上评估,模型可能”死记硬背”
- 泛化能力:需要在未见过的新数据上评估模型
分割方法:
1
2
3
4
5
6
7
8
9
方法1:随机分割
训练集:80%
测试集:20%
方法2:分层分割(分类问题)
保持各类别比例一致
方法3:时间分割(时间序列)
按时间顺序分割(早期数据训练,后期数据测试)
注意事项:
- 数据泄露:测试集不能参与训练过程
- 分布一致性:训练集和测试集应该来自同一分布
- 样本量:测试集要有足够样本量(至少几百个样本)
6.5 数据采样
类别不平衡问题
问题:不同类别的样本数量差异很大。
例子:
1
2
3
4
5
6
正类样本:100个
负类样本:10000个
↓
类别不平衡(1:100)
↓
问题:模型可能只学习到负类模式
采样方法
方法1:过采样(Oversampling)
1
2
3
4
5
增加少数类样本
↓
方法:SMOTE(合成少数类样本)
↓
结果:正类样本从100增加到1000
方法2:欠采样(Undersampling)
1
2
3
4
5
减少多数类样本
↓
方法:随机删除多数类样本
↓
结果:负类样本从10000减少到1000
方法3:组合采样
1
2
3
同时使用过采样和欠采样
↓
平衡各类别样本数量
6.6 缺失值处理
缺失值的类型
完全随机缺失(MCAR):缺失与数据值无关。
随机缺失(MAR):缺失与观测到的数据有关。
非随机缺失(MNAR):缺失与数据值本身有关。
处理方法
方法1:删除缺失值
1
2
适用:缺失值很少(<5%)
方法:直接删除包含缺失值的样本或特征
方法2:填充缺失值
1
2
3
4
5
6
7
8
数值型特征:
- 均值填充
- 中位数填充
- 众数填充
类别型特征:
- 众数填充
- "未知"类别填充
方法3:模型预测填充
1
2
3
4
5
使用其他特征预测缺失值
↓
方法:KNN、回归模型等
↓
结果:更准确的填充值
方法4:保留缺失值
1
2
3
某些模型(如XGBoost)可以自动处理缺失值
↓
不需要填充,模型会学习如何处理缺失值
6.7 数据预处理的完整流程
完整流程图:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
原始数据
↓
┌─────────────────────────────────┐
│ 数据清洗 │
│ - 异常值处理 │
│ - 重复值处理 │
│ - 错误值处理 │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ 数据转换 │
│ - 对数变换(如需要) │
│ - 标准化/归一化 │
│ - 类型转换 │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ 缺失值处理 │
│ - 删除或填充 │
│ - 或保留(XGBoost) │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ 数据分割 │
│ - 训练集(80%) │
│ - 测试集(20%) │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ 数据采样(如需要) │
│ - 处理类别不平衡 │
└─────────────────────────────────┘
↓
预处理后的数据(可用于特征工程)
关键原则:
- 训练集和测试集分开处理:测试集不能参与训练过程的任何步骤
- 保存预处理参数:保存均值、标准差、min、max等,用于新数据预处理
- 一致性:训练和预测时使用相同的预处理方法
7. 特征工程的最佳实践
7.1 如何选择特征函数?
根据字段类型选择
数值型字段:
- COUNT、SUM、MEAN、MAX、MIN、STD
- 例子:间隔天数 → MEAN(间隔天数)、STD(间隔天数)
类别型字段:
- MODE、NUM_UNIQUE
- 例子:地点 → MODE(地点)、NUM_UNIQUE(地点)
时间型字段:
- DIFF、TREND
- 例子:时间 → DIFF(时间)、TREND(时间)
根据业务含义选择
例子:活动记录
字段:地点
- MODE(地点):最常去的地点(行为模式)
- NUM_UNIQUE(地点):去过的不同城市数(多样性)
字段:类型
- MODE(类型):最常见的类型(行为模式)
字段:间隔天数
- MEAN(间隔天数):平均间隔(频率)
- STD(间隔天数):间隔的规律性(稳定性)
6.2 如何控制特征数量?
方法1:控制深度
depth = 1:只生成直接特征(数量少) depth = 2:包含聚合特征(数量中等) depth = 3:包含交叉特征(数量多)
建议:通常 depth = 2 就够用了
方法2:特征选择
训练后选择:
- 训练模型
- 提取特征重要性
- 只保留TopK重要特征
例子:
1
2
3
4
5
6
7
8
9
生成的特征:100个
特征重要性排序:
- 特征1:0.15(重要)
- 特征2:0.12(重要)
- ...
- 特征50:0.001(不重要)
选择Top20:
→ 只保留前20个重要特征
方法3:字段级配置
配置每个字段使用的特征函数:
1
2
3
4
5
6
field_config:
activity_records:
地点:
features: ['mode', 'num_unique'] # 只用这两个
间隔天数:
features: ['mean', 'std'] # 只用这两个
效果:减少生成的特征数量
6.3 特征质量如何评估?
方法1:特征重要性
训练模型后查看特征重要性:
流程:
1
2
3
4
5
6
7
训练XGBoost模型
↓
模型自动计算特征重要性(feature_importances_)
↓
按重要性值排序(从高到低)
↓
得到特征重要性排序列表
例子:
1
2
3
4
5
6
特征重要性:
- MODE(activity_records.地点):0.15(最重要)
- COUNT(activity_records):0.12
- MEAN(activity_records.间隔天数):0.10
- MODE(activity_records.类型):0.08
- ...(其他特征重要性较低)
方法2:相关性分析
计算特征与目标变量的相关性:
流程:
1
2
3
4
5
6
7
8
特征矩阵(feature_matrix)
↓
计算每个特征与目标变量的相关系数
↓
得到相关性值(-1到1之间)
- 接近1:正相关
- 接近-1:负相关
- 接近0:无关
例子:
1
2
3
4
特征与目标的相关性:
- COUNT(activity_records):0.6(高度相关)
- MODE(activity_records.地点):0.4(中等相关)
- 随机特征:0.01(几乎无关,应该删除)
方法3:Permutation Importance
打乱特征值,看性能下降:
流程:
1
2
3
4
5
6
7
8
9
10
11
12
步骤1:计算基准性能
用原始特征矩阵预测 → 得到基准分数(如F1=0.85)
↓
步骤2:打乱某个特征的值
随机打乱特征A的值 → 得到打乱后的特征矩阵
↓
步骤3:用打乱后的数据预测
用打乱后的特征矩阵预测 → 得到新分数(如F1=0.80)
↓
步骤4:计算重要性
重要性 = 基准分数 - 打乱后分数
= 0.85 - 0.80 = 0.05(特征A有用)
例子:
1
2
3
4
5
6
7
基准性能:F1 = 0.85
打乱特征A后:F1 = 0.80
→ 重要性 = 0.05(特征A有用)
打乱特征B后:F1 = 0.84
→ 重要性 = 0.01(特征B不太重要)
6.4 特征一致性如何保证?
问题:训练和预测时特征不一致
场景:
1
2
3
4
5
6
7
8
9
训练时:
特征顺序:[特征1, 特征2, 特征3]
特征名称:['COUNT(...)', 'MODE(...)', 'MEAN(...)']
预测时:
特征顺序:[特征2, 特征1, 特征3] # 顺序不同
特征名称:['COUNT(...)', 'MODE(...)', 'MEAN(...)'] # 名称相同
结果:模型预测错误 ❌
解决方案
方案1:保存特征定义文件
- 训练时保存特征定义
- 预测时加载并使用相同的定义
方案2:特征对齐
流程:
1
2
3
4
5
6
7
8
步骤1:确保特征顺序一致
查询特征矩阵 → 按训练时的特征顺序重新排列
↓
步骤2:确保特征名称一致
查询特征矩阵 → 使用训练时的特征名称
↓
步骤3:完成对齐
特征顺序和名称与训练时完全一致
方案3:缺失特征处理
流程:
1
2
3
4
5
6
7
8
9
10
11
步骤1:检查缺失特征
训练时的特征列表 vs 查询时的特征列表
↓
步骤2:找出缺失的特征
缺失特征 = 训练特征 - 查询特征
↓
步骤3:填充默认值
为每个缺失特征填充默认值(0或均值)
↓
步骤4:完成处理
查询特征矩阵包含所有训练时的特征
7. 常见问题与解决方案
7.1 特征缺失值处理
问题:特征矩阵中有缺失值
原因:
- 原始数据有缺失值
- 某些聚合函数无法计算(如所有值都是NaN)
解决方案:
方法1:填充默认值
流程:
1
2
3
4
5
6
7
8
9
10
检查特征矩阵中的缺失值
↓
数值特征:
- 缺失值 → 填充0或均值
- 例如:COUNT特征缺失 → 填充0
- 例如:MEAN特征缺失 → 填充该特征的均值
↓
类别特征:
- 缺失值 → 填充众数或"未知"
- 例如:MODE特征缺失 → 填充"未知"
方法2:删除缺失值过多的特征
流程:
1
2
3
4
5
6
7
计算每个特征的缺失值比例
↓
缺失值比例 = 缺失值数量 / 总样本数
↓
判断:缺失值比例 > 50%?
├─→ 是 → 删除该特征(缺失太多,信息不足)
└─→ 否 → 保留该特征(填充缺失值)
方法3:在模型训练时处理
说明:
- XGBoost模型可以自动处理缺失值
- 但需要确保训练和预测时处理方式一致
- 建议:统一在特征工程阶段处理缺失值
7.2 特征类型转换
问题:特征类型不匹配
场景:
1
2
3
4
5
6
7
训练时:
特征A:整数类型(int)
预测时:
特征A:浮点数类型(float)
结果:可能导致预测错误
解决方案:
流程:
1
2
3
4
5
6
7
检查特征类型
↓
统一转换为数值类型
- 所有特征 → 浮点数类型(float)
- 或所有特征 → 整数类型(int,如果适用)
↓
确保训练和预测时类型一致
7.3 特征对齐问题
问题:训练和预测时特征不一致
场景1:特征顺序不同
问题:
1
2
训练时:特征顺序 = [特征1, 特征2, 特征3]
预测时:特征顺序 = [特征2, 特征1, 特征3](顺序不同)
解决流程:
1
2
3
4
5
查询特征矩阵
↓
按训练时的特征顺序重新排列
↓
确保顺序一致
场景2:特征名称不同
问题:
1
2
训练时:'COUNT(activity_records)'
预测时:'COUNT(activity_records) '(有空格差异)
解决流程:
1
2
3
4
5
标准化特征名称
- 去除前后空格
- 统一大小写
↓
确保名称一致
场景3:缺少某些特征
问题:
1
2
训练时:有特征A、B、C
预测时:只有特征A、B(缺少C)
解决流程:
1
2
3
4
5
6
7
8
找出缺失的特征
缺失特征 = 训练特征集合 - 查询特征集合
↓
为每个缺失特征填充默认值
- 填充0(数值特征)
- 或使用训练时的默认值
↓
确保所有特征都存在
7.4 特征版本管理
问题:特征定义可能变化
场景:
1
2
3
4
5
6
7
版本1:使用特征A、B、C训练模型1
版本2:使用特征A、B、D训练模型2(特征C改为D)
问题:
- 模型1需要特征C
- 模型2需要特征D
- 如何管理不同版本的特征?
解决方案:
方法1:特征定义文件版本化
1
2
feature_definitions_v1.pkl # 版本1的特征定义
feature_definitions_v2.pkl # 版本2的特征定义
方法2:模型元数据中包含特征定义
流程:
1
2
3
4
5
6
7
每个模型文件包含:
- 模型本身(model)
- 特征定义(feature_definitions)
- 特征名称列表(feature_names)
- 版本号(version)
↓
模型文件自包含,包含所有必要信息
方法3:特征数据库
流程:
1
2
3
4
5
6
7
将特征定义存储在数据库中
↓
支持版本管理
- 不同版本的特征定义
- 版本查询和切换
↓
支持特征定义查询和管理
总结
本文深入介绍了特征工程的核心概念和实践方法。关键要点:
- 特征的本质:对原始数据的抽象表示,信息压缩与模式提取
- 为什么需要特征工程:原始数据格式不统一、包含噪声、粒度不匹配
- 特征工程的重要性:数据决定上限,特征工程是提升性能最有效的方法
- Featuretools:自动化特征生成,减少人工工作量
- 特征一致性:训练和预测时特征必须完全一致
