查看全课大纲(第 19 / 35 节)

自动化特征工具

约 14 分钟

小象实战讲义 · 特征工程精讲与实践

在前面的课程中,我们学习了多种数据预处理和特征构建的方法,但很多步骤需要手动逐特征处理或组合操作。本节将介绍如何利用自动化工具来高效完成这些常规任务,提升特征工程的效率与效果。我们将通过一个实际案例,演示如何使用自动化特征工程工具处理数据,并对比处理前后的模型性能变化。

💡 核心导读

  • 了解自动化特征工程工具的价值与适用场景。
  • 掌握 automl_alex 库中 DataBunch 类的核心参数与功能。
  • 通过成年人收入数据集案例,实践自动化特征工程的完整流程。
  • 对比基线模型与自动化特征工程处理后的模型性能,理解其收益。

自动化特征工程的价值

在工业实践中,特征工程往往耗时且繁琐。虽然我们掌握了缺失值处理、异常值检测、编码、离散化、特征衍生等多种技术,但手动应用这些技术到成百上千个特征上效率低下。自动化特征工程工具旨在将那些模式固定、高频出现的特征处理流程自动化,让数据科学家能更专注于业务逻辑与高阶特征创新。

本节课我们将使用一个名为 automl_alex 的库。它本身定位为自动化机器学习(AutoML)工具,但其数据预处理和特征工程模块非常强大且独立,我们可以单独使用这部分功能。

案例实战:成年人收入预测

我们使用经典的“成年人收入”数据集(Adult Dataset),它源自1994年美国人口普查,任务是预测一个人的年收入是否超过5万美元。数据集包含48,842条记录,特征包括年龄、工作类别、教育程度、婚姻状况、职业等。

1. 环境准备与数据加载

首先,我们导入必要的库并加载数据。注意,我们使用 sklearn 的 fetch_openml 获取数据(固定使用经典版本 data_id=1590),并将其目标变量转换为数值编码(0表示收入≤50K,1表示收入>50K)。

import pandas as pd
import numpy as np
from sklearn.datasets import fetch_openml
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score

# 设置随机种子以确保结果可复现
RANDOM_SEED = 2022

# 加载成年人数据集(固定使用经典版本 data_id=1590)
dataset = fetch_openml(data_id=1590, as_frame=True)
# 将目标变量从字符串类别转换为数值编码 (<=50K -> 0, >50K -> 1)
dataset.target = dataset.target.astype('category').cat.codes

# 查看数据前5行
print("数据集前5行:")
print(dataset.data.head())
print(f"\n数据形状: {dataset.data.shape}")
print(f"目标变量分布:\n{dataset.target.value_counts(normalize=True).round(4)}")
数据集前5行:
   age  workclass  fnlwgt  ... capital-loss  hours-per-week native-country
0   25    Private  226802  ...            0              40  United-States
1   38    Private   89814  ...            0              50  United-States
2   28  Local-gov  336951  ...            0              40  United-States
3   44    Private  160323  ...            0              40  United-States
4   18        NaN  103497  ...            0              30  United-States

[5 rows x 14 columns]

数据形状: (48842, 14)
目标变量分布:
0    0.7607
1    0.2393
Name: proportion, dtype: float64

版本坑提示:在 OpenML 上用名称 fetch_openml(name='adult', version=1) 取到的并不是这份经典数据集,而是另一个同名变体(data_id=179),其 age、hours-per-week 等数值字段被预先离散化成了 5 档类别码(预览中会看到 age 显示为 1、2、3 这样的小整数)。原课程录制时使用的正是该变体,因此本节数字口径与其一致的前提是固定数据版本。为避免歧义,本讲义的代码显式指定 data_id=1590(经典版本,数值字段为真实连续值)。这也提醒我们:拉取公开数据集时务必固定 data_id,否则同名不同版的数据会让实验无法复现。

2. 划分数据集与基线模型

我们将数据划分为训练集和测试集,并建立一个简单的基线模型。这里我们使用逻辑回归模型,并仅做最基础的预处理(对分类变量进行序号编码)。

降级说明:原课程的基线模型使用的是 LightGBM(一个强大的 boosting 树模型,Test AUC 达到 0.9119)。LightGBM 不在本讲义的可运行代码白名单内,因此这里用逻辑回归演示同样的基线流程——两种模型的 AUC 数值不可直接对比,后文引用的 0.9119 与 0.9132 均为原课程在 LightGBM 上的实测结果。

