动手学深度学习 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 \]

因为高阶多项式函数模型参数更多,模型函数的选择空间更大,所以高阶多项式函数比低阶多项式函数的复杂度更高。因此,高阶多项式函数 比 低阶多项式函数 更容易在相同的训练数据集上得到更低的训练误差

image-20250407161159606

2.2 训练数据集大小

影响欠拟合和过拟合的另一个重要因素是训练数据集的大小。

一般来说,如果训练数据集中 样本数过少,特别是比模型参数数量(按元素计)更少时,过拟合更容易发生

此外,泛化误差不会随训练数据集里样本数量增加而增大。

因此,在计算资源允许的范围之内,我们通常希望训练数据集大一些,特别是在模型复杂度较高时,例如层数较多的深度学习模型。

3. 多项式函数拟合实验

为了理解模型复杂度和训练数据集大小对欠拟合和过拟合的影响,下面我们以多项式函数拟合为例来实验。

3.1 生成数据集

我们将生成一个人工数据集。在训练数据集和测试数据集中,给定样本特征x,我们使用如下的三阶多项式函数来生成该样本的标签: \[ y=1.2x−3.4x^2 +5.6x^3 +5+ϵ \] 其中噪声项ϵ服从均值为0、标准差为0.01的正态分布。训练数据集和测试数据集的样本数都设为100。

1
2
3
4
5
6
7
8
9
10
n_train, n_test, true_w, true_b = 100, 100, [1.2, -3.4, 5.6], 5
features = torch.randn((n_train + n_test, 1))

poly_features = torch.cat((features, torch.pow(features, 2), torch.pow(features, 3)), 1)

labels = (true_w[0] * poly_features[:, 0] + true_w[1] * poly_features[:, 1]
+ true_w[2] * poly_features[:, 2] + true_b)

labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()), dtype=torch.float)

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]\)

下图举例

image-20250408144904225

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都是降不下来的。

image-20250408153707239

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一直很高,欠拟合的话参数加多点,过拟合的话样本加多点。