4、数据准备与划分:训练集/验证集/测试集划分、时间序列数据切分、分层抽样与随机抽样

在 Champion Challenger 对比分析中,数据划分的合理性直接决定模型评估的公正性与泛化能力的可信度。本章将系统讲解三种核心划分策略:随机抽样、分层抽样以及时间序列数据切分,并明确训练集、验证集、测试集各自的作用与划分比例。

4.1 训练集 / 验证集 / 测试集的作用与比例

在对比 Champion 与 Challenger 模型时,必须将数据划分为三个独立子集,避免信息泄露导致的评估偏差。

数据集 用途 典型比例 关键要求
训练集 模型参数学习、特征权重拟合 60% – 80% 数据量充足,覆盖主要分布模式
验证集 超参数调优、模型选择、早停判断 10% – 20% 与训练集分布一致,不参与训练
测试集 最终模型泛化能力评估、Champion vs Challenger 对比 10% – 20% 完全隔离,仅在最终评估时使用一次
实践要点:在 Champion Challenger 场景中,测试集必须严格保留至最终对比阶段。若在调参过程中反复使用测试集,会导致“数据窥探偏差”,使对比结果过于乐观。

4.2 随机抽样与分层抽样

随机抽样是最基础的划分方式,适用于数据分布均匀的场景。但当目标变量存在类别不平衡时,随机抽样可能导致验证集或测试集中某些类别缺失,从而无法真实反映模型在少数类上的表现。

分层抽样 通过保持每个子集中目标变量的类别比例与原始数据集一致,解决了上述问题。在 Champion Challenger 对比中,分层抽样能确保两个模型在相同的类别分布下进行公平比较。

选择建议:
  • 分类任务:优先使用分层抽样,按目标变量分层。
  • 回归任务:若目标变量分布严重偏态,可对目标值分桶后分层抽样。
  • 数据量极大(>100万行):随机抽样与分层抽样差异很小,可选用随机抽样以提升效率。
随机抽样 vs 分层抽样流程对比 原始数据集 随机抽样(无约束) 可能丢失少数类样本 分层抽样(按类别比例) 各子集类别比例与全集一致 随机划分结果 训练集: 类别A 70%, 类别B 30% 验证集: 类别A 85%, 类别B 15% 分层划分结果 训练集: 类别A 80%, 类别B 20% 验证集: 类别A 80%, 类别B 20%

4.3 时间序列数据切分

时间序列数据具有时间依赖性,不能使用随机抽样或分层抽样,否则会造成未来信息泄露。在 Champion Challenger 对比中,若使用时间序列数据(如股票价格、传感器读数、用户行为日志),必须采用时间顺序切分。

常见的时间序列切分方法包括:

  • 固定时间窗口切分:按时间顺序取前 70% 为训练集,中间 15% 为验证集,最后 15% 为测试集。
  • 滚动窗口切分:适用于在线学习或持续对比场景,训练集随时间推移滚动扩展。
  • 时间序列交叉验证:按时间顺序依次划分多个训练/验证对,每个验证集始终在训练集之后。
常见错误:在时间序列数据上使用随机抽样或 K 折交叉验证(不按时间顺序)会导致模型“看到未来”,在 Champion Challenger 对比中产生虚假的高分,实际部署时性能大幅下降。
时间序列数据切分:固定时间窗口 时间 → 训练集(70%) 验证集(15%) 测试集(15%) t₀ t₁ t₂ t₃ 关键原则:验证集和测试集的时间戳必须严格晚于训练集 禁止使用未来数据预测过去,否则 Champion Challenger 对比结果无效

4.4 数据划分的代码实现

以下代码展示了在 Python 中如何实现随机抽样、分层抽样以及时间序列切分,适用于 Champion Challenger 对比分析的数据准备阶段。

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, StratifiedShuffleSplit
from datetime import datetime

# ========== 1. 随机抽样 ==========
def random_split(df, target_col, test_size=0.2, val_size=0.1, random_state=42):
    """随机划分训练集、验证集、测试集"""
    # 先分出测试集
    train_val, test = train_test_split(
        df, test_size=test_size, random_state=random_state
    )
    # 从剩余数据中分出验证集
    val_ratio = val_size / (1 - test_size)
    train, val = train_test_split(
        train_val, test_size=val_ratio, random_state=random_state
    )
    return train, val, test

# ========== 2. 分层抽样 ==========
def stratified_split(df, target_col, test_size=0.2, val_size=0.1, random_state=42):
    """按目标变量分层划分训练集、验证集、测试集"""
    # 先分出测试集(分层)
    train_val, test = train_test_split(
        df, test_size=test_size, stratify=df[target_col], random_state=random_state
    )
    # 从剩余数据中分出验证集(分层)
    val_ratio = val_size / (1 - test_size)
    train, val = train_test_split(
        train_val, test_size=val_ratio,
        stratify=train_val[target_col], random_state=random_state
    )
    return train, val, test

# ========== 3. 时间序列切分 ==========
def temporal_split(df, date_col, train_ratio=0.7, val_ratio=0.15):
    """按时间顺序切分训练集、验证集、测试集"""
    df = df.sort_values(by=date_col).reset_index(drop=True)
    n = len(df)
    train_end = int(n * train_ratio)
    val_end = int(n * (train_ratio + val_ratio))
    train = df.iloc[:train_end]
    val = df.iloc[train_end:val_end]
    test = df.iloc[val_end:]
    return train, val, test

# ========== 使用示例 ==========
# 假设 df 是包含特征和目标变量的 DataFrame
# train, val, test = stratified_split(df, target_col='churn_label')
# train_t, val_t, test_t = temporal_split(df, date_col='transaction_date')

4.5 划分策略选择矩阵

根据数据类型和业务场景,选择合适的划分策略:

数据类型 推荐策略 原因 Champion Challenger 注意事项
独立同分布数据(IID) 随机抽样 数据无依赖关系,简单高效 确保随机种子固定,对比可复现
类别不平衡数据 分层抽样 保持各子集类别比例一致 两个模型在相同类别分布下对比
时间序列数据 时间顺序切分 避免未来信息泄露 Champion 和 Challenger 使用完全相同的切分时间点
多源异构数据 按来源分层 + 随机 保证各数据源在子集中均匀分布 对比时需控制数据源分布不变
最终检查清单:
  • 测试集是否从未参与任何训练或调参过程?
  • 时间序列数据是否按时间顺序切分,而非随机?
  • 分层抽样时,目标变量的类别比例在各子集中是否与全集一致?
  • Champion 和 Challenger 是否使用完全相同的数据划分?