CNN架构


CNN架构

典型的CNN架构堆叠了一些卷积层(通常每个卷积层都跟随着一个ReLU层),然后是一个池化层,然后是另外几个卷积层(+ReLU),然后是另一个池化层,以此类推。随着卷积网络的不断发展,图像变得越来越小,但是由于卷积层的存在,图像通常也越来越深(即具有更多的特征图)。在堆栈的顶部添加了一个常规的前馈神经网络,该网络由几个全连接层(+ReLU)组成,最后一层输出预测(例如输出估计类概率的softmax层)

一个常见的错误是使用太大的卷积内核。例如与其使用具有\(5\times5\)内核卷积层,不如使用具有\(3\times3\)内核的两层:它使用较少的参数并需要较少的计算,并且通常性能会更好。第一个卷积层是例外:它具有较大的内核,步幅通常为2或更大,这将减少图像的空间维度而不会丢失太多信息,由于输出图像通常只有三个通道,因此不需要太多的计算量。

这是实现简单的CNN来处理Fashion MNIST数据集的方法:

import tensorflow as tf
from tensorflow import keras
fashion_mnist = keras.datasets.fashion_mnist

fashion_mnist = keras.datasets.fashion_mnist

(train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data()
train_images = train_images[...,tf.newaxis] / 255.0

test_images = test_images[...,tf.newaxis] / 255.0
model = keras.models.Sequential([
    keras.layers.Conv2D(64, 7, activation='relu',
                        padding='same', input_shape=(28, 28, 1)),
    keras.layers.MaxPooling2D(2),
    keras.layers.Conv2D(128, 3, activation='relu', padding='same'),
    keras.layers.MaxPooling2D(2),
    keras.layers.Conv2D(256, 3, activation='relu', padding='same'),
    keras.layers.Conv2D(265, 3, activation='relu', padding='same'),
    keras.layers.MaxPooling2D(2),
    keras.layers.Flatten(),
    keras.layers.Dense(128, activation='relu'),
    keras.layers.Dropout(.5),
    keras.layers.Dense(64, activation='relu'),
    keras.layers.Dropout(.5),
    keras.layers.Dense(10, activation='softmax')
])
C:\ProgramData\Miniconda3\envs\tf2\lib\site-packages\numpy\_distributor_init.py:30: UserWarning: loaded more than 1 DLL from .libs:
C:\ProgramData\Miniconda3\envs\tf2\lib\site-packages\numpy\.libs\libopenblas.GK7GX5KEQ4F6UYO3P26ULGBQYHGQO7J4.gfortran-win_amd64.dll
C:\ProgramData\Miniconda3\envs\tf2\lib\site-packages\numpy\.libs\libopenblas.WCDJNK7YVMPZQ2ME2ZZHJJRJ3JIKNDB7.gfortran-win_amd64.dll
  warnings.warn("loaded more than 1 DLL from .libs:"
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 28, 28, 64)        3200      
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 14, 14, 64)        0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 14, 14, 128)       73856     
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 7, 7, 128)         0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 7, 7, 256)         295168    
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 7, 7, 265)         610825    
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 3, 3, 265)         0         
_________________________________________________________________
flatten (Flatten)            (None, 2385)              0         
_________________________________________________________________
dense (Dense)                (None, 128)               305408    
_________________________________________________________________
dropout (Dropout)            (None, 128)               0         
_________________________________________________________________
dense_1 (Dense)              (None, 64)                8256      
_________________________________________________________________
dropout_1 (Dropout)          (None, 64)                0         
_________________________________________________________________
dense_2 (Dense)              (None, 10)                650       
=================================================================
Total params: 1,297,363
Trainable params: 1,297,363
Non-trainable params: 0
_________________________________________________________________
model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(),
              metrics=['accuracy'])

history = model.fit(train_images, train_labels, epochs=10,
                    validation_data=(test_images, test_labels), batch_size=32)
