tensorflow之过拟合和欠拟合问题

同前面的示例一样,此示例中的代码将使用tf.keras API,您可以在TensorFlow Keras指南中了解更多信息。

在之前的两个例子中 - 电影评论分类和预测住房价格 - 我们看到模型对验证数据的准确性在经过多个epochs的训练后会达到峰值,然后开始下降。

换句话说,我们的模型会过度拟合训练数据。学习如何处理过度拟合很重要。尽管通常可以在训练集上实现高精度,但我们真正想要的是开发出在测试数据(或之前未见过的数据)有很好的泛化性能的模型。

过度拟合的反面是欠拟合。当测试数据仍有改进空间时,会发生欠拟合。出现这种情况的原因有很多:如果模型不够强大,过度正则化,或者根本没有经过足够长时间的训练。这意味着网络尚未学习训练数据中的相关模式。

如果训练时间过长,模型将开始过度拟合并从训练数据中学习模式,而这些模式不会推广到测试数据。我们需要取得平衡。如下所述,了解如何训练适当数量的时期是一项有用的技能。

为了防止过度拟合,最好的解决方案是使用更多的训练数据。受过更多数据训练的模型自然会更好地泛化。当没有大量的数据时,下一个最佳解决方案是使用正则化等技术。这些限制了模型可以存储的信息的数量和类型。如果一个网络只能记住少量的模式,那么优化过程将迫使它专注于最突出的模式,这些模式有更好的概括性。

在这个示例中,我们将探索两种常见的正则化技术 - 权重正则化和dropout - 并使用它们来改进我们的IMDB电影评论分类模型。

1.数据预处理

对句子进行one-hot编码。该模型将很快适应训练集。它将用于证明何时发生过度拟合,以及如何对抗它。

对我们的列表进行one-hot编码意味着将它们转换为0和1的向量。具体地说,这意味着例如将序列 [3, 5]转换为10,000维向量,除了索引3和5之外,该向量将是全零,其将是1。

#下载电影评论数据
NUM_WORDS=10000 #词典仅保留前10000个单词
(train_data,train_labels),(test_data,test_labels)=keras.datasets.imdb.load_data(num_words=NUM_WORDS)

def multi_hot_sequences(sequences,dimension):
    results=np.zeros((len(sequences),dimension))
    for i,word_indices in enumerate(sequences):
        results[i,word_indices]=1.0
    return results

train_data=multi_hot_sequences(train_data,dimension=NUM_WORDS)
test_data=multi_hot_sequences(test_data,dimension=NUM_WORDS)

print(train_data[0][0:10]) #[0. 1. 1. 0. 1. 1. 1. 1. 1. 1.]

2.模型构建

防止过度拟合的最简单方法是减小模型的大小,即模型中可学习参数的数量(由层数和每层单元数决定)。在深度学习中,模型中可学习参数的数量通常被称为模型的“容量”。直观地,具有更多参数的模型将具有更多“记忆能力”,因此将能够容易地学习训练样本与其目标之间的完美的字典式映射,没有任何泛化能力的映射,但是在对未知数据做出预测时这将是无用的。

始终牢记这一点:深度学习模型往往善于适应训练数据,但真正的挑战是泛化,而不是拟合。

扫描二维码关注公众号,回复: 2808800 查看本文章

另一方面,如果网络具有有限的记忆资源,则将不能容易地学习映射。为了最大限度地减少损失,它必须学习具有更强预测能力的压缩表示。同时,如果您使模型太小,则难以适应训练数据。“太多容量”和“容量不足”之间存在平衡。

不幸的是,没有魔法公式来确定模型的正确尺寸或架构(就层数而言,或每层的正确尺寸)。您将不得不尝试使用一系列不同的体系结构。

要找到合适的模型大小,最好从相对较少的图层和参数开始,然后开始增加图层的大小或添加新图层,直到您看到验证损失的收益递减为止。让我们在我们的电影评论分类网络上试试这个。

我们将仅使用Dense图层创建一个简单的模型,然后创建一个较小的版本,并进行比较。

