4. 特征工程基础:特征分类与特征理解
各位同学,咱们今天聊聊特征工程里最基础、也最容易被忽视的一环——特征分类与理解。说实话,我见过不少项目,模型结构选得挺花哨,调参也调得飞起,结果效果就是上不去。一查,原来是特征没理解透,把类别型当数值型硬塞进去了。嗯,这种坑,我踩过不止一次。
特征工程说白了,就是把原始数据变成模型能听懂的语言。但不同特征,模型得用不同方式去“听”。咱们先给特征分个类。
4.1 特征的三大家族
我个人习惯把特征分成三大类:数值型、类别型、文本型。当然还有图像、音频这些,但咱们离线批量生产流水线里,90% 的场景都逃不出这三类。
| 特征类型 | 典型例子 | 核心特点 | 常见处理方式 |
|---|---|---|---|
| 数值型 | 年龄、收入、点击次数 | 有大小、可运算 | 归一化、标准化、分箱 |
| 类别型 | 性别、城市、用户等级 | 离散值、无大小关系 | One-Hot、Label Encoding、Target Encoding |
| 文本型 | 评论、标题、描述 | 变长、语义丰富 | TF-IDF、Word2Vec、BERT Embedding |
你想想看,如果把“城市”这个类别型特征,直接用 1、2、3 编码丢给模型,模型会认为“北京(1) + 上海(2) = 深圳(3)”,这逻辑完全不对。我在项目中就见过有人这么干,结果模型学了一堆奇怪的“城市算术”。
4.2 数值型特征:不只是归一化
数值型特征看起来最简单,但坑也不少。很多人上来就做 Min-Max 归一化,其实不一定对。
核心原则:数值型特征的处理,取决于它的分布和业务含义。
- 正态分布:用 Z-Score 标准化,效果最好。
- 长尾分布:比如用户消费金额,大部分是小额,少数是大额。我建议先做 Log 变换,拉平分布,再归一化。
- 有业务阈值:比如年龄 0-120,直接用 Min-Max 缩放到 [0,1] 就行。
举个例子,我在做电商用户画像时,遇到过“购买次数”这个特征。大部分用户买 1-5 次,少数大客户买几百次。直接归一化后,普通用户和头部用户被压在一起,模型根本分不清。后来我做了 Log1p 变换,效果立竿见影。
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler, MinMaxScaler
# 模拟长尾分布
purchase_counts = np.array([1, 2, 3, 5, 10, 50, 200, 500])
# 错误做法:直接 Min-Max
scaler = MinMaxScaler()
bad_result = scaler.fit_transform(purchase_counts.reshape(-1, 1))
# 正确做法:先 Log 变换
log_counts = np.log1p(purchase_counts)
good_result = StandardScaler().fit_transform(log_counts.reshape(-1, 1))
print("原始值:", purchase_counts)
print("直接归一化:", bad_result.flatten().round(3))
print("Log+标准化:", good_result.flatten().round(3))
小技巧:对于稀疏的数值特征(比如很多 0),可以考虑做“是否为空”的二值特征,再配合原始值一起喂给模型。我经常这么干,效果很稳。
4.3 类别型特征:编码的艺术
类别型特征的处理,是特征工程里最容易出彩也最容易翻车的地方。我总结了几种常见编码方式,各有各的适用场景。
| 编码方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| One-Hot 编码 | 类别少(< 10 个) | 无偏、模型友好 | 维度爆炸 |
| Label Encoding | 有序类别(如等级) | 节省空间 | 引入虚假顺序 |
| Target Encoding | 高基数类别(如用户ID) | 捕捉目标相关性 | 容易过拟合 |
| Frequency Encoding | 类别分布差异大 | 简单、稳定 | 丢失类别语义 |
我曾经在一个广告点击率预估项目里,遇到“广告主 ID”这个特征,有几十万个不同值。One-Hot 直接炸内存,Label Encoding 又学出奇怪规律。最后我用 Target Encoding + 交叉验证平滑,才把效果稳住。
避坑指南:Target Encoding 一定要在训练集上做,并且要用 K-Fold 计算,防止数据泄露。我曾经偷懒直接在全体数据上算均值,结果线上效果崩得一塌糊涂。
4.4 文本型特征:从词袋到语义
文本型特征在离线流水线里,通常不是做深度学习那种端到端,而是提取统计特征或预训练向量。
我个人习惯分三步走:
- 清洗:去停用词、特殊符号、统一小写。
- 向量化:根据数据量选择方法。
- 数据量小(万级):TF-IDF,简单可解释。
- 数据量大(百万级):Word2Vec 或 FastText,训练快。
- 追求效果:用预训练的 Sentence-BERT,直接拿向量。
- 聚合:如果文本是用户历史行为描述,需要按用户聚合,取均值或拼接。
from sklearn.feature_extraction.text import TfidfVectorizer
docs = [
"这个商品质量很好,推荐购买",
"物流太慢了,差评",
"性价比很高,值得入手"
]
vectorizer = TfidfVectorizer(max_features=100, stop_words='english')
tfidf_matrix = vectorizer.fit_transform(docs)
print("特征词:", vectorizer.get_feature_names_out()[:10])
print("TF-IDF 矩阵形状:", tfidf_matrix.shape)
经验之谈:在离线批量生产里,文本特征最好提前计算好,存成向量文件。每次训练直接加载,别在流水线里实时做分词和向量化,太慢了。
4.5 特征理解:先看分布,再谈建模
特征理解不是玄学,是实打实的工程步骤。我每次拿到新数据,都会做三件事:
- 单变量分析:看分布、看缺失、看异常值。用直方图或箱线图扫一遍。
- 双变量分析:看特征与目标的关系。数值型用散点图或相关系数,类别型用分组统计。
- 业务校验:这个特征的含义是什么?它的取值范围合理吗?比如“年龄=200”这种,明显是脏数据。
我记得有一次,一个特征“用户活跃天数”出现了负数。团队里有人直接取值,觉得“反正模型能学”。我坚持查了数据源,发现是日志时间戳解析错了。你看,不理解特征,再好的模型也是白搭。
核心观点:特征理解不是一次性工作。每次数据源变更、业务调整,都要重新审视特征。我见过太多项目,上线半年后特征分布已经漂移了,模型还在用老参数跑。
4.6 本章知识体系
下面这张图,是我对本章知识结构的总结。你可以把它当作一个检查清单,做特征工程时对照着来。