4. 特征工程基础:特征选择与特征提取

好,咱们进入第四章。说实话,特征工程这块,是我在污染源识别项目里花时间最多的地方。你想想看,原始数据拿过来,几百个特征,有的有用,有的纯粹是噪声。如果不做处理,模型再牛也白搭。

这一章咱们重点聊两个事:特征选择特征提取。前者是“挑好的”,后者是“造新的”。我习惯把特征选择比作筛沙子,把特征提取比作炼钢——各有各的用处。

4.1 特征选择:从源头减负

特征选择,说白了就是去掉那些不重要的、冗余的特征。为什么要做?三个原因:

  • 防止过拟合:特征太多,模型容易记住噪声
  • 提升效率:少算几个特征,训练快很多
  • 增强可解释性:留下的特征少,容易跟业务方解释

特征选择有三种主流方法:过滤法、包裹法、嵌入法。我一个个说。

4.1.1 过滤法(Filter Method)

过滤法是最简单粗暴的。它不依赖任何模型,直接看特征和目标变量之间的“关系”。

常用的指标有:

  • 方差阈值:方差太小的特征,基本没变化,留着干嘛?
  • 皮尔逊相关系数:看特征和目标之间的线性关系
  • 卡方检验:适合分类问题,看特征和类别是否独立
  • 互信息:能捕捉非线性关系,我比较喜欢用这个

我个人的习惯:先用方差阈值筛掉那些“死气沉沉”的特征,再用互信息排个序,取前K个。这样速度快,效果也还行。

代码示例:

from sklearn.feature_selection import VarianceThreshold, SelectKBest, mutual_info_regression
import pandas as pd

# 假设 X 是特征矩阵,y 是目标变量
# 第一步:去掉方差小于0.01的特征
selector_var = VarianceThreshold(threshold=0.01)
X_var = selector_var.fit_transform(X)

# 第二步:用互信息选前20个特征
selector_mi = SelectKBest(score_func=mutual_info_regression, k=20)
X_selected = selector_mi.fit_transform(X_var, y)

# 看看选了哪些特征
selected_indices = selector_mi.get_support(indices=True)
print(f"选中的特征索引: {selected_indices}")

避坑指南:我曾经在某个项目里直接用皮尔逊系数选特征,结果模型效果很差。后来发现,污染源浓度和某些传感器读数之间是明显的非线性关系,皮尔逊系数根本抓不住。所以,互信息或者基于树模型的重要性排序,往往更靠谱

4.1.2 包裹法(Wrapper Method)

包裹法就“暴力”多了。它把特征选择当成一个搜索问题,用模型的表现来评价特征子集的好坏。

常见的有:

  • 递归特征消除(RFE):反复训练模型,每次去掉最不重要的特征
  • 前向选择:从空集开始,每次加一个最好的特征
  • 后向消除:从全集开始,每次去掉一个最差的特征

包裹法的优点是效果好,缺点是慢。特征一多,计算量指数级增长。

from sklearn.feature_selection import RFE
from sklearn.ensemble import RandomForestRegressor

# 用随机森林做包裹法
estimator = RandomForestRegressor(n_estimators=100, random_state=42)
selector_rfe = RFE(estimator, n_features_to_select=15, step=1)
selector_rfe.fit(X, y)

# 查看哪些特征被选中
print(f"选中的特征: {selector_rfe.support_}")
print(f"特征排名: {selector_rfe.ranking_}")

注意:包裹法容易过拟合,尤其是在特征多、样本少的情况下。我建议你先用过滤法粗筛一遍,再用包裹法精挑,这样既快又准。

4.1.3 嵌入法(Embedded Method)

嵌入法是我最常用的方法。它把特征选择直接嵌入到模型训练过程中,一举两得。

典型的代表:

  • Lasso回归(L1正则化):自动把不重要的特征系数压成0
  • 树模型的特征重要性:随机森林、XGBoost都能输出特征重要性排序