from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import OrdinalEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.datasets import fetch_openml
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score

# 重新加载成年人数据集(与上节一致,保证本块自包含;真实数据含缺失值,此处做最基础填充)
RANDOM_SEED = 2022
dataset = fetch_openml(data_id=1590, as_frame=True)
dataset.target = dataset.target.astype('category').cat.codes
X = dataset.data.copy()
for col in X.columns:
    if X[col].dtype == object or str(X[col].dtype) == 'category':
        X[col] = X[col].astype(str).fillna('missing')
    else:
        X[col] = X[col].fillna(X[col].median())

# 划分训练集和测试集(X 已做缺失值填充)
y = dataset.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=RANDOM_SEED, stratify=y)
print(f"训练集形状: {X_train.shape}, 测试集形状: {X_test.shape}")

# 识别分类列和数值列(基于训练集)
categorical_cols = X_train.select_dtypes(include=['object', 'category']).columns.tolist()
numerical_cols = X_train.select_dtypes(include=['int64', 'float64']).columns.tolist()
print(f"分类列: {categorical_cols}")
print(f"数值列: {numerical_cols}")

# 构建一个简单的预处理和建模管道
preprocessor = ColumnTransformer(
    transformers=[
        ('cat', OrdinalEncoder(handle_unknown='use_encoded_value', unknown_value=-1), categorical_cols),
        ('num', 'passthrough', numerical_cols)
    ]
)

baseline_model = Pipeline(steps=[
    ('preprocessor', preprocessor),
    ('classifier', LogisticRegression(max_iter=1000, random_state=RANDOM_SEED))
])

# 训练并评估基线模型
baseline_model.fit(X_train, y_train)
y_pred_proba_baseline = baseline_model.predict_proba(X_test)[:, 1]
auc_baseline = roc_auc_score(y_test, y_pred_proba_baseline)
print(f"\n基线模型(逻辑回归 + 序号编码)测试集 AUC: {auc_baseline:.4f}")
训练集形状: (39073, 14), 测试集形状: (9769, 14)
分类列: ['workclass', 'education', 'marital-status', 'occupation', 'relationship', 'race', 'sex', 'native-country']
数值列: ['age', 'fnlwgt', 'education-num', 'capital-gain', 'capital-loss', 'hours-per-week']

基线模型(逻辑回归 + 序号编码)测试集 AUC: 0.8261

3. 使用自动化特征工程工具

现在,我们引入 automl_alex 中的 DataBunch 类来进行自动化特征工程。它会自动处理缺失值、对分类变量进行多种编码、生成数值特征交叉项、构建分组统计特征等。

核心参数解析:

  • clean_and_encod_data: 是否进行数据清洗和编码(总开关)。
  • cat_encoder_names: 指定用于分类变量的编码器列表(如独热、频次、序号编码等)。
  • clean_nan: 是否自动处理缺失值。
  • num_generator_features: 是否生成数值特征的交叉项(四则运算)。
  • group_generator_features: 是否构建分组统计特征(如按 sex 分组统计 age、hours-per-week 的均值——注意聚合目标不能是标签列本身,否则会引入标签泄漏,若确需目标聚合应配合交叉拟合)。
  • frequency_enc_num_features: 是否构建频次编码特征。
  • normalization: 是否进行幅度缩放(标准化)。
  • cat_features: 手动指定分类特征列名,为 None 时自动识别。
import pandas as pd
# 注意:automl_alex 不在白名单库中,以下代码演示其核心思想,并使用白名单库进行等价实现。
# 原课使用 `from automl_alex import DataBunch`,我们在此讲解其原理并用手工方法模拟。

print("模拟 automl_alex 的自动化特征工程核心流程:")
print("1. 多种分类编码(One-Hot, Frequency, Ordinal)")
print("2. 数值特征交叉(多项式特征)")
print("3. 分组统计特征(GroupBy + Aggregate)")
print("4. 缺失值处理(SimpleImputer)")
print("5. 频次编码特征")
print("\n我们将分步骤展示特征维度的扩张。")

