模型选择
首先我们需要考虑误差!
首先在机器学习模型中误差有如下两种:
- 训练误差
- 泛化误差
训练误差指模型在训练数据集上表现出的误差
泛化误差指模型在任意一个测试数据样本上表现出的误差的期望
而在机器学习模型中应该关注降低泛化误差
在机器学习中,通常需要评估若干候选模型的表现并从中选择模型。这一过程称为模型选择(model selection)。可供选择的候选模型可以是有着不同超参数的同类模型。以多层感知机为例,我们可以选择隐藏层的个数,以及每个隐藏层中隐藏单元个数和激活函数。为了得到有效的模型,我们通常要在模型选择上下一番功夫。下面,我们来描述模型选择中经常使用的验证数据集(validation data set)。
在机器学习中,我们有两个数据集,一个是训练集,一个是测试集。严格意义讲,测试集只能使用一次。所以我们不能够根据测试集的数据选择模型,调参等。我们也无法从训练误差估计泛化误差,
因此我们也不能够更具训练集选择模型。那么既不能从测试集选择模型,又不能从训练集选择模型,那应该怎么办呢?
K折交叉验证
一种改善的方法是 K 折交叉验证( K -fold cross-validation ).
在 K 折交叉验证中,我们把原始训练数据集分割成 K 个不重合的子数据集,然后我们做 K 次模型训练和验证。每一次,我们使用一个子数据集验证模型,并使用其他 K−1 个子数据集来训练模型。在这 K 次训练和验证中,每次用来验证模型的子数据集都不同。最后,我们对这 K 次训练误差和验证误差分别求平均。
欠拟合和过拟合
模型训练中经常出现的两类典型问题:
- 一类是模型无法得到较低的训练误差,我们将这一现象称作欠拟合(underfitting)
- 另一类是模型的训练误差远小于它在测试数据集上的误差,我们称该现象为过拟合(overfitting)
模型复杂度
以多项式函数拟合为例,目标是寻找一个K阶多项式函数
来近似y。在上式中,wk是模型的权重参数,b是偏差参数。与线性回归相同,多项式函数拟合也使用平方损失函数。特别地,一阶多项式函数拟合又叫线性函数拟合。
因为高阶多项式函数模型参数更多,模型函数的选择空间更大,所以高阶多项式函数比低阶多项式函数的复杂度更高。因此,高阶多项式函数比低阶多项式函数更容易在相同的训练数据集上得到更低的训练误差。
给定训练数据集,模型复杂度和误差之间的关系通常如图3.4所示。给定训练数据集,如果模型的复杂度过低,很容易出现欠拟合;如果模型复杂度过高,很容易出现过拟合。应对欠拟合和过拟合的一个办法是针对数据集选择合适复杂度的模型。
训练数据集大小
影响欠拟合和过拟合的另一个重要因素是训练数据集的大小。一般来说,如果训练数据集中样本数过少,特别是比模型参数数量(按元素计)更少时,过拟合更容易发生。此外,泛化误差不会随训练数据集里样本数量增加而增大。
因此,在计算资源允许的范围之内,我们通常希望训练数据集大一些,特别是在模型复杂度较高时,例如层数较多的深度学习模型。
多项式函数拟合实验
导入必要的包
import d2lzh as d2l
from mxnet import autograd,nd,gluon
from mxnet.gluon import data as gdata,loss as gloss,nn
生成数据集
我们将生成一个人工数据集。在训练数据集和测试数据集中,给定样本特征 x ,我们使用如下的三阶多项式函数来生成该样本的标签:
其中噪声项 ϵ 服从均值为0、标准差为0.1的正态分布。训练数据集和测试数据集的样本数都设为100。
#设置训练集和测试集的样本数都为100,再设置w与b
train_n,test_n,true_w,true_b = 100,100,[1.8,-9.6,3.14],2.8
#随机生成特征值
features = nd.random.normal(shape=(train_n+test_n,1))
poly_features = nd.concat(features,nd.power(features,2),nd.power(features,3))
labels = poly_features[:,0]*true_w[0] + poly_features[:,1]*true_w[1] + poly_features[:,2]*true_w[2] + true_b
labels += nd.random.normal(scale=0.01,shape=labels.shape)
看一看生成的数据集的前两个样本。
features[:2],poly_features[:2],labels[:2]
(
[[-1.680917 ]
[-2.6438675]]
,
[[ -1.680917 2.8254821 -4.749401 ]
[ -2.6438675 6.9900355 -18.480726 ]]
,
[ -42.26117 -127.08934]
)
定义 训练 测试 模型
#首先定义作图函数semilogy,y轴用了对数尺度
def semilogy(x_vals,y_vals,x_label,y_label,x2_vals=None,y2_vals=None,legend=None,figsize=(3.5,2.5)):
d2l.set_figsize(figsize)
d2l.plt.xlabel(x_label)
d2l.plt.ylabel(y_label)
d2l.plt.semilogy(x_vals,y_vals)
if x2_vals and y2_vals:
d2l.plt.semilogy(x2_vals,y2_vals)
d2l.plt.legend(legend)
num_epochs, loss = 100, gloss.L2Loss()
def fit_and_plot(train_features, test_features, train_labels, test_labels):
net = nn.Sequential()
net.add(nn.Dense(1))
net.initialize()
batch_size = min(10, train_labels.shape[0])
train_iter = gdata.DataLoader(gdata.ArrayDataset(
train_features, train_labels), batch_size, shuffle=True)
trainer = gluon.Trainer(net.collect_params(), 'sgd',
{'learning_rate': 0.01})
train_ls, test_ls = [], []
for _ in range(num_epochs):
for X, y in train_iter:
with autograd.record():
l = loss(net(X), y)
l.backward()
trainer.step(batch_size)
train_ls.append(loss(net(train_features),
train_labels).mean().asscalar())
test_ls.append(loss(net(test_features),
test_labels).mean().asscalar())
print('final epoch: train loss', train_ls[-1], 'test loss', test_ls[-1])
semilogy(range(1, num_epochs + 1), train_ls, 'epochs', 'loss',
range(1, num_epochs + 1), test_ls, ['train', 'test'])
print('weight:', net[0].weight.data().asnumpy(),
'\nbias:', net[0].bias.data().asnumpy())
三阶多项式函数拟合(正常)
fit_and_plot(poly_features[:train_n, :], poly_features[train_n:, :],
labels[:train_n], labels[train_n:])
final epoch: train loss 0.002419042 test loss 0.0032950742
weight: [[ 1.6981984 -9.568022 3.1638238]]
bias: [2.7530537]
线性函数拟合(欠拟合)
fit_and_plot(features[:train_n, :], features[train_n:, :],
labels[:train_n], labels[train_n:])
final epoch: train loss 219.35103 test loss 108.207634
weight: [[14.531894]]
bias: [-9.7385435]
训练样本不足(过拟合)
fit_and_plot(poly_features[:2, :], poly_features[train_n:, :],
labels[:2], labels[train_n:])
final epoch: train loss 1.0184645e+16 test loss 596942100000000.0
weight: [[-1407827.5 3514087. -8941110. ]]
bias: [579296.6]