4. 特征工程:特征选择与特征构造

特征工程这词儿听起来挺大,说白了就是"让数据更好用"。我刚开始做分类项目时,总觉得算法选对了就万事大吉,结果模型跑出来准确率老是上不去。后来才发现,问题出在特征上,不是算法。

你想想看,给模型喂一堆乱七八糟的特征,它再厉害也很难学出好东西。这一章咱们就聊两件事:特征选择——把没用的特征砍掉;特征构造——造出更有力的新特征。

4.1 特征工程的核心价值

先问个问题:是不是特征越多越好?

当然不是。我见过不少项目,特征数量从几十个堆到上千个,模型效果反而变差了。为什么?冗余特征带来噪声,还会让模型过拟合。特征工程的目标就是:保留最有效的信息,去掉干扰,挖掘隐藏的关系。

核心观点 特征工程 = 特征选择(做减法)+ 特征构造(做加法)。两个方向缺一不可,顺序上我建议先做选择,再做构造。

4.2 特征选择——做减法

特征选择的目的很直接:从一堆特征里挑出真正有用的。方法有很多,这一节我们重点讲两个常用的——方差阈值和卡方检验。

4.2.1 方差阈值

方差阈值的思想特别朴素:如果一个特征的取值几乎不变,那它对分类基本没贡献。

举个例子,如果某个特征在所有样本里都是同一个值,或者只有极个别样本不同,那这个特征就没什么区分能力。方差阈值就是设定一个下限,方差低于这个值的特征直接扔掉。

我记得第一次用这个方法时,拿到的数据有200多个特征,跑完方差阈值一看,直接砍掉了将近一半。那些被删掉的特征里,很多都是用户ID、常量列之类的无用信息。

from sklearn.feature_selection import VarianceThreshold
import pandas as pd

# 假设 X_train 是训练数据,shape = (1000, 50)
selector = VarianceThreshold(threshold=0.01)
X_selected = selector.fit_transform(X_train)

# 看看哪些特征被保留了
保留的列 = selector.get_support(indices=True)
print(f"原始特征数: {X_train.shape[1]}")
print(f"保留特征数: {X_selected.shape[1]}")
小提示 threshold 的取值需要根据数据范围调整。如果特征是0-1标准化后的,0.01是个不错的起点。如果是原始数值,建议先归一化再计算方差。

4.2.2 卡方检验

卡方检验看的是特征与目标变量之间的相关性。它适合用在分类任务中,尤其是特征为离散值或经过分箱处理的情况。

简单理解:卡方值越大,说明特征与标签越"不独立",也就是越有用。

我在信贷评分项目中用过这个方法。当时有30多个用户行为特征,用卡方检验排序后,只取了前10个特征做训练,模型AUC反而提升了3个百分点。不相关的特征真的是在拖后腿。

from sklearn.feature_selection import SelectKBest, chi2
from sklearn.preprocessing import MinMaxScaler

# 卡方要求特征非负,先做归一化
scaler = MinMaxScaler()
X_scaled = scaler.fit_transform(X_train)

# 选择 top-20 特征
chi2_selector = SelectKBest(chi2, k=20)
X_kbest = chi2_selector.fit_transform(X_scaled, y_train)

# 查看每个特征的卡方分数
scores = chi2_selector.scores_
for i, score in enumerate(scores[:10]):
    print(f"特征 {i}: 卡方值 = {score:.2f}")
注意 卡方检验的输入特征必须是非负的。如果数据中有负数,先用 MinMaxScaler 或其他方法做归一化。我曾经在这个细节上吃过亏,直接跑 chi2 报错,排查了半天才发现是负数的问题。

4.3 特征构造——做加法

特征选择是去掉没用的,特征构造则是"造出"更有价值的新特征。这里讲两种经典方法:多项式特征和交互特征。

4.3.1 多项式特征

多项式特征就是把原始特征进行幂次组合。比如原始特征是 x₁ 和 x₂,多项式特征会生成 x₁²、x₂²、x₁·x₂ 等。

为什么要这么做?因为很多现实问题的决策边界不是线性的。加上多项式特征后,线性模型也能拟合非线性关系。

我个人习惯是先做一次多项式扩展,然后用正则化模型(比如 Lasso)自动筛选有用的项。这样既扩展了表达能力,又不会让维度爆炸。

from sklearn.preprocessing import PolynomialFeatures
import numpy as np

# 原始特征:两个特征
X_raw = np.array([[1, 2],
                  [3, 4],
                  [5, 6]])

# 生成 2 次多项式特征(包含交互项)
poly = PolynomialFeatures(degree=2, include_bias=False)
X_poly = poly.fit_transform(X_raw)

print("原始特征数:", X_raw.shape[1])
print("多项式特征数:", X_poly.shape[1])
print("特征名称:", poly.get_feature_names_out())
# 输出: ['x0', 'x1', 'x0^2', 'x0 x1', 'x1^2']

4.3.2 交互特征

交互特征是多项式特征的一种特例——只保留不同特征之间的乘积,不包含平方项。它的核心思想是:两个特征组合在一起,可能会产生1+1>2的效果。

举个具体的例子。在电商数据中,"点击次数"和"浏览时长"单独看都有用,但它们的乘积(点击次数 × 浏览时长)可能更能反映用户的真实兴趣强度。这就是交互特征的价值。

from sklearn.preprocessing import PolynomialFeatures

# 只生成交互特征,不生成平方项
interaction = PolynomialFeatures(degree=2,
                                  interaction_only=True,
                                  include_bias=False)
X_inter = interaction.fit_transform(X_raw)

print("交互特征名称:", interaction.get_feature_names_out())
# 输出: ['x0', 'x1', 'x0 x1']
实践经验 交互特征的数量会随着原始特征数平方增长。如果原始特征有50个,两两交互就是1225个新特征。建议先用特征选择砍掉一部分,再做交互构造,否则维度爆炸你扛不住。

4.4 如何选择:特征选择 vs 特征构造

很多同学会问:到底什么时候该做选择,什么时候该做构造?

我的经验是:先诊断,再行动。如果模型效果差是因为过拟合、噪声多,就优先做特征选择。如果模型欠拟合、表达力不够,就优先做特征构造。

下面这张表可以帮你快速决策:

维度 特征选择 特征构造
核心操作 砍掉无用特征 生成新特征
适用场景 特征多、噪声大、过拟合 特征少、欠拟合、线性太强
常用方法 方差阈值、卡方检验、Lasso 多项式、交互特征、分箱
风险 可能丢掉有用特征 维度爆炸、过拟合
推荐顺序 先做 后做
避坑提醒 我曾经在一个风控项目里先把特征从200个扩到5000个(做了3次多项式),然后直接扔给模型训练,结果内存爆了不说,模型效果还变差了。后来老老实实先做特征选择,保留30个核心特征,再做2次交互,最终效果远超之前。教训就是——不要上来就做特征构造,先砍再扩才是正道。
本章小结 特征选择和特征构造是特征工程的两条腿。选择帮你降低噪声、提升泛化;构造帮你挖掘关系、增强表达。两个方法配合使用,才能让模型真正"吃饱吃好"。
特征工程知识体系 特征工程 特征选择(做减法) 特征构造(做加法) 方差阈值 卡方检验 多项式特征 交互特征 核心思想 降低维度消除噪声 vs 挖掘特征间隐藏关系 过滤低方差特征 评估与标签相关性 生成幂次组合 生成特征乘积