随机森林本身就自带特征重要性评估,用起来特别方便。

from sklearn.ensemble import RandomForestRegressor
import numpy as np

# 训练随机森林
rf = RandomForestRegressor(n_estimators=200, random_state=42)
rf.fit(X, y)

# 获取特征重要性
importances = rf.feature_importances_
indices = np.argsort(importances)[::-1]

# 打印前10个最重要的特征
print("特征重要性排名:")
for i in range(10):
    print(f"{i+1}. 特征 {indices[i]}: {importances[indices[i]]:.4f}")

我的经验:在污染源识别项目中,我通常用随机森林的特征重要性做第一轮筛选,保留累计重要性达到95%的特征。然后再用包裹法微调一下。这样既保留了模型的可解释性,又保证了性能。

4.2 特征提取:降维的艺术

特征选择和特征提取的区别在哪?我打个比方:

  • 特征选择:从一堆苹果里挑出最好的几个
  • 特征提取:把一堆苹果榨成苹果汁,提取精华

特征提取最经典的方法就是主成分分析(PCA)

4.2.1 PCA的原理

PCA的核心思想很简单:找到数据方差最大的方向,把数据投影上去。说白了,就是在损失最少信息的前提下,降低维度

具体步骤:

  1. 对数据做中心化(减去均值)
  2. 计算协方差矩阵
  3. 求协方差矩阵的特征值和特征向量
  4. 按特征值大小排序,取前k个特征向量作为主成分

嗯,这里要注意:PCA对特征的尺度非常敏感。如果某个特征的数值范围特别大,它会主导主成分的方向。所以,做PCA之前一定要先标准化

from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

# 先标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 做PCA,保留95%的方差
pca = PCA(n_components=0.95)
X_pca = pca.fit_transform(X_scaled)

print(f"原始特征数: {X.shape[1]}")
print(f"降维后特征数: {X_pca.shape[1]}")
print(f"各主成分方差解释比例: {pca.explained_variance_ratio_}")

小技巧:我一般会画一个“累积方差解释率”的图,看看取多少个主成分比较合适。通常保留80%-95%的方差就够了,再多的话边际收益不大。

4.2.2 PCA在污染源识别中的应用

在污染源识别中,PCA特别有用。为什么呢?因为传感器数据往往高度相关——比如PM2.5和PM10的浓度变化趋势很相似。PCA可以把这些冗余信息压缩成几个综合指标。

我记得有一次,项目里用了50多个传感器特征,直接建模效果很差。用PCA降到10个主成分后,模型不仅训练快了,准确率还提升了5%。

但是要注意:PCA后的主成分是原始特征的线性组合,可解释性会变差。如果你需要跟环保局的人解释“为什么这个主成分代表工业污染源”,那可能有点费劲。这种情况下,我建议优先用特征选择,保留原始特征的含义。

4.3 本章知识体系

为了让你更直观地理解,我画了一张图:

特征工程知识体系 特征工程 特征选择 特征提取 过滤法 包裹法 嵌入法 PCA 其他方法 方差阈值 相关系数 互信息 RFE 前向选择 后向消除 Lasso 树模型重要性 标准化 协方差矩阵 特征值分解

4.4 总结与建议

好了,这一章的内容就这些。我最后给你几个实操建议:

  • 先做特征选择,再做特征提取:顺序别搞反了。先去掉噪声,再降维,效果更好。
  • 根据场景选方法:如果追求可解释性,用过滤法或嵌入法;如果追求极致性能,用包裹法。
  • 别忘了交叉验证:特征选择也是在“偷看”数据,一定要在交叉验证的框架下做,防止信息泄露。

最后说一句:特征工程没有银弹。我在不同项目里试过各种组合,最后发现,理解业务、理解数据,比单纯堆算法重要得多。多跟环保专家聊聊,搞清楚哪些传感器数据是关键的,往往比跑十遍PCA都管用。


专注资料整理