文章代码来源:《deep learning on keras》,非常好的一本书,大家如果英语好,推荐直接阅读该书,如果时间不够,可以看看此系列文章,文章为我自己翻译的内容加上自己的一些思考,水平有限,多有不足,请多指正,翻译版权所有,若有转载,请先联系本人。
个人方向为数值计算,日后会向深度学习和计算问题的融合方面靠近,若有相近专业人士,欢迎联系。
系列文章:
一、搭建属于你的第一个神经网络
二、训练完的网络去哪里找
三、【keras实战】波士顿房价预测
四、keras的function API
五、keras callbacks使用
六、机器学习基础Ⅰ:机器学习的四个标签
七、机器学习基础Ⅱ:评估机器学习模型
八、机器学习基础Ⅲ:数据预处理、特征工程和特征学习
九、机器学习基础Ⅳ:过拟合和欠拟合
十、机器学习基础Ⅴ:机器学习的一般流程十一、计算机视觉中的深度学习:卷积神经网络介绍
十二、计算机视觉中的深度学习:从零开始训练卷积网络
十三、计算机视觉中的深度学习:使用预训练网络
十四、计算机视觉中的神经网络:可视化卷积网络所学到的东西
在之前的例子中,我们注意到我们模型的表现都会在极少的训练批次后达到峰值,此后开始下降,即我们的模型会很快过拟合到训练数据。过拟合发生在每一个单独的机器学习问题中。学会如何解决过拟合问题对于掌控机器学习尤为重要。
在机器学习中最基础的主题就是优化和泛化的张力。“优化”代表着通过调整模型来在训练数据上表现最好,“泛化”则代表模型在未见过的数据上的表现。这场游戏的目标就是取得好的泛化,当然,你控制不了泛化,你只能基于训练数据调节模型。
在训练的一开始,优化和泛化是相关的:你在训练数据中的损失越低,你在测试函数中的损失也就越低。当这种情况发生时,你的模型就叫做欠拟合:这里还有可进步的空间;网络还没有将训练数据中的相关的数据建模完全。但是在训练数据进行相当次数的迭代以后,泛化就停止上升了,验证指标就停滞并开始下降:模型开始过拟合,比如说,它可能会开始学习一些对于训练数据很特别的特征,但是对于新来的数据可能完全不相关。
为了防止模型被训练数据中不相关的特征误导,最好地方法当然是弄更多的训练数据。一个模型用更多的数据去喂理所当然会有更好的泛化性。当增加数据行不通的时候,最好地方法是调节你模型存储的信息的质量,或者对于什么样的信息是允许被存储的加以限制。如果一个网络只能记住很少的特征,这个优化进程则主要集中于最突出的特征,这将会对于提高泛化性有很大的帮助。
用来解决过拟合的方法我们叫做正则化,让我们回顾一下一些常用的正则化方法并应用这些方法来提高之前章节提的电影分类模型的性能。
狙击过拟合
减少网络大小
最简单的预防过拟合的方法就是减少模型的大小,例如模型中可学习的参数数量(这有层数数量和每一层的单元数所决定)在深度学习中,模型中可学习参数的数量被称为模型的“容量”。直观来讲,一个有更多参数的模型将会有更多的“记忆容量”,因此这就很容易学习到一个像字典一样的映射关系,这样的映射关系没有任何的泛化能力。例如,一个有500000二进制参数的模型能够很容易的学习到MNIST训练集每一个数字的类别:我们对于每一个数字只需要一个二进制参数。这样一个模型对于分类i性能的数字样本没什么用。永远记住:深度学习模型趋向于拟合训练集,但是真正的挑战在于泛化,而不是拟合。
换句话来说,如果网络被记忆资源所限制,它就无法学习到那样的映射了,因此为了最小化损失,它就必须专注于学习压缩表示,预测的效果就取决于我们的目标了。同时,记住你应该使用有足够参数的模型使得它不会欠拟合:你的模型不应渴望记忆资源,在容量太大和容量不足之间需要寻找平衡。
不幸的是,没有什么有魔力的公式能够决定层所需的正确数量,以及每一层的正确大小。你必须评估一系列不同的结构(在你的验证集上而不是测试集上)来找出你的数据的正确模型大小。一般的找到合适大小的流程是从一些相关的比较少的层数和参数开始,然后开始提高层的大小并增加新的层知道你看到验证集的损失开始下降为止。
让我们来在电影评论分类网络中试一下。我们原始网络如下所示:
from keras import models
from keras import layers
model = models.Sequential()
model.add(layers.Dense(16, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
让我们用一个更小的网络来替换它
model = models.Sequential()
model.add(layers.Dense(4, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(4, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
这里比较了原始网络和更小网络中验证集的损失。
可以观察到小的网络过拟合迟于原始的,且其在过拟合后表现变差的比较慢。
接下来我们给模型加很多容量使得超过问题所需:
model = models.Sequential()
model.add(layers.Dense(512, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
这个大一点的网络几乎是在一开始就已经过拟合了,在一个批次以后过拟合就很严重了。其验证集的损失也充满了噪声。同时,给出两种网络的训练误差:
如你所见,大一点的网络训练损失很快就到了0.网络的容量越大,对训练数据建模就越快,但它就越容易过拟合。
增加正则化权重
或许你很熟悉奥卡姆剃刀原则:对一个事情给定两个解释,“简单的”那个看上去更像是对的,其做了最少的假设。这也被应用到了神经网络上了:给定一些训练数据和网络结构,有大量的权重值可以解释数据,简单的模型会有更小的概率过拟合。
一个“简单的模型”在这里就是一个参数值有更少的熵的模型,(或者说是参数总数更少的)。因此一个常用减轻过拟合的方法是采取小的值,让权重值更加正则。这就叫做权重正则化,这通过在损失函数加一个花费来做到的,这花费可以有以下两种类型:
- L1正则化,花费取决于权重系数的绝对值。
- L2正则化,花费取决于权重系数的平方。L2正则化在神经网络中也称为权重衰减。不要让不同的名字迷惑到你:权重衰减在数学形式上和L2正则化是一回事。
在keras里面,权重正则化通过权重正则化实例加进来作为关键词参数。让我们在电影评论分类网络中加入L2去那种正则化。
from keras import regularizers
model = models.Sequential()
model.add(layers.Dense(16, kernel_regularizer=regularizers.l2(0.001),
activation='relu', input_shape=(10000,)))
model.add(layers.Dense(16, kernel_regularizer=regularizers.l2(0.001),
activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
其中l2(0.001)意味着图层的权重矩阵将会乘以0.001加入到最终网络的损失中。注意乘法只在训练的时候加进去,也就是说网络的损失在训练的时候要比测试的时候高很多。
这里给出了L2正则化的惩罚:
如你所见,L2正则化以后模型更加耐过拟合了,尽管每一个模型都有相同数量的参数。
作为l2的替代,你可以看到如下的keras权重正则化:
from keras import regularizers
# L1 regularization
regularizers.l1(0.001)
# L1 and L2 regularization at the same time
regularizers.l1_l2(l1=0.001, l2=0.001)
添加drop out
dropout在是为了里面是一项最常用的正则化方法,由Hinton和他的学生在Toronto大学提出。dropout应用到“层”里面,由随机"dropping out"层在训练中学习到的要输出的特征。在测试的时候,没有单元被dropped out,层的输出值会按照dropout rate来缩放,以平衡在测试时和训练的时比有更多的单元。
给定layer_out的shape为(batch_size,features)
在训练的时候:
# At training time: we drop out 50% of the units in the output
layer_output *= np.randint(0, high=2, size=layer_output.shape)
在测试时:
# At test time:
layer_output *= 0.5
还有一种方法是在训练的时候两个操作都弄,然后在test 的时候output就不用变了。
# At training time:
layer_output *= np.randint(0, high=2, size=layer_output.shape)
# Note that we are scaling *up* rather scaling *down* in this case
layer_output /= 0.5
处理过程如图所示:
这样的方法看起来似乎有点奇怪和任意。为什么这样子可以帮助减少过拟合呢?hinton说他是被其它的事情激发的灵感,通过银行里的防欺诈机器,用他的话来说:“我去了我的银行。出纳员不断地变化, 我问他们中的一个为什么。他说他不知道, 但他们经常被转移。我想这一定是因为这需要员工之间的合作才能成功诈骗银行。这让我意识到, 在每个例子上随机删除不同的神经元子集, 可以防止阴谋, 从而减少过度拟合。”
其中的核心思想就是在输出值引入噪声来打破当噪声不存在时网络会记住的一些不重要的偶然模式。
在keras里面,你能使用dropout层来直接引入dropout。
model.add(layers.Dropout(0.5))
在之前的IMDB网络中加入两个dropout层,去看他们对于减少过拟合的效果有多好。
model = models.Sequential()
model.add(layers.Dense(16, activation='relu', input_shape=(10000,)))
model.add(layers.Dropout(0.5))
model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dropout(0.5))
model.add(layers.Dense(1, activation='sigmoid'))
画出结果:
我们又一次得到了网络的提升。
总结一下在神经网络里面防止过拟合的常用的方法:
- 更多的训练数据
- 减少网络的容量
- 增加权重正则化
- 增加dropout