Python复现Xception模型:深度可分离卷积与网络结构详解
本文详细解析Xception模型,该模型通过深度可分离卷积替换Inception v3中的多尺寸卷积核,有效减少参数量。文章梳理了Entry、Middle、Exit三个Flow的结构差异,对比了普通卷积与深度可分离卷积的参数计算,并提供基于Keras的完整Python复现代码及图像预测示例,帮助开发者快速掌握该经典深度学习架构。
Xception是在Inception之后提出的对Inception v3的另一种改进方案,学习这一架构具有重要的参考价值。
什么是Xception模型
Xception由谷歌公司提出,作为Inception v3的改进版本,其核心改进在于采用深度可分离卷积(depthwise separable convolution)替代了原Inception v3中的多尺寸卷积核特征响应操作。
在深入讲解Xception模型之前,首先需要明确深度可分离卷积块(depthwise separable convolution block)的基本概念。
深度可分离卷积块包含两个主要部分:深度可分离卷积和1x1普通卷积。其中,深度可分离卷积通常使用3x3大小的卷积核,主要作用是进行特征提取;而1x1普通卷积则用于调整通道数。
下图展示了深度可分离卷积块的结构示意图:

引入深度可分离卷积块的主要目的是以更少的参数替代传统的3x3普通卷积。
我们可以通过对比普通卷积与深度可分离卷积块来直观理解其差异:
假设存在一个3×3大小的卷积层,输入通道数为16,输出通道数为32。具体而言,32个3×3大小的卷积核会遍历16个通道中的每个数据,最终生成所需的32个输出通道。该过程所需的参数数量为16×32×3×3=4608个。
若应用深度可分离卷积,首先使用16个3×3大小的卷积核分别遍历16个通道的数据,得到16个特征图谱。随后,在融合操作前,使用32个1×1大小的卷积核遍历这16个特征图谱。此过程所需的参数数量为16×3×3+16×32×1×1=656个。
由此可见,depthwise separable convolution能够显著减少模型的参数量。
通俗地理解,深度可分离卷积结构块中,3x3卷积核的厚度仅为一层,它在输入张量上逐层滑动,每次卷积生成一个输出通道;卷积完成后,再利用1x1卷积调整通道厚度。
(注:视频中存在些许错误,感谢zl960929的提醒。Xception使用的深度可分离卷积块SeparableConv2D,其实际顺序为先进行深度可分离卷积,再进行1x1卷积。)
对于Xception模型而言,其整体结构可分为三个Flow,分别是Entry flow、Middle flow和Exit flow。
模型共包含14个Block,其中Entry flow包含4个,Middle flow包含8个,Exit flow包含2个。
具体结构如下所示:

其内部主要结构由残差卷积网络搭配SeparableConv2D层实现各个Block。在Xception模型中,Entry flow和Exit flow中常见的两种Block结构如下:

而Middle flow中主要使用的Block结构如下:

