学习D2L深度学习课程7:模型选择相关概念
数据集
在模型训练过程中存在两种误差:
- 训练误差:模型在训练数据上的误差
- 泛化误差:模型在新数据上的误差
一个训练的最终目标是降低泛化误差,从结果上来说,模型的训练误差其实不被关心。
训练误差低并不代表后续的泛化误差也会低,就像以往的模拟考试成绩好并无法推出未来考试成绩一定好(例如模拟考试好的原因可能是拥有完备的预先准备)。
针对这些误差,也会存在不同的数据集:
- 训练数据集:用来对模型进行训练
- 验证数据集:一个用来评估模型好坏的数据集,可以从训练数据集中拿出部分数据组成(例如50%),要注意,验证数据集的结果是用来调整超参数的,因此不能和训练数据集混合(即不能用来训练)
- 测试数据集:只用一次的数据集,是模型真正的应用场景,可以类比“未来的考试”,注意测试数据集不能和验证数据集混合(一般来说测试数据集应该是全新的,在训练过程中的不知道的数据),否则会影响超参数的调整
还存在一个问题:有可能出现数据不足的情况。此时可以使用K-折交叉验证方法进行计算。算法为:
- 将训练数据分割为K块
- 一共进行K轮训练,在第i轮训练中,选择第i块数据作为验证数据集,其余作为训练数据集
- 将这K轮训练的验证数据集上的误差平均,作为最终的验证集误差
- 常用K取值:5或10
总结来说:
- 训练数据集:用来训练模型参数
- 验证数据集:用来选择模型超参数
- 数据集不够大:使用K-折交叉验证
过拟合和欠拟合
过拟合和欠拟合出现的大致条件如下:
模型容量(复杂度)\数据 | 简单 | 复杂 |
---|---|---|
低 | 正常 | 欠拟合 |
高 | 过拟合 | 正常 |
也就是说,当面对简单数据时,如果选用过于复杂的模型,可能导致训练数据好是因为模型够复杂,能够记忆所有的训练数据标签,而非调参正确,这样在面对新的测试数据时有可能拟合效果不好;而面对复杂数据时,如果选用过于简单的模型,可能导致模型参数不足以有效拟合复杂数据,也导致面对测试数据拟合效果不好。 |
模型容量也可以被理解为拟合各种函数的能力。一个低容量模型难以拟合训练数据;而一个高容量的模型可以记住所有的训练数据。一个示意图如下:
随着模型容量的提升(复杂度提升),其训练误差会不断下降至0,理论上无论训练数据集多大,都可以使用复杂的模型完全记忆。然而泛化误差并不会无限下降,当模型复杂度超过一个临界点之后,模型泛化误差就会开始不断回升(模型被无关噪音的细节干扰),而这个临界点就是模型的一个最优容量。模型容量对误差的影响示意如下:
由此,深度学习的核心就是:在保证模型容量足够大的前提下,使用各种手段限制模型容量,使得最终泛化误差的下降。
对于一个模型的容量,其估计相关准则如下:
- 不同种类的算法之间,其模型容量难以被比较(例如树模型和神经网络之间的容量难以比较)
- 当给定一种模型种类时,有两个影响容量的主要因素(例如下图两种感知机的容量比较):
- 参数的个数
- 参数值的选择范围
一个定量的概念是VC维。VC维是统计学习理论的一个核心思想:对于一个分类模型,VC等于一个最大的数据集大小:这个数据集无论如何给定标号,都存在一个模型能对其进行完美分类。
也就是说,可以把模型复杂度理解成:此模型能够完美分类的数据集的最大大小。
举一个例子:线性分类器的VC维:一个2维输入()的感知机,其VC维为3。也就是说,任意三个输入,都可以用一个线性分类,将其完美的进行分类。但是,当存在四个输入时就有可能无法用线性分类器进行完美分类了([[多层感知机]]中提到的XOR问题)。示意图如下:
一些拓展:
- 支持维输入的线性感知机的VC维是
- 一些多层感知机的VC维是
VC维能够提供一个模型好的理论依据,因为它可以衡量训练和泛化误差之间的间隔。但是在深度学习中很少使用,因为衡量不准,且很难计算。
一个数据集的数据复杂度更加难以衡量,因为其拥有多个重要因素:
- 样本个数
- 每个样本的元素个数
- 时间、空间结构
- 多样性
总结来说:模型容量需要匹配数据复杂度,否则可能会导致欠拟合和过拟合;统计学提供了数学工具VC维来衡量模型复杂度,但是实际情况中一般靠观察训练误差和验证误差来进行感知。
代码例子
使用一个多项式拟合来探索这些概念。首先进行包的引入:
1 |
|
我们现在需要生成一些真实数据。生成的方法是:首先确定一个原型函数,然后在原型函数输出基础上,为其增加一个小的偏差值,最后就能形成一系列符合原型函数趋势,但又存在偏差的数据点。目前确定的原型函数为一个三阶多项式:
设置模型的参数数量最多为20(max_degree
),训练集和验证集数据个数均为100,同时记录真实原型函数的各个参数:
1 |
|
然后将进行随机初始化(所有使用features
向量进行储存),然后利用这个公式计算对应的标签(所有使用labels
向量进行储存)。相关代码如下:
1 |
|
可以对前两个数据点的相关信息进行输出检查:
1 |
|
然后可以设置一个方法,求输入一个数据集后整体的损失均值:
1 |
|
最后只需要定义训练函数即可:
1 |
|
训练时,如果只提取使用多项式的前四个维度:train(poly_features[:n_train, :4], poly_features[n_train:, :4], labels[:n_train], labels[n_train:])
,则其输出的训练参数是合理的:weight: [[ 5.010476 1.2354498 -3.4229028 5.503297 ]]
,训练和验证集的损失情况也是合理的:
而如果线性函数进行拟合(也就是只使用多项式前四个维度,当作一个线性函数),那么训练和验证集的损失均难以下降:
反之,如果使用了过高阶的多项式函数进行拟合,则会出现过拟合现象,虽然训练损失可以有效降低,但是在测试集上进行实验时,测试损失依然较高: