特征工程深入:超越基础转换的高级技法

FreeGuideOnline 最新 2026-06-14

特征工程深入:超越基础转换的高级技法

一、引言:为什么你需要进阶特征工程

基础数据清洗、缺失值处理和简单的独热编码,是每个数据从业者的入门课。但当面对高基数类别变量、时间依赖模式、非线性关系或海量文本时,这些“标准操作”往往会丢失关键信号,甚至引入噪声。进阶特征工程的目标是:构造更具表达能力的特征,压缩信息冗余,提升模型稳定性和可解释性。本教程将系统讲解面向结构化数据与文本的多种高级技法,同时附完整可运行代码,帮助你快速从“会处理数据”过渡到“会创造特征”。

二、高级编码技术:告别简单独热

传统独热编码在类别数过多时会导致维度爆炸,而标签编码人为引入顺序假设。我们需要根据业务与模型特性,选择更聪明的方案。

2.1 目标编码 (Target Encoding / Mean Encoding)

原理:用该类别对应的目标变量均值(回归)或后验概率(分类)替换类别,直接捕获类别与目标的关联强度。

适用场景:高基数类别变量;树模型、线性模型均适用。

风险:极易过拟合,必须配合平滑处理交叉验证

代码实践:使用 category_encoders 进行平滑目标编码

import pandas as pd
from category_encoders import TargetEncoder

# 示例数据
train = pd.DataFrame({
    'city': ['A','A','B','B','C','C','A','B'],
    'price': [200, 220, 150, 160, 300, 310, 205, 155]
})
test = pd.DataFrame({'city': ['A','B','C','D']})  # D为未见类别

# 初始化,smoothing参数控制正则强度
encoder = TargetEncoder(cols=['city'], smoothing=10)
train['city_encoded'] = encoder.fit_transform(train['city'], train['price'])
test['city_encoded'] = encoder.transform(test['city'])
print(test)

关键参数解释smoothing 值越大,全局均值权重越高,对罕见类别越稳健。实际项目中强烈建议结合 KFold 进行交叉目标编码,防止数据泄露。

2.2 证据权重编码 (Weight of Evidence, WoE)

原理:源自信用评分领域,计算每个类别中正负样本分布的对数比率:
WoE = ln(正例占比 / 负例占比)

优势:能将非线性关系线性化,且自带单调性约束,特别适合逻辑回归等线性模型。

注意:要求目标为二分类,且需处理分母为零(可加少量平滑)。

手动实现示例

import numpy as np

def calculate_woe(df, cat_col, target_col, eps=1e-6):
    # 计算全局正负概率
    global_pos = df[target_col].mean()
    global_neg = 1 - global_pos
    grouped = df.groupby(cat_col)[target_col].agg(['sum', 'count'])
    grouped['pos_rate'] = grouped['sum'] / grouped['count']
    grouped['neg_rate'] = 1 - grouped['pos_rate']
    grouped['woe'] = np.log((grouped['pos_rate'] + eps) / (grouped['neg_rate'] + eps))
    return grouped['woe'].to_dict()

woe_map = calculate_woe(train, 'city', 'target')

2.3 稀有类别与未知类别处理

将低频类别合并为 Other,将测试集中出现的全新类别映射为模型习得的“未知”标记,是保证编码稳健性的必要步骤。

  • 方法:基于阈值(如累计频率 < 5%)合并稀有值;
  • 实现:先用 pandas 计算频率,再 mask,最后在编码管道中统一处理。

三、特征交叉与交互:人工构造非线性

特征交互是解锁模型非线性能力的钥匙,尤其对线性模型和树模型都有显著增益。

3.1 显式特征交叉

加减乘除多项式

数值列直接衍生:feat_sum = f1 + f2, feat_ratio = f1 / (f2 + eps), feat_diff = f1 - f2。业务含义强的组合往往效果惊人(如“单均消费 = 总消费 / 订单数”)。

类别特征组合

将两个类别列拼接成新特征:pandasdf['A'] + '_' + df['B'],再用目标编码或低维嵌入处理。组合基数爆炸时,可只选择重要组合(通过卡方检验或互信息)。

3.2 笛卡尔积门控与注意力风格特征

门控特征:借鉴注意力机制,用一门控字段动态调节另一字段权重。例如: gated_feat = feature_A * sigmoid(feature_B)。 这能模拟条件依赖,适合深度模型但也可作为手工特征。

实现示例

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

df['gated_score'] = df['user_activity'] * sigmoid(df['time_decay_factor'])

3.3 利用群组聚合构造“长尾特征”

按某个类别分组,计算数值列的 mean, std, skew, kurtosis, ptp 等,再左连接回原表。这就是 featuretools 中“深度特征合成”的思想,可让树模型感知群组分布差异。

group_stats = df.groupby('user_id')['amount'].agg(['mean','std','max','min'])
df = df.merge(group_stats, on='user_id', suffixes=('','_grp'))

四、自动化特征选择:从海量候选中提纯

构造了大量特征后,筛选比创造更重要。高级选择方法可以平衡模型复杂度与性能。

4.1 基于模型重要性的递归消除 (RFE / RFECV)

用带 feature_importances_coef_ 的模型,递归删除最不重要特征。RFECV 通过交叉验证自动确定最佳特征数量。

from sklearn.feature_selection import RFECV
from sklearn.ensemble import RandomForestClassifier

