卷积神经网络CNNs的调优及性能提升技巧

最近我搭建了一个基于CNN的模型,在调优的过程中发现了一些通用的方法。在此想对这些方法做一个简述和实际的作用分析作为对我的工作的总结,也是希望能帮助其他像我这样的小白快速入门CNN。更详细的内容可以查看文章最后的论文以及链接,之后的学习和工作中有新的体会会在进行补充。这也是我的第一篇文章,如果各位大神们有任何宝贵意见和问题,我们可以一起讨论一起进步。

实验环境:

  1. Python 3.7
  2. Tensorflow 2.0.0
  3. Linux + VM

一、调整网络架构

在我们搭建CNN的过程中首要的步骤就是搭建合适的框架。 代码片

#Import tensorflow.keras.layers as layers
Conv1 = layers.Conv2D(16, (3, 3), activation='relu')  #卷积层
FC1 = layers.Dense(1024, activation='relu')           #全连接层

如果不想自己搭建,现在也有很多很棒的网络结构比如 ResNet,VGG,SimpleNet可供使用。这里提供一个我自己用的很简单的网络结构 CNN

def _my_cnn():
	input_img = tf.keras.Input(shape=(128, 128, 1))  # Input placeholder
    x = input_img
    for channel_num in [32, 64, 64, 128, 256]:
        x = layers.Conv2D(channel_num, (3, 3), activation='relu')(x)
        x = layers.MaxPooling2D((2, 2), padding='same')(x)
        
    x = layers.Flatten()(x)
    x = layers.Dense(1024, activation='relu')(x)
    x = layers.Dense(4, activation='linear')(x)   #我做的是回归问题,所以用的是linear
    model = Model(inputs=input_img, outputs=x, name='my_cnn_net')
    return model

如何确保我们的网络已经有足够的能力去拟合我们的问题了呢?
那就需要先让我们的网络过拟合。假设你的网络连过拟合都达不到,说明他的非线性拟合能力还不足,这就需要 加深你的网络 了。

二、过拟合

如果你的网络出现了过拟合,那就只需要解决过拟合的问题了。
那如何判断过拟合?

以我的模型的结果来看,这就是一个标准的过拟合。训练集的loss下降的很快,但过了第70代附近,训练集和测试集的loss逐渐分开。训练集loss有一个陡然的下降,但是测试集的val_loss开始趋于稳定甚至有小幅度的上升

在这里插入图片描述
这就意味着模型过于复杂,从而对训练数据产生偏向。所以需要降低模型复杂度来处理过拟合问题

三、Dropout层

处理过拟合最常用的方法就是添加Dropout。

Dropout层是用来在训练过程中以几率 p 随机冻结节点。

因为网络中某些节点随机被冻结,所以训练数据每一次看到的网络结构都是不一样的。所以,原本单一的网络就可以看成n个模型的集合,因此能够提高整体网络结构的泛化能力。

在这里插入图片描述
Dropout层一般用于层结构间,p 取0.5 代码片

 x = layers.Dense(1024, activation='relu')(x)
 x = layers.Dropout(0.5)(x)
 x = layers.Dense(512, activation='relu')(x)

但是添加了Dropout层会导致训练成本的上升,收敛需要更多的epoch迭代次数。所以要根据自己 GPU/CPU 的计算能力谨慎添加。


四、Regularization正则化

神经网络的训练是找到一个合适的节点权重来让激活函数拟合输出的过程。

我们的目的就是要找到合适的权重。

但是模型在训练的时候容易造成某些节点权重过大,导致一个微小的输入变化会导致结果的巨大变化。并且其他节点的小权重无法对输出进行有效的调整,这就导致了梯度爆炸的问题。

增加权重正则化可以将权重限制在可控范围内,有效防止了梯度爆炸问题。并且学者们还发现正则化还能减小模型的复杂性,从而能减小过拟合程度。

贴一下我的代码 代码片

x = layers.Conv2D(channel_num, (3, 3),  strides=(1, 1), padding='same', 
           kernel_initializer='glorot_uniform',      #初始化
           kernel_regularizer=tf.keras.regularizers.l2(0.0001))(x)  #正则化

一般使用 L2 正则化来处理过拟合问题,我系数 λ \lambda λ 采用的是1e-4,也可以使用5e-5。

正则化有L1, L2两种,当然也可以两种一起用,

具体应用可以参考Tensorflow Core.

