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 特征分层架构图

下图展示了从原始数据到模型特征的三层加工链路,以及各层之间的依赖关系。

风控特征分层架构图 原始数据源 用户表 / 订单表 / 行为日志 基础层 ETL 清洗 · 字段标准化 衍生层 四则运算 · 逻辑组合 聚合层 滑动窗口 · 时间衰减 模型特征宽表 训练 / 推理输入 加工方向 从左到右:数据 → 特征 从上到下:基础 → 聚合

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),并设置合理的生命周期管理策略。聚合层特征通常需要每日增量更新,基础层和衍生层可按需全量刷新。

1.6 本章小结