查看全课大纲(第 18 / 35 节)
- 1.特征工程简介
- 2.特征工程的处理流程
- 3.数据与特征概述
- 4.数据统计、相似度与数据探索
- 5.数据探索工具 Pandas_profiling
- 6.工具应用:《泰坦尼克》分析报告
- 7.构造一个微型链家租房数据集用于演示
- 8.数据预处理综述
- 9.缺失值处理实操
- 10.异常值处理实操
- 11.连续值特征构建
- 12.类别型数据特征构建
- 13.特征幅度缩放
- 14.特征离散化
- 15.特征编码:让类别数据参与建模
- 16.特征变换
- 17.时间特征
- 18.特征衍生
- 19.自动化特征工具
- 20.特征选择综述
- 21.特征选择方法
- 22.过滤式特征选择方法实操
- 23.包裹式特征选择方法
- 24.嵌入式特征选择
- 25.混序法与混合法特征选择
- 26.特征降维综述
- 27.案例实操:特征降维
- 28.特征监控概述与指标
- 29.Iris 数据集特征漂移检测案例
- 30.特征监控实战:用分布对比检测数据漂移
- 31.实时特征监控
- 32.电商销量预测项目背景介绍
- 33.数据预处理和特征工程(一)
- 34.数据预处理和特征工程(二)
- 35.数据建模
特征衍生
约 9 分钟
小象实战讲义 · 特征工程精讲与实践
特征衍生是特征构建的高级阶段,旨在从已有特征中挖掘更深层次的模式与关联,构建出更具表征能力的新特征。本节我们将学习两种核心的特征衍生方法:多项式特征和基于树模型的组合特征构建,它们能有效提升线性模型的非线性拟合能力,同时保持模型的可解释性与在线推理效率。
💡 核心导读
- 多项式特征:通过数学运算(如平方、交叉相乘)将连续数值特征扩展为高阶组合,揭示特征间的交互作用。
- 树模型构建组合特征:利用GBDT、随机森林等树模型的决策路径,自动学习并生成一系列条件组合特征,再输入给线性模型(如逻辑回归)进行训练,实现“树模型做特征工程,线性模型做预测”的经典范式。
- 工业实践优势:该方法结合了树模型强大的非线性拟合能力与线性模型的高效、可解释性,是工业界广泛应用的成熟方案。
一、多项式特征(Polynomial Features)
多项式特征是一种基于已有数值型特征,通过构造多项式项(如平方、立方、交叉项)来生成新特征的方法。它能够帮助线性模型捕捉特征之间的交互效应和非线性关系。
其核心是生成最高次幂不超过指定 degree 的所有多项式组合。例如,对于两个特征 x1 和 x2,degree=2 时会生成:x1, x2, x1^2, x1*x2, x2^2。
下面我们使用泰坦尼克数据集中的 Pclass(船舱等级)和 SibSp(堂兄弟姐妹/配偶数量)两个数值特征进行演示。
import pandas as pd
import numpy as np
from sklearn.preprocessing import PolynomialFeatures
# 构造一个微型可复现的泰坦尼克数据集样本
data = pd.DataFrame({
'Pclass': [3, 1, 3, 2, 1],
'SibSp': [1, 1, 0, 1, 0],
'Age': [22.0, 38.0, 26.0, 35.0, 35.0],
'Fare': [7.2500, 71.2833, 7.9250, 53.1000, 51.8625],
'Survived': [0, 1, 1, 1, 0]
})
# 选取用于构建多项式特征的两列
X_sample = data[['Pclass', 'SibSp']]
print("原始特征数据:")
print(X_sample)
print()
# 实例化多项式特征转换器,最高次为2,不包含常数项
poly = PolynomialFeatures(degree=2, include_bias=False)
# 拟合并转换数据
X_poly = poly.fit_transform(X_sample)
# 获取生成的特征名称
feature_names = poly.get_feature_names_out(['Pclass', 'SibSp'])
# 将结果转换为DataFrame以便查看
X_poly_df = pd.DataFrame(X_poly, columns=feature_names)
print("生成的多项式特征:")
print(X_poly_df)原始特征数据:
Pclass SibSp
0 3 1
1 1 1
2 3 0
3 2 1
4 1 0
生成的多项式特征:
Pclass SibSp Pclass^2 Pclass SibSp SibSp^2
0 3.0 1.0 9.0 3.0 1.0
1 1.0 1.0 1.0 1.0 1.0
2 3.0 0.0 9.0 0.0 0.0
3 2.0 1.0 4.0 2.0 1.0
4 1.0 0.0 1.0 0.0 0.0可以看到,除了原始特征 Pclass 和 SibSp 外,我们还得到了 Pclass^2、Pclass SibSp(交叉项)和 SibSp^2 三个新特征。
二、基于树模型构建组合特征
树模型(如决策树、随机森林、GBDT)通过一系列“if-else”规则对数据进行划分。每一条从根节点到叶子节点的路径,都是一组特征的特定条件组合。我们可以将这些路径(即叶子节点的索引)提取出来,进行独热编码(One-Hot Encoding),从而得到一系列新的二值组合特征。这些特征再输入给逻辑回归等线性模型,就构成了著名的 GBDT+LR 或 RF+LR 架构——该范式出自 Facebook 2014 年发表的论文(用树模型做特征变换,再用逻辑回归做最终的点击率预估),此后在工业界被长期沿用。
这种方法的核心思想是:让树模型负责自动、高效地发现有效的特征组合,让线性模型负责在这些组合特征上进行快速、可解释的预测。
2.1 GBDT + LR 范式
我们首先演示使用梯度提升树(GBDT)来构建组合特征,并与逻辑回归(LR)结合。
import pandas as pd
import numpy as np
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.preprocessing import OneHotEncoder
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
# 1. 准备数据(硬编码微型样本,包含缺失值填充逻辑)
data = pd.DataFrame({
'Pclass': [3, 1, 3, 2, 1, 3, 2, 3],
'Age': [22.0, 38.0, 26.0, 35.0, 35.0, np.nan, 54.0, 2.0],
'Fare': [7.25, 71.28, 7.92, 53.10, 51.86, 8.05, 51.86, 21.07],
'SibSp': [1, 1, 0, 1, 0, 0, 0, 3],
'Survived': [0, 1, 1, 1, 0, 0, 0, 1]
})
# 简单填充缺失值
data_filled = data.fillna(data['Age'].mean())
# 划分特征和标签
X = data_filled[['Pclass', 'Age', 'Fare', 'SibSp']]
y = data_filled['Survived']
# 简单按顺序划分训练集和测试集
X_train, X_test = X.iloc[:5], X.iloc[5:]
y_train, y_test = y.iloc[:5], y.iloc[5:]
print("训练集特征:")
print(X_train)
print("\n训练集标签:", y_train.values)
print("\n测试集特征:")
print(X_test)
print("测试集标签:", y_test.values)
print()
# 2. 使用GBDT拟合数据,并获取叶子节点索引
gbdt = GradientBoostingClassifier(n_estimators=5, random_state=42, max_depth=3)
gbdt.fit(X_train, y_train)
# apply方法返回每个样本在每棵树上的叶子节点索引
train_leaf_index_gbdt = gbdt.apply(X_train)[:, :, 0] # shape: (n_samples, n_estimators)
test_leaf_index_gbdt = gbdt.apply(X_test)[:, :, 0]
print("GBDT叶子节点索引(训练集前2个样本):")
print(train_leaf_index_gbdt[:2])
print()
# 3. 对叶子节点索引进行独热编码
one_hot_gbdt = OneHotEncoder(sparse_output=True)
# 在训练集索引上拟合编码器
X_train_onehot_gbdt = one_hot_gbdt.fit_transform(train_leaf_index_gbdt)
X_test_onehot_gbdt = one_hot_gbdt.transform(test_leaf_index_gbdt)
print(f"独热编码后特征维度(训练集): {X_train_onehot_gbdt.shape}")
print()
# 4. 将GBDT生成的特征输入逻辑回归
lr_on_gbdt_feat = LogisticRegression(max_iter=1000, random_state=42)
lr_on_gbdt_feat.fit(X_train_onehot_gbdt, y_train)
y_pred_proba_gbdt = lr_on_gbdt_feat.predict_proba(X_test_onehot_gbdt)[:, 1]
auc_gbdt_lr = roc_auc_score(y_test, y_pred_proba_gbdt)
print(f"AUC (GBDT特征 + LR): {auc_gbdt_lr:.4f}")
print()
# 5. 可选:将GBDT特征与原始特征拼接(转为密集数组后用 numpy 水平拼接)
X_train_ext = np.hstack([X_train_onehot_gbdt.toarray(), X_train])
X_test_ext = np.hstack([X_test_onehot_gbdt.toarray(), X_test])
lr_on_ext = LogisticRegression(max_iter=1000, random_state=42)
lr_on_ext.fit(X_train_ext, y_train)
y_pred_proba_ext = lr_on_ext.predict_proba(X_test_ext)[:, 1]
auc_ext = roc_auc_score(y_test, y_pred_proba_ext)
print(f"AUC (GBDT特征 + 原始特征 + LR): {auc_ext:.4f}")训练集特征:
Pclass Age Fare SibSp
0 3 22.0 7.25 1
1 1 38.0 71.28 1
2 3 26.0 7.92 0
3 2 35.0 53.10 1
4 1 35.0 51.86 0
训练集标签: [0 1 1 1 0]
测试集特征:
Pclass Age Fare SibSp
5 3 30.285714 8.05 0
6 2 54.000000 51.86 0
7 3 2.000000 21.07 3
测试集标签: [0 0 1]
GBDT叶子节点索引(训练集前2个样本):
[[5. 2. 2. 5. 4.]
[6. 6. 6. 6. 6.]]
独热编码后特征维度(训练集): (5, 20)
AUC (GBDT特征 + LR): 0.5000
AUC (GBDT特征 + 原始特征 + LR): 0.00002.2 随机森林 + LR 范式
同样的流程也适用于随机森林(Random Forest)。
import pandas as pd
import numpy as np
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import OneHotEncoder
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
# 1. 准备数据(与上一块完全相同的硬编码数据,保证可比性)
data = pd.DataFrame({
'Pclass': [3, 1, 3, 2, 1, 3, 2, 3],
'Age': [22.0, 38.0, 26.0, 35.0, 35.0, np.nan, 54.0, 2.0],
'Fare': [7.25, 71.28, 7.92, 53.10, 51.86, 8.05, 51.86, 21.07],
'SibSp': [1, 1, 0, 1, 0, 0, 0, 3],
'Survived': [0, 1, 1, 1, 0, 0, 0, 1]
})
data_filled = data.fillna(data['Age'].mean())
X = data_filled[['Pclass', 'Age', 'Fare', 'SibSp']]
y = data_filled['Survived']
X_train, X_test = X.iloc[:5], X.iloc[5:]
y_train, y_test = y.iloc[:5], y.iloc[5:]
# 2. 使用随机森林拟合数据,并获取叶子节点索引
rf = RandomForestClassifier(n_estimators=5, random_state=42, max_depth=3)
rf.fit(X_train, y_train)
train_leaf_index_rf = rf.apply(X_train)
test_leaf_index_rf = rf.apply(X_test)
print("RF叶子节点索引(训练集前2个样本):")
print(train_leaf_index_rf[:2])
print()
# 3. 对叶子节点索引进行独热编码
one_hot_rf = OneHotEncoder(sparse_output=True)
X_train_onehot_rf = one_hot_rf.fit_transform(train_leaf_index_rf)
X_test_onehot_rf = one_hot_rf.transform(test_leaf_index_rf)
print(f"独热编码后特征维度(训练集): {X_train_onehot_rf.shape}")
print()
# 4. 将RF生成的特征输入逻辑回归
lr_on_rf_feat = LogisticRegression(max_iter=1000, random_state=42)
lr_on_rf_feat.fit(X_train_onehot_rf, y_train)
y_pred_proba_rf = lr_on_rf_feat.predict_proba(X_test_onehot_rf)[:, 1]
auc_rf_lr = roc_auc_score(y_test, y_pred_proba_rf)
print(f"AUC (RF特征 + LR): {auc_rf_lr:.4f}")
print()
# 5. 对比:仅使用原始特征+逻辑回归
lr_raw = LogisticRegression(max_iter=1000, random_state=42)
lr_raw.fit(X_train, y_train)
y_pred_proba_raw = lr_raw.predict_proba(X_test)[:, 1]
auc_raw = roc_auc_score(y_test, y_pred_proba_raw)
print(f"AUC (仅原始特征 + LR): {auc_raw:.4f}")RF叶子节点索引(训练集前2个样本):
[[1 0 1 4 1]
[4 0 6 6 2]]
独热编码后特征维度(训练集): (5, 14)
AUC (RF特征 + LR): 0.5000
AUC (仅原始特征 + LR): 0.0000说明:由于我们使用的示例数据量极小(仅8行,测试集只有3个样本、其中1个正例),AUC 的可能取值本身只有 0、0.5、1 三种,因此无法反映方法优劣。在实际工业场景中,数据规模庞大,特征复杂,GBDT+LR 或 RF+LR 架构通常能显著超越仅使用原始特征的线性模型,因为它通过树模型自动发现了有效的非线性组合特征。
三、方法对比与工业实践要点
通过上面的演示,我们可以总结出以下要点:
- 效果对比:在原课程的泰坦尼克全量数据集实验中(以原课 notebook 输出为准确口径),GBDT特征+LR 的 AUC 达到 0.7746;再拼接原始特征后为 0.7743(略有损失,与数据规模有关);RF+LR 为 0.7479;而仅使用原始特征的逻辑回归仅为 0.6989。这验证了树模型构建组合特征的有效性。(注:课程视频中对后三值的口述与 notebook 输出略有出入,此处以确定性工件为准。)
- 工业界偏好:GBDT+LR(或其变种,如使用XGBoost、LightGBM)是工业界,尤其是在推荐系统、广告点击率预估等场景中,经过长期验证的成熟方案。其优势在于:
- 强大的表征能力:树模型自动学习复杂的特征交叉,省去了人工设计组合特征的繁琐。
- 高效的在线推理:最终的预测模型是逻辑回归,其计算复杂度低,响应速度快,非常适合高并发在线服务。
- 一定的可解释性:相比于深度神经网络,逻辑回归的权重可以一定程度上反映组合特征的重要性。
- 适用场景:当直接使用非线性模型(如GBDT、RF本身)效果已经很好时,为何还要用LR?答案在于效率与可控性。在需要极低延迟、模型需要频繁更新、或对模型可解释性有要求的场景中,“树模型做特征工程 + 线性模型做预测”的范式提供了优秀的平衡。
📝 动手练一练
练习 1(概念辨析):多项式特征扩展为什么对线性模型帮助最大,而对树模型收益有限?两者的决策机制差异在哪里?
👉 点击查看参考答案
线性模型只能对特征做加权求和,特征间的交互与非线性关系必须靠人工”造”出来——多项式特征恰好把 x1·x2、x² 这类交互项显式展开成新列,等于把非线性”翻译”回线性可用的形式,所以提升显著。树模型本身就是按阈值递归切分,天然能逼近任意非线性交互,把 x1·x2 显式喂进去通常只是让树更容易找到分裂点、边际收益小,且会成倍增加特征数、消耗更多内存与训练时间。这也是为什么树模型路线更常用”叶子节点编码”这类组合特征,而不是盲目做多项式展开。
练习 2(动手实算):两个特征 x1、x2,PolynomialFeatures(degree=2, include_bias=False) 会生成几列?如果原始特征有 10 个,degree=3 时会膨胀到多少列?这提示我们在什么情况下要警惕该方法的维度爆炸?
👉 点击查看参考答案
两特征 degree=2:x1、x2、x1²、x1·x2、x2²,共 5 列。十特征 degree=3(include_bias=False)会膨胀到 285 列——总项数正是组合数 C(13,3)-1,包含所有一次项、二次项(含自幂与交叉)与三次项。特征数继续增大或 degree 继续提高时,列数会按组合数爆炸式增长(30 特征 degree=3 已上千列)。警示:原始特征较多时,先做特征筛选/降维,或限制 interaction_only=True 只保留交叉项、只对少数强相关特征做多阶展开,避免”维度灾难”吃掉模型的泛化能力。
本章小结
本节我们深入学习了两种高级的特征衍生技术:多项式特征和基于树模型的组合特征构建。它们是特征工程从“手工制作”迈向“自动化学习”的关键一步,能够显著提升模型对数据中复杂模式的捕捉能力。
📋 行动清单
- 动手尝试多项式特征:在自己的数据集上,对数值型特征尝试
degree=2或3的多项式扩展,观察模型效果变化。 - 实现GBDT+LR流程:使用sklearn完整走通“GBDT拟合 -> 提取叶子索引 -> 独热编码 -> 逻辑回归训练”的流程,理解数据在各个环节的形态变化。
- 思考业务应用:结合你正在处理的实际业务问题,思考哪些特征之间存在潜在的交互效应,是否可以通过本节介绍的方法来自动挖掘这些组合。
—— 小象教研组