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 天逾期次数”),可用于规则制定。
- 监控与漂移检测:若某特征重要性随时间显著下降,可能表明数据分布发生偏移或特征失效。
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,说明该特征对区分好坏用户贡献最大。建议结合业务逻辑验证,避免过拟合到噪声特征。