【深度学习笔记】原来真的是人工智障——过拟合及其防止


上篇我们介绍了优化算法及其选择,这篇文章,我们谈谈过拟合。
过拟合这个词我们提到过很多次了,就是模型训练不当,导致对于训练数据精度很高,而验证数据等其他数据精度很低,说白了,就是炼丹炼过头了。

一个栗子

单说理论不好理解?那咱们来看个栗子,训练一个过拟合的、用于识别猫和狗的人工智障。为了简化代码(主要是懒),这次使用Tensorflow框架实现一个CNN,CNN后面的文章我们再详细介绍,然后不依赖框架实现一个CNN。
借助Keras,我们可以迅速搭建一个卷积神经网络模型,其中的relu、softmax是不是很熟悉,之前我们都手动实现过这些激活函数。

model = keras.models.Sequential([
        # 卷积层
        keras.layers.Conv2D(filters=32, kernel_size=3,
                            padding='same', activation='relu',
                            input_shape=[width, height, channel]),
        # 卷积层
        keras.layers.Conv2D(filters=32, kernel_size=3,
                            padding='same', activation='relu'),
        # 池化层
        keras.layers.MaxPool2D(pool_size=2),

        keras.layers.Conv2D(filters=64, kernel_size=3,
                            padding='same', activation='relu'),
        keras.layers.Conv2D(filters=64, kernel_size=3,
                            padding='same', activation='relu'),
        keras.layers.MaxPool2D(pool_size=2),

        keras.layers.Conv2D(filters=128, kernel_size=3,
                            padding='same', activation='relu'),
        keras.layers.Conv2D(filters=128, kernel_size=3,
                            padding='same', activation='relu'),
        keras.layers.MaxPool2D(pool_size=2),
        # 把参数“拍成一维数组”,以便与全连接层相连,输出结果
        keras.layers.Flatten(),
        # 全连接层
        keras.layers.Dense(128, activation='relu'),
        keras.layers.Dense(num_classes, activation='softmax')
    ])

再来看一眼我们的数据集:
202203241431889.png
dog文件夹中,清一色全是哈士奇,没错,我们将训练一个只认识哈士奇的人工智障。老规矩,完整代码及数据集下载在文末。
Tensorflow除了方便我们搭建网络之外,还提供了Tensorboard,可以方便我们监视训练过程中的loss等各种参数,我训练了200个epoch之后,得到了如下loss和accuracy的图像,其中蓝色曲线是验证集,橙色是训练集。

Loss曲线

202203241435416.svg

Accuracy曲线

202203241436394.svg

可以明显看出,在后期训练中,训练loss不断下降,验证loss基本不再下降,反而有升高迹象,精度也不再提高,反而比之前低了一点。从训练图像可以得出模型已经过拟合了,此时用验证集中的数据去进行验证模型,它可能会把猫说成狗,把狗说作是猫,而验证集中的数据,则识别精度很高,不会有太大偏差。

此外,我们期望得到的模型应该是可以区别狗和猫,那么它应该可以区别各种品种的狗和猫,我们找个金毛的图片放进去看看,结果如下:
202203241730040.gif
金毛被识别成不是狗,这并不符合我们的预期,其中一个原因就是模型泛化能力不足,也就是过拟合了。

为什么过拟合

看完上面的例子,相信也可以总结出过拟合发生的原因了:

  1. 数据集太小
  2. 训练过度,模型从训练集学习到过多的东西,导致表达能力过强

过拟合防止

既然模型过拟合是因为数据集或训练过度导致。那么我们只要适当扩充数据集,适当降低模型的表现能力不就可以防止了。那么改怎么降低模型表现能力呢?模型实际上就是一堆参数,我们适当对参数进行惩罚,不就是降低了其表现能力。

正则化

该方法通过在学习的过程中对大的权重进行惩罚,来抑制过拟合。很多过拟合原本就是因为权重参数取值过大才发生的。
例如为损失函数加上权重的平方范数(L2范数)。这样一来,就可以抑制权重变大。用符号表示的话,如果将权重记为$W$,$L2$范数的权值衰减就是 ,然后将这个$\frac{1}{2} \lambda W^2$加到损失函数上。这里,$\lambda$是控制正则化强度的超参数。$\lambda$设置得越大,对大的权重施加的惩罚就越重。此外,$\frac{1}{2} \lambda W^2$开头的$\frac{1}{2}$是用于将$\frac{1}{2} \lambda W^2$的求导结果变成$\lambda W$的调整用常量。
对于所有权重,权值衰减方法都会为损失函数加上 。因此,在求权重梯度的计算中,要为之前的误差反向传播法的结果加上正则化项的导数$\lambda W$。
$L2$范数相当于各元素的平方和,即$\sqrt{w_1 + w_2 + w_3 +...+w_i}$,除$L2$范数外,还有$L1、...、L\infty$范数,不过我们常用的是$L2$范数。

DropOut

正则化可以简单地抑制过拟合,但如果模型很复杂,正则化就有些心有余而力不足了。这种情况可以使用DropOut。
Dropout是一种在学习的过程中随机删除神经元的方法。训练时,随机选出隐藏层的神经元,然后将其删除。被删除的神经元不再进行信号的传递,训练时,每传递一次数据,就会随机选择要删除的神经元。然后,测试时,虽然会传递所有的神经元信号,但是对于各个神经元的输出,要乘上训练时的删除比例后再输出。

这里简单实现一下,这种实现效率较低,因为训练时如果进行恰当的计算的话,正向传播时就可以不用乘删除比例。关于高效的实现,可以参一些深度学习框架中的实现。

class Dropout:
    def __init__(self, dropout_ratio=0.5):
        self.dropout_ratio = dropout_ratio
        self.mask = None

    def forward(self, x, train_flg=True):
        if train_flg:
            self.mask = np.random.rand(*x.shape) > self.dropout_ratio
            return x * self.mask
        else:
            return x * (1.0 - self.dropout_ratio)

    def backward(self, dout):
        return dout * self.mask

每次正向传播时,self.mask中都会以False的形式保存要删除的神经元。self.mask会随机生成和x形状相同的数组,并将值比
dropout_ratio大的元素设为True。反向传播时的行为和ReLU相同。也就是说,正向传播时传递了信号的神经元,反向传播时按原样传递信号,正向传播时没有传递信号的神经元,反向传播时信号将停在那里。

声明:迟於|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - 【深度学习笔记】原来真的是人工智障——过拟合及其防止


栖迟於一丘