特征工程基础:特征构建、特征变换与特征编码
大家好,我是你们的老朋友。今天咱们聊聊特征工程里最基础、也最实用的三个操作:特征构建、特征变换和特征编码。说实话,很多刚入行的朋友总觉得模型调参才是王道,其实不然。我做了这么多年环境数据分析,发现数据本身的质量和特征的设计,往往比模型选择更重要。你想想看,垃圾数据喂进去,再牛的模型也白搭。
一、特征构建:让数据自己“说话”
特征构建,说白了就是创造新的特征。原始数据往往不够用,我们需要从现有特征中衍生出更有价值的信息。我个人习惯把特征构建分成两类:多项式特征和交互特征。
1. 多项式特征
多项式特征就是给现有特征加上平方、立方等非线性项。为什么要这么做?因为很多环境数据的关系不是线性的。比如气温和臭氧浓度,它们的关系就是典型的非线性——温度太高或太低,臭氧浓度都会下降。
核心思路: 用多项式特征捕捉非线性关系,但别过度。我曾经在一个项目中,把特征从3个扩展到了20个多项式特征,结果模型过拟合得一塌糊涂。
# 多项式特征构建示例
from sklearn.preprocessing import PolynomialFeatures
import numpy as np
# 假设我们有温度和湿度两个特征
X = np.array([[25, 60], [30, 55], [20, 70]])
poly = PolynomialFeatures(degree=2, include_bias=False)
X_poly = poly.fit_transform(X)
print("原始特征:", X)
print("多项式特征:", X_poly)
# 输出: 原始特征: [[25 60] [30 55] [20 70]]
# 多项式特征: [[25. 60. 625. 1500. 3600.]
# [30. 55. 900. 1650. 3025.]
# [20. 70. 400. 1400. 4900.]]
我的经验: degree参数别设太大,一般2或3就够了。环境数据噪声多,高阶多项式容易把噪声也学进去。
2. 交互特征
交互特征就是两个或多个特征的乘积。比如PM2.5浓度和风速的交互作用——风速大时,PM2.5扩散快,浓度低;风速小时,PM2.5容易堆积。单独看每个特征,可能看不出这种关系。
# 交互特征构建
from sklearn.preprocessing import PolynomialFeatures
# 只生成交互特征,不生成多项式
interaction = PolynomialFeatures(degree=2, interaction_only=True, include_bias=False)
X_inter = interaction.fit_transform(X)
print("交互特征:", X_inter)
# 输出: 交互特征: [[25. 60. 1500.]
# [30. 55. 1650.]
# [20. 70. 1400.]]
注意: 交互特征的数量会随着原始特征数量呈指数增长。100个特征做两两交互,就会产生4950个新特征。我建议先用领域知识筛选,别盲目生成。
二、特征变换:让数据更“听话”
特征变换的目的是改变数据的分布形态。很多模型(比如线性回归、SVM)都假设数据服从正态分布。但环境数据往往偏态严重——比如污染物浓度,大部分时间很低,偶尔爆表。这时候就需要变换。
1. 对数变换
对数变换是最常用的变换方法。它能把右偏的数据拉近正态分布。我记得有一次做空气质量预测,PM10的原始数据偏态系数高达5.8,取对数后降到了0.3左右,模型效果直接提升了15%。
import numpy as np
import matplotlib.pyplot as plt
# 模拟偏态数据
data = np.random.exponential(scale=2, size=1000)
log_data = np.log(data + 1) # +1 避免log(0)
print("原始数据偏度:", np.round(np.mean((data - np.mean(data))**3) / np.std(data)**3, 2))
print("变换后偏度:", np.round(np.mean((log_data - np.mean(log_data))**3) / np.std(log_data)**3, 2))
避坑指南: 我曾经直接对含零的数据取log,结果一堆-inf。后来养成了习惯,先加一个小常数(比如1或0.001),再取对数。
2. Box-Cox变换
Box-Cox变换是对数变换的“升级版”。它引入了一个参数λ,可以自动寻找最优的变换方式。当λ=0时,等价于对数变换;λ=1时,等价于不做变换。
from scipy import stats
# Box-Cox变换
boxcox_data, lambda_opt = stats.boxcox(data + 1) # 同样需要数据为正
print(f"最优λ值: {lambda_opt:.3f}")
print(f"变换后偏度: {np.round(np.mean((boxcox_data - np.mean(boxcox_data))**3) / np.std(boxcox_data)**3, 2)}")
关键点: Box-Cox变换要求数据全部为正数。如果数据有负值,可以考虑Yeo-Johnson变换,它不需要正数限制。
三、特征编码:让机器“看懂”类别
环境数据里有很多类别特征,比如天气状况(晴、阴、雨)、风向(东、南、西、北)、监测站点编号等。机器只能处理数值,所以我们需要编码。
1. 独热编码
独热编码把每个类别变成一个独立的二进制特征。比如“风向”有4个类别,就变成4个特征:is_东、is_南、is_西、is_北。每个样本只有一个位置是1,其余都是0。
from sklearn.preprocessing import OneHotEncoder
import pandas as pd
# 示例数据
weather = pd.DataFrame({'天气': ['晴', '阴', '雨', '晴', '雪']})
encoder = OneHotEncoder(sparse_output=False)
encoded = encoder.fit_transform(weather)
print("原始数据:\n", weather)
print("独热编码结果:\n", encoded)
print("类别顺序:", encoder.categories_)
注意: 独热编码会大幅增加特征维度。如果某个类别有100个取值,就会增加100个特征。我建议对取值超过20个的类别特征,先做频次编码或目标编码。
2. 标签编码
标签编码就是把类别映射成整数。比如“晴→0,阴→1,雨→2,雪→3”。这种方法简单,但会引入大小关系——机器会认为“雪(3) > 雨(2)”,这往往不合理。
from sklearn.preprocessing import LabelEncoder
label_encoder = LabelEncoder()
label_encoded = label_encoder.fit_transform(weather['天气'])
print("标签编码结果:", label_encoded)
print("类别映射:", dict(zip(label_encoder.classes_, range(len(label_encoder.classes_)))))
我的建议: 对于有序类别(比如“优、良、轻度污染、中度污染”),标签编码是合适的。对于无序类别(比如站点编号),用独热编码或目标编码更稳妥。
四、知识体系总览
下面这张图是我自己整理的,把今天讲的内容串了起来。你可以把它当作一个快速参考。
好了,今天的内容就到这里。特征工程这块,说白了就是“懂数据、会动手、多尝试”。没有万能的方法,只有最适合你当前数据的方案。希望这些经验能帮你少走弯路。