14.机器学习基础:过拟合与欠拟合

过拟合与欠拟合

**机器学习的根本问题就是优化和泛化之间的对立。**优化(optimization)是指调节模型以在训练数据上得到最佳性能(即机器学习中的学习),而泛化(generalization)是指训练好的模型在前所未见的数据上的性能好坏。机器学习的目的当然是得到良好的泛化,但你无法控制泛化,只能基于训练数据调节模型。

训练开始时,优化和泛化是相关的:训练数据上的损失越小,测试数据上的损失也就越小。这时的模型是欠拟合(underfit)的,即仍有改进的空间,网络还没有对训练数据中所有相关模型建模。但在训练数据上迭代一定次数之后,泛化不在提高,验证指标先是不变,然后开始变差,即模型开始过拟合。这时模型开始学习仅和训练数据有关的模式,但这种模式对新数据来说是错误或无关紧要的。

为了防止模型从训练数据中学到错误或无关紧要的模式,最有解决方法是获取更多的训练数据。模型的训练数据越多,泛化能力自然也越好。如果无法获取更多数据,次优解决方法是调节模型允许存储的信息量,或对模型允许存储的信息加以约束。如果一个网络只能记住几个模式,那么优化过程会迫使模型集中学习最重要的模式,这样更可能得到良好的泛化。

这种降低过拟合的方法叫做正则化(regularization)。我们简单介绍几种最常见的正则化方法。

减小网络大小

防止过拟合的最简单方法就是减小模型大小,即减少模型中可学习参数的个数(这由层数和每层的单元个数决定)。在深度学习中,模型中可学习参数的个数通常被成为模型的容量。直观上来看,参数更多的模型拥有更大的记忆容量,因此能够在训练样本和目标之间轻松的学会完美的字典式映射,这种映射没有任何泛化能力。

深度学习模型通常很擅长拟合训练数据,但真正的挑战在于泛化,而不是拟合。

于此相反,如果网络的记忆资源有限,则无法轻松学会这种映射。因此,为了让损失最小化,网络必须学会对目标具有很强的预测能力的压缩表示,这也正是我们感兴趣的数据表示。同时需要值得注意的是,使用的模型应该具有足够多的参数,以防欠拟合,即模型应避免记忆资源不足。即,在容量过大与容量不足之间找到一个折中。

我们必须评估一系列不同的网络架构(在验证集上评估,而不是在测试集上),以便为数据找到最佳的模型大小。要找到合适的模型大小,一般的工作流程是开始时选择相对较少的层和参数,然后逐渐增加层的大小或增加新层,直到这种增加对验证损失的影响变得很小。

我们在电影评论分类的网络测试一下:

from keras.datasets import imdb
from keras import models
from keras import layers
import numpy as np

(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)

def vectorize_sequences(sequences, dimension=10000):
    results = np.zeros((len(sequences), dimension))
    for i, sequence in enumerate(sequences):
        results[i, sequence] = 1.
    return results
    
x_train = vectorize_sequences(train_data)
x_test = vectorize_sequences(test_data)
y_train = np.asarray(train_labels).astype('float32')
y_test = np.asarray(test_labels).astype('float32')
x_val = x_train[:10000]
partial_x_train = x_train[10000:]
y_val = y_train[:10000]
partial_y_train = y_train[10000:]

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'])
original_history = original_model.fit(partial_x_train,
                    partial_y_train,
                    epochs=20,
                    batch_size=512,
                    validation_data=(x_val,y_val))

现在我们尝试用下面这个更小的网络来替换它

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'])
smaller_history = smaller_model.fit(partial_x_train,
                            partial_y_train,
                            epochs=20,
                            batch_size=512,
                            validation_data=(x_val,y_val))

在这里插入图片描述

