4. 特征工程基础:特征类型分类、衍生思路与重要性初探

在风控建模中,特征工程决定了模型效果的上限。原始数据通常包含多种类型,需要经过分类、衍生和筛选才能成为有效的模型输入。本节将系统梳理特征类型、常用衍生方法以及特征重要性的初步评估方式,并结合 Spark 实现给出实操要点。

4.1 特征类型分类

风控特征按数据性质可分为三类:数值型、类别型和时序型。不同类型对应不同的预处理和衍生策略。

类型 定义 风控常见字段 Spark 数据类型
数值型 连续或离散的数值,可进行数学运算 收入、年龄、负债率、交易金额 DoubleType, IntegerType, LongType
类别型 有限个离散取值,无大小顺序 职业、学历、婚姻状况、地区编码 StringType, IntegerType(需编码)
时序型 按时间顺序排列的观测值序列 近 6 个月还款记录、近 30 天登录频次 ArrayType(DoubleType), 时间戳列
实操提示:在 Spark 中,建议使用 printSchema() 检查字段类型,避免将类别型误判为数值型(如“性别”用 0/1 编码时仍应视为类别型)。

4.2 特征衍生思路

特征衍生是从原始字段中构造新特征的过程,目的是捕捉更复杂的用户行为模式。以下是风控场景中常用的衍生方法。

4.2.1 数值型衍生

  • 统计聚合:对同一用户的多笔交易计算均值、最大值、最小值、标准差、变异系数。
  • 比率/占比:负债收入比、信用卡使用率、近 3 个月逾期次数 / 总借款次数。
  • 分箱/离散化:将年龄、收入等连续值按业务规则或等频分箱转换为类别特征。

4.2.2 类别型衍生

  • 独热编码:适用于取值较少的类别(如婚姻状况)。
  • 目标编码:用类别对应的目标变量均值(如坏账率)替换原始类别值,适用于高基数类别(如地区编码)。
  • 交叉组合:将两个或多个类别拼接为新特征(如“职业_学历”)。

4.2.3 时序型衍生

  • 滑动窗口统计:近 7 天、近 30 天、近 90 天的交易次数、金额总和、最大单笔金额。
  • 趋势特征:近 3 个月还款金额的线性斜率、最近一次逾期距今的天数。
  • 周期性特征:交易时间的小时、星期几、是否节假日。
Spark 实现要点:时序型衍生通常使用 window() 函数配合 groupBy 和 agg 完成滑动窗口计算。注意设置合理的 partitionBy 和 orderBy 以避免数据倾斜。
特征衍生流程与类型映射 特征衍生流程与类型映射 原始数据 数值型特征 统计聚合 比率/分箱 类别型特征 独热编码 目标编码/交叉组合 时序型特征 滑动窗口统计 趋势/周期性 最终特征集

4.3 特征重要性初探

特征重要性用于衡量每个特征对模型预测能力的贡献度,是特征筛选和解释性分析的基础。在风控场景中,常用的评估方法包括以下三种。

方法 原理 适用模型 风控场景特点
树模型内置重要性 基于特征在树节点分裂时带来的不纯度减少(Gini / 信息增益) 随机森林、XGBoost、LightGBM 快速、可解释性强,适合初筛
Permutation Importance 随机打乱某特征的值,观察模型性能下降程度 模型无关(通用) 能反映特征的真实边际贡献
SHAP Values 基于博弈论 Shapley 值,计算每个特征对预测的边际贡献 模型无关(通用) 可解释性最强,但计算成本高
注意:在 Spark 中,树模型(如 RandomForestClassifier)训练后可直接通过 .featureImportances 获取重要性向量。Permutation Importance 可使用 pyspark.ml.evaluation 配合自定义 UDF 实现。SHAP 在 Spark 中推荐使用 shap 库的 Spark 集成版本,或对采样数据计算。

4.3.1 特征重要性在风控中的应用

  • 特征筛选:剔除重要性排名靠后的特征,降低维度,减少过拟合风险。
  • 业务解释:高重要性特征通常对应强风控信号(如“近 30 天逾期次数”),可用于规则制定。
  • 监控与漂移检测:若某特征重要性随时间显著下降,可能表明数据分布发生偏移或特征失效。
