❝Xception: Deep Learning with Depthwise Separable Convolutions❞
1.前言
卷积神经网络学习路线这个系列旨在盘点从古至今对当前CV影响很大的那些经典网络。为了保证完整性我会将之前漏掉的一些网络补充下去,已经介绍了非常多的经典网络,这个系列可能也快要迎来完结了。接着卷积神经网络学习路线(九)| 经典网络回顾之GoogLeNet系列 也就是Inception V3之后,Google提出了XceptionNet,这是对Inception V3的一种改进,主要使用了深度可分离卷积来替换掉Inception V3中的卷积操作。
2. 铺垫
为了更好的说明Xception网络,我们首先需要从Inception V3来回顾一下。下面的Figure1展示了Inception V3的结构图。可以看到Inception的核心思想是将多种特征提取方式如卷积,卷积,卷积,等产生的特征图进行了concate,达到融合多种特征的效果。
InceptionV3 结构
然后,从Inception V3的结构联想到了一个简化的Inception结构,如Figure2所示。
简化的Inception结构
再然后将Figure2的结构进行改进,就获得了Figure3所示的结构。可以看到,在这个结构中先用一个卷积,然后连接三个卷积,这三个卷积只将前面卷积的一部分(这里是的通道)作为每个卷积的输出。同时Figure4则为我们展示了将这一想法应用到极致,即每个通道接一个卷积的结构。
极致的Inception
3. Xception原理
Xception中主要采用了深度可分离卷积。这个卷积我们之前已经介绍的很清楚了,请看这篇推文:【综述】神经网络中不同种类的卷积层 。那么深度可分离卷积和上面Figure4中的极致Inception结构有什么区别呢?
- 极致的Inception。
- 第一步:普通的卷积。
- 第二步:对卷积结果的每个channel,分别进行卷积操作,并将结果concate。
- 深度可分离卷积。
- 第一步:「Depthwise 卷积」 ,对输入的每个channel,分别进行卷积操作,并将结果concate。
- 第二步:「Pointwise 卷积」 ,对 Depthwise 卷积中的concate结果,进行卷积操作。
可以看到两者的操作顺序是不一致的,Inception先进行卷积,再进行卷积,深度可分离卷积恰好相反。作者在论文中提到这个顺序差异并不会对网络精度产生大的影响。同时作者还有一个有趣的发现,在Figure4展示的「极致的 Inception”模块」中,用于学习空间相关性的卷积和用于学习通道相关性的卷积「之间」如果不使用激活函数,收敛过程会更快,并且结果更好,如下图所示。
点卷积之前是否使用激活函数实验
4. Xception网络结构
Xception的网络结构如Figure5所示。
Xception的结构
图里面的sparsableConv
就是深度可分离卷积。另外,每个小块的连接采用的是residule connection(图中的加号),而不是原Inception中的concate。
5. 实验结果
Table1表示几种网络结构在ImageNet上的对比结果。
几种网络结构在ImageNet上的对比结果
Table2表示几种网络结构在「JFT数据集」上的对比。可以看到在大数据上的提升会比Table1好一点。
Table2
6. 总结
Xception主要是在Inception V3的基础上引入了深度可分离卷积,在基本不增加网络复杂度的前提下提高了模型的效果。
7. 代码实现
Keras代码实现如下:
代码语言:javascript复制from keras.models import Model
from keras import layers
from keras.layers import Dense, Input, BatchNormalization, Activation
from keras.layers import Conv2D, SeparableConv2D, MaxPooling2D, GlobalAveragePooling2D, GlobalMaxPooling2D
from keras.applications.imagenet_utils import _obtain_input_shape
from keras.utils.data_utils import get_file
WEIGHTS_PATH = 'https://github.com/fchollet/deep-learning-models/releases/download/v0.4/xception_weights_tf_dim_ordering_tf_kernels.h5'
def Xception():
# Determine proper input shape
input_shape = _obtain_input_shape(None, default_size=299, min_size=71, data_format='channels_last', include_top=False)
img_input = Input(shape=input_shape)
# Block 1
x = Conv2D(32, (3, 3), strides=(2, 2), use_bias=False)(img_input)
x = BatchNormalization()(x)
x = Activation('relu')(x)
x = Conv2D(64, (3, 3), use_bias=False)(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)
residual = Conv2D(128, (1, 1), strides=(2, 2), padding='same', use_bias=False)(x)
residual = BatchNormalization()(residual)
# Block 2
x = SeparableConv2D(128, (3, 3), padding='same', use_bias=False)(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)
x = SeparableConv2D(128, (3, 3), padding='same', use_bias=False)(x)
x = BatchNormalization()(x)
# Block 2 Pool
x = MaxPooling2D((3, 3), strides=(2, 2), padding='same')(x)
x = layers.add([x, residual])
residual = Conv2D(256, (1, 1), strides=(2, 2), padding='same', use_bias=False)(x)
residual = BatchNormalization()(residual)
# Block 3
x = Activation('relu')(x)
x = SeparableConv2D(256, (3, 3), padding='same', use_bias=False)(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)
x = SeparableConv2D(256, (3, 3), padding='same', use_bias=False)(x)
x = BatchNormalization()(x)
# Block 3 Pool
x = MaxPooling2D((3, 3), strides=(2, 2), padding='same')(x)
x = layers.add([x, residual])
residual = Conv2D(728, (1, 1), strides=(2, 2), padding='same', use_bias=False)(x)
residual = BatchNormalization()(residual)
# Block 4
x = Activation('relu')(x)
x = SeparableConv2D(728, (3, 3), padding='same', use_bias=False)(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)
x = SeparableConv2D(728, (3, 3), padding='same', use_bias=False)(x)
x = BatchNormalization()(x)
x = MaxPooling2D((3, 3), strides=(2, 2), padding='same')(x)
x = layers.add([x, residual])
# Block 5 - 12
for i in range(8):
residual = x
x = Activation('relu')(x)
x = SeparableConv2D(728, (3, 3), padding='same', use_bias=False)(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)
x = SeparableConv2D(728, (3, 3), padding='same', use_bias=False)(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)
x = SeparableConv2D(728, (3, 3), padding='same', use_bias=False)(x)
x = BatchNormalization()(x)
x = layers.add([x, residual])
residual = Conv2D(1024, (1, 1), strides=(2, 2), padding='same', use_bias=False)(x)
residual = BatchNormalization()(residual)
# Block 13
x = Activation('relu')(x)
x = SeparableConv2D(728, (3, 3), padding='same', use_bias=False)(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)
x = SeparableConv2D(1024, (3, 3), padding='same', use_bias=False)(x)
x = BatchNormalization()(x)
# Block 13 Pool
x = MaxPooling2D((3, 3), strides=(2, 2), padding='same')(x)
x = layers.add([x, residual])
# Block 14
x = SeparableConv2D(1536, (3, 3), padding='same', use_bias=False)(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)
# Block 14 part 2
x = SeparableConv2D(2048, (3, 3), padding='same', use_bias=False)(x)
x = BatchNormalization()(x)
x = Activation('relu')(x)
# Fully Connected Layer
x = GlobalAveragePooling2D()(x)
x = Dense(1000, activation='softmax')(x)
inputs = img_input
# Create model
model = Model(inputs, x, name='xception')
# Download and cache the Xception weights file
weights_path = get_file('xception_weights.h5', WEIGHTS_PATH, cache_subdir='models')
# load weights
model.load_weights(weights_path)
return model
8. 参考
- 论文原文:https://arxiv.org/abs/1610.02357
- https://blog.csdn.net/u014380165/article/details/75142710
- https://blog.csdn.net/lk3030/article/details/84847879