underfit-overfit.md 11.1 KB
Newer Older
A
Aston Zhang 已提交
1
# 欠拟合、过拟合、选择模型和调节超参数
2

A
Aston Zhang 已提交
3
在前几节基于Fashion-MNIST数据集的实验中,我们评价了机器学习模型在训练数据集和测试数据集上的表现。如果你动手改变过实验中的模型结构或者超参数的话,你也许发现了:当模型在训练数据集上更准确时,在测试数据集上的准确率既可能上升又可能下降。这是为什么呢?
4

A
revise  
Aston Zhang 已提交
5 6

## 训练误差和泛化误差
7

A
Aston Zhang 已提交
8
在解释上面提到的现象之前,我们需要区分训练误差(training error)和泛化误差(generalization error):前者指模型在训练数据集上表现出的误差,后者指模型在任意一个测试数据样本上表现出的误差的期望。训练误差和泛化误差的计算可以利用我们之前介绍过的损失函数,例如线性回归用到的平方损失函数和Softmax回归用到的交叉熵损失函数。
9

A
Aston Zhang 已提交
10
假设训练数据集和测试数据集里的每一个样本都是从同一个概率分布中相互独立地生成的。基于该独立同分布假设,给定任意一个机器学习模型及其参数和超参数,它的训练误差的期望和泛化误差都是一样的。然而从之前的章节中我们了解到,模型的参数并不是事先给定的,而是通过训练数据集训练模型而学习出的。所以,训练误差的期望小于或等于泛化误差。也就是说,通常情况下,由训练数据集学到的模型参数会使模型在训练数据集上的表现优于或等于在测试数据集上的表现。由于无法从训练误差估计泛化误差,降低训练误差并不意味着泛化误差一定会降低。我们希望通过适当降低模型的训练误差,从而能够间接降低模型的泛化误差。
11 12 13 14


## 欠拟合和过拟合

A
Aston Zhang 已提交
15
给定测试数据集,我们通常用机器学习模型在该测试数据集上的误差来反映泛化误差。当模型无法得到较低的训练误差时,我们将这一现象称作欠拟合(underfitting)。当模型的训练误差远小于它在测试数据集上的误差时,我们称该现象为过拟合(overfitting)。在实践中,我们要尽可能同时避免欠拟合和过拟合的出现。虽然有很多因素可能导致这两种拟合问题,在这里我们重点讨论两个因素:模型复杂度和训练数据集大小。
16 17


A
Aston Zhang 已提交
18
### 模型复杂度
19

A
Aston Zhang 已提交
20
为了解释模型复杂度,让我们以多项式函数拟合为例。给定一个由标量数据特征$x$和对应的标量标签$y$组成的训练数据集,多项式函数拟合的目标是找一个$K$阶多项式函数
21

A
Aston Zhang 已提交
22
$$\hat{y} = b + \sum_{k=1}^K x^k w_k$$
A
Aston Zhang 已提交
23

A
Aston Zhang 已提交
24 25 26
来近似$y$。上式中,带下标的$w$是模型的权重参数,$b$是偏差参数。和线性回归相同,多项式函数拟合也使用平方损失函数。特别地,一阶多项式函数拟合又叫线性函数拟合。

高阶多项式函数比低阶多项式函数的复杂度更高,例如高阶多项式函数模型参数更多,模型函数的选择范围更广。正因为如此,高阶多项式函数比低阶多项式函数更容易在相同的训练数据集上得到更低的训练误差。给定训练数据集,模型复杂度的和误差之间的关系通常如图3.4所示。给定训练数据集,如果模型的复杂度过低,很容易出现欠拟合;如果模型复杂度过高,很容易出现过拟合。
27

A
Aston Zhang 已提交
28
![模型复杂度对欠拟合和过拟合的影响](../img/capacity_vs_error.svg)
29 30


A
Aston Zhang 已提交
31
### 训练数据集大小
32

A
Aston Zhang 已提交
33
影响欠拟合和过拟合的另一个重要因素是训练数据集大小。一般来说,如果训练数据集过小,特别是比模型参数数量更小时,过拟合更容易发生。
M
muli 已提交
34

A
Aston Zhang 已提交
35
此外,泛化误差不会随训练数据集里样本数量增加而增大。因此,在计算资源允许范围之内,我们通常希望训练数据集大一些,特别当模型复杂度较高时,例如训练层数较多的深度学习模型时。
36 37


A
Aston Zhang 已提交
38
## 多项式函数拟合实验
39

A
Aston Zhang 已提交
40
为了理解模型复杂度和训练数据集大小对欠拟合和过拟合的影响,下面让我们以多项式函数拟合为例来实验。首先导入实现需要的包或模块。
41

M
muli 已提交
42
```{.python .input}
A
Aston Zhang 已提交
43 44 45
import sys
sys.path.append('..')
import gluonbook as gb
A
Aston Zhang 已提交
46 47
from mxnet import autograd, gluon, nd
from mxnet.gluon import data as gdata, loss as gloss, nn
A
Aston Zhang 已提交
48
```
49

