3. 训练集切分基础:时间序列切分、随机切分、分层抽样切分的基本原理
在信用额度定价模型中,训练集的切分方式直接影响模型对未来收益的预测能力与稳定性。错误的切分策略会导致时间穿越、样本偏差或收益估计失真。本节系统阐述三种核心切分方法的基本原理、适用场景与数学约束。
3.1 三种切分方法的本质差异
| 切分方法 | 核心假设 | 数据顺序敏感性 | 类别平衡保证 | 典型应用场景 |
|---|---|---|---|---|
| 时间序列切分 | 未来分布与过去存在时序依赖 | 强(必须保留时间顺序) | 不保证 | 贷后收益预测、滚动率建模 |
| 随机切分 | 样本独立同分布(i.i.d.) | 无 | 不保证 | A/B测试对照、特征筛选基线 |
| 分层抽样切分 | 关键分层变量在训练/测试集分布一致 | 无 | 强制保证 | 信用评分卡、违约率极低场景 |
关键原则:信用额度定价中,时间序列切分是默认首选。随机切分仅适用于无时间依赖的特征工程验证,分层抽样切分用于处理严重类别不平衡(如违约率 < 2%)时的收益估计。
3.2 时间序列切分(Time Series Split)
时间序列切分严格保留数据的时间顺序,训练集只使用历史数据,测试集使用未来数据。这是信用额度定价中最符合业务逻辑的切分方式,因为模型上线后只能基于过去预测未来。
3.2.1 切分规则
- 固定窗口切分:选取连续时间区间作为训练集,后续连续区间作为测试集。例如:使用2023年1月-6月数据训练,2023年7月-9月数据测试。
- 滚动窗口切分:训练集窗口随时间向前滑动,每次保留固定长度的时间窗口。例如:训练集窗口长度6个月,每次向前滚动1个月,生成多个训练/测试对。
- 扩展窗口切分:训练集起点固定,终点随时间扩展。例如:训练集从2023年1月开始,依次扩展至2月、3月……测试集始终为下一个完整月份。
3.2.2 时间序列切分流程图
实践建议:在信用额度定价中,推荐使用扩展窗口切分。因为随着时间推移,训练数据不断积累,模型能学习到更完整的宏观经济周期与客群行为演变模式。滚动窗口可能丢弃早期有价值的历史违约样本。
3.3 随机切分(Random Split)
随机切分将全部样本按比例(通常 70%/20%/10% 或 80%/20%)随机分配到训练集、验证集和测试集。该方法假设所有样本来自同一分布且相互独立。
3.3.1 适用条件与风险
- 适用条件:数据无时间结构、无群体结构(如同一客户多次借贷)、仅用于特征重要性排序或基线模型对比。
- 主要风险:在信用额度定价中,随机切分极易造成时间穿越——用未来的样本信息预测过去的收益表现,导致模型在线上表现远低于离线测试。
- 数学表达:设总样本量为 N,训练集比例 p,则训练集样本数 = N × p,测试集样本数 = N × (1-p)。每个样本被选入训练集的概率为 p,且相互独立。
避坑提醒:如果数据中包含同一客户在不同时间点的多次借贷记录,随机切分可能导致同一客户的样本同时出现在训练集和测试集中,造成信息泄露。此时应使用客户级分组切分(Group Split)替代随机切分。
3.4 分层抽样切分(Stratified Split)
分层抽样切分在保持训练集与测试集比例的同时,强制保证关键分层变量(如违约标签、信用等级、产品类型)在训练集和测试集中的分布与原始数据集一致。
3.4.1 分层切分流程
- 定义分层变量:通常选择目标变量(违约/非违约)或对收益影响最大的类别变量。
- 划分层(Strata):将全部样本按分层变量的取值划分为互斥的子集。例如:违约样本层、非违约样本层。
- 层内随机切分:在每个层内独立进行随机切分,保持各层训练/测试比例相同。
- 合并子集:将所有层的训练子集合并为最终训练集,测试子集合并为最终测试集。
3.4.2 分层抽样 vs 随机切分对比表
| 对比维度 | 随机切分 | 分层抽样切分 |
|---|---|---|
| 违约样本在测试集中的比例 | 可能偏离原始比例 ±30% | 与原始比例严格一致 |
| 低违约率场景(< 1%) | 测试集可能包含 0 个违约样本 | 保证测试集包含与原始比例一致的违约样本 |
| 收益估计稳定性 | 方差大,单次切分结果不可靠 | 方差小,多次切分结果稳定 |
| 实现复杂度 | 低(一行代码) | 中(需指定分层变量) |
3.5 三种切分方法的决策流程
3.6 代码实现示例
以下代码展示三种切分方法在 Python 中的标准实现,使用 sklearn.model_selection 和 pandas。
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, TimeSeriesSplit
# 模拟信用额度定价数据
np.random.seed(42)
n_samples = 10000
df = pd.DataFrame({
'loan_date': pd.date_range('2023-01-01', periods=n_samples, freq='D'),
'credit_score': np.random.normal(650, 50, n_samples),
'default_flag': np.random.binomial(1, 0.03, n_samples), # 3% 违约率
'revenue': np.random.exponential(500, n_samples)
})
# ========== 1. 时间序列切分(扩展窗口) ==========
# 按日期排序
df = df.sort_values('loan_date').reset_index(drop=True)
# 定义切分点:前70%训练,后30%测试
split_idx = int(len(df) * 0.7)
train_ts = df.iloc[:split_idx]
test_ts = df.iloc[split_idx:]
print(f"时间序列切分 - 训练集: {train_ts.shape[0]} 样本, 测试集: {test_ts.shape[0]} 样本")
# ========== 2. 随机切分 ==========
train_rand, test_rand = train_test_split(
df, test_size=0.3, random_state=42
)
print(f"随机切分 - 训练集: {train_rand.shape[0]} 样本, 测试集: {test_rand.shape[0]} 样本")
# ========== 3. 分层抽样切分(按违约标签分层) ==========
train_strat, test_strat = train_test_split(
df, test_size=0.3, random_state=42,
stratify=df['default_flag'] # 按违约标签分层
)
# 验证分层效果
print(f"\n原始数据集违约率: {df['default_flag'].mean():.4f}")
print(f"随机切分测试集违约率: {test_rand['default_flag'].mean():.4f}")
print(f"分层切分测试集违约率: {test_strat['default_flag'].mean():.4f}")
输出解读:分层抽样切分能保证测试集的违约率与原始数据集高度一致(约 0.03),而随机切分在单次运行中可能偏离至 0.025 或 0.035。在信用额度定价中,这种偏差会导致收益估计的系统性误差,进而影响额度定价策略的可靠性。
3.7 切分方法选择清单
- 第一步:检查数据是否包含时间戳字段(贷款日期、申请日期、放款日期)。如果有,默认使用时间序列切分。
- 第二步:检查是否存在同一客户多次借贷。如果有,在时间序列切分基础上增加客户级分组约束。
- 第三步:检查目标变量(违约/非违约)的分布。如果违约率 < 5% 且无时间依赖,使用分层抽样切分。
- 第四步:仅当数据无时间结构、无客户重复、且目标变量分布均衡时,才使用随机切分。
- 第五步:无论使用哪种切分方法,始终在切分后验证训练集与测试集的关键特征分布是否一致。