在 电影评论分类:二分类问题 中,第四部分验证所设计的模型时,将数据分为训练集、验证集和测试集。我们没有在训练的模型的相同数据上对模型进行评估,原因很快显而易见:仅仅经过几轮之后,模型就开始过拟合。也就是所说,随着训练的进行,模型在训练数据上的性能始终在提高,但在前所未见的数据上的性能则不再变化,甚至开始下降。
然而机器学习的目的是得到可以泛化的模型,即在前所未见的数据上表现很好的模型,而模型的过拟合则是核心难点。我们只能控制可以观察的事情,所以能够可靠地衡量模型的泛化能力非常重要。
那怎么来衡量一个模型它的泛化能力?如何对一个机器学习模型进行评估呢?
衡量一个模型的泛化能力和评估模型的重点是将数据集划分为3个集合:训练集、验证集和测试集。在训练数据山训练模型,在验证集上评估模型。一旦找到最佳参数,就在测试数据上最后测试一次。
那为什么要分3个集合而不直接将数据分为一个训练集和一个测试集2个集合呢?在训练集上训练模型,然后在测试集上评估模型。这样简单得多。
原因在于开发模型时总是需要调节模型配置,比如选择层数或每层大小【这叫模型的超参数,以便和模型参数(即权重)区分开】。这个调节过程需要使用模型在验证集上的性能作为反馈信号。这个调节过程本质上就是一种学习:在某个参数空间中寻找良好的
模型配置。因此,如果基于模型在验证集上的性能来调节模型配置,会很快导致模型在验证集上过拟合。即使并没有在验证集上直接训练模型也会如此。【此处不是很理解。是在测试集上的性能来调节模型配置还是在验证集上?】
造成这一现象的关键在于信息泄露。每次基于模型在验证集上的性能来调节模型的超参数,都会有一些关于验证数据的信息泄露到模型中。如果对每个参数只调节一次,那么泄露的信息很少,验证集仍然可以可靠地评估模型。但如果多次重复这一过程(运行一次实验,在验证集上评估,然后修改模型),那么将会有越来越多的关于验证集的信息泄露到模型中。
最后,得到的模型在验证集上的性能非常好(人为造成的),因为这正是我们优化模型的目的。我们关心的是模型在全新数据上的性能,而不是在验证数据上的性能,因此需要使用一个完全不同的、前所未见的数据集来评估模型,它就是测试集。
将数据划分为训练集、验证集和测试集可能看起来很简单,但如果可用数据很少,还有几种高级方法可以派上用场,先了解三种经典的评估方法:简单的流出验证、K折验证,以及带有打乱数据的重复K折验证。
一、简单的流出验证
留出一定比例的数据作为测试集,在剩余的数据上训练模型,然后在测试集上评估模型。如前所述,为了防止信息泄露,不能基于测试集来调节模型,所以还应该保留一个验证集。
#简单留出验证的伪代码实现
num_validation_samples = 10000
np.random.shuffle(data)#通常需要打乱数据
validation_data= data[:num_validation_samples]#定义验证集
data = data[num_validation_samples:]
training_data = data[:]#定义训练集
#在训练数据上训练模型
model = get_model()
model.train(train_data)
#并在验证数据上评估模型
validation_score = model.evaluate(validation_data)
#现在可以调节模型、重新训练、评估、然后再次调节……
#一旦调节好超参数,通常就在非测试数据上从头开始训练最终模型
model = get_model()
model.train(np.concattennate(training_data, validation_data]))
#评估模型的泛化能力
test_score = model.evaluate(test_data)
二、K折验证
K折验证(K-fold validation)将数据划分为大小相同的K个分区。对于每个分区i,在剩余的K-1个分区上训练模型,然后在分区i上评估模型。最终分数等于K个分数的平均值。对于不同的训练集-测试集划分,如果模型性能的变化很大,那么这种方法很有用。
与留出验证一样,这种方法也需要独立的验证集进行模型校正。
#K折交叉验证的伪代码实现
k = 4
num_validation_samples = len(data) // k
np.random.shuffle(data)
validation_scores = []
for fold in range(k):
#选择验证数据分区
validation_data = data[num_validation_samples*fold:
num_validation_samples *(fold+1)]
#将剩余数据作为训练数据
training_data = data[:num_validation_samples*fold] +
data[num_validation_samples*(fold+1):]
#创建一个全新的模型实例(未训练)
model = get_model()
model.train(training_data)
validation_score = model.evaluate(validation_data)
validation_scores.append(validation_score)
validation_scores = np.average(validation_scores)
#在所有非测试数据上训练最终模型
model = get_model()
model.train(data)
#在测试数据上评估模型的泛化能力
test_score = model.evaluate(test_data)
三、带有打乱数据的重复K折验证
如果可用的数据相对较少,而又需要尽可能精确的评估模型,那么可以选择带有打乱数据的重复K折验证。这种方法在Kaggle竞赛中特别有用。
具体做法是多次使用K折验证,在每次将数据划分为K个分区之前都将数据打乱。最终分数是每次K折验证分数的平均值。注意这种方法一共要训练和评估P×K个模型。(P是重复次数),计算代价很大。
评估模型的注意事项:
(1)数据代表性:希望训练集和测试集都能够代表当前数据。例如想要对数字图像进行分类,而图像样本是按类别排序的,如果将前80%作为训练集,剩余20%作为测试集,那么会导致训练集只包含类别0-7,而测试集中只包含类别8~9。因此,在将数据划分为训练集和测试集之前,通常应该随机打乱数据。
(2)时间箭头:如果想要根据过去预测未来,比如股票走势等,那么在划分数据前不应该将数据随机打乱。因为这样子做会造成时间泄露:模型将在未来数据上得到有效训练。在这种情况下,应该始终确保测试集中所有数据的时间都晚于训练集数据。
(3)数据冗余:如果数据中的某些数据点出现了两次,那么打乱数据并划分为训练集和验证集会导致训练集和验证集之间的数据冗余。从效果上来看,我们是在部分训练数据上评估模型,这是极其糟糕的!一定要确保训练集和验证集之间没有交集。
总结:
(1)根据这段内容,明白将数据集拆分为训练集、验证集和测试集的必要性:开发模型过程中调节模型配置,这个调节过程需要使用模型在验证集上的性能作为反馈信号;
(2)根据模型在验证集上的表现来修改模型的配置(超参数)。
说明:本文摘抄自《Python深度学习》 [美]弗朗索瓦·肖莱 著 张亮 译。由于没有链接,所以文章标注为【原创】。在这个平台上发布这篇文章,一来,可以提供给需要的人作一些参考;二来,是为了自己查阅和回顾方便。