(1)构建一个基础模型

baseline_model=keras.Sequential([keras.layers.Dense(16,activation=tf.nn.relu,input_shape=(NUM_WORDS,)),
                                keras.layers.Dense(16,activation=tf.nn.relu),
                                keras.layers.Dense(1,activation=tf.nn.sigmoid)])
baseline_model.compile(optimizer='adam',loss='binary_crossentropy',metrics=['accuracy','binary_crossentropy'])
baseline_model.summary()
Layer (type)                 Output Shape              Param #   
=================================================================
dense_4 (Dense)              (None, 16)                160016    
_________________________________________________________________
dense_5 (Dense)              (None, 16)                272       
_________________________________________________________________
dense_6 (Dense)              (None, 1)                 17        
=================================================================
Total params: 160,305
Trainable params: 160,305
Non-trainable params: 0
#模型训练
baseline_history=baseline_model.fit(train_data,train_labels,epochs=20,batch_size=512,validation_data=(test_data,test_labels),verbose=2)

(2)创建一个较小的模型

创建一个隐藏单元较少的模型,与我们刚刚创建的基础模型进行比较:

smaller_model=keras.Sequential([keras.layers.Dense(4,activation=tf.nn.relu,input_shape=(NUM_WORDS,)),
                               keras.layers.Dense(4,activation=tf.nn.relu),
                               keras.layers.Dense(1,activation=tf.nn.sigmoid)])
smaller_model.compile(optimizer='adam',loss='binary_crossentropy',metrics=['accuracy','binary_crossentropy'])
smaller_model.summary()
#并使用相同的数据训练模型:
smaller_history =smaller_model.fit(train_data,train_labels,epochs=20,batch_size=512,validation_data=(test_data,test_labels),verbose=2)

(3)创建一个更大的模型

接下来,让我们在这个基准测试中添加一个容量大得多的网络,远远超出问题的范围:

#创建一个更大的模型
bigger_model=keras.Sequential([keras.layers.Dense(512,activation=tf.nn.relu,input_shape=(NUM_WORDS,)),
                               keras.layers.Dense(512,activation=tf.nn.relu),
                               keras.layers.Dense(1,activation=tf.nn.sigmoid)])
bigger_model.compile(optimizer='adam',loss='binary_crossentropy',metrics=['accuracy','binary_crossentropy'])
bigger_model.summary()
bigger_history=bigger_model.fit(train_data,train_labels,epochs=20,batch_size=512,validation_data=(test_data,test_labels),verbose=2)

3.绘制训练和验证损失图

实线表示训练损失,虚线表示验证损失(记住:较低的验证损失表示更好的模型)。在这里,较小的网络开始过度拟合晚于基线模型,并且一旦开始过度拟合,其性能下降得慢得多。

def plot_history(histories, key='binary_crossentropy'):
  plt.figure(figsize=(16,10))
    
  for name, history in histories:
    val = plt.plot(history.epoch, history.history['val_'+key],
                   '--', label=name.title()+' Val')
    plt.plot(history.epoch, history.history[key], color=val[0].get_color(),
             label=name.title()+' Train')

  plt.xlabel('Epochs')
  plt.ylabel(key.replace('_',' ').title())
  plt.legend()

  plt.xlim([0,max(history.epoch)])
  plt.show()
plot_history([('baseline', baseline_history),
              ('smaller', smaller_history),
              ('bigger', bigger_history)])

网络容量越大,能够越快地对训练数据进行建模(导致训练损失低),但过度拟合的可能性越大(导致训练和验证损失之间的差异很大) 

4.正则化策略

(1)权重正则化

你可能熟悉奥卡姆的剃刀原则:给出两个解释的东西,最可能是正确的解释是“最简单”的解释,即做出最少量假设的解释。这也适用于神经网络学习的模型:给定一些训练数据和网络架构,有多组权重值(多个模型)可以解释数据,而简单模型比复杂模型更不容易过度拟合。

