跳转至

特征工程实践

概述

特征工程是将原始数据转化为模型可用特征的过程,是机器学习项目中对模型性能影响最大的环节之一。本文涵盖特征类型、编码方法、缩放、特征选择和特征提取。


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 预处理文档

评论 #