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)
特征缩放流程对比:训练集 vs 验证集 训练集原始特征 fit() 计算参数 transform() 缩放 验证集原始特征 使用训练集参数 transform() 缩放 参数传递(μ, σ, min, max) ⚠ 严禁在验证集上重新 fit(),否则造成数据泄露

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)
类别编码方法选择决策树 类别特征 是否有序? 是 标签编码 否 类别基数 < 20? 是 → 独热编码 否 → 目标编码 💡 目标编码需配合交叉验证或平滑,防止过拟合 高基数类别(如邮编)建议先聚合再编码

3.5 特征工程在训练/验证划分中的关键约束

特征工程的所有步骤(选择、变换、缩放、编码)必须严格遵循“训练集→验证集”的独立流程,避免信息泄露:

  • 特征选择:使用训练集计算统计量(如方差、互信息),验证集仅应用筛选结果。
  • 特征变换:对数变换、Box-Cox的参数(如λ)从训练集估计,验证集直接使用。
  • 标准化/归一化:μ、σ、min、max、median、IQR全部来自训练集。
  • 类别编码:目标编码的类别均值、平滑参数仅从训练集计算;独热编码的类别集合以训练集为准,验证集出现新类别时需做兜底处理(如映射为“未知”或全局均值)。
常见错误: 在划分训练集和验证集之前对整个数据集进行标准化或目标编码。这会导致验证集的信息“泄露”到训练过程中,使模型在验证集上的表现虚高,最终在真实业务环境中大幅衰退。正确的做法是:先划分,后工程。