在这种情况下,“简单模型”是一个模型,其中参数值的分布具有较少的熵(或者具有较少参数的模型,如我们在上面的部分中所见)。因此,减轻过度拟合的常见方法是通过强制其权重仅采用较小的值来对网络的复杂性施加约束,这使得权重值的分布更“规则”。这被称为“权重正则化”,并且通过向网络的损失函数添加与具有大权重相关联的成本来完成。这个成本有两种:

  • L1正则化,其中所添加的成本与权重系数的绝对值(即权重的所谓“L1范数”)成比例。

  • L2正则化,其中增加的成本与权重系数的值的平方成比例(即,权重的所谓“L2范数”)。L2正则化在神经网络的背景下也称为权重衰减。不要让不同的名字让你感到困惑:权重衰减在数学上与L2正则化完全相同。

tf.keras,通过将权重正则化实例作为关键字参数传递给层来添加权重正则化。现在让我们添加L2权重正则化。

#添加正则化
l2_model=keras.models.Sequential([keras.layers.Dense(16,kernel_regularizer=keras.regularizers.l2(0.001),activation=tf.nn.relu,input_shape=(NUM_WORDS,)),
                                 keras.layers.Dense(16,kernel_regularizer=keras.regularizers.l2(0.001),activation=tf.nn.relu),
                                 keras.layers.Dense(1,activation=tf.nn.sigmoid)])
l2_model.compile(optimizer='adam',loss='binary_crossentropy',metrics=['accuracy','binary_crossentropy'])
l2_model_history=l2_model.fit(train_data,train_labels,epochs=20,batch_size=512,validation_data=(test_data,test_labels),verbose=2)

plot_history([('baseline',baseline_history),('l2',l2_model_history)])

l2(0.001)意味着图层权重矩阵中的每个系数都会增加0.001 * weight_coefficient_value网络的总损失。请注意,由于此惩罚仅在训练时添加,因此在训练时此网络的损失将远高于在测试时间。

如您所看到的,L2正则化模型比基线模型更能抵抗过拟合,即使两个模型具有相同数量的参数。

(2) Doupout正则化

Doupout是由Hinton和他在多伦多大学的学生开发的最有效和最常用的神经网络正则化技术之一。应用于网络层的Doupout包括在训练期间随机“Doupout”(即设置为零)网络层的多个输出特征。假设一个给定的层通常会在训练期间为给定的输入样本返回一个向量[0.2,0.5,1.3,0.8,1.1]; 在应用dropout之后,这个向量将有几个随机分布的零,例如[0,0.5,1.3,0,1.1]。“Doupout比率”是被淘汰的特征的一部分; 它通常设置在0.2和0.5之间。在测试时,没有神经元被丢弃,而是将网络层的输出值按比例缩小到Doupout比率,

在tf.keras中,您可以通过Dropout网络层在网络中引入dropout,该层将在之前应用于网络层的输出。

让我们在IMDB网络中添加两个Dropout层,看看它们在减少过度拟合方面做得如何:

#增加Dropout正则化
dpt_model=keras.models.Sequential([keras.layers.Dense(16,activation=tf.nn.relu,input_shape=(NUM_WORDS,)),
                                  keras.layers.Dropout(0.5),
                                  keras.layers.Dense(16,activation=tf.nn.relu),
                                  keras.layers.Dropout(0.5),
                                  keras.layers.Dense(1,activation=tf.nn.sigmoid)])
dpt_model.compile(optimizer='adam',loss='binary_crossentropy',metrics=['accuracy','binary_crossentropy'])
dpt_model_history=l2_model.fit(train_data,train_labels,epochs=20,batch_size=512,validation_data=(test_data,test_labels),verbose=2)

添加Dropout是对基线模型的明显改进。

5.回顾一下:防止神经网络中过拟合的最常见方法:

  • 获取更多训练数据。
  • 减少网络容量。
  • 添加权重正则化。
  • 添加Dropout。

另外两个防止过拟合的重要方法是数据增强(data-augmentation)和批量标准化(batch normalization)。

猜你喜欢

转载自blog.csdn.net/u013230189/article/details/81702881
今日推荐