A
Aston Zhang 已提交
50 51
### 生成数据集

A
reg  
Aston Zhang 已提交
52
我们将生成一个人工数据集。在训练数据集和测试数据集中,给定样本特征$x$,我们使用如下的三阶多项式函数来生成的该样本的标签:
A
Aston Zhang 已提交
53 54 55

$$y = 1.2x - 3.4x^2 + 5.6x^3 + 5 + \epsilon,$$

A
reg  
Aston Zhang 已提交
56
其中噪音项$\epsilon$服从均值为0和标准差为0.1的正态分布。训练数据集和测试数据集的样本数都设为100。
A
Aston Zhang 已提交
57

A
Aston Zhang 已提交
58
```{.python .input}
A
Aston Zhang 已提交
59
n_train = 100
A
Aston Zhang 已提交
60
n_test = 100
61
true_w = [1.2, -3.4, 5.6]
A
Aston Zhang 已提交
62
true_b = 5
63

A
Aston Zhang 已提交
64 65 66 67 68
features = nd.random.normal(shape=(n_train + n_test, 1))
poly_features = nd.concat(features, nd.power(features, 2),
                          nd.power(features, 3))
labels = (true_w[0] * poly_features[:, 0] + true_w[1] * poly_features[:, 1]
          + true_w[2] * poly_features[:, 2] + true_b)
A
Aston Zhang 已提交
69
labels += nd.random.normal(scale=0.1, shape=labels.shape)
A
Aston Zhang 已提交
70
```
71

A
Aston Zhang 已提交
72
看一看生成数据集的前5个样本。
A
Aston Zhang 已提交
73 74

```{.python .input}
A
Aston Zhang 已提交
75
features[:5], poly_features[:5], labels[:5]
76 77
```

A
Aston Zhang 已提交
78
### 定义、训练和测试模型
79

A
Aston Zhang 已提交
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
我们先定义作图函数,其中y轴是基于对数尺度。该作图函数`semilogy`也被定义在`gluonbook`包中供后面章节调用。

```{.python .input}
from IPython.display import set_matplotlib_formats
def semilogy(x_vals, y_vals, x_label, y_label, x2_vals=None, y2_vals=None,
             legend=None, figsize=(3.5, 2.5)):
    gb.plt.rcParams['figure.figsize'] = figsize
    set_matplotlib_formats('retina')
    gb.plt.xlabel(x_label)
    gb.plt.ylabel(y_label)
    gb.plt.semilogy(x_vals, y_vals)
    if x2_vals and y2_vals:
        gb.plt.semilogy(x2_vals, y2_vals)
        gb.plt.legend(legend)
    gb.plt.show() 
```

A
Aston Zhang 已提交
97
和线性回归一样,多项式函数拟合也使用平方损失函数。由于我们将尝试使用不同复杂度的模型来拟合生成的数据集,我们把模型定义部分放在`fit_and_plot`函数中。多项式函数拟合的训练和测试步骤与之前介绍的Softmax回归中的这些步骤类似。
98

M
muli 已提交
99
```{.python .input}
A
Aston Zhang 已提交
100 101
num_epochs = 100
loss = gloss.L2Loss()
A
Aston Zhang 已提交
102

A
Aston Zhang 已提交
103
def fit_and_plot(train_features, test_features, train_labels, test_labels):
A
Aston Zhang 已提交
104 105
    net = nn.Sequential()
    net.add(nn.Dense(1))
M
muli 已提交
106
    net.initialize()
A
Aston Zhang 已提交
107
    batch_size = min(10, train_labels.shape[0])
A
Aston Zhang 已提交
108
    train_iter = gdata.DataLoader(gdata.ArrayDataset(
A
Aston Zhang 已提交
109
        train_features, train_labels), batch_size, shuffle=True)
A
Aston Zhang 已提交
110 111
    trainer = gluon.Trainer(net.collect_params(), 'sgd',
                            {'learning_rate': 0.01})
A
Aston Zhang 已提交
112
    train_ls, test_ls = [], []
A
Aston Zhang 已提交
113
    for _ in range(num_epochs):
A
Aston Zhang 已提交
114
        for X, y in train_iter:
115
            with autograd.record():
A
Aston Zhang 已提交
116
                l = loss(net(X), y)
A
Aston Zhang 已提交
117
            l.backward()
118
            trainer.step(batch_size)
A
Aston Zhang 已提交
119 120 121 122
        train_ls.append(loss(net(train_features),
                             train_labels).mean().asscalar())
        test_ls.append(loss(net(test_features),
                            test_labels).mean().asscalar())
A
Aston Zhang 已提交
123
    print('final epoch: train loss', train_ls[-1], 'test loss', test_ls[-1])
A
Aston Zhang 已提交
124 125
    semilogy(range(1, num_epochs+1), train_ls, 'epochs', 'loss',
             range(1, num_epochs+1), test_ls, ['train', 'test'])
A
Aston Zhang 已提交
126
    return ('weight:', net[0].weight.data(), 'bias:', net[0].bias.data())
127 128
```

