动手学深度学习 3.11 模型选择、欠拟合和过拟合
前言
从零开始学习ai文章系列计划是个人在《动手学深度学习》和《磨菇书》两本书的学习中的个人笔记,文章也会以课本中的章节分开,即每个章节一片笔记。我会尽量的把主要内容以及遇到的难点进行记录与解决,如果哪里有错误的欢迎指正。或者不清晰的可以直接查看原文部分。
《动手学深度学习》原文(课本):https://tangshusen.me/Dive-into-DL-PyTorch/#/
《动手学深度学习》代码:https://github.com/ShusenTang/Dive-into-DL-PyTorch
(由于有时候公式太多,可能会直接贴图片)
1. 模型选择
在机器学习中,通常需要评估若干候选模型的表现并从中选择模型。这一过程称为模型选择(model selection)。
以多层感知机为例,我们可以选择隐藏层的个数,以及每个隐藏层中隐藏单元个数和激活函数。
1.1 验证数据集
从严格意义上讲,测试集只能在所有超参数和模型参数选定后使用一次。不可以使用测试数据选择模型,如调参。
然而在实际应用中,由于数据不容易获取,测试数据极少只使用一次就丢弃。因此,实践中验证数据集和测试数据集的界限可能比较模糊。
1.2 K折交叉验证
K折交叉验证中,我们把原始训练数据集分割成K个不重合的子数据集。
我们做K次模型训练和验证。每一次,我们使用一个子数据集验证模型,并使用其他K−1个子数据集来训练模型。
最后,我们对这K次训练误差和验证误差分别求平均。
2. 欠拟合和过拟合
2.1 模型复杂度
为了解释模型复杂度,我们以多项式函数拟合为例。给定一个由标量数据特征x和对应的标量标签y组成的训练数据集,多项式函数拟合的目标是找一个K阶多项式函数来近似y。 \[ \hat{y}=b+\sum ^{K}_{k=1} {x^kw_k} \] 在上式中,\(w_k\)是模型的权重参数,\(b\)是偏差参数。
一阶多项式函数拟合又叫线性函数拟合。
直观感受举例k=4的话,则4阶多项式如下 \[ \hat{y}=b+x^1w_1+x^2w_2+x^3w_3+x^4w_4 \]
因为高阶多项式函数模型参数更多,模型函数的选择空间更大,所以高阶多项式函数比低阶多项式函数的复杂度更高。因此,高阶多项式函数 比 低阶多项式函数 更容易在相同的训练数据集上得到更低的训练误差。

2.2 训练数据集大小
影响欠拟合和过拟合的另一个重要因素是训练数据集的大小。
一般来说,如果训练数据集中 样本数过少,特别是比模型参数数量(按元素计)更少时,过拟合更容易发生。
此外,泛化误差不会随训练数据集里样本数量增加而增大。
因此,在计算资源允许的范围之内,我们通常希望训练数据集大一些,特别是在模型复杂度较高时,例如层数较多的深度学习模型。
3. 多项式函数拟合实验
为了理解模型复杂度和训练数据集大小对欠拟合和过拟合的影响,下面我们以多项式函数拟合为例来实验。
3.1 生成数据集
我们将生成一个人工数据集。在训练数据集和测试数据集中,给定样本特征x,我们使用如下的三阶多项式函数来生成该样本的标签: \[ y=1.2x−3.4x^2 +5.6x^3 +5+ϵ \] 其中噪声项ϵ服从均值为0、标准差为0.01的正态分布。训练数据集和测试数据集的样本数都设为100。
1 | n_train, n_test, true_w, true_b = 100, 100, [1.2, -3.4, 5.6], 5 |
torch.randn 生成(200,1)的随机数
torch.pow(features, 2) 与 torch.pow(features, 3) 分别生成features中每个数2次幂与3次幂构成的tensor
torch.cat中dim为1,即从列方向,将3个tensor拼接起来。即生成列表tensor \([x,x^2,x^3]\)
下图举例

3.2 定义、训练和测试模型
我们先定义作图函数 semilogy (画图具体实现自行查看代码)。
和线性回归一样,多项式函数拟合也使用平方损失函数。因为我们将尝试使用不同复杂度的模型来拟合生成的数据集,所以我们把模型定义部分放在 fit_and_plot 函数中。

3.3 三阶多项式函数拟合(正常)
1 | fit_and_plot(poly_features[:n_train, :], poly_features[n_train:, :], labels[:n_train], labels[n_train:]) |

3.4 线性函数拟合(欠拟合)
1 | fit_and_plot(features[:n_train, :], features[n_train:, :], labels[:n_train], labels[n_train:]) |

为了不让大伙迷糊3.3和3.4有什么区别,以下进行简单说明。
根据之前的定义,features只有一列,即x。poly_features中除了x,还有x²,x三次方。而我们函数中的网络定义的权重参数,则是根据输入的列数进行确定的


尝试将原本的3权重换成4权重,添加多一维无意义的组,label还是原来的。
1 | poly_features2 = torch.cat((features, torch.pow(features, 2), torch.pow(features, 3),torch.pow(features, 4)), 1) |

发现虽然没原来顺滑,但loss也会逐渐变小,原因是它包含了必要的前3维数据,可以认为经过训练后网络将第四维新加权重置为接近0的值。
而如果仅保留部分维度的数据,即上面的仅保留了1维的线性网络,或者自行构建仅保留2维(下图)网络计算,其loss都是降不下来的。

3.5 训练样本不足(过拟合)
事实上,即便使用与数据生成模型同阶的三阶多项式函数模型,如果训练样本不足,该模型依然容易过拟合。
让我们只使用两个样本来训练模型。显然,训练样本过少了,甚至少于模型参数的数量。这使模型显得过于复杂,以至于容易被训练数据中的噪声影响。
1 | fit_and_plot(poly_features[0:2, :], poly_features[n_train:, :], labels[0:2], labels[n_train:]) |

4 小结
本章节挺适合做个小结进行说明的。
我们初始的输入其实只有x,然后我们目标label是 3阶的多项式的一个结果。
如果我们隐藏层参数越多,我们能获取的x的次方就越多,即我们实验中的输入poly_features,这里我们将隐藏层拟合这一步骤直接用手动计算poly_features代替了。
简单说如果隐藏层参数不够,则仅拟合出 \(x\) 或仅拟合出 \(x,x^2\) 然后传递给后面的层(如实验函数中的net = torch.nn.Linear(train_features.shape[-1], 1))进行计算,那么无论如何,其loss都是降不下来的。
反过来说,如果隐藏层参数过多,不仅拟合出 \(x,x^2,x^3\) 还有其他一堆 \(x^4,x^5,x^6\) 等,那也是没关系的,因为已经包含了必要的 \(x,x^2,x^3\) ,其他多余的参数会在后续进行优化,会被某一层的网络权重置为0。
因此能简单判断,如果loss一直很高,欠拟合的话参数加多点,过拟合的话样本加多点。