rf = RandomForestClassifier(n_estimators=50, random_state=42)
selector = RFECV(rf, step=1, cv=3, scoring='roc_auc')
selector.fit(X_train, y_train)
print(f"Optimal features: {selector.n_features_}")

4.2 稳定性选择 (Stability Selection)

结合随机子采样与Lasso,多次运行选择变量,统计每个特征被选中的频率,保留超过阈值的特征。适合高维数据,控制假阳率。

from sklearn.linear_model import Lasso
from stability_selection import StabilitySelection  # 第三方库

selector = StabilitySelection(base_estimator=Lasso(), n_bootstrap_iterations=20, threshold=0.6)
selector.fit(X, y)
selected = selector.get_support()

4.3 互信息与SHAP辅助筛选

  • 互信息:衡量特征与目标间的非线性依赖程度,使用 sklearn.feature_selection.mutual_info_classif
  • SHAP重要性:事后解释工具,取绝对值的均值作为全局重要度,兼顾一致性与交互效应,可剔除无贡献特征。

五、时间序列特征工程:让时序信号可学习

时间维度蕴含大量因果与周期模式,需要专门提炼。

5.1 滞后特征与滑动窗口聚合

创建 t-1, t-7 等滞后值;窗口大小为 7 的均值、标准差、最小/最大值。

# 假设 df 已按时间排序
df['lag_1'] = df.groupby('id')['value'].shift(1)
df['rolling_mean_7'] = df.groupby('id')['value'].transform(lambda x: x.rolling(7).mean())

严禁未来信息泄露:所有滞后和窗口统计必须仅使用过去数据。

5.2 傅里叶项与周期性编码

对于“小时”、“星期”等周期类别,用正弦/余弦编码比独热更紧凑: sin_col = sin(2 * pi * hour / 24)cos_col = cos(2 * pi * hour / 24)
当存在多个周期时,可叠加傅里叶级数。

5.3 距离上次事件的时间与计数

“距上次购买天数”、“用户评论累计次数”这类特征对预测流失、违约极有效。注意对未来事件设置掩码。

六、文本特征的高级处理:从词袋到语义嵌入

文本不仅是词频向量。

6.1 基于领域知识的短语检测与实体替换

如果数据中有明确模式(如产品型号、错误代码),用正则抽取并作为类别特征,压缩词典空间:

import re
df['error_code'] = df['log'].str.extract(r'(ERR_[A-Z0-9]+)')

6.2 TF‑IDF 加权后的主题特征与N-gram组合

使用 TfidfVectorizer 不仅提取单词,还可提取 n-gram(如 (2,3)),捕获短语含义。然后通过 TruncatedSVD 降维得到主题特征,比原始高维稀疏矩阵更稳定。

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.decomposition import TruncatedSVD

tfidf = TfidfVectorizer(ngram_range=(1,3), max_features=10000)
X_tfidf = tfidf.fit_transform(corpus)
svd = TruncatedSVD(n_components=50)
X_svd = svd.fit_transform(X_tfidf)

6.3 预训练语言模型的句向量

利用小型BERT(如 paraphrase-MiniLM)将文本转为固定维度向量,作为高质量语义特征。适合中小规模数据集,可离线批量计算。

from sentence_transformers import SentenceTransformer
model = SentenceTransformer('paraphrase-MiniLM-L6-v2')
embeddings = model.encode(text_list, show_progress_bar=True)

七、特征缩放与变换的进阶选择

不同模型对数值范围敏感度不同,选择正确的变换和缩放方法至关重要。

7.1 针对偏态分布的 Box-Cox / Yeo-Johnson

对数变换无法处理零值和负值。Yeo-Johnson 是 Box-Cox 的扩展,支持全部实数域,使分布更趋近正态,对线性模型和KMeans提效明显。

from sklearn.preprocessing import PowerTransformer
pt = PowerTransformer(method='yeo-johnson')
df['transformed'] = pt.fit_transform(df[['original']])

7.2 针对离群值的 Robust Scaling

用中位数和四分位距替代均值方差,减轻异常值影响。

from sklearn.preprocessing import RobustScaler
scaler = RobustScaler()
df_scaled = scaler.fit_transform(df[['feature']])

7.3 针对神经网络的分位数变换

QuantileTransformer 将数据映射到均匀或正态分布,有效处理重尾分布,提升梯度下降稳定性。

八、最佳实践与防坑指南

  1. 特征工程管道化:使用 sklearn.pipelinefeature-engine 将编码、缩放、选择串行化,避免训练和测试间的不一致。
  2. 慎防数据泄露:任何涉及目标变量(目标编码)、未来信息(时序滞后)或测试集统计量的操作,必须在交叉验证内部完成计算。
  3. 业务可解释性闭环:构造特征后,计算其与目标的相关性,并使用SHAP验证是否符合业务常识。一个无法解释的强特征可能只是噪音的巧合。
  4. 版本记录与文档:为重要特征编写“数据卡片”(来源、计算逻辑、更新频率),便于协作与问题追踪。

九、总结

进阶特征工程不是技巧的堆砌,而是对数据本质的重新表达。从高基数编码的平滑艺术,到时间序列的因果窗口,再到文本的语义投射,每项技法都应服膺于业务理解和模型需求。建议读者将本文代码在真实竞赛或项目中逐一实践,积累属于自己的“特征工具箱”。记住,优秀的特征工程往往是模型效果差异的决定性因素。

下一步学习建议:深入研究自动化特征工程工具 featuretools,以及图神经网络如何自动学习关系特征。