深度学习:多层感知机、模型选择
多层感知机
多层感知机就是含有至少一个隐藏层的由全连接层组成的神经网络,且每个隐藏层的输出通过激活函数进行变换。多层感知机的层数和各隐藏层中隐藏单元个数都是超参数。多层感知机按以下方式计算输出:
其中\(\phi\)表示激活函数。在分类问题中,我们可以对输出\(\boldsymbol{O}\)做softmax运算,并使用softmax回归中的交叉熵损失函数。
在回归问题中,我们将输出层的输出个数设为1,并将输出\(\boldsymbol{O}\)直接提供给线性回归中使用的平方损失函数。
隐藏层
多层感知机在单层神经网络的基础上引入了一到多个隐藏层(hidden layer)。隐藏层位于输入层和输出层之间。
具体来说,给定一个小批量样本\(\boldsymbol{X} \in \mathbb{R}^{n \times d}\),其批量大小为\(n\),输入个数为\(d\)。假设多层感知机只有一个隐藏层,其中隐藏单元个数为\(h\)。记隐藏层的输出(也称为隐藏层变量或隐藏变量)为\(\boldsymbol{H}\),有\(\boldsymbol{H} \in \mathbb{R}^{n \times h}\)。因为隐藏层和输出层均是全连接层,可以设隐藏层的权重参数和偏差参数分别为\(\boldsymbol{W}_h \in \mathbb{R}^{d \times h}\)和 \(\boldsymbol{b}_h \in \mathbb{R}^{1 \times h}\),输出层的权重和偏差参数分别为\(\boldsymbol{W}_o \in \mathbb{R}^{h \times q}\)和\(\boldsymbol{b}_o \in \mathbb{R}^{1 \times q}\)。
我们先来看一种含单隐藏层的多层感知机的设计。其输出\(\boldsymbol{O} \in \mathbb{R}^{n \times q}\)的计算为
也就是将隐藏层的输出直接作为输出层的输入。如果将以上两个式子联立起来,可以得到
从联立后的式子可以看出,虽然神经网络引入了隐藏层,却依然等价于一个单层神经网络:其中输出层权重参数为\(\boldsymbol{W}_h\boldsymbol{W}_o\),偏差参数为\(\boldsymbol{b}_h \boldsymbol{W}_o + \boldsymbol{b}_o\)。不难发现,即便再添加更多的隐藏层,以上设计依然只能与仅含输出层的单层神经网络等价。
激活函数
上述问题的根源在于全连接层只是对数据做仿射变换(affine transformation),而多个仿射变换的叠加仍然是一个仿射变换。解决问题的一个方法是引入非线性变换,例如对隐藏变量使用按元素运算的非线性函数进行变换,然后再作为下一个全连接层的输入。这个非线性函数被称为激活函数(activation function)。
ReLU函数
ReLU(rectified linear unit)函数提供了一个很简单的非线性变换。给定元素\(x\),该函数定义为
可以看出,ReLU函数只保留正数元素,并将负数元素清零。
sigmoid函数
sigmoid函数可以将元素的值变换到0和1之间:
sigmoid函数在早期的神经网络中较为普遍,但它目前逐渐被更简单的ReLU函数取代。
tanh函数
tanh(双曲正切)函数可以将元素的值变换到-1和1之间:
简洁实现
点击查看代码
import d2lzh as d2l
from mxnet import gluon, init
from mxnet.gluon import loss as gloss, nn
#定义模型
net = nn.Sequential()
net.add(nn.Dense(256, activation='relu'),
nn.Dense(10))
net.initialize(init.Normal(sigma=0.01))
#训练模型
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
loss = gloss.SoftmaxCrossEntropyLoss()
trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.5})
num_epochs = 5
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, None,
None, trainer)
模型选择、欠拟合和过拟合
改变过实验中的模型结构或者超参数:当模型在训练数据集上更准确时,它在测试数据集上却不一定更准确。
训练误差和泛化误差
区分训练误差(training error)和泛化误差(generalization error)。
前者指模型在训练数据集上表现出的误差,后者指模型在任意一个测试数据样本上表现出的误差的期望,并常常通过测试数据集上的误差来近似。
在机器学习里,我们通常假设训练数据集(训练题)和测试数据集(测试题)里的每一个样本都是从同一个概率分布中相互独立地生成的。
基于该独立同分布假设,给定任意一个机器学习模型(含参数),它的训练误差的期望和泛化误差都是一样的。
一般情况下,由训练数据集学到的模型参数会使模型在训练数据集上的表现优于或等于在测试数据集上的表现。由于无法从训练误差估计泛化误差,一味地降低训练误差并不意味着泛化误差一定会降低。
机器学习模型应关注降低泛化误差。
模型选择
在机器学习中,通常需要评估若干候选模型的表现并从中选择模型。这一过程称为模型选择(model selection)。
可供选择的候选模型可以是有着不同超参数的同类模型。
- 以多层感知机为例,我们可以选择隐藏层的个数,以及每个隐藏层中隐藏单元个数和激活函数。为了得到有效的模型,我们通常要在模型选择上下一番功夫。
验证数据集
从严格意义上讲,测试集只能在所有超参数和模型参数选定后使用一次。不可以使用测试数据选择模型,如调参。
由于无法从训练误差估计泛化误差,因此也不应只依赖训练数据选择模型。
鉴于此,我们可以预留一部分在训练数据集和测试数据集以外的数据来进行模型选择。
这部分数据被称为验证数据集,简称验证集(validation set)。
例如,我们可以从给定的训练集中随机选取一小部分作为验证集,而将剩余部分作为真正的训练集。
然而在实际应用中,由于数据不容易获取,测试数据极少只使用一次就丢弃。
因此,实践中验证数据集和测试数据集的界限可能比较模糊。从严格意义上讲,除非明确说明,否则本书中实验所使用的测试集应为验证集,实验报告的测试结果(如测试准确率)应为验证结果(如验证准确率)。
k折交叉验证
由于验证数据集不参与模型训练,当训练数据不够用时,预留大量的验证数据显得太奢侈。
一种改善的方法是\(k\)折交叉验证(\(k\)-fold cross-validation)。
- 在\(k\)折交叉验证中,我们把原始训练数据集分割成\(k\)个不重合的子数据集,然后我们做\(k\)次模型训练和验证。
- 每一次,我们使用一个子数据集验证模型,并使用其他\(k-1\)个子数据集来训练模型。
- 在这\(k\)次训练和验证中,每次用来验证模型的子数据集都不同。
- 最后,我们对这\(k\)次训练误差和验证误差分别求平均。
欠拟合和过拟合
模型训练中经常出现的两类典型问题:
- 一类是模型无法得到较低的训练误差,我们将这一现象称作欠拟合(underfitting);
- 另一类是模型的训练误差远小于它在测试数据集上的误差,我们称该现象为过拟合(overfitting)。
- 在实践中,我们要尽可能同时应对欠拟合和过拟合。
模型复杂度
为了解释模型复杂度,我们以多项式函数拟合为例。给定一个由标量数据特征\(x\)和对应的标量标签\(y\)组成的训练数据集,多项式函数拟合的目标是找一个\(K\)阶多项式函数
来近似\(y\)。在上式中,\(w_k\)是模型的权重参数,\(b\)是偏差参数。与线性回归相同,多项式函数拟合也使用平方损失函数。特别地,一阶多项式函数拟合又叫线性函数拟合。
因为高阶多项式函数模型参数更多,模型函数的选择空间更大,所以高阶多项式函数比低阶多项式函数的复杂度更高。
因此,高阶多项式函数比低阶多项式函数更容易在相同的训练数据集上得到更低的训练误差。
训练数据集大小
影响欠拟合和过拟合的另一个重要因素是训练数据集的大小。
一般来说,如果训练数据集中样本数过少,特别是比模型参数数量(按元素计)更少时,过拟合更容易发生。
此外,泛化误差不会随训练数据集里样本数量增加而增大。
因此,在计算资源允许的范围之内,我们通常希望训练数据集大一些,特别是在模型复杂度较高时,如层数较多的深度学习模型。
小结
- 由于无法从训练误差估计泛化误差,一味地降低训练误差并不意味着泛化误差一定会降低。机器学习模型应关注降低泛化误差。
- 可以使用验证数据集来进行模型选择。
- 欠拟合指模型无法得到较低的训练误差,过拟合指模型的训练误差远小于它在测试数据集上的误差。
- 应选择复杂度合适的模型并避免使用过少的训练样本。
参考文献
《动手学深度学习》