Xception和SqueezeNet一样,是一种降低参数量的轻量级神经网络,它主要使用了
对比一下深度可分离卷积和普通卷积的区别:
下图为普通的卷积操作,每个卷积核对应通道与输入对应通道进行卷积操作后求和,输出通道数为卷积核数。
下图为深度分离卷积操作的第一步-深度卷积,我是这样理解的,使用了一个卷积核通道数与输入通道数相等的卷积核,每个卷积核的通道与输入对应通道卷积操作后不进行求和(也可以这样理解,使用了多个(与输入通道数相等)单通道卷积核,每个卷积核负责输入的一个通道,卷积操作后不进行求和,而是Conact(深度方向堆叠),这样会得到与输入通道数(channels)相等的输出。
关于深度卷积和普通卷积的区别,这篇文章讲的也很清楚:
DepthwiseConv2D和Conv2D详解
下图为深度分离卷积操作的第二步-逐点卷积(pointwise convolution):就是使用1x1的普通卷积,输出通道数等与卷积核数(output channels ==filters)。
Xception引入了Entry flow、Middle flow、Exit flow三个flow,Entry flow主要用来不断下采样,减小空间维度;Middle flow用来学习关联关系,优化特征;Exit flow是汇总,整理特征,传递给全连接层表达信息。
上表中的Conv和SeparableConV由下面的Layer表示
import tensorflow as tf
from tensorflow.keras.layers import *
from tensorflow.keras import Model
class Conv(Model):#普通卷积块
def __init__(self,filters=32,kernel_size=(3,3),strides=2):
super().__init__()
self.filters=filters
self.kernel_size=kernel_size
self.strides=strides
self.layers1=[]
self.layers1.append(Conv2D(filters=self.filters,kernel_size=self.kernel_size,strides=self.strides,padding='same'))
self.layers1.append(BatchNormalization())
self.layers1.append(Activation('relu'))
def call(self,x):
for layer in self.layers1.layers:
x=layer(x)
return x
class Separable_residual(Model):#深度可分卷积+残差块,用于Entry_flow和Exit_flow
def __init__(self,mode=2,filters1=128,filters2=128):
super().__init__()
if mode==2:
self.a1=Activation('relu')
else:
self.a1=Activation(None)
self.c1=SeparableConv2D(filters=filters1,kernel_size=3,strides=1,padding='same')
self.b1=BatchNormalization()
self.a2=Activation('relu')
self.c2=SeparableConv2D(filters=filters2,kernel_size=3,strides=1,padding='same')
self.b2=BatchNormalization()
self.p2=MaxPooling2D(pool_size=(3,3),strides=2,padding='same')
self.residual=Conv(filters=filters2,kernel_size=1,strides=2)
def call(self,x):
residual=self.residual(x)
x=self.a1(x)
x=self.c1(x)
x=self.b1(x)
x=self.a2(x)
x=self.c2(x)
x=self.b2(x)
x=self.p2(x)
y=x+residual
return y
class Middle_Separable_residual(Model):#middle_flow module
def __init__(self):
super().__init__()
self.layers1=[]
for i in range(3):
self.layers1.append(Activation('relu'))
self.layers1.append(SeparableConv2D(filters=728,kernel_size=3,padding='same'))
def call(self,x):
residual=x
for layer in self.layers1.layers:
x=layer(x)
y=x+residual
return y
def Entry_flow(x,filters_list):
x=Conv()(x)
x=Conv(filters=64,strides=1)(x)
for filters in filters_list:
if filters==128:
x=Separable_residual(mode=1)(x)
else:
x=Separable_residual(2,filters,filters)(x)
return x
def Middle_flow(x):
x=Middle_Separable_residual()(x)
return x
def Exit_flow(x):
x=Separable_residual(mode=2,filters1=728,filters2=1024)(x)
x=SeparableConv2D(filters=1536,kernel_size=3,padding='same')(x)
x=Activation('relu')(x)
x=SeparableConv2D(filters=2048,kernel_size=3,padding='same')(x)
x=Activation('relu')(x)
x=GlobalAveragePooling2D()(x)
x =Dense(1000, activation='softmax')(x)
return x
def Xception(input,arg_list):
x=Entry_flow(input,arg_list)
x=Middle_flow(x)
x=Exit_flow(x)
return x
##用下面代码简单运行一下模型,验证其正确性
import numpy as np
inputs = np.zeros((1, 299, 299, 3), dtype=np.float32)
outputs = Xception(inputs,[128,256,728])
outputs.shape
这里的Xception没有用父类tensorflow.keras.Model封装起来,封装也挺简单,参考我之前写的其他模型就能封装,这里直接 def 函数建立模型。
keras深度可分离卷积SeparableConv2D与DepthwiseConv2D
Xception 网络结构的原理与 Tensorflow2.0 实现