3、特征工程入门:特征选择、特征变换、标准化与归一化、类别特征编码
在风险定价模型中,原始数据通常无法直接用于建模。特征工程是将原始数据转化为模型可有效利用的特征向量的过程,直接影响模型的预测精度与稳定性。本章聚焦于特征选择、特征变换、标准化与归一化、类别特征编码四大核心环节。
3.1 特征选择:剔除噪声与冗余
特征选择旨在从原始特征集合中筛选出对目标变量(如违约概率)最具解释力的子集,降低维度、防止过拟合、提升模型可解释性。常用方法分为三类:
| 方法类别 | 典型算法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| 过滤法 | 方差阈值、卡方检验、互信息 | 特征数量极大,快速初筛 | 计算高效,独立于模型 | 忽略特征间交互 |
| 包裹法 | 递归特征消除(RFE)、前向选择 | 特征数中等,追求最优子集 | 考虑特征组合效果 | 计算成本高,易过拟合 |
| 嵌入法 | Lasso回归、树模型特征重要性 | 模型训练与选择同步 | 效率与效果平衡 | 依赖模型假设 |
风险定价提示: 在信用评分卡场景中,优先使用嵌入法(如Lasso)或包裹法(如RFE),因为它们能保留与违约状态强相关的非线性组合特征。过滤法仅用于初始维度超过1000时的快速降维。
3.2 特征变换:处理偏态与非线性
风险定价模型(如逻辑回归)通常假设特征与对数几率呈线性关系。当特征分布严重偏态或存在非线性趋势时,需进行变换。
- 对数变换:适用于收入、贷款金额等右偏分布特征,公式为
log(1 + x),可压缩量纲、抑制异常值影响。 - Box-Cox变换:自动寻找最优幂参数 λ,使数据更接近正态分布,适用于正值特征。
- 多项式特征:引入特征的平方项或交叉项(如
年龄²、收入 × 负债率),捕捉非线性关系。
实战案例: 某消费金融数据集中的“近6个月查询次数”特征呈长尾分布,原始值域为[0, 98]。直接入模导致逻辑回归系数不稳定。经对数变换后,特征分布趋于对称,模型AUC提升约3.2%。
import numpy as np
import pandas as pd
from scipy import stats
# 对数变换示例
df['log_query'] = np.log1p(df['query_count'])
# Box-Cox变换示例
transformed_data, lambda_opt = stats.boxcox(df['loan_amount'] + 1)
df['boxcox_amount'] = transformed_data
print(f"最优Box-Cox λ: {lambda_opt:.3f}")
3.3 标准化与归一化:统一量纲
不同特征往往具有不同的量纲和数值范围(如年龄20~60 vs 收入0~10⁶)。若不处理,基于距离的模型(如KNN、SVM)和梯度下降优化会偏向数值大的特征。
| 方法 | 公式 | 输出范围 | 适用模型 | 对异常值敏感度 |
|---|---|---|---|---|
| Z-score标准化 | z = (x - μ) / σ |
均值0,标准差1 | 线性回归、SVM、PCA | 中等 |
| Min-Max归一化 | x' = (x - min) / (max - min) |
[0, 1] | 神经网络、KNN | 高(受极值影响大) |
| Robust缩放 | x' = (x - median) / IQR |
无固定范围 | 含较多异常值的数据 | 低 |
关键注意: 标准化与归一化的参数(μ、σ、min、max等)必须仅从训练集计算,然后应用到验证集和测试集。若使用全量数据计算,会造成信息泄露,导致验证集性能被高估。
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
# 仅从训练集拟合
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
# 用训练集的参数转换验证集
X_val_scaled = scaler.transform(X_val)
3.4 类别特征编码:将文本转化为数值
风险定价模型中包含大量类别特征,如职业、地区、婚姻状况、贷款用途等。模型无法直接处理字符串,需将其编码为数值形式。
3.4.1 常见编码方法对比
| 编码方法 | 原理 | 输出维度 | 适用场景 | 风险定价注意事项 |
|---|---|---|---|---|
| 标签编码 | 将类别映射为整数(0,1,2,…) | 1列 | 有序类别(如学历:高中<本科<硕士) | 无序类别会引入虚假顺序关系 |
| 独热编码 | 每个类别生成一个0/1二元列 | 类别数K列 | 无序类别(如职业、地区) | 类别过多时维度爆炸,需结合稀疏矩阵 |
| 目标编码 | 用类别对应的目标均值替代原始值 | 1列 | 高基数类别(如邮编、商户ID) | 易过拟合,需配合交叉验证或平滑 |
| 频率编码 | 用类别在样本中的出现频率替代 | 1列 | 类别分布极不均匀 | 丢失类别与目标的关系信息 |
风险定价最佳实践:
- 对于低基数无序类别(如婚姻状况、性别),优先使用独热编码。
- 对于高基数类别(如职业代码超过50种),使用目标编码并加入贝叶斯平滑,防止小样本类别过拟合。
- 对于有序类别(如信用评级AAA、AA、A、BBB…),使用标签编码并保持顺序一致性。
3.4.2 目标编码的平滑实现
import pandas as pd
import numpy as np
def target_encoding_smooth(series, target, prior=None, alpha=10):
"""
贝叶斯平滑目标编码
series: 类别特征
target: 目标变量(0/1)
prior: 全局先验均值,默认使用target整体均值
alpha: 平滑强度参数
"""
if prior is None:
prior = target.mean()
# 计算每个类别的统计量
stats = target.groupby(series).agg(['count', 'mean'])
stats.columns = ['cnt', 'mean']
# 平滑公式: (cnt * mean + alpha * prior) / (cnt + alpha)
smooth = (stats['cnt'] * stats['mean'] + alpha * prior) / (stats['cnt'] + alpha)
return series.map(smooth)
# 示例:对职业类别进行目标编码
df['job_encoded'] = target_encoding_smooth(df['job'], df['default_flag'], alpha=15)
3.5 特征工程在训练/验证划分中的关键约束
特征工程的所有步骤(选择、变换、缩放、编码)必须严格遵循“训练集→验证集”的独立流程,避免信息泄露:
- 特征选择:使用训练集计算统计量(如方差、互信息),验证集仅应用筛选结果。
- 特征变换:对数变换、Box-Cox的参数(如λ)从训练集估计,验证集直接使用。
- 标准化/归一化:μ、σ、min、max、median、IQR全部来自训练集。
- 类别编码:目标编码的类别均值、平滑参数仅从训练集计算;独热编码的类别集合以训练集为准,验证集出现新类别时需做兜底处理(如映射为“未知”或全局均值)。
常见错误: 在划分训练集和验证集之前对整个数据集进行标准化或目标编码。这会导致验证集的信息“泄露”到训练过程中,使模型在验证集上的表现虚高,最终在真实业务环境中大幅衰退。正确的做法是:先划分,后工程。