在机器学习中,数据管道的完整性至关重要。数据的分割和使用方式对模型性能的影响与算法本身一样大。早期关于数据分区的决策不仅影响开发,还影响部署和持续监控。有效的数据分割将模型开发与验证和性能评估分开,确保可重复性和有意义的结果。
本文探讨了机器学习中数据分割背后的原则。阐述了为什么分割数据很重要,并研究了核心概念:训练集、验证集和测试集。然后讨论了高级分割策略,并提供了实际的代码示例和可视化。最后,提供了用于稳健的、可用于生产的机器学习工作流的可行指南。
为什么分割数据?
机器学习模型的泛化能力决定其好坏,即能否在未见过的数据上提供可靠的预测。分割数据有助于隔离开发周期的不同阶段:
- 模型训练:从已知数据中学习。
- 超参数调整:在不过度拟合的情况下进行优化。
- 最终评估:估计未来性能。
在单个未分割的数据集上进行训练和验证会带来严重风险,如数据泄露、过拟合和偏差。以健康信息学项目为例,若训练集和测试集包含相同时期或机构的患者数据,离线评估的模型性能在新医院或未来患者中无法体现,实际结果会令人失望,部署可能带来不可接受的风险。稳健的分区在训练管道和实际使用之间创建有意义的界限,正确保留的测试数据的指标是生产性能的最佳代理。
数据分割类型
- 训练集:用于拟合模型参数,通常是最大的数据集分区,占全数据的 60 - 80%。关键特征包括大小足够大以捕获基础数据分布、具有生产中预期的多样性以及进行随机打乱(时间序列数据需保持时间顺序)。例如使用 scikit-learn 进行分割:
from sklearn.model_selection import train_test_split; X_train, X_temp, y_train, y_temp = train_test_split( X, y, test_size=0.4, random_state=42 )
。 - 验证集:用于超参数调整和模型选择,必须不受模型参数或训练数据衍生的特征工程的影响。常见策略有留出验证(留出 10 - 20%的数据作为静态验证集,简单快速但小数据集时不太可靠)和交叉验证(将数据多次分割,生成验证指标分布,更稳健但计算量大)。例如在深度学习中使用
EarlyStopping
进行早期停止:from tensorflow.keras.callbacks import EarlyStopping; early_stop = EarlyStopping(monitor='val_loss', patience=5); model.fit(X_train, y_train, validation_data=(X_val, y_val), callbacks=[early_stop])
,时间序列数据需小心分割,应在训练数据之后的序列上进行验证。 - 测试集:提供模型泛化的最终无偏度量,应视为“神圣”,在所有开发完成之前不应被查看或使用。原则包括不偷看、先分区、正确解释指标等。例如最终分割:
X_val, X_test, y_val, y_test = train_test_split(X_temp, y_temp, test_size=0.5, random_state=42 )
,基于测试集性能或逻辑进行部署会使模型失去价值。
高级分割技术
- K 折交叉验证:在可用数据稀缺或需要对多个模型进行稳健筛选时很关键。将数据集分为 k 个等大小的“折”,模型训练 k 次,每次用不同的折作为验证集,其余作为训练集。可减少评估方差,确保每个观测值用于训练和验证,揭示模型对特定数据分割的敏感性。例如:
from sklearn.model_selection import KFold; kf = KFold(n_splits=5, shuffle=True, random_state=42); for train_idx, val_idx in kf.split(X): X_train, X_val = X[train_idx], X[val_idx]; y_train, y_val = y[train_idx], y[val_idx]
。 - 分层抽样:对于不平衡分类任务,防止默认随机分割导致验证和测试集性能偏差,通过在所有分割中保留每个类的比例来保护。例如:
from sklearn.model_selection import StratifiedKFold; skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42); for train_idx, val_idx in skf.split(X, y): # Splits maintain class distribution pass
。 - 时间序列分割:朴素打乱会破坏顺序结构,在预测、异常检测或序列建模任务中打破假设,应按时间顺序分割时间序列数据,训练早期区间,在后期区间进行验证和测试。经典的训练 - 验证 - 测试分割:
|--- train ---|--- validation ---|--- test ---| earliest --------------------> latest time
,时间序列的交叉验证可使用扩展窗口(如 scikit-learn 的TimeSeriesSplit
)。
数据分割在实践中
- 可重复性:为分割设置随机种子以确保结果稳定可重复,记录数据和代码的版本。
- 处理小数据集:可用样本少时,留出验证不可靠,可使用 K 折交叉验证或留一法交叉验证,在极小数据集(<200 样本)中可利用领域知识、仔细的正则化和特征选择进行增强。
- 避免陷阱和不良模式:避免数据泄露(如包含未来衍生特征、重复记录等)、不正确的分层(忽略类不平衡)和不正确的随机化(未按要求打乱或打乱时间顺序数据)。对于高风险应用,应手动审核分区数据的重复、泄露和类完整性。
可视化数据分割过程
常规的 60/20/20 三向分割:|------------------------------ Full Dataset ------------------------------| |-------- Train (60%) --------|-- Val (20%) --|------ Test (20%) -------|
,标准分割通过 scikit-learn:X_train, X_temp, y_train, y_temp = train_test_split(X, y, test_size=0.4, random_state=42); X_val, X_test, y_val, y_test = train_test_split(X_temp, y_temp, test_size=0.5, random_state=42)
,K 折交叉验证:Fold 1: [train][train][train][train][val] Fold 2: [train][train][train][val][train]... Fold k: [val][train][train][train][train]
,分层或基于组的分割在视觉上与上述示例相似,但在所有分区中保持自定义分组或类比例。
建议和清单
- 分区前探索:在任何模型开发、EDA 或特征工程之前先分割测试集。
- 将测试数据视为只读:在最终评估之前不要访问或分析测试集。
- 适当使用分层:特别是对于不平衡或罕见事件任务。
- 审核泄露和重复:审查分区逻辑和样本来源。
- 匹配生产环境:对于序列或时间数据,在分割中保留实际世界的顺序。
- 记录一切:记录数据版本、随机种子、代码和分割策略的理由。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。