Spark实现风控特征分层与样本清洗实战 - 第1节
1.1 风控特征的定义与核心价值
在信贷、支付、营销等金融场景中,风控特征是指从原始业务数据中提取的、用于区分“好用户”与“坏用户”的数值化或类别化指标。它是风控模型(如评分卡、XGBoost、神经网络)的输入原料,直接决定了模型的上限。
核心公式: 风控模型效果 ≈ 特征质量 × 样本质量 × 算法调优。特征工程是风控建模中最耗时、最见功力的环节,通常占项目总工作量的 60%–80%。
风控特征的价值体现在三个层面:
- 区分度: 好的特征能清晰分离违约用户与正常用户,例如“近3个月逾期天数”比“注册天数”更具区分力。
- 稳定性: 特征分布随时间波动小,避免模型在跨时间验证时失效(PSI < 0.1 为佳)。
- 可解释性: 特征含义明确,便于业务方理解模型决策逻辑,满足监管合规要求。
1.2 特征分层体系:基础层 / 衍生层 / 聚合层
风控特征并非平铺直叙,而是按加工深度和时间粒度分为三个层次。分层设计有助于团队协作、复用计算逻辑、降低重复开发成本。
| 层次 | 定义 | 典型特征举例 | 加工方式 | 存储粒度 |
|---|---|---|---|---|
| 基础层 | 直接从原始数据清洗、标准化后得到的字段,几乎不做跨字段计算 | 年龄、性别、注册渠道、身份证号归属地、设备型号 | ETL 清洗、格式统一、缺失值填充 | 用户级 / 订单级 |
| 衍生层 | 基于基础字段进行四则运算、逻辑组合、时间差计算得到的特征 | 年龄分段(18-25/26-35/36+)、收入负债比、注册到首次借款天数 | SQL 表达式、UDF、Spark SQL 窗口函数 | 用户级 / 事件级 |
| 聚合层 | 对用户历史行为进行统计汇总,反映长期或近期行为模式 | 近30天借款次数、近6个月最大逾期天数、近90天平均消费金额 | 滑动窗口聚合、时间衰减加权、Spark 分组聚合 | 用户级(时间窗口) |
分层价值: 基础层保证数据可追溯;衍生层封装业务逻辑,避免重复计算;聚合层提供时序行为画像,是模型区分度的主要来源。在 Spark 中,三层特征可分别对应不同的 DataFrame 处理阶段,便于增量更新和回溯。
1.3 特征分层架构图
下图展示了从原始数据到模型特征的三层加工链路,以及各层之间的依赖关系。
1.4 分层设计的工程价值
在 Spark 大规模数据处理中,特征分层带来的工程收益非常明显:
- 复用性: 基础层特征只需计算一次,衍生层和聚合层可基于基础层增量计算,避免重复读取原始数据。
- 回溯能力: 分层存储使得历史特征可以按时间窗口重新聚合,支持模型上线后的离线回测和冠军/挑战者实验。
- 并行优化: 基础层适合宽表存储(Parquet + 分区裁剪);聚合层适合使用 Spark 的窗口函数和分组聚合,充分利用集群并行能力。
- 血缘追踪: 每层特征都记录上游依赖,当原始数据质量出现问题时,可以快速定位受影响的特征和模型。
常见误区: 不要将所有特征一股脑塞进同一张宽表。缺少分层会导致:① 特征计算逻辑混乱,难以维护;② 回溯历史时需要重跑全部逻辑,耗时巨大;③ 特征之间依赖关系不清晰,容易引入数据泄露(Data Leakage)。
1.5 分层特征在 Spark 中的典型实现模式
以下代码片段展示了三层特征在 Spark 中的典型处理流程(伪代码风格,仅示意分层逻辑):
// 1. 基础层:读取原始表,做简单清洗
val baseDF = spark.read.parquet("/data/raw/user_info")
.select("user_id", "age", "gender", "reg_channel")
.filter($"age".between(18, 100))
.na.fill("unknown", Seq("gender"))
// 2. 衍生层:基于基础字段做计算
val derivedDF = baseDF
.withColumn("age_group", when($"age" < 25, "18-25")
.when($"age" < 36, "26-35").otherwise("36+"))
.withColumn("is_high_risk_channel",
when($"reg_channel".isin("channel_A","channel_B"), 1).otherwise(0))
// 3. 聚合层:按用户分组,滑动窗口聚合
val aggDF = spark.read.parquet("/data/raw/order_log")
.filter($"order_time" >= date_sub(current_date(), 30))
.groupBy("user_id")
.agg(
count("order_id").alias("order_cnt_30d"),
sum("order_amount").alias("total_amount_30d"),
max("overdue_days").alias("max_overdue_30d")
)
// 4. 最终宽表:关联三层特征
val featureWideTable = derivedDF
.join(aggDF, Seq("user_id"), "left")
.select("user_id", "age_group", "is_high_risk_channel",
"order_cnt_30d", "total_amount_30d", "max_overdue_30d")
生产建议: 实际项目中,建议将三层特征分别写入不同的 Hive 分区表(如 base_features、derived_features、agg_features),并设置合理的生命周期管理策略。聚合层特征通常需要每日增量更新,基础层和衍生层可按需全量刷新。