4. 特征工程:环境数据的特征提取、选择与缩放

各位同学好,我是老张。今天咱们聊聊特征工程——这个环节,说实在的,比调模型参数重要得多。我在环境监测项目里踩过的坑,十有八九都跟特征没处理好有关。

你想想看,原始数据就像刚从河里捞上来的鱼,又腥又乱。温度、湿度、PM2.5、风速……这些数据直接扔进模型,效果往往很差。为什么?因为模型看不懂这些原始数值背后的规律。特征工程,就是帮模型「翻译」数据的过程。

特征工程核心流程 原始环境数据 温度/湿度/PM2.5/风速 特征提取 统计量/时序/交互特征 特征选择 过滤/包裹/嵌入法 特征缩放 标准化/归一化/鲁棒缩放 高质量特征集

4.1 特征提取:从原始数据中挖出金子

特征提取,说白了就是「无中生有」。原始数据里只有温度、湿度、PM2.5、风速这四个字段,但我们可以变出几十个有用特征。

我个人的习惯是,先做统计特征。比如过去1小时的平均温度、过去3小时的湿度变化率、PM2.5的峰值。这些统计量能反映环境变化的趋势,而不是某个瞬间的值。

💡 核心思路: 环境数据是时序数据,时间窗口内的统计量比单点值更有预测价值。

举个例子。我在做空气质量预测项目时,发现只用当前PM2.5值预测未来1小时,准确率只有65%。但加入「过去3小时PM2.5变化率」和「过去1小时最大风速」后,准确率直接跳到82%。

交互特征也很重要。温度和湿度单独看,信息有限。但「温度×湿度」这个交互特征,能反映体感舒适度,对预测空调负荷特别有用。风速和PM2.5的比值,能判断污染物是本地源还是外来输送。

# 特征提取示例代码
import pandas as pd
import numpy as np

def extract_features(df):
    # 统计特征
    df['temp_mean_1h'] = df['temperature'].rolling(6).mean()  # 10分钟粒度,6个点=1小时
    df['humid_std_3h'] = df['humidity'].rolling(18).std()
    df['pm25_max_1h'] = df['pm25'].rolling(6).max()
    
    # 交互特征
    df['temp_humid_interact'] = df['temperature'] * df['humidity']
    df['wind_pm25_ratio'] = df['wind_speed'] / (df['pm25'] + 0.001)  # 避免除零
    
    # 时序差分特征
    df['temp_diff_1h'] = df['temperature'].diff(6)
    df['pm25_diff_30min'] = df['pm25'].diff(3)
    
    return df
🔧 小技巧: 做差分特征时,注意时间窗口要跟数据采样频率匹配。我见过有人用10分钟数据做1小时差分,结果窗口设成60——那是60个点,不是60分钟。

4.2 特征选择:别让垃圾特征拖后腿

特征提取完后,你可能搞出几十个特征。但特征不是越多越好。你想想看,模型就像一个人,你给他太多信息,他反而抓不住重点。

我常用的特征选择方法有三种:

方法 原理 适用场景
过滤法 计算特征与目标的相关性 快速筛选,初步降维
包裹法 用模型评估特征子集 特征数较少时效果好
嵌入法 模型训练时自动选择特征 树模型、L1正则化

过滤法最简单。计算每个特征与目标变量的皮尔逊相关系数,保留相关性高的。但要注意,相关系数只捕捉线性关系。我曾经用这个方法筛掉了一个特征,后来发现它跟目标是非线性关系——模型加上它后效果反而更好。

嵌入法我比较推荐。用随机森林或XGBoost训练一次,看特征重要性排序。重要性低于某个阈值的,直接扔掉。这样做的好处是,模型自己告诉你哪些特征有用。

# 特征选择示例:基于随机森林的特征重要性
from sklearn.ensemble import RandomForestRegressor

model = RandomForestRegressor(n_estimators=100, random_state=42)
model.fit(X_train, y_train)

# 获取特征重要性
importance = pd.DataFrame({
    'feature': feature_names,
    'importance': model.feature_importances_
}).sort_values('importance', ascending=False)

# 保留重要性累计占比前90%的特征
cumsum = importance['importance'].cumsum()
keep_features = importance[cumsum <= 0.9]['feature'].tolist()
⚠️ 避坑指南: 我曾经在PM2.5预测项目中,用过滤法选了10个相关性最高的特征。结果模型过拟合严重——因为这些特征之间高度相关(多重共线性)。后来我加了VIF(方差膨胀因子)检查,才把问题解决。

4.3 特征缩放:让所有特征站在同一起跑线

温度范围是-10到40,湿度是0到100,PM2.5是0到500,风速是0到30。这些数值的量级差太多了。如果不做缩放,模型会默认数值大的特征更重要——这显然是错的。

常用的缩放方法有三种:

  • 标准化(Z-score): 减去均值,除以标准差。处理后均值为0,标准差为1。适合数据近似正态分布的情况。
  • 归一化(Min-Max): 缩放到[0,1]区间。适合数据有明确边界的情况,比如湿度0-100。
  • 鲁棒缩放(RobustScaler): 用中位数和四分位距。适合数据有异常值的情况。

我个人习惯用标准化。为什么?因为环境数据里经常有异常值——传感器故障、极端天气等。归一化会被异常值拉偏,而标准化相对稳健一些。

但要注意,如果数据本身不是正态分布,标准化后效果可能不好。比如PM2.5的分布通常是右偏的(大部分时间浓度低,偶尔爆表)。这时候可以先做对数变换,再标准化。

# 特征缩放示例
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler

# 标准化(推荐默认使用)
scaler_std = StandardScaler()
X_train_scaled = scaler_std.fit_transform(X_train)
X_test_scaled = scaler_std.transform(X_test)

# 归一化(适合有明确边界的特征)
scaler_mm = MinMaxScaler()
X_train_mm = scaler_mm.fit_transform(X_train)

# 鲁棒缩放(适合有异常值的数据)
scaler_robust = RobustScaler()
X_train_robust = scaler_robust.fit_transform(X_train)
🔧 小技巧: 缩放时一定要先fit训练集,再用同样的scaler去transform测试集。千万别把训练集和测试集混在一起fit——那叫数据泄露,模型评估结果会虚高。

4.4 实战经验总结

做了这么多年环境数据项目,我总结了几条铁律:

  1. 特征提取要「先广后精」。先尽可能多地生成候选特征,再用选择方法筛选。别一开始就想着「这个特征可能没用」——你永远不知道哪个特征会带来惊喜。
  2. 特征选择要「交叉验证」。别只看一次结果。我习惯用5折交叉验证,看特征重要性是否稳定。有些特征在某一折上表现很好,换一折就拉胯——这种特征要小心。
  3. 特征缩放要「按组处理」。温度、湿度这类自然属性用标准化;PM2.5、CO这类污染物浓度,我建议先取对数再标准化。风速比较特殊,它本身有物理意义(0表示无风),归一化可能更合适。

嗯,特征工程就讲到这里。记住一句话:好的特征工程,能让简单模型跑出复杂模型的效果;糟糕的特征工程,能让深度学习模型也翻车。我在项目里见过太多次了。


公众号:蓝海资料掘金营,微信deep3321