4. 特征工程:环境数据的特征提取、选择与缩放
各位同学好,我是老张。今天咱们聊聊特征工程——这个环节,说实在的,比调模型参数重要得多。我在环境监测项目里踩过的坑,十有八九都跟特征没处理好有关。
你想想看,原始数据就像刚从河里捞上来的鱼,又腥又乱。温度、湿度、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
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()
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)
4.4 实战经验总结
做了这么多年环境数据项目,我总结了几条铁律:
- 特征提取要「先广后精」。先尽可能多地生成候选特征,再用选择方法筛选。别一开始就想着「这个特征可能没用」——你永远不知道哪个特征会带来惊喜。
- 特征选择要「交叉验证」。别只看一次结果。我习惯用5折交叉验证,看特征重要性是否稳定。有些特征在某一折上表现很好,换一折就拉胯——这种特征要小心。
- 特征缩放要「按组处理」。温度、湿度这类自然属性用标准化;PM2.5、CO这类污染物浓度,我建议先取对数再标准化。风速比较特殊,它本身有物理意义(0表示无风),归一化可能更合适。
嗯,特征工程就讲到这里。记住一句话:好的特征工程,能让简单模型跑出复杂模型的效果;糟糕的特征工程,能让深度学习模型也翻车。我在项目里见过太多次了。
公众号:蓝海资料掘金营,微信deep3321