特征工程实践
概述
特征工程是将原始数据转化为模型可用特征的过程,是机器学习项目中对模型性能影响最大的环节之一。本文涵盖特征类型、编码方法、缩放、特征选择和特征提取。
1. 特征类型
| 类型 | 示例 | 处理方式 |
|---|---|---|
| 数值型 | 年龄、收入、温度 | 缩放、分箱、交互 |
| 类别型 | 城市、学历、颜色 | 编码(one-hot, label) |
| 文本型 | 评论、标题 | TF-IDF, 词嵌入 |
| 时间型 | 日期、时间戳 | 提取年/月/日/星期/小时 |
| 序数型 | 评级(1-5星) | 保持顺序的编码 |
2. 类别特征编码
2.1 One-Hot 编码
import pandas as pd
from sklearn.preprocessing import OneHotEncoder
# pandas
df_encoded = pd.get_dummies(df, columns=["city"], drop_first=True)
# sklearn
ohe = OneHotEncoder(drop='first', sparse_output=False)
encoded = ohe.fit_transform(df[["city"]])
适用:类别数较少(< 20),无序类别。
问题:高基数类别会产生维度爆炸。
2.2 Label 编码
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
df["city_encoded"] = le.fit_transform(df["city"])
适用:有序类别(如学历:小学 < 中学 < 大学),或树模型。
2.3 Target 编码
用目标变量的统计量替代类别:
# 使用类别的目标均值
target_means = df.groupby("city")["target"].mean()
df["city_target_enc"] = df["city"].map(target_means)
# 添加平滑,防止小样本类别过拟合
def target_encode_smoothed(df, col, target, alpha=10):
global_mean = df[target].mean()
stats = df.groupby(col)[target].agg(['mean', 'count'])
smoothed = (stats['count'] * stats['mean'] + alpha * global_mean) / (stats['count'] + alpha)
return df[col].map(smoothed)
适用:高基数类别特征。注意交叉验证时需要在每折内计算。
2.4 频率编码
freq = df["city"].value_counts(normalize=True)
df["city_freq"] = df["city"].map(freq)
2.5 编码方法选择
| 方法 | 类别数 | 模型类型 | 信息保留 |
|---|---|---|---|
| One-Hot | 低 (< 20) | 线性模型 | 无序信息 |
| Label | 有序 | 树模型 | 顺序信息 |
| Target | 高 | 所有 | 与目标关系 |
| 频率 | 高 | 所有 | 分布信息 |
| 嵌入 | 极高 | 深度学习 | 学习表示 |
3. 数值特征缩放
3.1 标准化(Standardization)
\[
x' = \frac{x - \mu}{\sigma}
\]
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
适用:假设数据近似正态分布的模型(线性回归、SVM、神经网络)。
3.2 最小-最大缩放(Min-Max Scaling)
\[
x' = \frac{x - x_{\min}}{x_{\max} - x_{\min}}
\]
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
X_scaled = scaler.fit_transform(X)
适用:需要特定范围(如 [0, 1])的模型(神经网络、图像像素)。
3.3 鲁棒缩放(Robust Scaling)
\[
x' = \frac{x - Q_2}{Q_3 - Q_1}
\]
from sklearn.preprocessing import RobustScaler
scaler = RobustScaler()
X_scaled = scaler.fit_transform(X)
适用:存在异常值时(使用中位数和 IQR 而非均值和标准差)。
3.4 对数变换
# 处理右偏分布
df["log_income"] = np.log1p(df["income"]) # log(1+x) 处理0值
# Box-Cox 变换(自动找最佳变换)
from scipy.stats import boxcox
df["income_bc"], lambda_opt = boxcox(df["income"] + 1)
4. 特征选择
4.1 过滤法(Filter)
基于统计指标独立评估每个特征:
from sklearn.feature_selection import mutual_info_classif, SelectKBest, f_classif
# 互信息
mi_scores = mutual_info_classif(X, y)
mi_df = pd.DataFrame({"feature": X.columns, "MI": mi_scores}).sort_values("MI", ascending=False)
# F-test
selector = SelectKBest(f_classif, k=20)
X_selected = selector.fit_transform(X, y)
selected_features = X.columns[selector.get_support()]
| 方法 | 适用 | 衡量 |
|---|---|---|
| 互信息 (MI) | 分类/回归 | 非线性依赖 |
| F-test | 分类 | 线性关系 |
| 卡方检验 | 分类 + 类别特征 | 独立性 |
| 方差 | 任意 | 信息量(低方差 = 无用) |
4.2 包裹法(Wrapper)
from sklearn.feature_selection import RFE
from sklearn.ensemble import RandomForestClassifier
# 递归特征消除
rfe = RFE(
estimator=RandomForestClassifier(n_estimators=100),
n_features_to_select=20,
step=5
)
rfe.fit(X, y)
selected = X.columns[rfe.support_]
4.3 嵌入法(Embedded)
利用模型自身的特征重要性:
# L1 正则化(自动特征选择)
from sklearn.linear_model import LassoCV
lasso = LassoCV(cv=5)
lasso.fit(X, y)
selected = X.columns[lasso.coef_ != 0]
# 树模型特征重要性
from sklearn.ensemble import GradientBoostingClassifier
model = GradientBoostingClassifier().fit(X, y)
importances = pd.Series(model.feature_importances_, index=X.columns)
top_features = importances.nlargest(20).index
5. 特征提取
5.1 PCA
from sklearn.decomposition import PCA
pca = PCA(n_components=0.95) # 保留 95% 方差
X_pca = pca.fit_transform(X_scaled)
print(f"原始维度: {X.shape[1]}, PCA 后: {X_pca.shape[1]}")
5.2 自编码器
import torch.nn as nn
class Autoencoder(nn.Module):
def __init__(self, input_dim, latent_dim):
super().__init__()
self.encoder = nn.Sequential(
nn.Linear(input_dim, 128),
nn.ReLU(),
nn.Linear(128, latent_dim),
)
self.decoder = nn.Sequential(
nn.Linear(latent_dim, 128),
nn.ReLU(),
nn.Linear(128, input_dim),
)
def forward(self, x):
z = self.encoder(x)
return self.decoder(z), z
6. 特征构造
6.1 数值交互
# 多项式特征
from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures(degree=2, interaction_only=True, include_bias=False)
X_poly = poly.fit_transform(X[["age", "income"]])
# 产生: age, income, age*income
# 手动构造
df["income_per_age"] = df["income"] / (df["age"] + 1)
df["bmi"] = df["weight"] / (df["height"] / 100) ** 2
6.2 时间特征
df["year"] = df["date"].dt.year
df["month"] = df["date"].dt.month
df["day_of_week"] = df["date"].dt.dayofweek
df["hour"] = df["date"].dt.hour
df["is_weekend"] = df["day_of_week"].isin([5, 6]).astype(int)
# 周期性编码(避免 12月 和 1月 被视为很远)
df["month_sin"] = np.sin(2 * np.pi * df["month"] / 12)
df["month_cos"] = np.cos(2 * np.pi * df["month"] / 12)
6.3 分组统计
# 用户级聚合特征
user_stats = df.groupby("user_id").agg({
"amount": ["mean", "sum", "count", "std"],
"category": "nunique",
"date": ["min", "max"]
}).reset_index()
user_stats.columns = ["_".join(col).strip("_") for col in user_stats.columns]
7. AutoML 特征工程
| 工具 | 功能 |
|---|---|
| Featuretools | 自动深度特征合成 |
| tsfresh | 时间序列特征自动提取 |
| AutoFeat | 自动特征构造 + 选择 |
| FLAML / AutoGluon | 端到端 AutoML |
# Featuretools 示例
import featuretools as ft
es = ft.EntitySet(id="data")
es.add_dataframe(dataframe=df, dataframe_name="orders", index="order_id")
feature_matrix, features = ft.dfs(
entityset=es,
target_dataframe_name="orders",
max_depth=2,
)
参考资料
- "Feature Engineering for Machine Learning" - Zheng & Casari
- "Feature Engineering and Selection" - Kuhn & Johnson
- scikit-learn 预处理文档