基于书籍《Hands-on Machine Learning with Scikit-Learn, Keras & TensorFlow》的笔记
文章目录
2.3 创建测试集
在这个阶段主动搁置部分数据听起来可能有些奇怪,但事实上这确实是经过缜密思考的。
原因是:
我们只是简单地大概浏览了一遍数据,还需要更多的信息才能最终确定使用何种算法
而人脑是个非常神奇的模式检测(模式识别)系统,因为其非常容易过拟合,例如你可能在浏览一遍后,就陷入了某个看似有趣的测试数据模式,从而选择某个特殊的机器学习模型。但事实上,当你使用测试集对泛化误差进行估算时,估计结果将会过于乐观。而当你真正地上线整个系统时,可能表现并不如预期那样优秀,这很常见,被称之为data snooping bias-即数据洞察错误。
创建测试集的常见方案是:按比例随机选择一些实例,然后放在一边即可。
在实际工程应用中,由于我们的数据集随时会更新,所以测试集可能会不断更新,为了保证获取稳定的训练测试分割比例,常见的解决方案是为每一个实例都使用一个标识符来决定是否进入测试集(假定每个实例都有一个唯一且不变的标识符)。
例如,你可以计算每个实例标识符的哈希值,如果这个哈希值小于或等于最大哈希值(如果训练集没有被选入,即使数据集更新,之前训练集的实例依旧满足这个规则(如何实现笔者并不是很明白,恳请读者在评论区指教)的20%(注意确保训练集和测试集的比例),则将该实例放入测试集,这可以确保测试集在多个运行里保持一致,即使数据集更新了,也可以将新实例的20%放入新的测试集,而之前训练集中的实例并不会被放入新的测试集。
在实践中,我们一般都会使用Scikit-Learn提供的函数,可以通过多种方式将数据集分为多个子集。最简单也最常用的函数是
train_test_split()。其源码大概如下:
import numpy as np
def split_train_test(data, test_ratio):
shuffled_indices = np.random.permutation(len(data))
test_set_size = int(len(data) * test_ratio)
test_indices = shuffled_indices[:test_set_size]
train_indices = shuffled_indices[test_set_size:]
return data.iloc[train_indices], data.iloc[test_indices]
它有random_state参数,可以设置随机生成器种子(一般设置为宇宙的终极答案42)。其次,你还可以把行数相同的多个数据集一次性发送给它,它会根据相同的索引将其拆分(例如,当你有一个单独的标签DataFrame时,这就非常有用):
from sklearn.model_selection import train_test_split
train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)
随机抽样的方法往往适用于比较庞大的数据集(相较于属性的数量而言),但如果实际数据集较小,而我们想要确保抽取的测试集能够很好地覆盖各种类型。这时候,使用分层抽样会是更好地办法,例如,在本项目中,收入中位数是一个非常重要的特征。我们想要在测试集中覆盖收入中位数的各种类型,
分层抽样也要讲究技巧,每一层的数据不应该太小,否则模型会认为数据不足的层的重要性不大从而导致预测该层的正确率极低(所有,很多时候,抛开数学,我们可以用人脑的思考方式来监控机器学习模型的学习是否在正确的轨道上)。
根据median_income的数据分布,大多数处于15000~60000美元,也有一部分远超60000美元,所以我们将其分为5层进行抽样。
housing['income_cat']=pd.cut(housing['median_income'],bins=[0.,1.5,3.0,4.5,6.,np.inf],labels=[1,2,3,4,5])
housing['income_cat'].hist()
现在我们可以根据收入类别分层抽样了,其使用Scikit-Learn的 StratifiedShuffleSplit类
from sklearn.model_selection import StratifiedShuffleSplit
split = StratifiedShuffleSplit(n_splits=1, test_size=0.2,random_state=42)
for train_index, test_index in split.split(housing,housing["income_cat"]):
strat_train_set = housing.loc[train_index]
strat_test_set = housing.loc[test_index]
>>> housing["income_cat"].value_counts() / len(housing)
使用类似代码还可以测量完整数据集中的5个收入类别的比例分布。
根据上图对Overall-Stratified-Random的比较,可以看到,Stratified能够基本与原数据集分布保持一致,而Random偏差较大。
接下来,我们可以删除income_cat属性,将数据恢复原样了-(我们已经获得了我们想要的训练集和测试集):
for set in (strat_train_set, strat_test_set):
set.drop(["income_cat"], axis=1, inplace=True)
创建测试集我们专门花一个博客进行讲解,是因为这是机器学习项目中经常被忽视但实则至关重要的一部分。
在讨论交叉验证时,这里的很多思想也大有裨益。