4、样本切分原则:避免未来信息泄露、训练集/验证集/测试集的时间顺序划分、滑动窗口与扩展窗口

在信用额度定价模型中,时间序列数据的样本切分与独立同分布(i.i.d.)数据有本质区别。错误的切分方式会导致未来信息泄露,使模型在离线验证时表现优异,上线后却大幅衰退。本章围绕时序样本切分的三大核心原则展开:避免未来信息泄露、时间顺序划分、滑动窗口与扩展窗口。

4.1 未来信息泄露:时序建模的头号风险

未来信息泄露是指模型在训练时接触到了“未来”的真实标签或特征,导致验证指标失真。在信用额度定价中,常见泄露场景包括:

  • 全局归一化:使用全量数据的均值/标准差对特征做标准化,相当于用未来数据调整过去样本。
  • 标签穿越:将未来逾期标签直接关联到当前时间点的特征上,而未做时间对齐。
  • 特征混用:使用未来时间点的宏观指标(如下月CPI)作为当前样本的输入特征。
⚠️ 风险提示: 未来信息泄露在离线AUC上可能带来0.05~0.10的虚高提升,但上线后实际KS往往下降30%以上。这是时序建模中最隐蔽、破坏性最大的错误之一。

4.2 时间顺序划分:训练集 → 验证集 → 测试集

时序样本必须严格按照时间先后顺序进行划分,禁止随机打乱。标准划分策略如下:

数据集 时间范围 用途 占比参考
训练集 最早(如 T₀ ~ T₃) 模型参数学习 60% ~ 70%
验证集 中间(如 T₃ ~ T₄) 超参数调优、早停 15% ~ 20%
测试集 最晚(如 T₄ ~ T₅) 最终性能评估 15% ~ 20%

划分时需注意:

  • 验证集必须位于训练集之后,测试集位于验证集之后。
  • 禁止使用未来数据做交叉验证(如K-Fold),应使用时间序列交叉验证。
  • 每个数据集内部保持时间连续性,不跳跃、不混排。
💡 实践建议: 在信用额度定价项目中,建议以“月”为最小时间单位进行切分。例如:训练集使用2023年1~6月,验证集使用2023年7~8月,测试集使用2023年9~10月。

4.3 滑动窗口与扩展窗口:两种主流时序切分策略

当需要多次验证模型在时间轴上的稳定性时,滑动窗口和扩展窗口是两种标准方法。下图展示了二者的核心差异:

滑动窗口与扩展窗口对比图 滑动窗口 vs 扩展窗口:时序样本切分策略 滑动窗口(固定窗口大小) 训练集 T1~T2 验证 测试 训练集 T2~T3 验证 测试 训练集 T3~T4 验证 测试 窗口大小固定(如3个月) 每次整体向后滑动1个时间步 扩展窗口(窗口大小递增) 训练集 T1~T2 验证 测试 训练集 T1~T3 验证 测试 训练集 T1~T4 验证 测试 窗口大小随训练轮次递增 保留全部历史数据,仅向后扩展 时间轴 →

4.4 两种窗口策略的对比与选择

对比维度 滑动窗口 扩展窗口
窗口大小 固定(如3个月) 递增(从3个月到12个月)
训练数据量 每轮相同 每轮递增
对概念漂移的敏感度 高(仅用近期数据) 低(包含历史数据)
计算开销 较低 较高(数据量持续增长)
适用场景 快速迭代、短期预测 长期趋势建模、稳定性评估
📌 关键决策: 在信用额度定价中,如果业务策略频繁调整(如每季度更新额度模型),推荐使用滑动窗口;如果希望模型学习长期信用行为规律,且数据量充足,推荐使用扩展窗口。实践中常将两者结合:用扩展窗口训练最终模型,用滑动窗口做回测验证。

4.5 时序切分代码示例(Python)

以下代码展示了如何使用 TimeSeriesSplit 实现滑动窗口与扩展窗口切分:

import numpy as np
from sklearn.model_selection import TimeSeriesSplit

# 模拟时间序列数据(共24个月)
n_months = 24
X = np.random.randn(n_months, 5)
y = np.random.randint(0, 2, n_months)

# 滑动窗口:固定训练集大小为12个月,验证集3个月,测试集3个月
tscv_sliding = TimeSeriesSplit(
    n_splits=3,
    max_train_size=12,   # 固定窗口大小
    test_size=3
)

print("=== 滑动窗口切分 ===")
for i, (train_idx, test_idx) in enumerate(tscv_sliding.split(X)):
    print(f"Fold {i+1}: 训练集 {train_idx[0]}-{train_idx[-1]}, 测试集 {test_idx[0]}-{test_idx[-1]}")

# 扩展窗口:不限制训练集大小,逐步扩展
tscv_expanding = TimeSeriesSplit(
    n_splits=3,
    max_train_size=None,  # 不限制,逐步扩展
    test_size=3
)

print("\n=== 扩展窗口切分 ===")
for i, (train_idx, test_idx) in enumerate(tscv_expanding.split(X)):
    print(f"Fold {i+1}: 训练集 {train_idx[0]}-{train_idx[-1]}, 测试集 {test_idx[0]}-{test_idx[-1]}")

4.6 实践检查清单

在信用额度定价项目中,每次进行样本切分前,请逐项确认:

  1. 时间顺序:训练集、验证集、测试集是否严格按时间先后排列?
  2. 无未来信息:特征工程中是否使用了未来时间点的数据(如未来宏观指标)?
  3. 窗口策略:是否根据业务迭代频率选择了合适的窗口类型?
  4. 验证集独立性:验证集是否完全独立于训练集,且位于训练集之后?
  5. 测试集隔离:测试集是否仅在最终评估时使用,从未参与训练或调参?
💡 进阶技巧: 对于信用额度定价模型,建议在滑动窗口或扩展窗口的基础上,额外保留一个“冷启动”测试集(如最新3个月数据),专门用于评估模型在最新市场环境下的表现。这有助于提前发现概念漂移。