Copyright © Microsoft Corporation. All rights reserved. 适用于License版权许可
英文为Regularization,用于防止过拟合。
在深度神经网络中,我们遇到的另外一个挑战,就是网络的泛化问题。所谓泛化,就是模型在测试集上的表现要和训练集上一样好。经常有这样的例子:一个模型在训练集上千锤百炼,能到达99%的准确率,拿到测试集上一试,准确率还不到90%。这说明模型过度拟合了训练数据,而不能反映真实世界的情况。解决过度拟合的手段和过程,就叫做泛化。
神经网络的两大功能:回归和分类。这两类任务,都会出现欠拟合和过拟合现象,如下图所示:
上图是回归任务中的三种情况,依次为:欠拟合、正确的拟合、过拟合。
上图是分类任务中的三种情况,依次为:分类欠妥、正确的分类、分类过度。由于分类可以看作是对分类边界的拟合,所以我们经常也统称其为拟合。
上图中对于“深入敌后”的那颗绿色点样本,正确的做法是把它当作噪音看待,而不要让它对网络产生影响。而对于上例中的欠拟合情况,如果简单的(线性)模型不能很好地完成任务,我们可以考虑使用复杂的(非线性或深度)模型,即加深网络的宽度和深度,提高神经网络的能力。
但是如果网络过于宽和深,就会出现第三张图展示的过拟合的情况。
出现过拟合的原因:
充分理解过拟合的原因之后,我们先制作一个数据集,让其符合上面的第三条:制造样本噪音。但是如何制作一个合理的噪音呢?这让笔者想起了一篇讲解傅里叶变换的文章,一个复合的傅里叶变换公式可以是这样的:
$$ y = {4 \sin (\theta) \over \pi} + {4 \sin (5\theta) \over 5\pi} \tag{1} $$
这个公式可以在$[0,2\pi]$之间制作出下图:
其中,绿色的点是公式1的第一部分的结果,蓝色的点是整个公式1的结果。我们可以把绿色的点作为测试/验证基线,可以看到它是一条标准的正弦曲线。而蓝色的点作为带噪音的训练样本,该训练样本只有25个数据。
然后我们使用MiniFramework,可以很方便地搭建起下面这个模型:
这个模型的复杂度要比训练样本级大很多,所以可以重现过拟合的现象,当然还需要设置好合适的参数,代码片段如下:
def SetParameters():
num_hidden = 16
max_epoch = 20000
batch_size = 5
learning_rate = 0.1
eps = 1e-6
hp = HyperParameters41(
learning_rate, max_epoch, batch_size, eps,
net_type=NetType.Fitting,
init_method=InitialMethod.Xavier,
optimizer_name=OptimizerName.SGD)
return hp, num_hidden
我们故意把最大epoch次数设置得比较大,以充分展示过拟合效果。训练结束后,首先看损失函数值和精度值的变化曲线:
蓝色为训练集,红色为验证集。可以看到,训练集上的损失函数值很快降低到极点,精确度很快升高到极点,而验证集上的表现正好相反。说明网络对训练集很适应,但是越来越不适应验证集数据,出现了严重的过拟合。验证集的精确度为0.9605。
再看下图的拟合情况:
红色拟合曲线严丝合缝地拟合了每一个样本点,也就是说模型学习到了样本的误差。绿色点所组成的曲线,才是我们真正想要的拟合结果。
我们将要使用MNIST数据集做例子,模拟出令一个过拟合(分类)的情况。从上面的过拟合出现的4点原因分析,第2点和第3点对于MNIST数据集来说并不成立,MNIST数据集有60000个样本,这足以保证它的特征分布的一致性,少数样本的噪音也会被大多数正常的数据所淹没。但是如果我们只选用其中的很少一部分的样本,则特征分布就可能会有偏差,而且独立样本的噪音会变得突出一些。
再看过拟合原因中的第1点和第4点,我们利用第14章中的已有知识和代码,搭建一个复杂网络很容易,而且迭代次数完全可以由代码来控制。
首先,只使用1000个样本来做训练,如下面的代码所示,调用一个ReadLessData(1000)函数,并且用GenerateValidationSet(k=10)函数把1000个样本分成900和100两部分,分别做为训练集和验证集:
def LoadData():
mdr = MnistImageDataReader(train_image_file, train_label_file, test_image_file, test_label_file, "vector")
mdr.ReadLessData(1000)
mdr.Normalize()
mdr.GenerateDevSet(k=10)
return mdr
然后,我们搭建一个深度网络:
这个网络有5个全连接层,前4个全连接层后接ReLU激活函数层,最后一个全连接层接Softmax分类函数做10分类。由于我们在第14章就已经搭建好了深度神经网络的Mini框架,所以可以简单地搭建这个网络,如下代码所示:
def Net(dateReader, num_input, num_hidden, num_output, params):
net = NeuralNet(params)
fc1 = FcLayer(num_input, num_hidden, params)
net.add_layer(fc1, "fc1")
relu1 = ActivatorLayer(Relu())
net.add_layer(relu1, "relu1")
fc2 = FcLayer(num_hidden, num_hidden, params)
net.add_layer(fc2, "fc2")
relu2 = ActivatorLayer(Relu())
net.add_layer(relu2, "relu2")
fc3 = FcLayer(num_hidden, num_hidden, params)
net.add_layer(fc3, "fc3")
relu3 = ActivatorLayer(Relu())
net.add_layer(relu3, "relu3")
fc4 = FcLayer(num_hidden, num_hidden, params)
net.add_layer(fc4, "fc4")
relu4 = ActivatorLayer(Relu())
net.add_layer(relu4, "relu4")
fc5 = FcLayer(num_hidden, num_output, params)
net.add_layer(fc5, "fc5")
softmax = ActivatorLayer(Softmax())
net.add_layer(softmax, "softmax")
net.train(dataReader, checkpoint=1)
net.ShowLossHistory()
net.train(dataReader, checkpoint=1)函数的参数checkpoint的含义是,每隔1个epoch记录一次训练过程中的损失值和准确率。可以设置成大于1的数字,比如10,意味着每10个epoch检查一次。也可以设置为小于1大于0的数比如0.5,假设在一个epoch中要迭代100次,则每50次检查一次。
在main过程中,设置一些超参数,然后调用刚才建立的Net进行训练:
if __name__ == '__main__':
dataReader = LoadData()
num_feature = dataReader.num_feature
num_example = dataReader.num_example
num_input = num_feature
num_hidden = 30
num_output = 10
max_epoch = 200
batch_size = 100
learning_rate = 0.1
eps = 1e-5
params = CParameters(
learning_rate, max_epoch, batch_size, eps,
LossFunctionName.CrossEntropy3,
InitialMethod.Xavier,
OptimizerName.SGD)
Net(dataReader, num_input, num_hidden, num_hidden, num_hidden, num_hidden, num_output, params)
在超参数中,我们指定了:
最终我们可以得到下图:
在训练集上(蓝色曲线),很快就达到了损失函数值趋近于0,准确度100%的程度。而在验证集上(红色曲线),损失函数值缺越来越大,准确度也在下降。这就造成了一个典型的过拟合网络,即所谓U型曲线,无论是损失函数值和准确度,都呈现出了这种分化的特征。
我们再看打印输出部分:
epoch=199, total_iteration=1799
loss_train=0.0015, accuracy_train=1.000000
loss_valid=0.9956, accuracy_valid=0.860000
time used: 5.082462787628174
total weights abs sum= 1722.470655813152
total weights = 26520
little weights = 2815
zero weights = 27
testing...
rate=8423 / 10000 = 0.8423
结果说明:
在着手解决过拟合的问题之前,我们先来学习一下关于偏差与方差的知识,以便得到一些理论上的指导,虽然神经网络是一门实验学科。
有了直观感受和理论知识,下面我们看看如何解决过拟合问题:
ch16, Level0,Level1