特征重要性评估方法对比 特征重要性评估方法对比 树模型内置重要性 基于不纯度减少 快速、可解释 适合初筛 Permutation Importance 打乱特征值观察性能下降 模型无关 反映边际贡献 SHAP Values 基于 Shapley 值 可解释性最强 计算成本高 风控应用场景 特征筛选 · 业务解释 · 监控与漂移检测 降低维度 · 规则制定 · 分布偏移预警

4.4 Spark 实操:特征类型识别与简单衍生

以下代码演示如何在 Spark 中识别特征类型,并对数值型和类别型特征进行基础衍生。

from pyspark.sql import SparkSession
from pyspark.sql.functions import col, when, countDistinct, mean, stddev
from pyspark.ml.feature import StringIndexer, OneHotEncoder, VectorAssembler

spark = SparkSession.builder.appName("FeatureEngineering").getOrCreate()

# 加载示例数据
df = spark.read.csv("user_profile.csv", header=True, inferSchema=True)

# 1. 特征类型识别:通过数据类型和唯一值数量判断
numeric_cols = [f.name for f in df.schema.fields if f.dataType.simpleString() in ('int', 'bigint', 'double', 'float')]
categorical_cols = [f.name for f in df.schema.fields if f.dataType.simpleString() == 'string']

# 进一步过滤:唯一值较少的数值列视为类别型
for col_name in numeric_cols[:]:
    distinct_count = df.select(countDistinct(col_name)).collect()[0][0]
    if distinct_count < 20:
        numeric_cols.remove(col_name)
        categorical_cols.append(col_name)

print("数值型特征:", numeric_cols)
print("类别型特征:", categorical_cols)

# 2. 数值型衍生:计算均值、标准差
df_derived = df.withColumn("income_log", log(col("income") + 1)) \
               .withColumn("debt_ratio", col("debt") / (col("income") + 1))

# 3. 类别型编码:StringIndexer + OneHotEncoder
indexer = StringIndexer(inputCols=categorical_cols,
                        outputCols=[c + "_index" for c in categorical_cols])
df_indexed = indexer.fit(df_derived).transform(df_derived)

encoder = OneHotEncoder(inputCols=[c + "_index" for c in categorical_cols],
                        outputCols=[c + "_ohe" for c in categorical_cols])
df_encoded = encoder.fit(df_indexed).transform(df_indexed)

df_encoded.select("income_log", "debt_ratio", *[c + "_ohe" for c in categorical_cols]).show(5)
实操要点:
  • 使用 inferSchema=True 自动推断类型,但需人工复核(如“用户ID”可能被误判为数值型)。
  • 类别型特征在编码前建议先做 StringIndexer 处理缺失值(设置 handleInvalid="keep")。
  • 时序型衍生建议使用 groupBy("user_id").agg(...) 配合 window 函数,注意分区键选择以避免数据倾斜。

4.5 特征重要性初探:Spark 树模型示例

使用 Spark MLlib 的随机森林模型快速评估特征重要性。

from pyspark.ml.classification import RandomForestClassifier
from pyspark.ml.feature import VectorAssembler

# 组装特征向量
feature_cols = numeric_cols + [c + "_ohe" for c in categorical_cols]
assembler = VectorAssembler(inputCols=feature_cols, outputCol="features")
df_vec = assembler.transform(df_encoded)

# 训练随机森林
rf = RandomForestClassifier(featuresCol="features", labelCol="label",
                            numTrees=100, maxDepth=10, seed=42)
model = rf.fit(df_vec)

# 获取特征重要性
importance = model.featureImportances
print("特征重要性向量:", importance)

# 将重要性映射到特征名
feature_importance_list = [(feature_cols[i], importance[i]) for i in range(len(feature_cols))]
sorted_importance = sorted(feature_importance_list, key=lambda x: x[1], reverse=True)
print("Top 5 重要特征:")
for name, imp in sorted_importance[:5]:
    print(f"  {name}: {imp:.4f}")
结果解读:输出中重要性最高的特征通常对应强风控信号。例如“近 30 天逾期次数”重要性为 0.32,说明该特征对区分好坏用户贡献最大。建议结合业务逻辑验证,避免过拟合到噪声特征。