比较原始网络与更小网络的验证损失,圆点是更小网络的验证损失值,十字是原始网络的验证损失值(更小的验证损失对应更好地模型

从图中可以看出,更小的网络开始过拟合的时间要晚于参考网络(更小的网络在6轮开始过拟合,原始网络在第三轮开始过拟合)。

该图像通过以下代码可以画出

import matplotlib.pyplot as plt

original_loss = original_history.history['val_loss']
smaller_loss = smaller_history.history['val_loss']
epochs = range(1, 21)

plt.plot(epochs, original_loss, '+', label='Original model')
plt.plot(epochs, smaller_loss, 'bo', label='Smaller model')
plt.xlabel('Epochs')
plt.ylabel('Validation loss')
plt.legend()
plt.show()

在添加一个容量更大的网络

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_history = bigger_model.fit(partial_x_train,
                                  partial_y_train,
                                  epochs=20,
                                  batch_size=512,
                                  validation_data=(x_val,y_val))

比较两种模型的验证损失值

在这里插入图片描述

由此可见,更大的网络只过了不到两回合就开始过拟合。

画图象代码如下:

import matplotlib.pyplot as plt

plt.clf()
bigger_loss = bigger_history.history['val_loss']
epochs = range(1, 21)

plt.plot(epochs, original_loss, '+', label='Original model')
plt.plot(epochs, bigger_loss, 'bo', label='Bigger model')
plt.xlabel('Epochs')
plt.ylabel('Validation loss')
plt.legend()
plt.show()

下图同时给出了两个网络的训练损失。

在这里插入图片描述

如图所见,更大的网络的训练损失很快就会接近于零。网络的容量越大,它拟合训练数据(即得到很小的训练损失)的速度就越快,但也更容易过拟合(导致训练损失有很大的差异)。

添加权重正则化

给定一些训练数据和一种网络架构,很多组权重值(即很多模型)都可以解释这些数据。简单模型比复杂模型更不容易过拟合。

这里的简单模型是指参数值分布的熵更小的模型(或参数更少的模型)。因此,一种常见的降低过拟合的方法就是强制让模型权重只能取较小的值,从而限制模型的复杂度,这使得权重值的分布更加规则。这种方法叫做权重正则化,其实现方法是向网络损失函数中添加与较大权重值相关的成本。这个成本有两种形式。

  • L1正则化(L1 regularization):添加的成本与权重系数的绝对值(权重的L1范数)成正比。
  • L2正则化(L2 regularization):添加的成本与权重系数的平方(权重的L2范数)成正比。神经网络的L2正则化也叫做权重衰减(weight decay)。不要被不同的名称搞混,权重衰减与L2正则化在数学上是完全相同的

在Keras中,添加权重正则化的方法是向层传递权重正则化项实例(weight regularizer)作为关键字参数。下列代码将对电影评论网络中添加L2权重正则化。

from kears 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(0.001) 的意思是该层权重矩阵的每个系数都会使网络总损失增加 0.001 * weight_coefficient_value。值得注意的是,由于这个惩罚项只在训练时添加所以这个网络的训练损失会比测试损失大很多

在这里插入图片描述

上图显示了L2正则化惩罚的影响,即使两个模型的参数个数相同,具有L2正则化的模型(原点)比参考模型(十字)更不容易拟合。

下图显示了加权重与不加权重的训练损失与从测试损失区别
在这里插入图片描述

代码如下:

plt.clf()

original_val_loss = original_history.history['val_loss']
original_train_loss = original_history.history['loss']
L2_val_loss = L2_history.history['val_loss']
L2_train_loss = L2_history.history['loss']

figure,axes = plt.subplots(nrows=1,ncols=2, figsize=(18,6))
axes[0].plot(epochs, original_val_loss, '+', label='val_loss')
axes[0].plot(epochs, original_train_loss, 'bo', label='train_loss')
axes[0].set_xlabel('Epochs')
axes[0].set_ylabel('Loss')
axes[0].set_title('Validation and Train with Original')
axes[0].legend()
axes[1].plot(epochs, L2_val_loss, '+', label='val_loss')
axes[1].plot(epochs, L2_train_loss, 'bo', label='train_loss')
axes[1].set_xlabel('Epochs')
axes[1].set_ylabel('Loss')
axes[1].set_title('Validation and Train Loss with L2')
axes[1].legend()

除了L2权重正则化,Keras 还有其他方法

from keras import regularizers

# L1 正则化
regularizers.l1(0.001)
# L2 正则化
regularizers.l2(0.001)
# 同时做L1 和 L2 正则化
regularizers.l1_l2(l1=0.001, l2=0.001)

添加 dropout 正则化

dropout 是神经网络最有效也是最常用的正则化方法之一。对某一层使用 dropout,就是在训练过程中随机将该层的一些输出特征舍弃(设置为0)。假设在训练过程中,某一层对给定输入样本的返回值应该是向量 [0.2, 0.5, 1.3, 0.8, 1.1]。使用dropout后,这个向量会有几个随机的元素变为0,比如 [0, 0.5, 1.3, 0,1.1]。

dropout比率是被设为0的特征所占的的比例,通常在0.2~0.5范围内。测试时没有单元被舍弃,而该层的输出值需要按照dropout比率缩小,因为这时比训练时有更多的单元被激活,需要加以平衡。

假设有一个包含某层输出的 Numpy 矩阵 layer_output,其形状为 (batch_size, featuires)。训练时,我们随机将矩阵中一部分值设为0。

# 训练时,舍弃50%的输出单元。
layer_output *= np.random.randint(0, high=2, size=layer_output.shape)

# 测试时,我们将输出按 dropout 比率缩小。这里我们乘以0.5(因为前面舍弃了一半的单元)。
layer_output *= 0.5

# 注意,为了实现这一过程,还可与让两个运算都在训练时进行,而测试时输出保持不变。这通常也是实践中的实现方式。
layer_output *= np.random.randint(0, high=2, size=layer_output.shape)
layer_output /= 0.5 # 它是成比例放大而不是缩小。

在这里插入图片描述

其核心思想是在层的输出值引入噪声,打破不显著的偶然模式。如果没有噪声的话,网络将会记住这些偶然模式。

在Keras中,你可以通过Dropout层向网络中引入dropout,dropout将被应用于前面一层的输出。

model.add(layers.Dropout(0.5))

我们那IMDB数据集作比较

dropout_model = models.Sequential()
dropout_model.add(layers.Dense(16, activation='relu', input_shape=(10000,)))
dropout_model.add(layers.Dropout(0.5))
dropout_model.add(layers.Dense(16, activation='relu'))
dropout_model.add(layers.Dropout(0.5))
dropout_model.add(layers.Dense(1,activation='sigmoid'))
dropout_model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
dropout_history = dropout_model.fit(partial_x_train,
                                    partial_y_train,
                                    epochs=20,
                                    batch_size=512,
                                    validation_data=(x_val,y_val))

得出以下图示:

在这里插入图片描述

从上图可以看出,这种方法的性能相比参考网络有明显提高。

话上图代码如下:

import matplotlib.pyplot as plt

original_val_loss = original_history.history['val_loss']
dropout_val_loss = dropout_history.history['val_loss']
epochs = range(1,21)

plt.plot(epochs, original_val_loss, '+', label='Original model')
plt.plot(epochs, dropout_val_loss, 'bo', label='Dropout-regularized model')
plt.xlabel('Epochs')
plt.ylabel('Validation loss')
plt.legend()
plt.show()

总结

防止神经网络过拟合的常用方法包括:

  • 获取更多的训练数据
  • 减小网络容量
  • 添加权重正则化
    ot(epochs, original_val_loss, ‘+’, label=‘Original model’)
    plt.plot(epochs, dropout_val_loss, ‘bo’, label=‘Dropout-regularized model’)
    plt.xlabel(‘Epochs’)
    plt.ylabel(‘Validation loss’)
    plt.legend()
    plt.show()

### 总结

防止神经网络过拟合的常用方法包括:

- 获取更多的训练数据
- 减小网络容量
- 添加权重正则化
- 添加dropout正则化
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值