本次重点(模型轻量化):
● Inception设计理念
● 点卷积
● 深度可分离卷积
● Bottleneck结构
注:Xception算法整体结构是其次,主要是了解以上四个结构。
今天详解Xception算法,由于Xception模型在极大的减少了网络参数量和计算复杂度的同时,可以保持卓越的性能表现。因此,Xception模型已经被广泛地应用与图像分类、目标检测等任务中。
在计算机视觉领域,卷积神经网络(CNN)已经成为最主流的方法,比如GoogLenet,VGG-16,Incepetion等模型。CNN史上的一个里程碑事件是ResNet模型的出现,ResNet可以训练出更深的CNN模型,从而实现更高的准确度。ResNet模型的核心是通过建立前面层与后面层之间的“短路连接”,进而训练出更深的CNN网络。
随着图像分类的准确率不断提高,网络的深度越来越深,图像分类的错误率也越来越低,从2012的AlexNet,2013年的ZFNet,2014年的GoogLeNet,再到后面2015年的ResNet,准确率已经超过了人类的水平,所以单纯从准确率方面考虑的话已经很难提升了;因此人们就开始从其他方面考虑,比如参数量和计算量,软硬件协同等。
今天我们要介绍的是Xception模型,Xception是Google继Inception后提出的对Inception V3的另一种改进,主要是采用深度可分离卷积(Depthwise Separable Convolution)来替换原来Inception V3中的卷积操作。
论文原文:Xception.pdf
前面说了,由于CNN模型的精度已经很难进一步提升,所以研究者们就把注意力放到了减少模型参数量和计算量上,因此Xception应运而生。而Xception就是研究者们在Inception V3模型上的进一步改进,通过用深度可分离卷积替换Inception V3中的多尺寸卷积核特征响应操作,最终达到了精度的略微提升和参数量的减少。在讲解Xception之前,我们先对它的前身Inception进行一番了解,然后再一步步往Xception方面讲述。
2.1多尺度卷积核
Inception 最初提出的版本,其核心思想就是使用多尺寸卷积核去观察输入数据。
举个例子,我们看某个景象由于远近不同,同一个物体的大小也会有所不同,那么不同尺度的卷积核观察的特征就会有这样的结果。于是就有了如下的网络结构图:
于是我们的网络就变胖了,增加了网络的宽度,同时也提高了对于不同尺度的适应程度。
2.2 点卷积(Pointwise Conv)
但是我们的网络变胖了的同时,计算量也变大了,所以我们就要想办法减少参数量来减少计算量,于是在Inception v1 中的最终版本加上了1x1 卷积核。
使用 1x1 卷积核对输入的特征图进行降维处理,这样就会极大得减少参数量,从而减少计算。
举例,输入数据的维度是256维,经过1x1 卷积之后,我们输出的维度是64维,参数量是原来的 1 4 \frac{1}{4} 41 。
这就是 Pointwise Convolution,俗称叫做 1x1 卷积,简写为 PW,主要用于数据降维,减少参数量。
2.3 卷积核替换
就算有了 PW ,由于 5x5 和 7x7 卷积核直接计算参数量还是非常大,训练时间还是比较长,我们还要再优化。
人类的智慧是无穷的,于是就想出了使用多个小卷积核替代大卷积核 的方法,这就是 Inception v3,如图3所示:
使用两个 3x3 卷积核来代替 5x5 卷积,效果上差不多,但参数量减少很多,达到了优化的目的。不仅参数量少,层数也多了,深度也变深了。
除了规整的的正方形,我们还有分解版本的 3x3 = 3x1 + 1x3,这个效果在深度较深的情况下比规整的卷积核更好。
我们假设输入 256 维,输出 512 维,计算一下参数量:
● 5x5 卷积核: 256 × 5 × 5 × 512 = 3276800 256\times5\times5\times512=3276800 256×5×5×512=3276800
● 两个 3x3 卷积核: 256 × 3 × 3 × 256 + 256 × 3 × 3 × 512 = 589824 + 1179648 = 1769472 256\times3\times3\times256+256\times3\times3\times512=589824+1179648=1769472 256×3×3×256+256×3×3×512=589824+1179648=1769472
结果对比: 1769472 3276800 = 0.54 \frac{1769472}{3276800}=0.54 32768001769472=0.54
我们可以看到参数量对比,两个 3x3 的卷积核的参数量是 5x5 一半,可以大大加快训练速度。
2.4 Bottleneck
我们发现就算用了上面的结构和方法,我们的参数量还是很大,于是乎我们结合上面的方法创造出了 Bottleneck 的结构降低参数量。
Bottleneck 三步走是先 PW卷积 对数据进行降维,再进行常规卷积核的卷积,最后 PW 卷积对数据进行升维。我们举个例子,方便我们了解:
根据上图,我们来做个对比计算,假设输入 feature map 的维度为 256 维,要求输出维度也是 256 维。有以下两种操作:
● 直接使用 3x3 的卷积核。256 维的输入直接经过一个 3×3×256 的卷积层,输出一个 256 维的 feature map ,那么参数量为:256×3×3×256 = 589,824 。
● 先经过 1x1 的卷积核,再经过 3x3 卷积核,最后经过一个 1x1 卷积核。 256 维的输入先经过一个 1×1×64 的卷积层,再经过一个 3x3x64 的卷积层,最后经过 1x1x256 的卷积层,则总参数量为:256×1×1×64 + 64×3×3×64 + 64×1×1×256 = 69,632 。
经过两种方式的对比,我们可以很明显的看到后者的参数量远小于前者的。
2.5 深度可分离卷积(Depthwise Separable Convolutions)
深度可分离卷积(Depthwise Separable Convolutions)不同之处在于,其不仅仅涉及空间维度,还涉及深度维度(即 channel 维度)相较于常规卷积操作,其参数数量和运算成本比较低。通常输入图像会具有3个channel:R、G、B。在经过一系列卷积操作后,输入特征图就会变为多个channel。对于每个channel而言,我们可以将其想成对该图像某种特定特征的解释说明。例如输入图像中,“红色” channel 解释描述了图像中的“红色”特征,“绿色” channel 解释描述了图像中的“绿色”特征,“蓝色” channel 解释描述了图像中的“蓝色”特征。又例如 channel 数量为64的输出特征图,就相当于对原始输入图像的64种不同的特征进行了解释说明。
类似空间可分离卷积,深度可分离卷积也是将卷积核分成两个单独的小卷积核,分别进行2种卷积运算:深度卷积运算和逐点卷积运算。 首先,让我们看看正常的卷积是如何工作的。
1)标准卷积
假设我们有一个 12 × 12 × 3 12\times12\times3 12×12×3的输入图像,即图像尺寸为 12 × 12 12\times12 12×12,通道数为 3 3 3,对图像进行 5 × 5 5\times5 5×5卷积,没有填充(padding)且步长为1。如果我们只考虑图像的宽度和高度,使用 5 × 5 5\times5 5×5卷积来处理 12 × 12 12\times12 12×12大小的输入图像,最终可以得到一个 8 × 8 8\times8 8×8的输出特征图。至于如何计算的就涉及到卷积运算了,这里给出一个动图1解释如下:
图1 单通道卷积运算
然而,由于图像有3个通道,我们的卷积核也需要有3个通道。 这就意味着,卷积核在每个位置进行计算时,实际上会执行 5 × 5 × 3 = 75 5\times5\times3=75 5×5×3=75次乘法。如图2所示,我们使用一个 5 × 5 × 3 5\times5\times3 5×5×3的卷积核进行运算,最终可以得到 8 × 8 × 1 8\times8\times1 8×8×1的输出特征图。
单个三通道特征图卷积计算过程也可以用一张动图3解释如下:
图3 单个三通道特征图卷积运算
如果我们想增加输出的 channel 数量让网络学习更多种特征呢?这时我们可以创建多个卷积核,比如256个卷积核来学习256个不同类别的特征。此时,256个卷积核会分别进行运算,得到256个 8 × 8 × 1 8\times8\times1 8×8×1的输出特征图。如 图4 所示。
2)深度卷积运算
我们第二个已经讲解过了DW卷积,而深度可分离卷积就运用到了DW卷积。首先,我们对输入图像进行深度卷积运算,这里的深度卷积运算其实就是逐通道进行卷积运算。对于一副 12 × 12 × 3 12\times12\times3 12×12×3的输入图像而言,我们使用大小为 5 × 5 5\times5 5×5的卷积核进行逐通道计算,计算方式如图5所示:
图5 深度卷积运算
这里其实就是使用3个 5 × 5 × 1 5\times5\times1 5×5×1的卷积核分别提取输入图像中3个 channel 的特征,每个卷积核计算完成后,会得到3个 8 × 8 × 1 8\times8\times1 8×8×1的输出特征图,将这些特征图堆叠在一起就可以得到大小为 8 × 8 × 3 8\times8\times3 8×8×3的最终输出特征图。这里我们可以发现深度卷积运算的一个缺点,即深度卷积运算缺少通道间的特征融合 ,并且运算前后通道数无法改变。
因此,接下来就需要连接一个逐点卷积来弥补它的缺点。
3)逐点卷积运算
前面我们使用深度卷积运算完成了从一幅 12 × 12 × 3 12\times12\times3 12×12×3的输入图像中得到 8 × 8 × 3 8\times8\times3 8×8×3的输出特征图,并且发现仅使用深度卷积无法实现不同通道间的特征融合,而且也无法得到与标准卷积运算一致的 8 × 8 × 256 8\times8\times256 8×8×256的特征图。那么,接下来就让我们看一下如何使用逐点卷积实现这两个任务。逐点卷积其实就是 1 × 1 1\times1 1×1卷积,因为其会遍历每个点,所以我们称之为逐点卷积。 1 × 1 1\times1 1×1卷积在前面的内容中已经详细介绍了,这里我们还是结合上边的例子看一下它的具体作用。
我们使用一个3通道的 1 × 1 1\times1 1×1卷积对上文中得到的 8 × 8 × 3 8\times8\times3 8×8×3的特征图进行运算,可以得到一个 8 × 8 × 1 8\times8\times1 8×8×1的输出特征图。如 图6 所示。此时,我们就使用逐点卷积实现了融合3个通道间特征的功能。
此外,我们可以创建256个3通道的 1 × 1 1\times1 1×1卷积对上文中得到的 8 × 8 × 3 8\times8\times3 8×8×3的特征图进行运算,这样,就可以实现得到与标准卷积运算一致的 8 × 8 × 256 8\times8\times256 8×8×256的特征图的功能。如 图7 所示。
图7 输出通道为256的逐点卷积
4)深度可分离卷积的意义
上文中,我们给出了深度可分离卷积的具体计算方式,那么使用深度可分离卷积代替标准卷积有什么意义呢?
这里我们看一下上文例子中标准卷积的乘法运算个数,我们创建了256个 5 × 5 × 3 5\times5\times3 5×5×3的卷积核进行卷积运算,每个卷积核会在输入图片上移动 8 × 8 8\times8 8×8次,因此总的乘法运算个数为:
256 × 3 × 5 × 5 × 8 × 8 = 1228800 256\times3\times5\times5\times8\times8=1228800 256×3×5×5×8×8=1228800
而换成深度可分离卷积后,在深度卷积运算时,我们使用3个 5 × 5 × 1 5\times5\times1 5×5×1的卷积核在输入图片上移动 8 × 8 8\times8 8×8次,此时乘法运算个数为:
$3\times5\times5\times8\times8=4800
在逐点卷积运算时,我们使用256个 1 × 1 × 3 1\times1\times3 1×1×3的卷积在输入特征图上移动 8 × 8 8\times8 8×8次,此时乘法运算个数为:
256 × 1 × 1 × 3 × 8 × 8 = 49152 256\times1\times1\times3\times8\times8=49152 256×1×1×3×8×8=49152
将这两步运算相加,即可得到,使用深度可分离卷积后,总的乘法运算个数变为:53952。可以看到,深度可分离卷积的运算量相较标准卷积而言,计算量少了很多。
3.网络结构
Xception的具体网络结构如图11所示:
图11 Xception网络结构
Xception包含三个部分:输入部分(Entry flow),中间部分(Middle flow)和结尾部分(Exit flow);其中所有卷积层和可分离卷积层后面都使用Batch Normalization处理,所有的可分离卷积层使用一个深度乘数1(深度方向并不进行扩充)。
对于Entry flow,首先使用了两个3x3卷积(conv1,conv2)降低特征图尺寸,同时增加了特征图个数;接着是3个含跳连的深度可分离卷积堆叠模块。
对于Middle flow,包含了8个一模一样的含跳连的深度可分离卷积堆叠模块。
对于Exit flow,首先是一个含跳连的深度可分离卷积堆叠模块,接着是一些深度可分离卷积层以及全局平均池化层,最后用全连接层输出分类结果。
我的环境:
● 语言环境:Python3.8
● 编译器:Jupyter Lab
● 深度学习环境:Pytorch
○ torch1.12.1+cu113
○ torchvision0.13.1+cu113
1. SeparableConv模块定义
结构图中的SeparableConv组件就是depthwise separable convolution(深度可分离卷积),它是由depthwise conv(dw)和pointwise conv(pw)组成。根据原论文描述,dw与pw的先后顺序对最终效果并无影响,其结构定义如下
class SeparableConv(nn.Module):
def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0):
super(SeparableConv, self).__init__()
self.depthwise = nn.Conv2d(in_channels, in_channels, kernel_size, stride, padding, groups=in_channels)
self.pointwise = nn.Conv2d(in_channels, out_channels, kernel_size=1)
def forward(self, x):
x = self.depthwise(x)
x = self.pointwise(x)
return x
2. XceptionBlock_1定义
根据结构图,Entry flow由两个卷积层和三个相同的子模块构成,我们将这个子模块命名为XceptionBlock_1,其结构如上图。可以看出,XceptionBlock_1的两个分支以残差的的形式进行连接,其中主分支需要经过两个SeparableConv层和一个最大池化层,残差分支则经过步距为2的1*1卷积层。XceptionBlock_1模块的定义如下
class XceptionBlock_1(nn.Module):
def __init__(self, in_channels, out_channels):
super(XceptionBlock_1, self).__init__()
self.relu = nn.ReLU()
self.sepconv1 = SeparableConv(in_channels, out_channels, kernel_size=3, padding=