5.7 特征工程实战
特征工程是机器学习中”将原始数据转化为模型能理解的形式”的过程。2026 年,虽然深度学习减少了对手工特征工程的依赖,但在传统 ML 和结构化数据场景中,特征工程仍然是决定模型性能的最关键因素。好的特征能让简单的模型超越复杂的模型。
5.7.1 数值特征处理
数值特征是机器学习中最常见的特征类型。不同的处理方式适应不同的模型和数据分布。
标准化(Standardization / Z-score Normalization):x' = (x - μ) / σ
- 结果:特征均值为 0,标准差为 1。数据服从标准正态分布 N(0,1)
- 适用模型:假设数据服从正态分布的模型——线性回归、逻辑回归、SVM、PCA、LDA
- 重要:标准化不改变数据分布的形状——如果原始数据是偏态的,标准化后仍然是偏态的
- 使用 Scikit-learn 的
StandardScaler:它在训练集上计算 μ 和 σ,然后用同样的 μ 和 σ 转换测试集——不能从测试集上重新计算 μ 和 σ,那会导致数据泄露 - 为什么重要:对于基于距离的模型(SVM、KNN),如果特征的量纲不同(如年龄 0-100,收入 0-1000000),模型会完全被大量纲特征主导。标准化后所有特征在同一尺度上,模型公平对待每个特征
归一化(Normalization / Min-Max Scaling):x' = (x - min) / (max - min)
- 结果:所有特征值被压缩到 [0, 1](或 [-1, 1])区间
- 适用模型:对特征尺度敏感但不假设正态分布的模型——神经网络(尤其是使用 sigmoid/tanh 激活时)、KNN、K-Means
- 优点:保持了原始数据的分布形状,只是线性缩放了范围
- 缺点:对异常值非常敏感——一个异常值就能让 max 变得极大,导致所有正常值被压缩到接近 0 的狭小区间
- 使用 Scikit-learn 的
MinMaxScaler:可以指定 feature_range 参数(如 (0, 1) 或 (-1, 1))
分箱(Binning / Discretization):把连续值切成几段,变成离散类别。
- 直觉:有时”年龄段”比”具体年龄”更有预测力。一个 22 岁和一个 23 岁的人消费习惯可能差不多,但 22 岁和 65 岁的人完全不同
- 方法:等宽分箱(每个箱的宽度相同)、等频分箱(每个箱的样本数相同)、基于业务逻辑的分箱(如年龄:0-18 青少年,19-35 青年,36-60 中年,60+ 老年)
- 优点:减少异常值的影响(一个 150 岁的录入错误在”60+“这个箱子里不碍事);捕捉非线性关系(线性模型 + 分箱可以拟合非线性关系)
- 缺点:丢失了细节信息(箱内的差异被忽略);分箱边界的选择影响结果
- 实现:
KBinsDiscretizerin Scikit-learn
对数变换(Log Transform):x' = log(x + 1)
- 适用数据:长尾分布(Long-tail)——大部分值很小,少数值极大。如收入、房价、网页浏览量、社交媒体的点赞数
- 效果:压缩大值,拉伸小值,让分布更接近正态分布。这对于假设正态分布的模型(线性回归)非常有效
- 注意:只能对正数做对数变换。如果数据中有 0,加一个小的常数(如 1)再取对数
- 直觉:log(100) = 4.6, log(10000) = 9.2, log(1000000) = 13.8——1 万到 100 万在原始空间的差距是 99 万,在 log 空间的差距只有 4.6
Box-Cox 变换:对数变换的泛化版本,能自动找到最优的变换参数 λ。当 λ=0 时等价于对数变换。scipy.stats.boxcox 实现。
幂变换(Power Transform):Scikit-learn 的 PowerTransformer 提供了 Box-Cox(仅正数)和 Yeo-Johnson(可处理负数和零)两种方法。
5.7.2 类别特征编码
类别特征(如”城市”、“性别”、“职业”)不能直接输入模型——模型只能处理数字。不同的编码方法适用于不同的场景。
One-Hot 编码(独热编码):每个类别变成一个独立的 0/1 列。
- “北京” → [1, 0, 0],“上海” → [0, 1, 0],“深圳” → [0, 0, 1]
- 优点:不引入虚假的顺序关系(北京不大于上海,上海不大于深圳);适用于大多数分类算法
- 缺点:类别数量多时,特征维度爆炸(100 个城市 = 100 列);稀疏矩阵(大部分是 0)
- 处理未知类别:测试集中出现训练集中没有的类别时,所有 one-hot 列都是 0(或单独设置一个”未知”列)
- 实现:
pd.get_dummies(df, columns=['city'])或OneHotEncoderin Scikit-learn
Label 编码(标签编码):每个类别映射到一个整数。
- “北京” → 0, “上海” → 1, “深圳” → 2
- 优点:不增加维度,内存效率高
- 致命缺点:引入了虚假的顺序关系。模型会认为”深圳(2) > 上海(1) > 北京(0)“,但实际上城市之间没有大小关系
- 适用场景:树模型(决策树、随机森林、XGBoost)可以处理 Label 编码,因为它们只关心”哪个值”而不是”值的大小”;有序的类别(如”低中高”→ 0,1,2,这是有意义的)
- 实现:
LabelEncoderin Scikit-learn
Ordinal 编码(序数编码):Label 编码的变体,用于有自然顺序的类别。
- 学历:“高中” → 0, “本科” → 1, “硕士” → 2, “博士” → 3(有意义的大小关系)
- 满意度:“不满意” → 0, “一般” → 1, “满意” → 2, “非常满意” → 3
- 关键:只对有自然顺序的类别使用 Ordinal 编码
Target 编码(目标编码):用目标变量的统计量来编码类别。
- 每个类别替换为该类别对应的目标变量的均值(或中位数):
- “北京” → 北京地区的平均房价 5.2 万/m^2
- “上海” → 上海地区的平均房价 5.8 万/m^2
- 优点:捕捉了类别和目标变量之间的关系;不增加维度
- 缺点:容易过拟合(尤其是小类别——只有 3 个样本的类别,均值极不可靠);需要平滑(加入先验均值来正则化)和交叉验证(在训练集的子集上计算编码,避免数据泄露)
- 实现:
TargetEncoderin Scikit-learn(0.24+),或category_encoders库 - 2026 年:Target 编码在 Kaggle 比赛中非常流行,是处理高基数类别特征(如”用户 ID”、“商品 ID”)的标准方法
Count 编码(计数编码):每个类别替换为该类别在数据中出现的次数。
- 直觉:出现次数多的类别可能更重要或更”主流”
- 优点:简单,不增加维度
- 缺点:丢失了类别本身的信息,只保留了频率信息
- 适用场景:高基数类别特征的快速基线编码
Frequency 编码(频率编码):Count 编码除以总数,转为频率比例。
选择编码方法的决策流程:
- 类别数量 < 10:One-Hot 编码(最安全,不引入偏置)
- 类别数量 10-100:树模型用 Label 编码,线性模型用 Target 编码或 One-Hot(选重要的类别)
- 类别数量 > 100:Target 编码或 Count 编码(One-Hot 会导致维度爆炸)
- 类别有自然顺序:Ordinal 编码
- 测试集可能出现未知类别:One-Hot(未知类别全 0)或 Target 编码(用全局均值)
5.7.3 文本特征提取
文本不能直接输入模型,需要先转化为数值向量。2026 年,从传统的 TF-IDF 到预训练 BERT Embedding,文本特征提取的方法经历了巨大的演进。
TF-IDF(词频-逆文档频率):传统 NLP 的标准方法。
- TF(词频):一个词在文档中出现的频率。
TF(t,d) = 词t在文档d中出现的次数 / 文档d的总词数 - IDF(逆文档频率):一个词在语料库中的”稀有度”。
IDF(t) = log(总文档数 / 包含词t的文档数) - TF-IDF = TF × IDF:在一个文档中频繁出现,但在整个语料库中很少出现的词,权重最高。这抓住了”关键词”的直觉——“量子”在一篇物理论文中频繁出现,但在所有文档中很少出现,所以它是这篇论文的关键词
- 输出:每个文档变成一个稀疏向量,维度 = 词汇表大小(通常几千到几万)
- 适用场景:文本分类、信息检索、关键词提取、文档相似度计算
- 实现:
TfidfVectorizerin Scikit-learn
Word2Vec:2013 年提出的词嵌入方法,是 NLP 从”符号处理”到”语义理解”的转折点。
- 核心思想:一个词的含义由它周围的词决定(“You shall know a word by the company it keeps”)
- 训练方式:CBOW(用上下文预测中心词)或 Skip-gram(用中心词预测上下文)
- 输出:每个词变成一个稠密向量(通常 100-300 维),语义相近的词向量距离近——vec(“国王”) - vec(“男人”) + vec(“女人”) ≈ vec(“女王”)
- 局限:每个词只有一个固定的向量,无法处理多义词(“bank”可以是”银行”也可以是”河岸”)
BERT Embedding(上下文相关嵌入):2018 年提出,2026 年已成为标准。
- 核心思想:不是每个词一个固定向量,而是根据上下文动态生成向量——“我在银行取钱”中的”银行”和”我坐在河岸上”中的”河岸”会产生不同的向量
- 使用方式:通常不需要自己训练 BERT,直接用预训练模型(
bert-base-uncased、sentence-transformers等)来提取 Embedding - 输出:每个 token 产生一个向量(通常 768 维),整句话的向量可以通过取 CLS token 的向量或对全部 token 取平均得到
- 适用场景:语义搜索、文本聚类、情感分析、任何需要”理解语义”的 NLP 任务
- 实现:
sentence-transformers库,一行代码model.encode("今天天气真好")得到一个 768 维向量
2026 年文本特征提取的实践建议:
- 简单任务(关键词匹配、基本文本分类):TF-IDF 仍然有效,速度快,可解释性强
- 语义理解任务(相似度、聚类、搜索):用 sentence-transformers 提取 BERT Embedding
- 需要最高质量:用 GPT-4 或 Claude 的 API 提取 Embedding(如
text-embedding-3-large,维度可达 3072)
5.7.4 时间特征处理
时间数据包含丰富的信息,但原始的时间戳(如 2024-03-15 14:30:00)对模型无用。需要从中提取出有意义的特征。
基础时间特征:
- 年、月、日、星期几(1-7)、小时、分钟
- 是否是周末(0/1)、是否是节假日(0/1)
- 一天的哪个时段:凌晨(0-6)、上午(6-12)、下午(12-18)、晚上(18-24)
周期性编码:小时和月份有周期性——23 点和 0 点只差 1 小时,但数值上差 23。直接用数值会丢失这个信息。
hour_sin = sin(2π × hour / 24)和hour_cos = cos(2π × hour / 24)month_sin = sin(2π × month / 12)和month_cos = cos(2π × month / 12)- 这样 23 点和 0 点的 sin/cos 值非常接近,模型能学到”它们很接近”
滞后特征(Lag Features):用于时间序列预测。把过去的值作为特征。
- 预测明天的温度,今天的温度是 lag_1,昨天的温度是 lag_2
- 窗口统计:过去 7 天的均值、最大值、最小值、标准差
滚动窗口特征:不是固定滞后,而是计算过去 N 天窗口内的统计量。如过去 7 天的平均销售额、过去 30 天的最大股价。
5.7.5 地理特征处理
地理特征(经纬度、地址)需要特殊处理,因为两个经纬度之间的欧氏距离不等于实际距离。
直接使用经纬度:树模型(XGBoost、随机森林)可以直接用经纬度作为两个数值特征,模型会自动学到”这个区域的人行为不同”。
地理聚类:用 K-Means 对经纬度聚类,把每个样本分配到最近的聚类中心。聚类 ID 作为类别特征。
距离计算:用 Haversine 公式计算两个经纬度之间的球面距离(公里)。geopy.distance.distance((lat1, lon1), (lat2, lon2)).km。
Geohash:把经纬度编码成一个字符串,不同精度的 Geohash 代表不同大小的区域。Geohash 的前缀相同 = 两个点在同一区域。
5.7.6 特征交叉
特征交叉(Feature Crossing)是把两个或多个特征组合起来创造新特征。这是高阶特征工程的核心技术。
直觉:单个特征可能信息量有限,但组合起来可能产生强大的预测能力。
- “年龄” + “性别” → “35 岁男性”和”35 岁女性”的消费习惯完全不同
- “城市” + “职业” → “北京的程序员”和”上海的渔民”的生活方式完全不同
- “时间” + “地点” → “周五晚上的酒吧街”和”周一早上的办公区”的人流量完全不同
实现方式:
- 手动交叉:
df['age_gender'] = df['age_bin'].astype(str) + '_' + df['gender'],然后对结果做 One-Hot 或 Target 编码 - 多项式特征:
PolynomialFeaturesin Scikit-learn,自动生成特征的乘积和平方项。如两个特征 (a, b) → (1, a, b, a^2, ab, b^2) - 决策树自动交叉:树模型自动学会特征交叉——树的每个分支就是”满足条件 A 且满足条件 B”的交叉
注意:特征交叉会导致维度爆炸(n 个类别特征两两交叉 = n^2 个组合)。需要用特征选择来筛选有用的交叉特征,或者用 FM(因子分解机)和 FFM(场感知因子分解机)来隐式学习特征交叉。
5.7.7 特征选择
不是特征越多越好。无关特征 = 噪声,冗余特征 = 浪费计算。特征选择的目标是保留最有信息量的特征。
Filter 方法(过滤法):根据统计指标筛选特征,与模型无关。
- 方差阈值:删除方差极低(接近 0)的特征——如果一个特征在所有样本上几乎不变,它对预测没有帮助。如”是否有头发”这个特征对于”全是光头”的数据集没有任何信息量
- 相关系数:计算每个特征与目标变量的相关系数(Pearson 用于连续变量),保留相关系数绝对值大的特征
- 卡方检验:用于类别特征——检验特征和目标变量是否独立。不独立 = 特征有用
- 互信息:衡量特征和目标变量之间的依赖关系,能捕捉非线性关系。
mutual_info_classif和mutual_info_regressionin Scikit-learn
Wrapper 方法(包裹法):用模型性能来评估特征子集的质量。
- 前向选择:从空集开始,每次添加一个对模型改进最大的特征,直到模型性能不再提升
- 后向消除:从全量特征开始,每次删除一个对模型影响最小的特征
- 递归特征消除(RFE):训练模型,删除最不重要的特征(特征权重最小),重新训练,重复直到达到目标特征数
- 优点:考虑特征之间的交互,选出的特征子集质量高
- 缺点:计算量大(需要多次训练模型),不适合特征数量很大的场景
Embedded 方法(嵌入法):特征选择与模型训练同时进行。
- L1 正则化(Lasso):训练过程中,不重要的特征权重自动归零——自动做特征选择
- 树模型的特征重要性:随机森林和 XGBoost 训练后,可以查看
feature_importances_——每个特征在树的分裂中贡献了多少。特征重要性低的特征可以考虑删除 - 排列重要性(Permutation Importance):打乱某个特征的值,看模型性能下降多少。下降越多 = 特征越重要。这比树模型自带的特征重要性更可靠,因为它不依赖模型内部机制
- SHAP 值:基于博弈论的 Shapley 值,为每个特征分配”贡献值”。SHAP 不仅能告诉你特征重不重要,还能告诉你”这个特征值越大,预测值越大还是越小”
2026 年特征选择的实践建议:
- 特征数量 < 100:用模型的特征重要性(Embedded)做初筛,然后用交叉验证验证
- 特征数量 100-1000:先 Filter(方差阈值 + 互信息)筛掉明显无用的特征,再用 Embedded
- 特征数量 > 1000:Filter + L1 正则化(Lasso),或使用 AutoML 的自动特征选择
- 深度学习(图像、文本):特征选择通常不需要——CNN 和 Transformer 自动学习特征
← 上一节:5.6 交叉验证完全指南 | 下一节:5.8 2026 年:AutoML 和自动化特征工程 →