Xception网络部分的Python实现代码
#-------------------------------------------------------------#
# Xception的网络部分
#-------------------------------------------------------------#
from keras.preprocessing import image
from keras.models import Model
from keras import layers
from keras.layers import Dense,Input,BatchNormalization,Activation,Conv2D,SeparableConv2D,MaxPooling2D
from keras.layers import GlobalAveragePooling2D,GlobalMaxPooling2D
from keras import backend as K
from keras.applications.imagenet_utils import decode_predictions
def Xception(input_shape = [299,299,3],classes=1000):
img_input = Input(shape=input_shape)
#--------------------------#
# Entry flow
#--------------------------#
#--------------------#
# block1
#--------------------#
# 299,299,3 -> 149,149,64
x = Conv2D(32, (3, 3), strides=(2, 2), use_bias=False, name='block1_conv1')(img_input)
x = BatchNormalization(name='block1_conv1_bn')(x)
x = Activation('relu', name='block1_conv1_act')(x)
x = Conv2D(64, (3, 3), use_bias=False, name='block1_conv2')(x)
x = BatchNormalization(name='block1_conv2_bn')(x)
x = Activation('relu', name='block1_conv2_act')(x)
#--------------------#
# block2
#--------------------#
# 149,149,64 -> 75,75,128
residual = Conv2D(128, (1, 1), strides=(2, 2), padding='same', use_bias=False)(x)
residual = BatchNormalization()(residual)
x = SeparableConv2D(128, (3, 3), padding='same', use_bias=False, name='block2_sepconv1')(x)
x = BatchNormalization(name='block2_sepconv1_bn')(x)
x = Activation('relu', name='block2_sepconv2_act')(x)
x = SeparableConv2D(128, (3, 3), padding='same', use_bias=False, name='block2_sepconv2')(x)
x = BatchNormalization(name='block2_sepconv2_bn')(x)
x = MaxPooling2D((3, 3), strides=(2, 2), padding='same', name='block2_pool')(x)
x = layers.add([x, residual])
#--------------------#
# block3
#--------------------#
# 75,75,128 -> 38,38,256
residual = Conv2D(256, (1, 1), strides=(2, 2),padding='same', use_bias=False)(x)
residual = BatchNormalization()(residual)
x = Activation('relu', name='block3_sepconv1_act')(x)
x = SeparableConv2D(256, (3, 3), padding='same', use_bias=False, name='block3_sepconv1')(x)
x = BatchNormalization(name='block3_sepconv1_bn')(x)
x = Activation('relu', name='block3_sepconv2_act')(x)
x = SeparableConv2D(256, (3, 3), padding='same', use_bias=False, name='block3_sepconv2')(x)
x = BatchNormalization(name='block3_sepconv2_bn')(x)
x = MaxPooling2D((3, 3), strides=(2, 2), padding='same', name='block3_pool')(x)
x = layers.add([x, residual])
#--------------------#
# block4
#--------------------#
# 38,38,256 -> 19,19,728
residual = Conv2D(728, (1, 1), strides=(2, 2),padding='same', use_bias=False)(x)
residual = BatchNormalization()(residual)
x = Activation('relu', name='block4_sepconv1_act')(x)
x = SeparableConv2D(728, (3, 3), padding='same', use_bias=False, name='block4_sepconv1')(x)
x = BatchNormalization(name='block4_sepconv1_bn')(x)
x = Activation('relu', name='block4_sepconv2_act')(x)
x = SeparableConv2D(728, (3, 3), padding='same', use_bias=False, name='block4_sepconv2')(x)
x = BatchNormalization(name='block4_sepconv2_bn')(x)
x = MaxPooling2D((3, 3), strides=(2, 2), padding='same', name='block4_pool')(x)
x = layers.add([x, residual])
#--------------------------#
# Middle flow
#--------------------------#
#--------------------#
# block5--block12
#--------------------#
# 19,19,728 -> 19,19,728
for i in range(8):
residual = x
prefix = 'block' + str(i + 5)
x = Activation('relu', name=prefix + '_sepconv1_act')(x)
x = SeparableConv2D(728, (3, 3), padding='same', use_bias=False, name=prefix + '_sepconv1')(x)
x = BatchNormalization(name=prefix + '_sepconv1_bn')(x)
x = Activation('relu', name=prefix + '_sepconv2_act')(x)
x = SeparableConv2D(728, (3, 3), padding='same', use_bias=False, name=prefix + '_sepconv2')(x)
x = BatchNormalization(name=prefix + '_sepconv2_bn')(x)
x = Activation('relu', name=prefix + '_sepconv3_act')(x)
x = SeparableConv2D(728, (3, 3), padding='same', use_bias=False, name=prefix + '_sepconv3')(x)
x = BatchNormalization(name=prefix + '_sepconv3_bn')(x)
x = layers.add([x, residual])
#--------------------------#
# Exit flow
#--------------------------#
#--------------------#
# block13
#--------------------#
# 19,19,728 -> 10,10,1024
residual = Conv2D(1024, (1, 1), strides=(2, 2),
padding='same', use_bias=False)(x)
residual = BatchNormalization()(residual)
x = Activation('relu', name='block13_sepconv1_act')(x)
x = SeparableConv2D(728, (3, 3), padding='same', use_bias=False, name='block13_sepconv1')(x)
x = BatchNormalization(name='block13_sepconv1_bn')(x)
x = Activation('relu', name='block13_sepconv2_act')(x)
x = SeparableConv2D(1024, (3, 3), padding='same', use_bias=False, name='block13_sepconv2')(x)
x = BatchNormalization(name='block13_sepconv2_bn')(x)
x = MaxPooling2D((3, 3), strides=(2, 2), padding='same', name='block13_pool')(x)
x = layers.add([x, residual])
#--------------------#
# block14
#--------------------#
# 10,10,1024 -> 10,10,2048
x = SeparableConv2D(1536, (3, 3), padding='same', use_bias=False, name='block14_sepconv1')(x)
x = BatchNormalization(name='block14_sepconv1_bn')(x)
x = Activation('relu', name='block14_sepconv1_act')(x)
x = SeparableConv2D(2048, (3, 3), padding='same', use_bias=False, name='block14_sepconv2')(x)
x = BatchNormalization(name='block14_sepconv2_bn')(x)
x = Activation('relu', name='block14_sepconv2_act')(x)
x = GlobalAveragePooling2D(name='avg_pool')(x)
x = Dense(classes, activation='softmax', name='predictions')(x)
inputs = img_input
model = Model(inputs, x, name='xception')
model.load_weights("xception_weights_tf_dim_ordering_tf_kernels.h5")
return model
图片预测
建立网络后,可以使用以下代码进行图像预测。
def preprocess_input(x):
x /= 255.
x -= 0.5
x *= 2.
return x
if __name__ == '__main__':
model = Xception()
img_path = 'elephant.jpg'
img = image.load_img(img_path, target_size=(299, 299))
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
x = preprocess_input(x)
print('Input image shape:', x.shape)
preds = model.predict(x)
print(np.argmax(preds))
print('Predicted:', decode_predictions(preds))
预测所需的已训练好的Xception模型权重文件可在https://github.com/fchollet/deep-learning-models/releases下载,获取方式非常便捷。
预测结果示例如下:
Predicted: [[('n02504458', 'African_elephant', 0.47570863), ('n01871265', 'tusker', 0.3173351), ('n02504013', 'Indian_elephant', 0.030323735), ('n02963159', 'cardigan', 0.0007877756), ('n02410509', 'bison', 0.00075616257)]]以上就是Python神经网络Xception模型复现的详细内容,更多关于Xception模型复现的资料请关注本站其他相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













