3. 数据准备与校验:评估所需数据源、数据质量检查、特征一致性校验
收益评估的可靠性,首先取决于输入数据的质量。在Spark模型评估流程中,数据准备与校验是承上启下的关键环节——上游承接模型训练与离线存储,下游直接影响收益计算与归因分析的准确性。本章围绕评估所需的数据源、数据质量检查以及特征一致性校验三个核心模块展开,确保评估结果可追溯、可复现、可信赖。
3.1 评估所需数据源
模型收益评估通常需要融合多个数据源,涵盖模型预测结果、用户行为日志、业务财务数据以及实验分层信息。下表列出典型的数据源及其关键字段:
| 数据源 | 存储格式 | 关键字段 | 更新频率 | 用途 |
|---|---|---|---|---|
| 模型预测表 | Parquet | user_id, model_version, score, predict_time | 每日全量 | 计算模型评分与分组 |
| 用户行为日志 | ORC | user_id, action_type, action_time, amount | 实时/小时级 | 转化率、客单价、留存 |
| 财务流水表 | Parquet | order_id, user_id, revenue, cost, profit | T+1 | 收入、成本、毛利 |
| 实验分层表 | Hive 表 | user_id, experiment_id, group_id, bucket | 静态/每日 | AB实验分组与分流校验 |
| 特征快照表 | Parquet | user_id, feature_name, feature_value, snapshot_date | 每日全量 | 特征一致性校验 |
数据源对齐原则:所有数据源必须基于同一时间窗口(如UTC+8自然日)进行对齐,避免因时区或调度延迟导致的数据漂移。建议在ETL阶段统一使用
ds 分区字段,并记录数据生成时间戳 etl_time。
3.2 数据质量检查
数据质量检查是评估流程的第一道防线。Spark环境下,建议从完整性、准确性、一致性和时效性四个维度进行自动化校验。以下为典型检查项与Spark实现示例:
3.2.1 完整性检查
- 空值率:核心字段(如 user_id、score、revenue)空值率不得超过 1%。
- 记录数波动:当日记录数与历史7日均值偏差不超过 ±20%。
- 主键唯一性:user_id + model_version + ds 组合主键无重复。
// Spark Scala 示例:空值率与记录数波动检查
val df = spark.read.parquet("hdfs://path/to/prediction_table/ds=2025-03-01")
val nullRate = df.filter($"user_id".isNull || $"score".isNull).count().toDouble / df.count()
val recordCount = df.count()
val avgCount7d = spark.sql("""
SELECT AVG(cnt) as avg_cnt FROM (
SELECT ds, COUNT(*) as cnt FROM prediction_table
WHERE ds BETWEEN '2025-02-22' AND '2025-02-28' GROUP BY ds
)
""").first().getDouble(0)
assert(nullRate < 0.01, s"空值率异常: $nullRate")
assert(math.abs(recordCount - avgCount7d) / avgCount7d < 0.2, s"记录数波动异常: $recordCount vs $avgCount7d")
3.2.2 准确性检查
- 数值范围:score 应在 [0,1] 区间;revenue 和 profit 不应为负数(特殊业务除外)。
- 分布合理性:评分分布不应出现单值占比超过 90% 的异常集中情况。
3.2.3 时效性检查
- 数据延迟:T+1 数据必须在次日 10:00 前就绪;实时数据延迟不超过 30 分钟。
- 分区连续性:最近 7 天分区必须全部存在,无缺失。
常见踩坑点:财务流水表常因跨天结算导致 revenue 字段在 T+1 日才更新,若直接与 T 日预测表 join,会造成收益低估。建议对财务数据采用“T+2”延迟对齐策略,或使用业务发生时间(而非入账时间)作为关联键。
3.3 特征一致性校验
特征一致性校验是评估环节中最容易被忽视、但影响最大的步骤。模型训练时的特征分布与评估时的特征分布若不一致,收益评估将产生系统性偏差。校验分为两个层面:
3.3.1 离线特征 vs 在线特征一致性
对比模型训练时使用的离线特征快照与评估时从线上日志提取的特征,确保特征构造逻辑、缺失值填充方式、分桶边界完全一致。
// 特征分布对比:使用 Spark 的 ApproximateQuantile 进行分位数校验
val offlineFeatures = spark.read.parquet("feature_snapshot/ds=2025-02-28")
val onlineFeatures = spark.read.parquet("online_feature_log/ds=2025-03-01")
val offlineQuantiles = offlineFeatures.stat.approxQuantile("feature_amount", Array(0.1, 0.5, 0.9), 0.01)
val onlineQuantiles = onlineFeatures.stat.approxQuantile("feature_amount", Array(0.1, 0.5, 0.9), 0.01)
// 允许相对误差 5%
val tolerance = 0.05
offlineQuantiles.zip(onlineQuantiles).foreach { case (off, on) =>
assert(math.abs(off - on) / math.abs(off + 1e-6) < tolerance,
s"特征分位数偏差过大: offline=$off, online=$on")
}
3.3.2 时间窗口特征一致性
评估期与训练期的特征分布应保持稳定。使用 PSI(群体稳定性指标)量化分布偏移:
| PSI 区间 | 稳定性判断 | 建议操作 |
|---|---|---|
| PSI < 0.1 | 稳定 | 无需处理 |
| 0.1 ≤ PSI < 0.25 | 轻度偏移 | 标记特征,评估影响 |
| PSI ≥ 0.25 | 严重偏移 | 停止评估,排查原因 |
// PSI 计算核心逻辑(分箱后)
def computePSI(expected: Array[Double], actual: Array[Double]): Double = {
expected.zip(actual).map { case (e, a) =>
val eAdj = if (e == 0) 1e-6 else e
val aAdj = if (a == 0) 1e-6 else a
(aAdj - eAdj) * math.log(aAdj / eAdj)
}.sum
}
最佳实践:将特征一致性校验封装为独立的 Spark Job,每日在评估流程启动前自动运行。一旦 PSI 或分位数校验失败,立即触发告警并阻断下游收益计算,避免无效评估结果扩散。
3.4 校验结果汇总与决策
完成上述检查后,需生成一份结构化的校验报告,明确每个检查项的通过状态。以下为报告模板示例:
| 检查类别 | 检查项 | 阈值 | 实际值 | 状态 |
|---|---|---|---|---|
| 完整性 | user_id 空值率 | < 1% | 0.03% | ✅ 通过 |
| 完整性 | 记录数波动 | ±20% | +5.2% | ✅ 通过 |
| 准确性 | score 范围 [0,1] | 100% | 99.98% | ✅ 通过 |
| 时效性 | T+1 数据就绪 | 10:00 前 | 09:47 | ✅ 通过 |
| 特征一致性 | PSI (feature_amount) | < 0.1 | 0.07 | ✅ 通过 |
| 特征一致性 | 分位数偏差 (feature_amount) | < 5% | 2.3% | ✅ 通过 |
自动化集成建议:将上述校验逻辑封装为 Spark 应用,通过调度平台(如 Airflow)每日定时执行。校验结果写入 Hive 审计表,并同步至企业微信/钉钉告警群。只有所有检查项均通过时,才触发下游收益评估 Job。