原文地址:https://github.com/fchollet/deep-learning-with-python-notebooks/blob/master/4.4-overfitting-and-underfitting.ipynb
Overfitting and underfitting
在前面章节中看到的所有例子中 - 电影评论情绪预测,主题分类和房价回归 - 我们都可以注意到,我们的模型在持有验证数据上的表现总是会在几个时期和然后开始退化,即我们的模型会很快开始过度适应训练数据。过度拟合发生在每一个机器学习问题中。学习如何处理过度拟合对于掌握机器学习至关重要。机器学习的根本问题是优化和泛化之间的紧张关系。 “优化”是指调整模型以获得训练数据上可能的最佳性能(“机器学习”中的“学习”)的过程,而“泛化”是指训练的模型对其具有的数据从未见过。游戏的目标当然是获得很好的泛化,但是你不能控制泛化;您只能根据其训练数据调整模型。
在训练开始时,优化和泛化是相关的:训练数据的损失越低,测试数据的损失就越低。发生这种情况时,你的模型被认为是不合适的:仍有进展;该网络尚未模拟训练数据中的所有相关模式。但是,在对训练数据进行一定次数的迭代之后,泛化会停止改进,验证度量标准失速然后开始降级:模型开始过度拟合,即它开始学习特定于训练数据的模式,但它们是在涉及新数据时会引起误导或无关紧要。
为了防止模型在训练数据中发现误导或不相关的模式,最好的解决方案当然是获得更多的训练数据。训练更多数据的模型自然会更好地推广。当不再可能时,下一个最佳解决方案是调整模型允许存储的信息数量,或者对允许存储的信息添加限制。如果一个网络只能记住少量的模式,那么优化过程将迫使它把注意力集中在最突出的模式上,这种模式具有更好的泛化能力。
以这种方式处理过度拟合的过程称为正则化。让我们回顾一些最常见的正则化技术,让我们将其应用到实践中,以改进前一章中的电影分类模型。
注意:在这个笔记本中,我们将使用IMDB测试集作为我们的验证集。在这种情况下并不重要。
我们使用第3章第5节的代码准备数据:
from keras.datasets import imdb
import numpy as np
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)
def vectorize_sequences(sequences, dimension=10000):
# Create an all-zero matrix of shape (len(sequences), dimension)
results = np.zeros((len(sequences), dimension))
for i, sequence in enumerate(sequences):
results[i, sequence] = 1. # set specific indices of results[i] to 1s
return results
# Our vectorized training data
x_train = vectorize_sequences(train_data)
# Our vectorized test data
x_test = vectorize_sequences(test_data)
# Our vectorized labels
y_train = np.asarray(train_labels).astype('float32')
y_test = np.asarray(test_labels).astype('float32')
战斗过度
减少网络的大小
防止过度拟合的最简单方法是减小模型的大小,即模型中可学习参数的数量(由层数和每层单位数决定)。在深度学习中,模型中可学习参数的数量通常称为模型的“容量”。直观地说,具有更多参数的模型将具有更多的“记忆能力”,因此将能够轻松地学习训练样本与其目标之间的完美字典式映射,这是一种没有任何泛化能力的映射。例如,一个具有50万个二进制参数的模型可以很容易地用来学习MNIST训练集中每个数字的类别:对于每个50,000个数字,我们只需要10个二进制参数。这样的模型对于分类新的数字样本是无用的。始终牢记这一点:深度学习模型倾向于适合训练数据,但真正的挑战是泛化,而不适合。另一方面,如果网络具有有限的记忆资源,它将不能容易地学习这种映射,并且因此为了使其损失最小化,它将不得不求助于学习具有关于目标 - 正是我们感兴趣的表示类型。同时,请记住,您应该使用具有足够参数的模型,以避免不适合:您的模型不应该因记忆资源而不足。在“容量太大”和“容量不足”之间存在妥协。
不幸的是,没有一个不可思议的公式来确定正确的层数,或者每层的正确尺寸。您将不得不评估一系列不同的体系结构(当然,在您的验证集上,而不是您的测试集上),以便为您的数据找到正确的模型大小。找到合适的模型大小的一般工作流程是从相对较少的图层和参数开始,并开始增加图层的大小或添加新图层,直到看到验证丢失的回报递减。
让我们在我们的电影评论分类网络上试试这个。我们原来的网络是这样的:
from keras import models
from keras import layers
original_model = models.Sequential()
original_model.add(layers.Dense(16, activation='relu', input_shape=(10000,)))
original_model.add(layers.Dense(16, activation='relu'))
original_model.add(layers.Dense(1, activation='sigmoid'))
original_model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['acc'])
现在让我们尝试用这个更小的网络替换它:
smaller_model = models.Sequential()
smaller_model.add(layers.Dense(4, activation='relu', input_shape=(10000,)))
smaller_model.add(layers.Dense(4, activation='relu'))
smaller_model.add(layers.Dense(1, activation='sigmoid'))
smaller_model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['acc'])
以下是原始网络和较小网络的验证损失比较。 点是较小网络的验证损失值,并且十字架是初始网络(记住:较低的验证损失表示更好的模型)。
original_hist = original_model.fit(x_train, y_train,
epochs=20,
batch_size=512,
validation_data=(x_test, y_test))
smaller_model_hist = smaller_model.fit(x_train, y_train,
epochs=20,
batch_size=512,
validation_data=(x_test, y_test))
epochs = range(1, 21)
original_val_loss = original_hist.history['val_loss']
smaller_model_val_loss = smaller_model_hist.history['val_loss']
import matplotlib.pyplot as plt
# b+ is for "blue cross"
plt.plot(epochs, original_val_loss, 'b+', label='Original model')
# "bo" is for "blue dot"
plt.plot(epochs, smaller_model_val_loss, 'bo', label='Smaller model')
plt.xlabel('Epochs')
plt.ylabel('Validation loss')
plt.legend()
plt.show()
正如您所看到的,较小的网络比参考时间晚开始过度拟合(经过6个历元而不是4个),并且一旦开始过拟合,其性能下降速度就会慢得多。
现在,对于踢球,让我们在这个基准测试中增加一个具有更多容量的网络,远远超出问题所需:
bigger_model = models.Sequential()
bigger_model.add(layers.Dense(512, activation='relu', input_shape=(10000,)))
bigger_model.add(layers.Dense(512, activation='relu'))
bigger_model.add(layers.Dense(1, activation='sigmoid'))
bigger_model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['acc'])
bigger_model_hist = bigger_model.fit(x_train, y_train,
epochs=20,
batch_size=512,
validation_data=(x_test, y_test))
以下是与参考价格相比更大的网络票价的情况。 点是较大网络的验证损失值,而十字是初始网络。
bigger_model_val_loss = bigger_model_hist.history['val_loss']
plt.plot(epochs, original_val_loss, 'b+', label='Original model')
plt.plot(epochs, bigger_model_val_loss, 'bo', label='Bigger model')
plt.xlabel('Epochs')
plt.ylabel('Validation loss')
plt.legend()
plt.show()
更大的网络几乎马上就开始过度配备,仅仅过了一个时代,并且过度适应更严重。 其验证损失也更加嘈杂。
同时,这里是我们两个网络的训练损失:
original_train_loss = original_hist.history['loss']
bigger_model_train_loss = bigger_model_hist.history['loss']
plt.plot(epochs, original_train_loss, 'b+', label='Original model')
plt.plot(epochs, bigger_model_train_loss, 'bo', label='Bigger model')
plt.xlabel('Epochs')
plt.ylabel('Training loss')
plt.legend()
plt.show()
正如你所看到的,更大的网络很快就会使训练损失接近零。 网络容量越大,训练数据建模速度就越快(导致训练损失低),但过度拟合更容易(导致训练和验证损失之间存在很大差异)。
增加体重正则化
你可能熟悉奥卡姆剃刀原理:给出两种解释,最可能是正确的解释是“最简单”的解释,即最少量假设的解释。这也适用于神经网络学习的模型:给定一些训练数据和网络体系结构,有多组权重值(多个模型)可以解释数据,较简单的模型不太适合复杂的过拟合。
在这种情况下,一个“简单模型”是一个参数值分布的熵较小的模型(或者一个参数较少的模型,正如我们在上面的章节中看到的那样)。因此,减轻过度拟合的一种常见方法是通过强制其权重仅取小值来限制网络的复杂性,这使得权重值的分布更加“常规”。这被称为“权重正则化”,并且通过向网络的损失函数添加与具有大权重相关的成本来完成。这种成本有两种口味:
L1正则化,其中加入的成本与权重系数的绝对值成比例(即所谓的权重的“L1范数”)。
L2正则化,其中所增加的成本与权重系数值的平方(即所谓的权重的“L2范数”)成正比。 L2正则化在神经网络中也称为权重衰减。不要让不同的名字让你感到困惑:体重衰减在数学上与L2正则化完全相同。
在Keras中,重量正则化是通过将重量调节器实例作为关键字参数传递给图层来添加的。让我们在我们的电影评论分类网络中添加L2权重正则化:
from keras import regularizers
l2_model = models.Sequential()
l2_model.add(layers.Dense(16, kernel_regularizer=regularizers.l2(0.001),
activation='relu', input_shape=(10000,)))
l2_model.add(layers.Dense(16, kernel_regularizer=regularizers.l2(0.001),
activation='relu'))
l2_model.add(layers.Dense(1, activation='sigmoid'))
l2_model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['acc'])
l2(0.001)意味着该层的权重矩阵中的每个系数将增加0.001 * weight_coefficient_value到网络的总损失。 请注意,由于这种惩罚仅在训练时间添加,因此该训练网络的损失在训练时会比在测试时间高得多。
以下是我们二级正规化惩罚的影响:
l2_model_hist = l2_model.fit(x_train, y_train,
epochs=20,
batch_size=512,
validation_data=(x_test, y_test))
l2_model_val_loss = l2_model_hist.history['val_loss']
plt.plot(epochs, original_val_loss, 'b+', label='Original model')
plt.plot(epochs, l2_model_val_loss, 'bo', label='L2-regularized model')
plt.xlabel('Epochs')
plt.ylabel('Validation loss')
plt.legend()
plt.show()
正如您所看到的,即使两个模型具有相同数量的参数,具有L2正则化(点)的模型也比参考模型(十字架)更能抵抗过度拟合。
作为L2正规化的替代品,您可以使用以下Keras体重调节器之一:
扫描二维码关注公众号,回复:
1813393 查看本文章
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)
添加丢失
辍学是由Hinton和他的学生在多伦多大学开发的最有效和最常用的神经网络正则化技术之一。应用于图层的失落包括在训练期间随机“辍学”(即设置为零)层的许多输出特征。假设一个给定的层通常会在训练过程中为给定的输入样本返回一个向量[0.2,0.5,1.3,0.8,1.1]在应用丢弃之后,该向量将具有随机分布的几个零条目,例如, [0,0.5,1.3,0,1.1]。 “辍学率”是正在被归零的功能的一小部分;它通常设置在0.2和0.5之间。在测试时间,没有单位退出,而是将图层的输出值按照与退出率相等的因子缩小,以平衡更多单位比训练时更活跃的事实。考虑包含shape(layer_size,features)图层的输出layer_output的Numpy矩阵。在训练时,我们会随机调零矩阵中的一小部分值:
# At training time: we drop out 50% of the units in the output
layer_output *= np.randint(0, high=2, size=layer_output.shape)
在测试时间,我们将通过退出率缩小输出。 在这里我们缩小0.5(因为我们之前下降了一半的单位):
# At test time:
layer_output *= 0.5
注意,这个过程可以通过在训练时进行两个操作并在测试时保持输出不变来实现,这通常是实践中实现的方式:
# 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
这种技术可能看起来很奇怪和任意。 为什么这会帮助减少过度配合? Geoff Hinton表示,他受到了银行所使用的防欺诈机制的启发 - 用他自己的话说:“我去了银行,出纳员一直在变,我问他们其中一个为什么,他说他 我不知道但他们有很多东西,我想这一定是因为它需要员工之间的合作来成功地欺骗银行,这让我意识到,在每个例子中随机删除一个不同的神经元子集可以防止阴谋,从而避免阴谋 减少过度配合“。
其核心思想是在一个图层的输出值中引入噪声可以打破不显着的事件模式(Hinton称之为“阴谋”),如果没有噪声出现,网络将开始记忆。
在Keras中,您可以通过Dropout图层在网络中引入丢失,该图层将应用于其之前的图层输出,例如:
model.add(layers.Dropout(0.5))
让我们在我们的IMDB网络中添加两个Dropout图层,以查看它们在减少过度拟合方面的表现如何:
dpt_model = models.Sequential()
dpt_model.add(layers.Dense(16, activation='relu', input_shape=(10000,)))
dpt_model.add(layers.Dropout(0.5))
dpt_model.add(layers.Dense(16, activation='relu'))
dpt_model.add(layers.Dropout(0.5))
dpt_model.add(layers.Dense(1, activation='sigmoid'))
dpt_model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['acc'])
dpt_model_hist = dpt_model.fit(x_train, y_train,
epochs=20,
batch_size=512,
validation_data=(x_test, y_test))