Epoch 1/10
1875/1875 [==============================] - 13s 5ms/step - loss: 0.7189 - accuracy: 0.7408 - val_loss: 0.3981 - val_accuracy: 0.8490
Epoch 2/10
1875/1875 [==============================] - 10s 5ms/step - loss: 0.4162 - accuracy: 0.8581 - val_loss: 0.3345 - val_accuracy: 0.8808
Epoch 3/10
1875/1875 [==============================] - 10s 5ms/step - loss: 0.3519 - accuracy: 0.8819 - val_loss: 0.2957 - val_accuracy: 0.8928
Epoch 4/10
1875/1875 [==============================] - 10s 5ms/step - loss: 0.3161 - accuracy: 0.8937 - val_loss: 0.3076 - val_accuracy: 0.8939
Epoch 5/10
1875/1875 [==============================] - 10s 5ms/step - loss: 0.2877 - accuracy: 0.9029 - val_loss: 0.2882 - val_accuracy: 0.8986
Epoch 6/10
1875/1875 [==============================] - 10s 5ms/step - loss: 0.2702 - accuracy: 0.9091 - val_loss: 0.2671 - val_accuracy: 0.9047
Epoch 7/10
1875/1875 [==============================] - 10s 5ms/step - loss: 0.2505 - accuracy: 0.9147 - val_loss: 0.2730 - val_accuracy: 0.9053
Epoch 8/10
1875/1875 [==============================] - 10s 5ms/step - loss: 0.2381 - accuracy: 0.9185 - val_loss: 0.2787 - val_accuracy: 0.9049
Epoch 9/10
1875/1875 [==============================] - 10s 5ms/step - loss: 0.2241 - accuracy: 0.9245 - val_loss: 0.2976 - val_accuracy: 0.9036
Epoch 10/10
1875/1875 [==============================] - 10s 5ms/step - loss: 0.2116 - accuracy: 0.9259 - val_loss: 0.2853 - val_accuracy: 0.9110
  • 第一层使用64个相当大的滤波器(\(7\times7\)),但没有步幅,因为输入图像不是很大。设置了input_size=(28,28,1),因为图像是\(28\times28\),具有单个颜色通道(即灰度)
  • 有一个最大池化层,池化大小为2,因此它将每个空间维度除以2
  • 重复相同的结构两次:两个卷积层,紧接着是一个最大池化层。对于较大的图像,可以重复多次此结构
  • 随着CNN向输出层沿伸,滤波器的数量会增加(最初是64,然后是128,然后是256):增长是有意义的,因为底层特征通常很少,但是有很多不同的方法可以将它们组合成更高层次的特征。通常的做法是在每个池化层之后将滤波器的数量加倍:由于池化层将每个空间维度除以2,所以能负担得起对下一层特征图数量加倍而不必担心参数数量、内存使用量或计算量的暴增
  • 接下来是全连接的网络,该网络由两个隐藏层的密集层和一个密集输出层组成。在输入密集层之前,需要将输入展平,因为密集网络需要每个实例的一维特征阵列。还添加了两个dropout层,每层的dropout率均为50%,以减少过拟合的情况

该CNN在测试集上达到91%以上,它不是最好的,但是也相当不错。

多年来,已经开发了这种基本架构的变体,导致该领域取得了惊人的进步。衡量这一进展的好方法是在竞赛中的错误率。

LeNet-5

LeNet-5架构可能是最广为人知的CNN架构。它是由Yann LeCun于1998年创建的,已被广泛用于手写数字识别(MNIST)

类型 特征图 大小 内核大小 步幅 激活函数
Out 全连接 - 10 - - RBF
F6 全连接 - 84 - - tanh
C5 卷积 120 \(1\times1\) \(5\times5\) 1 tanh
S4 平均池化 16 \(5\times5\) \(2\times2\) 2 tanh
C3 卷积 16 \(10\times10\) \(5\times5\) 1 tanh
S2 平均池化 6 \(14\times14\) \(2\times2\) 2 tanh
C1 卷积 6 \(28\times28\) \(5\times5\) 1 tanh
In 输入 1 \(32\times32\) - - -
  • MNIST图像为\(28\times28\)像素,但是将其填充为\(32\times32\)像素并在送入网络之前进行了归一化。网络的其余部分不使用任何填充,这就是随着网络延展而尺寸不断缩小的原因

  • 平均池化层比一般的池化层要复杂一些:每个神经元计算其输入的平均值,然后将结果乘以可学习的系数(每个特征图一个),并添加一个可学习的偏置项(同样每个特征图一个),最后应用激活函数

  • C3特征图中的大多数神经元仅连接到了在S2特征图中的三个或者四个神经元(而不是S2特征图中的所有6个)

  • 输出层:每个神经元的输出是输入向量和权重向量之间的欧几里得距离的平方,而不是计算输入向量和权重向量的矩阵乘法。每个输入测量图像属于特定数字类别的程度。交叉熵成本函数现在是首选,因为它对不良预测的惩罚更大,产生更大的梯度并收敛更快