A
Aston Zhang 已提交
129
### 三阶多项式函数拟合(正常)
130

A
Aston Zhang 已提交
131
我们先使用与数据生成函数同阶的三阶多项式函数拟合。实验表明,这个模型的训练误差和在测试数据集的误差都较低。训练出的模型参数也接近真实值。
132

M
muli 已提交
133
```{.python .input}
A
Aston Zhang 已提交
134 135
fit_and_plot(poly_features[:n_train, :], poly_features[n_train:, :],
             labels[:n_train], labels[n_train:])
136 137
```

A
Aston Zhang 已提交
138
### 线性函数拟合(欠拟合)
139

A
Aston Zhang 已提交
140
我们再试试线性函数拟合。很明显,该模型的训练误差在迭代早期下降后便很难继续降低。在完成最后一次迭代周期后,训练误差依旧很高。线性模型在非线性模型(例如三阶多项式函数)生成的数据集上容易欠拟合。
141

M
muli 已提交
142
```{.python .input}
A
Aston Zhang 已提交
143 144
fit_and_plot(features[:n_train, :], features[n_train:, :], labels[:n_train],
             labels[n_train:])
145 146 147 148
```

### 训练量不足(过拟合)

A
Aston Zhang 已提交
149 150 151
事实上,即便是使用与数据生成模型同阶的三阶多项式函数模型,如果训练量不足,该模型依然容易过拟合。

让我们仅仅使用两个样本来训练模型。显然,训练样本过少了,甚至少于模型参数的数量。这使模型显得过于复杂,以至于容易被训练数据中的噪音影响。在迭代过程中,即便训练误差较低,但是测试数据集上的误差却很高。这是典型的过拟合现象。
152

M
muli 已提交
153
```{.python .input}
A
Aston Zhang 已提交
154 155
fit_and_plot(poly_features[0:2, :], poly_features[n_train:, :], labels[0:2],
             labels[n_train:])
156 157
```

A
Aston Zhang 已提交
158
我们将在后面的章节继续讨论过拟合问题以及应对过拟合的方法,例如正则化和丢弃法。
159

A
Aston Zhang 已提交
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176

## 选择模型和调节超参数

我们已经知道,训练误差无法被用来估计泛化误差。那我们是否可以根据测试数据集上的误差来选择模型和调节超参数呢?答案是否定的。原因很简单:为降低测试数据集误差而修改模型和超参数将使本节开始的“独立同分布假设”不再成立。此时测试数据集的误差无法正确反映泛化误差。

为了选择模型和调参,我们可以切分原始训练数据集:其中大部分样本组成新的训练数据集,剩下的组成验证数据集(validation data set)。我们在新的训练数据集上训练模型,并根据模型在验证数据集上的表现选择模型和调节超参数。最后,我们在测试数据集上评价模型的表现。


### $K$ 折交叉验证

验证模型还有很多其他的方法。其中一种常用方法叫做$K$ 折交叉验证($k$-fold cross-validation)。

在K折交叉验证中,我们把原始训练数据集分割成$K$个不重合的子数据集。然后我们做$K$次模型训练和验证。每一次,我们使用一个子数据集验证模型,并使用其他$K-1$个子数据集来训练模型。在这$K$次训练和验证中,每次用来验证模型的子数据集都不同。最后,我们只需对这$K$次训练误差和验证误差分别求平均作为最终的训练误差和验证误差。

我们将在本章最后一节实验$K$ 折交叉验证。


A
Aston Zhang 已提交
177
## 小结
178

A
Aston Zhang 已提交
179 180 181
* 我们希望通过适当降低模型的训练误差,从而能够间接降低模型的泛化误差。
* 欠拟合指模型无法得到较低的训练误差;过拟合指模型的训练误差远小于它在测试数据集上的误差。
* 我们应选择复杂度合适的模型并避免使用过少的训练样本。
A
fit  
Aston Zhang 已提交
182
* 我们要避免根据测试数据集上的误差来选择模型和调节超参数。
183 184 185 186


## 练习

A
Aston Zhang 已提交
187
* 如果用一个三阶多项式模型来拟合一个线性模型生成的数据,可能会有什么问题?为什么?
A
Aston Zhang 已提交
188
* 在我们本节提到的三阶多项式拟合问题里,有没有可能把100个样本的训练误差的期望降到0,为什么?
S
Sheng Zha 已提交
189

A
Aston Zhang 已提交
190 191 192 193

## 扫码直达[讨论区](https://discuss.gluon.ai/t/topic/983)

![](../img/qr_underfit-overfit.svg)