学习目标
学习本课后,你将能够:
- 解释训练集、验证集和测试集的不同作用,以及为什么测试集不能用于调参。
- 根据实际部署场景选择随机、分层、分组或按时间划分。
- 识别由重复记录、分组、时间、特征和预处理造成的数据泄漏。
- 使用验证集、交叉验证和最终独立测试建立可复现的评估流程。
1. 为什么要划分数据?
模型不能只在见过的记录上表现良好。我们需要估计它在部署后遇到新观测时的表现,这就是泛化能力的检验。
在训练样本上评估通常过于乐观,因为模型已经针对这些数据进行了调整。划分数据是在模拟开发阶段可见的过去与尚未知晓的未来之间的边界。
2. 三种数据集,三种不同职责
训练集(train)用于拟合模型参数和学习数据转换规则。
验证集(validation)用于开发阶段的选择:比较模型、选择超参数、特征、分类阈值及其他设置。
测试集(test)用于对已经选定的完整流程进行独立的最终评估。它用于估计预期表现,而不是再次调参。
3. 为什么不能反复查看测试集
每次在看到测试指标后修改模型,测试集就间接参与了开发。即使优化算法没有在测试记录上拟合,开发者也已经利用测试结果选择方案。
这会产生自适应过拟合:在许多候选方案中,逐渐选出恰好在当前测试样本上表现较好的方案,最终指标因此偏高。如果测试集已经被反复使用,应重新收集独立测试集,或明确说明评估局限。
4. 随机划分与分层划分
当观测近似独立同分布且行顺序没有意义时,可以随机划分。在类别不平衡的分类任务中,普通随机划分可能导致不同子集的类别比例差异很大。
分层划分会在训练、验证和测试子集中保留近似的类别比例。它有助于处理稀有类别,但不能消除记录之间的依赖、时间泄漏或重复数据。分层并不是保证评估公平的万能方法。
5. 分组划分:避免模型认出已有对象
假设医学影像数据集中每位患者有几十张图像。如果同一患者的部分图像进入训练集,其他图像进入测试集,模型可能识别患者的个体特征。此时指标回答的是“能否识别熟悉的患者”,而不是“能否处理新患者”。
分组划分会把同一组的所有记录放在同一个子集中。组可以是患者、客户、建筑、设备、生产批次或源文档。即使组 ID 不适合作为普通特征输入模型,它仍可能是划分数据所必需的字段。
6. 按时间划分:用过去预测未来
对于需求、能耗、故障或价格预测,随机打乱往往是不合适的。它可能让较晚的记录进入训练集、较早的记录进入测试集,使模型间接学习未来信息。
应使用时间边界:较早时期用于训练,之后时期用于验证,最新时期用于测试。如果模型采用滚动窗口定期重训,应评估多个连续时间窗口。还要考虑预测跨度、数据发布延迟和季节性。
7. 数据泄漏:信息越过了边界
当拟合或选择过程使用了真实预测时不可获得的信息,或使用了本应留给独立评估的信息,就发生了数据泄漏。常见原因包括:不同子集中出现重复记录;评估新对象时同一对象同时出现在训练和测试中;时间序列被随机打乱;使用目标事件发生之后才产生的特征;在划分前完成标准化或特征选择。
特征的实际含义可能让泄漏很隐蔽。例如预测明天的能耗,却使用明天实际的建筑入住人数。该数值虽然存在于表格中,但预测时还无法获知。
8. 预处理是模型流程的一部分
均值填补、标准化、特征选择、PCA、类别词典和目标编码都会从数据中学习参数。因此只能在训练集上 fit,然后将学到的转换应用于验证集和测试集,不得重新拟合。
正确流程是:先划分数据;在训练部分拟合转换;转换其他部分;训练模型;在验证集上比较。交叉验证时,整个 pipeline 都应在每个折内重新拟合。这样可以防止验证集统计信息泄漏到训练过程。
9. 交叉验证与超参数调优
数据量较少时,单次验证划分可能不稳定。K 折交叉验证将训练部分分成 K 折:每一折轮流作为验证集,其余折用于训练。指标的平均值和波动可以显示结果对划分方式有多敏感。
分类任务常用 StratifiedKFold;相关对象使用 GroupKFold;时间任务使用 TimeSeriesSplit。如果没有额外的独立数据集,嵌套交叉验证可以评估完整的超参数选择流程。测试集仍然必须保持独立。
10. 建立诚实的评估协议
训练前应记录:目标变量和预测跨度、独立观测单位、分组和时间边界、预测时可用的特征、评价指标和基线、缺失值处理规则以及调参流程。选定方案后,冻结 pipeline,并在测试集上进行一次最终评估。
保存随机种子、数据版本、划分代码、重要分组的指标,以及置信区间或各折之间的波动。脱离背景的单个数字无法说明模型是否可靠。
划分策略比较
| 策略 | 适用场景 | 主要风险 |
|---|---|---|
| 随机划分 | 独立且分布相近的观测 | 隐藏的分组或时间依赖 |
| 分层划分 | 含稀有类别的分类任务 | 不能消除依赖或泄漏 |
| 分组划分 | 新患者、客户、建筑或设备 | 独立组数量过少 |
| 时间划分 | 预测未来时期 | 特征可用时间边界错误 |
示例:分组划分与 Pipeline
示例展示了基本原则:先完成分组划分,缺失值填补和标准化则放在 Pipeline 内部。假设字段名称和特征列表已经准备好。
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.model_selection import GroupShuffleSplit
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.linear_model import LogisticRegression
# 先分离目标变量和分组字段。
X, y, groups = data[features], data["target"], data["patient_id"]
splitter = GroupShuffleSplit(
n_splits=1, test_size=0.2, random_state=42
)
train_idx, test_idx = next(splitter.split(X, y, groups))
numeric = Pipeline([
("imputer", SimpleImputer(strategy="median")),
("scale", StandardScaler()),
])
categorical = Pipeline([
("imputer", SimpleImputer(strategy="most_frequent")),
("encode", OneHotEncoder(handle_unknown="ignore")),
])
preprocess = ColumnTransformer([
("num", numeric, numeric_features),
("cat", categorical, categorical_features),
])
model = Pipeline([
("preprocess", preprocess),
("classifier", LogisticRegression(max_iter=1000)),
])
model.fit(X.iloc[train_idx], y.iloc[train_idx])
# 只有方案固定后才使用测试集。对于时间任务,应改用按时间顺序的划分;对于类别不平衡的分类任务,可以考虑分层,但不能破坏分组和时间约束。
实践任务
实践:预测设备故障。表格包含 device_id、timestamp、temperature、vibration、maintenance_flag 和 failure_next_24h。判断预测前 24 小时哪些特征可用。设计两种场景:预测已知设备和预测全新设备。分别选择划分方式,定义 train/validation/test,并说明 imputer 和 scaler 应在哪部分拟合。
- 说明独立观测单位和部署场景。
- 标记预测时尚不可知的特征。
- 选择并论证数据划分策略。
- 描述 fit/transform 顺序以及最终测试的时机。