AlexNet

AlexNet CNN架构在2012年的ImageNet ILSVRC挑战赛中大获全胜:它的前五位错误率是17%,而第2位的错误率是26%。它是由Alex Krizhevsky、Ilya Sutskever和Geoffrey Hinton开发的,与LeNet-5相似,只是更大更深,它是第一个将卷积层直接堆叠在一起的方法,而不是将池化层堆叠在每个卷积层之上

类型 特征图 大小 内核 步幅 填充 激活
Out 全连接 - 1000 - - - Softmax
F10 全连接 - 4096 - - - ReLU
F9 全连接 - 4096 - - - ReLU
S8 最大池化 256 \(6\times6\) \(3\times3\) 2 valid -
C7 卷积 256 \(13\times13\) \(3\times3\) 1 same ReLU
C6 卷积 384 \(13\times13\) \(3\times3\) 1 same ReLU
C5 卷积 384 \(13\times13\) \(3\times3\) 1 same ReLU
S4 最大池化 256 \(13\times13\) \(3\times3\) 2 valid -
C3 卷积 256 \(27\times27\) \(5\times5\) 1 same ReLU
S2 最大池化 96 \(27\times27\) \(3\times3\) 2 valid -
C1 卷积 96 \(55\times55\) \(11\times11\) 4 valid ReLU
In 输入 3(RGB) \(227\times227\) - - - -

为了减少过拟合,作者使用了两种正则化技术。首先,他们在训练期间对F9层和F10层的输出使用了dropout率为50%的dropout技术;其次,他们通过随机变换图像的各种偏移量、水平翻转及更改亮度条件来执行数据增强

数据增强

数据增强通过生成每个训练实例的许多变体来人为地增加训练集的大小。这减少了过拟合,使之成为一种正则化技术。生成的实例应尽可能逼真:理想情况下,给定来自增强训练集的图像,人类应该不能区分它是否被增强。简单地添加白噪声将无济于事。修改应该是可以学习的(不是白噪声)。

例如,可以将训练集(训练集中的图片数量各不相同)中的每张图片稍微移动、旋转和调整大小,将生成的图片添加到训练集中。这迫使模型能更容忍图片中物体的位置、方向和大小的变化。对于更能容忍不同光照条件的模型,可以类似地生成许多具有各种对比度的图像。通常,还可以水平翻转图片(文本和其他非对称物体除外)。通过组合这些变换,可以大大增加训练集的大小

AlexNet还在层C1和C3的ReLU之后立即使用归一化步骤,称为局部响应归一化(LRN):最强激活的神经元会抑制位于相邻特征图中相同位置的其他神经元(在生物学神经元中已观察到这种竞争性激活)。这鼓励不同的特征图专业化,将它们分开,迫使它们探索更广泛的特征,从而最终改善泛化能力。