正则化的细节我没其他大佬那么厉害,就不说那么多了,可以参考正则化。我主要还是说说如何使用。

L1正则化

L1正则化的公式为
∥ ω ∥ 1 = ∑ j = 1 n ∣ ω j 2 ∣ \left \| \omega \right \|_{1} = \sum_{j=1}^{n} \left | \omega_{j}^{2} \right | ω1=j=1nωj2
L1正则化可以生成稀疏的特征向量,并且可以在一定程度上减小过拟合。

L2正则化

L2正则化的公式为
∥ ω ∥ 2 = ∑ j = 1 n ω j 2 \left \| \omega \right \|_{2}= \sum_{j=1}^{n}\omega_{j}^{2} ω2=j=1nωj2
L2正则化通过对大权重实现惩罚以均衡权重,从而减小模型复杂性。模型加入L2可以获得更好的泛化能力。

五、BatchNormalization层

BN层是用来替代LRN层,用于加速收敛,提高泛化能力。使用BN层后,模型不再需要Dropout层了。
论文地址:https://arxiv.org/abs/1502.03167
BN层的本质是对样本的局部归一化,可以减少训练成本从而提高训练的速度。

贴一下我的代码 代码片

from tf.keras import layers
x = layers.Conv2D(channel_num, (3, 3),  strides=(1, 1), padding='same', kernel_initializer='glorot_uniform',
                      kernel_regularizer=tf.keras.regularizers.l2(0.0001), use_bias=False)(x)
x = layers.BatchNormalization(momentum=0.9, epsilon=1e-5, trainable=True)(x, training= ~test)  
x = layers.ReLU()(x)
x = layers.MaxPooling2D((2, 2), padding='same')(x)

注意use_bias要设为False,激活函数要放BN层后面。
但是这个方法虽然论文中表现优异,其他模型中也广泛使用,但是在我的模型中未见很优秀的效果, 不过不妨试试~~

六、学习率下降策略

学习率就是训练中的步长,对训练十分重要。

想象一下你要雕刻一个维纳斯雕像。
从一大块原石开始,首先肯定要拿大一点的雕刻刀去掉大块的石块,雕刻出维纳斯大致的轮廓。
然后要换一个小一点的刻刀来刻画细节,才能雕刻出美丽的维纳斯雕像。

在神经网络的训练中,学习率就是这样一把刻刀,来雕刻权重这个原石。
我们一般学习率一开始可以设置大一点( lr=0.001 ),以获得一个不错的收敛速度,就像用大雕刻刀去掉大块的石块。
而到训练的后期,需要仔细雕琢的时候,大的刻刀可能就不能满足要求了。
所以我们需要慢慢减小学习率。

tensorflow2提供了可选的内回调函数 代码片

from tf.keras.callbacks import LearningRateScheduler,ReduceLROnPlateau
reduceLR = ReduceLROnPlateau(monitor='loss', factor=0.5, patience=10, mode='auto',
                             min_delta=1e-3, cooldown=10,min_lr=1e-5)             # LR 检测训练停滞
                                 
LRschedule = LearningRateScheduler(schedule, verbose=0)        # 这里的schedule要自己定义一个下降函数,详细的可以看TF core
'''
fit函数里有很多参数,其中callbacks里可以设置回调函数。
'''    
model.fit(train, label, epochs=epoch, callbacks=[csv_logger, reduceLR], shuffle=True, workers=4)               

回调函数的具体应用可以参考Tensorflow Core.

我使用的是LR停滞检测函数 ReduceLROnPlateau。函数中参数的意思是,在经过patience个训练周期后没有min_delta的变化(min或max)就会下降学习率LR。下降的系数为factor,下降后会冷却cooldown个训练周期之后重新开始检测。

学习率下降策略 + Adam 在我的模型中表现优异,可以很快的收敛。
在这里插入图片描述可以看到loss和val_loss都很快的下降了,模型的精度也得到了提升。


七、一些其他方法

ACB是一个混合卷积层,拥有多种卷积核,理论上可以提取更多的特征。
更多的数据一直是提升机器学习性能最有效的手段,如果没有办法获得更多的数据,数据增强就是一个很好的办法。

另外,十分十分推荐在搭建网络的过程中多看官方文档,比如pytorchtensorflow2

多尝试,多尝试, 多尝试!重要的事说三遍。希望大家都能早日搭出优秀的模型!

Logo

更多推荐