4、时间序列切分(上):按时间顺序划分训练/验证/测试集,避免未来信息泄露
在额度定价模型中,时间维度是最容易被忽视却又最具破坏力的泄露源。当模型使用未来的数据去“预测”过去的行为时,看似完美的 AUC 指标背后,是上线后断崖式下跌的区分能力。本节将系统讲解时间序列切分的核心原则与实操方法。
4.1 为什么额度定价必须使用时间序列切分
额度定价模型与普通分类模型有一个本质区别:样本之间存在时间依赖关系。用户的还款表现、收入变化、信用评分都随时间动态演化,如果随机打乱数据,模型会学到“未来模式”,导致严重的过拟合。
| 切分方式 | 训练集 | 验证集 | 测试集 | 泄露风险 |
|---|---|---|---|---|
| 随机切分 | 任意时间 | 任意时间 | 任意时间 | 极高 — 未来信息污染训练 |
| 时间序列切分 | 最早时间 | 中间时间 | 最晚时间 | 极低 — 严格按时间隔离 |
| 分层时间切分 | 按时间+风险分层 | 按时间+风险分层 | 按时间+风险分层 | 低 — 兼顾时间与分布 |
关键原则:任何时刻,训练集的时间戳必须严格早于验证集,验证集的时间戳必须严格早于测试集。这是额度定价模型上线后保持稳定的第一道防线。
4.2 时间序列切分的三种标准模式
根据数据量和业务场景的不同,常用的时间序列切分有三种模式。以下按推荐优先级排列:
- 固定时间窗口切分 — 适用于数据量充足、业务稳定的场景。设定一个固定长度的训练窗口(如最近12个月),验证集为接下来的3个月,测试集为再接下来的3个月。
- 滚动时间窗口切分 — 适用于数据量有限、需要充分利用历史数据的场景。训练窗口随时间滚动,每次切分都保留最新的验证和测试数据。
- 时间序列交叉验证 — 适用于样本量较小、需要更稳健评估的场景。按时间顺序依次切分多组训练/验证对,最终取平均指标。
实践建议:对于额度定价模型,推荐优先使用固定时间窗口切分。滚动窗口和交叉验证虽然能更充分地利用数据,但容易引入时间相关性,导致验证指标过于乐观。
4.3 时间序列切分流程详解
以下是一个完整的固定时间窗口切分实现流程,包含数据排序、时间边界划分和样本分配三个核心步骤:
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
def time_series_split(df, date_col, train_end, val_end, test_end):
"""
按时间顺序切分训练/验证/测试集
Parameters:
df: 原始数据框
date_col: 时间列名
train_end: 训练集截止日期
val_end: 验证集截止日期
test_end: 测试集截止日期
Returns:
train_df, val_df, test_df
"""
# 1. 按时间排序
df = df.sort_values(by=date_col).reset_index(drop=True)
# 2. 划分时间边界
train_df = df[df[date_col] < train_end].copy()
val_df = df[(df[date_col] >= train_end) & (df[date_col] < val_end)].copy()
test_df = df[(df[date_col] >= val_end) & (df[date_col] < test_end)].copy()
# 3. 检查各集合样本量
print(f"训练集: {len(train_df)} 条, 时间范围: {train_df[date_col].min()} ~ {train_df[date_col].max()}")
print(f"验证集: {len(val_df)} 条, 时间范围: {val_df[date_col].min()} ~ {val_df[date_col].max()}")
print(f"测试集: {len(test_df)} 条, 时间范围: {test_df[date_col].min()} ~ {test_df[date_col].max()}")
return train_df, val_df, test_df
# 使用示例
train_end = datetime(2023, 6, 30)
val_end = datetime(2023, 9, 30)
test_end = datetime(2023, 12, 31)
train, val, test = time_series_split(loan_data, 'apply_date', train_end, val_end, test_end)
常见陷阱:切分时务必检查时间边界是否严格对齐。如果训练集和验证集之间存在时间重叠(哪怕只有1天),模型就可能学到未来信息。建议在切分后打印各集合的时间范围进行人工校验。
4.4 时间序列切分与额度定价的适配关系
额度定价模型通常使用申请日作为时间锚点。这是因为用户的额度决策完全基于申请时的信息,后续的还款表现只是标签。如果使用表现期结束日作为时间锚点,就会引入严重的未来信息泄露。
核心规则:时间锚点 = 申请日(或审批日),而非表现期结束日。标签(是否逾期、逾期天数)使用申请日后固定窗口(如6个月)内的表现。
下表总结了不同时间锚点选择对模型的影响:
| 时间锚点 | 训练集含义 | 验证集含义 | 泄露风险 | 推荐度 |
|---|---|---|---|---|
| 申请日 | 早期申请的用户 | 中期申请的用户 | 无 | 强烈推荐 |
| 审批日 | 早期审批的用户 | 中期审批的用户 | 极低 | 推荐 |
| 表现期结束日 | 早期结束表现期的用户 | 中期结束表现期的用户 | 极高 | 禁止使用 |
| 放款日 | 早期放款的用户 | 中期放款的用户 | 低 | 可接受 |
4.5 时间序列切分的可视化流程
下图展示了固定时间窗口切分的完整流程,从原始数据到最终的三集合划分:
4.6 时间序列切分的常见误区与避坑指南
- 误区一:使用随机种子打乱数据后再切分 — 这会完全破坏时间顺序,导致未来信息泄露。正确做法是先按时间排序,再切分。
- 误区二:切分后不检查时间范围 — 数据中可能存在异常时间戳(如未来日期、历史补录数据),导致切分边界错位。建议切分后打印 min/max 时间进行校验。
- 误区三:验证集和测试集时间窗口过短 — 如果窗口内样本量不足,评估指标方差会很大。建议验证集和测试集至少包含 3 个月的数据,且样本量不低于 5000 条。
- 误区四:忽略节假日和业务周期 — 额度定价模型受季节性影响显著(如双十一、春节)。切分时应确保训练集和验证集覆盖完整的业务周期,避免只在淡季训练、旺季验证。
进阶技巧:对于存在明显季节性的业务,可以在时间序列切分的基础上增加周期性对齐。例如,训练集使用 2022 年 1-12 月的数据,验证集使用 2023 年 1-3 月的数据,测试集使用 2023 年 4-6 月的数据,确保每个集合都包含完整的季度周期。
4.7 时间序列切分与额度定价模型的联动关系
下图展示了时间序列切分在额度定价模型全流程中的位置,以及它如何影响后续的模型训练、验证和上线部署:
本节核心结论:时间序列切分是额度定价模型的第一道防线。它确保模型在训练时看不到未来的任何信息,从而在真实业务场景中保持稳定的区分能力。下一节将深入讨论时间序列切分的进阶技巧,包括如何处理不规则时间间隔、多时间锚点对齐以及大规模数据下的高效切分策略。