\[b_i=a_i(k+a\sum_{j=j_{low}}^{j_{high}}a_j^2)^{-\beta}\quad\mbox{其中}\left\{ \begin{aligned} j_{high}&=min(i+\frac r2,f_n-1)\\ j_{low}&=max(0,i-\frac r2) \\ \end{aligned} \right.\]

  • \(b_i\)是位于特征图\(i\)中位于第\(u\)行和\(v\)列的神经元归一化输出(此公式中仅考虑位于此行此列的神经元,因此未显示\(u\)\(v\)
  • \(a_i\)是ReLU之后但未归一化之前该神经元的激活
  • \(k,\alpha,\beta\)\(r\)是超参数。\(k\)称为偏置,\(r\)称为深度半径
  • \(f_n\)是特征图的数量

例如,如果r=2且神经元具有很强的激活作用,它将抑制特征图中紧接着其自身上方和下方的神经元的激活

在AlexNet中,超参数的设置如下:\(r=2,\alpha=0.00002,\beta=0.75,k=1\)。这个步骤可以使用tf.nn.local_response_normalization()函数来实现(若要在Keras模型中使用它,需要使用Lambda层来包装)

GoogLeNet

GoogLeNet架构由Google研究院的Christian Szegedy等人开发。它将前5名的错误率降低到7%以下而赢得了2014年的ILSVRC讨战。这种出色的性能很大程度上是由于该网络比以前的CNN更深。称为盗梦空间(inception)模块的子网能使GoogLeNet比以前的架构更有效地使用参数:GoogLeNet实际上具有AlexNet十分之一的参数

为什么Inception模块具有带\(1\times1\)的卷积层。这些层肯定不会识别任何特征,因为它们一次只能看到一个像素。

  • 尽管它们无法识别空间特征,但是它们可以识别沿深度维度的特征
  • 它们输出比输入更少的特征图,因此它们充当了瓶颈层,这意味着它们降低了维度。这减少了计算量和参数数量,加快了训练速度,并提高了泛化能力
  • 每对卷积层([1X1,3X3]和[1X1,5X5])就像一个强大的卷积层,能够识别更复杂的模式。实际上这对卷积层不是在整个图像上扫描简单的线性分类器,而是在整个图像上扫描了两层神经网络

Inception模块能够输出以各种比例尺寸识别的复杂模式的特征图

VGGNet

ILSVRC 2014挑战赛的亚军是VGGNet,由牛津大学视觉几何组(VGG)研究实验室的Karen Simonyan和Andrew Zisserman开发。它具有非常简单和经典的架构,具有2或3个卷积层和一个池化层,然后又有2或3个卷积层和一个池化层,以此类推(总共达到16或19个卷积层,具体取决于VGG变体),以及最终的有2个隐藏层的密集网络和输出层。它仅使用\(3\times3\)滤波器,但是用了许多滤波器

ResNet

何凯明等使用残差网络(或ResNet)赢得了ILSVRC 2015挑战赛,其前5名的错误率低于3.6%。获胜的变体使用了由152层组成的非常深的CNN(其他变体有34、50和101层)。它证实了一个趋势:模型变得越来越深,参数越来越少。能够训练这种深层网络关键是跳过连接(也成为快捷连接):馈入层的信号也添加到位于堆栈上方的层的输出中。

在训练神经网络时,目标是使其称为目标函数\(h(x)\)的模型。如果将输入\(x\)添加到网络的输出(即添加跳过连接),则网络将被迫建模\(f(x)=h(x)+x\)而不是\(h(x)\),这称为残差学习

初始化常规神经网络时,其权重接近零,因此网络仅输出接近零的值。如果添加跳过连接,则生成的网络仅输出其输入的副本。

如果添加许多跳过连接,即使几层还没有开始学习,网络也可以开始取得进展。借助跳过连接,信号可以轻松地在整个网络中传播。深度残差网络可以看作是残差单元(RU)的堆栈,其中每个残差单元都是具有跳过连接的小型神经网络

Xcepion

GoogLeNet架构的另一种值得注意的变体:Xception(代表Extreme Inception)有Franois Chollet于2016年提出,它在很大的视觉任务(3.5亿张图像和17000个类别)明显优于Inception-v3.就像Inception-v4一样,它融合了GoogLeNet和ResNet的思想,但是它用称为深度学习方向可分离卷积层的特殊类型替换了inception模块。这些层以前曾在某些CNN架构中使用过,但它们并不像在Xception架构那样重要。虽然常规卷积层使用的滤波器试图同时识别空间模式和跨通道模式,但可分离的的卷积层的强烈假设是空间模式和跨通道模式可以单独建模。因此它由两部分组成:第一部分为每个输入特征图应用一个空间滤波器,然后第二部分专门寻找跨通道模式——它是具有\(1\times1\)滤波器的常规卷积层

SeNet

ILSVRC 2017挑战赛获胜的架构是Squeeze-and-Excitation Network(SENet)。该架构拓展了现有架构(例如inception网络和ResNets),并提高了它们的性能。这使得SENet以2.25%的前5名错误率赢得了比赛。inception和ResNet的拓展版本分别成为SE-Inception和SE-ResNet。SENet的增强来自于这样一个事实——SENet向原始架构中的每个单元(即每个inception模块或每个残差单元)添加了一个称为SE块的小型神经网络

SE块分析其连接的单元的输出,仅专注于深度维度(它不关心任何空间模式),并了解哪一些特征通常最活跃。然后,它使用此信息重新校准特征图。一个SE块可能会了解到嘴、鼻子和眼睛通常在图片中同时出现:如果看到嘴巴和鼻子,还应该看到眼睛。因此,如果该块在嘴和鼻子特征途中看到强烈的激活,而在眼睛特征图中只有轻微的激活,则它将增强眼睛特征图(更准确地说,它将降低无关的特征图)。如果眼睛和其他东西有点混淆,则特征图重新校准有助于解决不确定性

SE块仅有三层组成:全局平均池化层、使用ReLU激活函数的隐藏密集层和使用sigmoid激活函数的密集输出层

相关