4. 数据准备

贷后模型的时间外验证与稳定性回测,成败往往不取决于算法,而取决于数据准备是否严谨。很多团队在建模阶段指标漂亮,一到时间外回测就出现大幅衰减,根源通常不是模型本身,而是时间切片错位、观察期与表现期定义混乱、标签未成熟或样本选取有偏。本节围绕这五个要点,给出可落地的工程方法。

一、核心原理:时间维度是贷后验证的第一性约束

贷后模型预测的是"未来某段时间内是否发生违约/逾期"这类事件。因此每条样本天然带有三个时间坐标:观察点(Observation Point)、观察期(Observation Window)、表现期(Performance Window)。观察点是做决策的时刻,观察期用于采集该时刻可见的特征,表现期用于等待标签发生。

时间外验证的本质,是用"过去训练、未来验证"的方式模拟真实上线场景。若随机切分样本,会造成特征穿越和标签泄漏,回测结果虚高。正确做法是按时间轴做前向切片:训练集时间早于验证集,验证集早于测试集,且各集合之间保留足够的标签成熟缓冲带。

关键原则:任何进入模型的变量,其取值时间必须严格早于观察点;任何标签,其表现期必须完整落在数据可用范围内。违反任一条,回测即失真。

二、方法流程:从时间切片到样本落地

  1. 确定观察点频率:贷后监控常用月度或周度观察点。频率越高,样本越多,但相邻样本特征高度相关,需注意去重与抽样。
  2. 定义观察期长度:通常取观察点前3、6、12个月,用于计算逾期次数、用信率、还款行为等特征。观察期过长会引入陈旧信息,过短则特征不稳定。
  3. 定义表现期长度:根据产品账期与风险暴露节奏确定。短期现金贷常用30/60/90天,分期产品常用6/12期。表现期必须覆盖绝大多数违约发生的时间窗。
  4. 按时间切片划分数据集:训练、验证、测试按时间先后排列,并在边界处留出表现期长度的缓冲,避免标签重叠。
  5. 样本选取与抽样:确定准入客群、排除规则、好坏样本定义,必要时对多数类做下采样或对少数类做加权。
  6. 标签成熟度检查:只保留表现期已完整结束的样本,未成熟样本打标为"不确定"并排除。
  7. 数据清洗:处理缺失、异常、重复、穿越变量,输出建模宽表。

三、案例场景:某消费分期贷后模型的切片设计

假设某消费分期产品账期12期,数据覆盖2022-01至2024-06。团队要构建贷后早期预警模型,预测观察点后90天内是否发生M1+逾期。切片方案如下:

数据集观察点区间表现期标签成熟截止用途
训练集2022-01 ~ 2022-1290天2023-03拟合模型
验证集2023-01 ~ 2023-0690天2023-09调参、选阈值
测试集2023-07 ~ 2023-1290天2024-03时间外验证
监控集2024-01 ~ 2024-0390天2024-06稳定性回测

注意训练集与验证集之间、验证集与测试集之间均无表现期重叠,且每个集合的标签成熟截止日均早于数据截止日2024-06,保证标签完整。

四、标签成熟度与数据清洗要点

标签成熟度是贷后建模最容易被忽视的坑。若观察点为2024-05,表现期90天,则标签要到2024-08才成熟。在2024-06的数据快照中,这批样本的标签是"未成熟"的,若强行打标为"好客户",会系统性低估风险。

常见错误:把"表现期未结束但当前未逾期"的样本当作负样本。这会让模型学到"时间越近越安全"的伪规律,时间外回测时性能骤降。

数据清洗需重点关注以下清单:

  • 穿越变量剔除:任何在观察点之后产生的字段,如"未来最大逾期天数""结清日期"等,必须排除。
  • 缺失值处理:区分"真缺失"与"业务无此环节",前者可填充,后者应单独编码。
  • 异常值处理:对金额、比率类字段做分位数截断,避免极端值主导训练。
  • 重复样本去重:同一客户同一观察点只保留一条,防止样本权重失衡。
  • 标签一致性校验:核对逾期天数口径、M1/M2定义、核销与展期处理规则。
  • 时间字段格式统一:全部转为标准日期类型,避免字符串比较导致切片错误。

下面是一段标签成熟度检查与切片生成的伪代码示例:

# 假设 df 含 customer_id, obs_date, perf_days, dpd_max, snapshot_date
df['perf_end'] = df['obs_date'] + pd.to_timedelta(df['perf_days'], unit='D')
df['mature'] = df['perf_end'] <= df['snapshot_date']

# 仅保留成熟样本
df_mature = df[df['mature']].copy()

# 定义标签:表现期内最大逾期天数 >= 30 为坏
df_mature['label'] = (df_mature['dpd_max'] >= 30).astype(int)

# 按观察点时间切片
train = df_mature[df_mature['obs_date'] < '2023-01-01']
valid = df_mature[(df_mature['obs_date'] >= '2023-01-01') & (df_mature['obs_date'] < '2023-07-01')]
test  = df_mature[df_mature['obs_date'] >= '2023-07-01']

print('成熟样本占比:', df['mature'].mean())
print('训练/验证/测试:', len(train), len(valid), len(test))

五、误区复盘

误区一:随机切分代替时间切分。随机切分让未来信息泄漏到训练集,回测AUC虚高0.05以上,上线后迅速回落。

误区二:表现期一刀切。不同产品、不同客群的违约节奏不同,统一用90天可能漏掉长账期产品的后期风险。应按产品分层定义表现期。

误区三:忽视样本选取偏差。只取"已放款"客户建模,会遗漏被拒绝客群,导致模型在审批场景下无法直接使用。需明确模型应用场景,决定是否引入拒绝推断。

误区四:清洗时删除过多样本。过度清洗会改变客群分布,使训练集与真实客群脱节。清洗规则应记录、可复现,并评估对样本量的影响。

建议在数据准备阶段产出一份"数据字典+切片说明+清洗日志"三件套,作为时间外验证的可审计依据。后续每次回测都应基于同一套定义,确保结果可比。

综上,数据准备不是建模前的杂活,而是决定时间外验证可信度的地基。把时间切片、观察期与表现期、样本选取、标签成熟度和数据清洗这五件事做扎实,后续的稳定性回测才有意义。