机器学习的过拟合和欠拟合
来源:《pytho深度学习》-弗朗索瓦.肖莱
机器学习的目的是得到可以泛化(generalize)的模型,即在前所未见的数据上表现很好的模型,而过拟合则是核心难点。
- 泛化:训练集上训练的模型在多大程度上能够对新的实例预测出正确输出。(归纳:从特殊到一般)
- 噪声:存在我们无法观察到的额外的隐藏变量或其他数据异常(比如数据录入错误)。如果存在噪声,过分复杂的假设可能不仅学习潜在的函数,而且也学习数据中的噪声,导致很差的拟合。
过拟合和欠拟合
- 机器学习的根本问题是优化和泛化之间的对立。优化(optimization)是指调节模型以在训练数据上得到最佳性能(即机器学习中的学习),而泛化(generalization)是指训练好的模型在前所未见的数据上的性能好坏。机器学习的目的当然是得到良好的泛化,但你无法控制泛化,只能基于训练数据调节模型。
- 训练开始时,优化和泛化是相关的:训练数据上的损失越小,测试数据上的损失也越小。这时的模型是欠拟合(underfit)的,即仍有改进的空间,网络还没有对训练数据中所有相关模式建模。但在训练数据上迭代一定次数之后,泛化不再提高,验证指标先是不变,然后开始变差,即模型开始过拟合。这时模型开始学习仅和训练数据有关的模式,但这种模式对新数据来说是错误的或无关紧要的。
- 为了防止模型从训练数据中学到错误或无关紧要的模式,最优解决方法是获取更多的训练数据。模型的训练数据越多,泛化能力自然也越好。如果无法获取更多数据,次优解决方法是调节模型允许存储的信息量,或对模型允许存储的信息加以约束。如果一个网络只能记住几个模式,那么优化过程会迫使模型集中学习最重要的模式,这样更可能得到良好的泛化。这种降低过拟合的方法叫作正则化(regularization)。
常见的正则化方法
1. 减小网络大小
- 防止过拟合的最简单的方法就是减小模型大小,即减少模型中可学习参数的个数(这由层数和每层的单元个数决定)。在深度学习中,模型中可学习参数的个数通常被称为模型的容量(capacity)。直观上来看,参数更多的模型拥有更大的记忆容量(memorization capacity),因此能够在训练样本和目标之间轻松地学会完美的字典式映射,这种映射没有任何泛化能力。例如,拥有500 000 个二进制参数的模型,能够轻松学会MNIST 训练集中所有数字对应的类别——我们只需让50 000 个数字每个都对应10 个二进制参数。但这种模型对于新数字样本的分类毫无用处。始终牢记:深度学习模型通常都很擅长拟合训练数据,但真正的挑战在于泛化,而不是拟合。
- 与此相反,如果网络的记忆资源有限,则无法轻松学会这种映射。因此,为了让损失最小化,网络必须学会对目标具有很强预测能力的压缩表示,这也正是我们感兴趣的数据表示。同时请记住,你使用的模型应该具有足够多的参数,以防欠拟合,即模型应避免记忆资源不足。在容
量过大与容量不足之间要找到一个折中。 - 不幸的是,没有一个魔法公式能够确定最佳层数或每层的最佳大小。你必须评估一系列不同的网络架构(当然是在验证集上评估,而不是在测试集上),以便为数据找到最佳的模型大小。要找到合适的模型大小,一般的工作流程是开始时选择相对较少的层和参数,然后逐渐增加层的大小或增加新层,直到这种增加对验证损失的影响变得很小。
- ,更大网络的训练损失很快就接近于零。网络的容量越大,它拟合训练数据(即得到很小的训练损失)的速度就越快,但也更容易过拟合(导致训练损失和验证损失有很大差异)。
- 添加权重正则化
- 你可能知道奥卡姆剃刀(Occam’s razor)原理:如果一件事情有两种解释,那么最可能正确的解释就是最简单的那个,即假设更少的那个。这个原理也适用于神经网络学到的模型:给定一些训练数据和一种网络架构,很多组权重值(即很多模型)都可以解释这些数据。简单模型比复杂模型更不容易过拟合。
- 这里的简单模型(simple model)是指参数值分布的熵更小的模型(或参数更少的模型)。因此,一种常见的降低过拟合的方法就是强制让模型权重只能取较小的值,从而限制模型的复杂度,这使得权重值的分布更加规则(regular)。这种方法叫作权重正则化(weight regularization),其实现方法是向网络损失函数中添加与较大权重值相关的成本(cost)。这个成本有两种形式。
- L1 正则化(L1 regularization):添加的成本与权重系数的绝对值[权重的 L1 范数(norm)]成正比。
- L2 正则化(L2 regularization):添加的成本与权重系数的平方(权重的L2 范数)成正比。神经网络的L2 正则化也叫权重衰减(weight decay)。不要被不同的名称搞混,权重衰减与L2 正则化在数学上是完全相同的。
- 添加dropout 正则化
- dropout 是神经网络最有效也最常用的正则化方法之一,对某一层使用dropout,就是在训练过程中随机将该层的一些输出特征舍弃(设置为0)。假设在训练过程中,某一层对给定输入样本的返回值应该是向量[0.2, 0.5,1.3, 0.8, 1.1]。使用dropout 后,这个向量会有几个随机的元素变成0,比如[0, 0.5,1.3, 0, 1.1]。dropout 比率(dropout rate)是被设为0 的特征所占的比例,通常在0.2~0.5范围内。测试时没有单元被舍弃,而该层的输出值需要按dropout 比率缩小,因为这时比训练时有更多的单元被激活,需要加以平衡。
- 其核心思想是在层的输出值中引入噪声,打破不显著的偶然模式。如果没有噪声的话,网络将会记住这些偶然模式。
- 获取更多的训练数据
- 训练数据少,相对地缺乏代表性,具有偶然性,增大训练数据集有助于防止过拟合。
训练集、验证集和测试集
- 评估模型的重点是将数据划分为三个集合:训练集、验证集和测试集。在训练数据上训练模型,在验证数据上评估模型。一旦找到了最佳参数,就在测试数据上最后测试一次。
- 开发模型时总是需要调节模型配置,比如选择层数或每层大小[这叫作模型的超参数(hyperparameter),以便与模型参数(即权重)区分开]。这个调节过程需要使用模型在验证数据上的性能作为反馈信号。这个调节过程本质上就是一种学习:在某个参数空间中寻找良好的模型配置。因此,如果基于模型在验证集上的性能来调节模型配置,会很快导致模型在验证集上过拟合,即使你并没有在验证集上直接训练模型也会如此。
- 造成这一现象的关键在于信息泄露(information leak)。每次基于模型在验证集上的性能来调节模型超参数,都会有一些关于验证数据的信息泄露到模型中。如果对每个参数只调节一次,那么泄露的信息很少,验证集仍然可以可靠地评估模型。但如果你多次重复这一过程(运行一次实验,在验证集上评估,然后据此修改模型),那么将会有越来越多的关于验证集的信息泄露到模型中。
- 最后,你得到的模型在验证集上的性能非常好(人为造成的),因为这正是你优化的目的。你关心的是模型在全新数据上的性能,而不是在验证数据上的性能,因此你需要使用一个完全不同的、前所未见的数据集来评估模型,它就是测试集。你的模型一定不能读取与测试集有关的任何信息,既使间接读取也不行。如果基于测试集性能来调节模型,那么对泛化能力的衡量是不准确的。
- 将数据划分为训练集、验证集和测试集可能看起来很简单,但如果可用数据很少,还有几种高级方法可以派上用场。我们先来介绍三种经典的评估方法:简单的留出验证、K 折验证,以及带有打乱数据的重复K 折验证。
- 简单的留出验证
- 留出一定比例的数据作为测试集。在剩余的数据上训练模型,然后在测试集上评估模型。如前所述,为了防止信息泄露,你不能基于测试集来调节模型,所以还应该保留一个验证集。
- 这是最简单的评估方法,但有一个缺点:如果可用的数据很少,那么可能验证集和测试集包含的样本就太少,从而无法在统计学上代表数据。这个问题很容易发现:如果在划分数据前进行不同的随机打乱,最终得到的模型性能差别很大,那么就存在这个问题。接下来会介绍K 折验证与重复的K 折验证,它们是解决这一问题的两种方法。
- K 折验证
- K 折验证(K-fold validation)将数据划分为大小相同的K 个分区。对于每个分区i,在剩余的K-1 个分区上训练模型,然后在分区i 上评估模型。最终分数等于K 个分数的平均值。对于不同的训练集- 测试集划分,如果模型性能的变化很大,那么这种方法很有用。与留出验证一样,这种方法也需要独立的验证集进行模型校正。
- 带有打乱数据的重复K 折验证
- 如果可用的数据相对较少,而你又需要尽可能精确地评估模型,那么可以选择带有打乱数据的重复K 折验证(iterated K-fold validation with shuffling)。我发现这种方法在Kaggle 竞赛中特别有用。具体做法是多次使用K 折验证,在每次将数据划分为K 个分区之前都先将数据打乱。最终分数是每次K 折验证分数的平均值。注意,这种方法一共要训练和评估P×K 个模型(P是重复次数),计算代价很大。
- 简单的留出验证
评估模型的注意事项
- 数据代表性(data representativeness)
- 你希望训练集和测试集都能够代表当前数据。例如,你想要对数字图像进行分类,而图像样本是按类别排序的,如果你将前80% 作为训练集,剩余20% 作为测试集,那么会导致训练集中只包含类别07,而测试集中只包含类别89。这个错误看起来很可笑,却很常见。因此,在将数据划分为训练集和测试集之前,通常应该随机打乱数据。
- 时间箭头(the arrow of time)
- 如果想要根据过去预测未来(比如明天的天气、股票走势等),那么在划分数据前你不应该随机打乱数据,因为这么做会造成时间泄露(temporalleak):你的模型将在未来数据上得到有效训。在这种情况下,你应该始终确保测试集中所有数据的时间都晚于训练集数据。
- 数据冗余(redundancy in your data)
- 如果数据中的某些数据点出现了两次(这在现实中的数据里十分常见),那么打乱数据并划分成训练集和验证集会导致训练集和验证集之间的数据冗余。从效果上来看,你是在部分训练数据上评估模型,这是极其糟糕的!一定要确保训练集和验证集之间没有交集。