深度可分离卷积
传统卷积是卷积核的channel = 输入feature map的channel数。传统卷积这样做可以提取到图像数据的空间信息和channel信息。如果想同时提取空间和通道信息且减小参数量,可以怎么做?这个时候就到深度可分离卷积出场了!
深度可分离卷积包括两个步骤:
1、 Depthwise Separable Convolutions:卷积核的数量与输入feature map的channel数一致 ,但每一个卷积核的channel都为1。各个卷积核只处理feature map二维的空间信息,而不处理跨通道的信息即单通道的卷积。如下图:
2、Pointwise Convolution:
前面的深度可分离卷积这样做只能提取到二维的空间信息,会丢失跨通道维度上的信息。这个时候可以再利用1x1的卷积核对Depthwise Separable Convolutions堆叠得到的feature map进行通道信息的处理,即卷积核的空间维度大小为1,channel维度与输入feature map的维度一致,专门提取跨channel维度的特征信息!这样就把这两种特征信息解耦了。如下图:
这样最终得到的feature map大小就可以达到传统卷积一致。
这种分步的卷积有什么好处呢?
最重要的一点是大大减少了参数量!
举个栗子:
假设输入的feature map的大小为 5x5x3,经过3x3的卷积层得到的feature map为3x3x128(忽略bias)
传统卷积参数量:3x3x3x128=3456
深度可分离卷积参数量:3x3x3 + 1x1x3x128 = 411
3456 / 3456 约等于8.4。这参数量差了8倍!
所以,这就是轻量化网络中广泛使用 Depthwise Separable Convolutions的原因!
SeparableConv Block
Xception的网络架构是由Inception_v3中的第一个Inception模块简化改进得来的。
如下图:
首先改pool层为1x1的卷积,去掉第三层。改为一层1x1卷积和一层3x3的卷积,进一步简化把第一层的1x1卷积改为一个,再简化,把1x1卷积的输出的feature map按通道均分喂给第二层3x3的卷积,各个branch的3x3卷积处理分配到channel数的feature map。进行极致的化简就是,把channel切分为1,用多个branch单独处理一个channel的feature map如下图:
参考B站up:同济子豪兄
网络整体架构
整体大模块分为三部分:Entry flow、Middle flow、Exit flow,且引入了残差连接。
Pytorch代码实现
Xception轻量化的关键就是定义深度可分离卷积,
沦文中是先进行pointwise convolution 再进行deepwise convolution
而**先进行pointwise convolution再进行deepwise convolution的方式更适用于需要更好特征提取能力的任务,而先进行deepwise convolution再进行pointwise convolution的方式更适用于需要减少参数数量和计算量的任务。**所以,这里实现就使用先进行deepwise convolution再进行pointwise convolution的方式!
SeparableConv
class SeperableConv2d(nn.Module):
def __init__(self,in_channel,out_channel,bias=True):
super(SeperableConv2d, self).__init__()
#深度可分离卷积
self.dconv = nn.Conv2d(in_channel,in_channel,3,1,1,dilation=1,
groups=in_channel,bias=bias)
self.pconv = nn.Conv2d(in_channel,out_channel,1,bias=bias)
def forward(self,x):
x = self.dconv(x)
x = self.pconv(x)
return x
ResidualConnection
残差连接
class ResidualConnection(nn.Module):
def __init__(self,in_channel,out_channel):
super(ResidualConnection, self).__init__()
self.conv1x1 = nn.Conv2d(in_channel,out_channel,1,stride=2)
self.bn = nn.BatchNorm2d(out_channel)
def forward(self,x):
x = self.conv1x1(x)
x = self.bn(x)
return x
OK,这里实现了小模块。接下来就是三大主要的模块。
Entry flow
注意:这里的第一个seperableconv之前是没有进行relu的
class entryflow_PoolBlock(nn.Module):
def __init__(self,in_channel,out_channel,relu1=True):
super(entryflow_PoolBlock, self).__init__()
self.relu1 = relu1
self.residual = ResidualConnection(in_channel, out_channel)
if relu1:
self.relu1 = nn.ReLU(inplace=True)
self.sepconv_b1 = SeperableConv2d(in_channel,out_channel)
self.bn1 = nn.BatchNorm2d(out_channel)
self.relu2 = nn.ReLU(out_channel)
self.sepconv_b2 = SeperableConv2d(out_channel,out_channel)
self.bn2 = nn.BatchNorm2d(out_channel)
self.maxpool = nn.MaxPool2d(3,stride=2,padding=1)
def forward(self,x):
identity = self.residual(x)
# 第1个Separable Conv前面没有ReLU
if self.relu1:
x = self.relu1(x)
x = self.sepconv_b1(x)
x = self.bn1(x)
x = self.relu2(x)
x = self.sepconv_b2(x)
x = self.bn2(x)
x = self.maxpool(x)
x = x + identity
return x
Middle flow
Middle flow比较简单,输入的空间、channel维度都不变
class middelfow_PoolBlock(nn.Module):
def __init__(self,inchannel):
super(middelfow_PoolBlock, self).__init__()
self.pipeline = nn.Sequential(
nn.ReLU(inplace=True),
SeperableConv2d(inchannel,inchannel,bias=False),
nn.BatchNorm2d(inchannel)
)
def forward(self,x):
x = self.pipeline(x)
x = self.pipeline(x)
x = self.pipeline(x)
return x
Exit flow
class exitflow_PoolBlock(nn.Module):
def __init__(self,in_channel,out_channel):
super(exitflow_PoolBlock, self).__init__()
self.identity = ResidualConnection(in_channel,out_channel)
self.relu1 = nn.ReLU(inplace=True)
self.sepconv1 = SeperableConv2d(in_channel,in_channel,bias=False)
self.bn1 = nn.BatchNorm2d(in_channel)
self.relu2 = nn.ReLU(inplace=True)
self.sepconv2 = SeperableConv2d(in_channel,out_channel,bias=False)
self.bn2 = nn.BatchNorm2d(out_channel)
self.maxpool = nn.MaxPool2d(3,2,1)
def forward(self,x):
identity = self.identity(x)
x = self.relu1(x)
x = self.sepconv1(x)
x = self.bn1(x)
x = self.relu2(x)
x = self.sepconv2(x)
x = self.bn2(x)
x = self.maxpool(x)
x = x + identity
return x
加上最后的几层输出写在主网络的定义,这样就差不多了
主网络Xception
class Xception(nn.Module):
def __init__(self,num_classes=5):
super(Xception, self).__init__()
#entry flow
self.ef_conv1 = nn.Sequential(
nn.Conv2d(3,32,3,2,1,bias=False),
nn.BatchNorm2d(32),
nn.ReLU(inplace=True)
)
self.ef_conv2 = nn.Sequential(
nn.Conv2d(32,64,3,1,1,bias=False),
nn.BatchNorm2d(64),
nn.ReLU(inplace=True)
)
self.entryflow_b1 = entryflow_PoolBlock(64,128,relu1=False)
self.entryflow_b2 = entryflow_PoolBlock(128,256)
self.entryflow_b3 = entryflow_PoolBlock(256,728)
#middle flow
self.middle_flow_8 = nn.ModuleList([middelfow_PoolBlock(inchannel=728)] * 8)
#exit flow
self.exitflow_conv1 = exitflow_PoolBlock(in_channel=728,out_channel=1024)
self.exitflow_conv2 = nn.Sequential(
SeperableConv2d(in_channel=1024,out_channel=1536,bias=False),
nn.ReLU(inplace=True),
SeperableConv2d(in_channel=1536,out_channel=2048,bias=False),
nn.ReLU(inplace=True)
)
self.avgpool = nn.AdaptiveAvgPool2d(1)
self.flatten = nn.Flatten()
self.fc = nn.Sequential(
nn.Linear(2048,num_classes),
nn.ReLU(inplace=True),
nn.Softmax(dim=1)
)
def forward(self,x):
x = self.ef_conv1(x)
x = self.ef_conv2(x)
x = self.entryflow_b1(x)
x = self.entryflow_b2(x)
x = self.entryflow_b3(x)
# Middle Flow
for block in self.middle_flow_8:
x = block(x)
x = self.exitflow_conv1(x)
x = self.exitflow_conv2(x)
x = self.avgpool(x)
x = self.flatten(x)
x = self.fc(x)
return x
总结
Xception结构的成功揭示了降低参数量也可以使得模型学习到不错的特征,揭示了深度可分离卷积的强大!Xception通过对空间信息和通道信息的充分解耦实现参数量的减少,且效果不会大幅减低!
x)
x = self.exitflow_conv2(x)
x = self.avgpool(x)
x = self.flatten(x)
x = self.fc(x)
return x
# 总结
Xception结构的成功揭示了降低参数量也可以使得模型学习到不错的特征,揭示了深度可分离卷积的强大!Xception通过对空间信息和通道信息的充分解耦实现参数量的减少,且效果不会大幅减低!