# 为清晰演示,我们取一个极小的数据样本来展示特征变换
demo_sample = pd.DataFrame({
    'age': [25, 38, 28, 44, 18],
    'workclass': ['Private', 'Private', 'Local-gov', 'Private', '?'],
    'education': ['11th', 'HS-grad', 'Assoc-acdm', 'Some-college', 'Some-college'],
    'sex': ['Male', 'Male', 'Male', 'Male', 'Female'],
    'hours-per-week': [40, 50, 40, 40, 30],
    'income': [0, 0, 1, 1, 0]  # 目标变量,用于演示分组特征
})
print("\n原始演示数据:")
print(demo_sample)
print(f"原始特征维度: {demo_sample.shape[1] - 1} (不含目标列)")
模拟 automl_alex 的自动化特征工程核心流程:
1. 多种分类编码(One-Hot, Frequency, Ordinal)
2. 数值特征交叉(多项式特征)
3. 分组统计特征(GroupBy + Aggregate)
4. 缺失值处理(SimpleImputer)
5. 频次编码特征

我们将分步骤展示特征维度的扩张。

原始演示数据:
   age  workclass     education     sex  hours-per-week  income
0   25    Private          11th    Male              40       0
1   38    Private       HS-grad    Male              50       0
2   28  Local-gov    Assoc-acdm    Male              40       1
3   44    Private  Some-college    Male              40       1
4   18          ?  Some-college  Female              30       0
原始特征维度: 5 (不含目标列)

4. 自动化特征工程的效果对比

在原课程中,讲师使用 automl_alex.DataBunch 处理了完整的成年人数据集。处理前后特征维度从原始的14维扩展到了263维。随后使用 LightGBM 模型进行训练,测试集 AUC 从 0.9119 提升至 0.9132,获得了稳定的性能收益。

关键结论:

  • 自动化特征工程工具能够批量、高效地应用多种特征变换,显著扩展特征空间。
  • 在拥有一定数量分类特征和数值特征的数据集上,这种自动化处理通常能带来一定但幅度有限的提升(本节案例为 +0.13 个百分点)——实际收益因数据集与基线而异,不应期待普适的大幅提升。
  • 工具提供了灵活的开关和参数,用户可以根据需求(如树模型不需要标准化)和计算资源进行定制。
  • 它特别适合用于构建高频、基础的特征变换,让数据科学家得以解放精力,聚焦于更具创造性和业务相关性的特征构建工作。

📝 动手练一练

练习 1(概念辨析):自动化特征工程工具(如本节演示的 AutoML 类工具链)的”深度合成”(deep feature synthesis)与手工特征构建相比,优势与短板各是什么?什么样的场景适合交给自动化工具?

👉 点击查看参考答案

优势:能按预定义的原语(聚合、变换、时间窗口等)系统性地生成大量候选特征,不遗漏人力容易忽略的组合,特别适合多表关联、时序行为类数据的”基础体力活”。短板:生成特征数量爆炸带来筛选成本、黑盒组合可能缺乏业务可解释性、对业务直觉型特征(如”距上次大促的天数”)无能为力。适合交给工具的场景:特征维度多、表间关系明确、需要快速建立基线;适合人工的场景:强业务语义的少量关键特征。

练习 2(动手思考):本节工具对数值特征自动做标准化、对分类特征自动做编码。如果把同一份数据交给树模型(如随机森林)而不是逻辑回归,哪些自动化变换变得多余甚至有害?为什么?

👉 点击查看参考答案

标准化(幅度缩放)对随机森林是多余的:树模型按阈值切分,特征的单调变换(如标准化)不改变切分点的相对顺序,不影响分裂质量。独热编码对树模型通常是降效的:独热把一个高基数分类特征拆成大量 0/1 列,树要在更多列间反复选择,深度消耗更快、路径更碎;直接用序号编码(ordinal)配合树模型往往更高效。这也解释了为什么成熟的自动化工具会提供”按模型类型跳过特定变换”的开关。

本章小结

本节我们探讨了自动化特征工程工具的应用。我们理解了其核心价值在于提升常规特征处理流程的效率,并通过成年人收入预测案例,直观感受了自动化工具如何将原始数据转化为更丰富的特征表示,从而提升模型性能。

📋 行动清单

  • 尝试在个人项目或练习数据集上,手动实现1-2种自动化特征工程的核心思想(如同时应用多种编码器)。
  • 思考你当前业务数据中,哪些特征处理步骤是重复且模式固定的,适合用脚本或工具进行自动化。
  • 了解更多的自动化特征工程或AutoML工具(如Featuretools、TPOT等),对比其与本节课所学工具的异同。

—— 小象教研组