1. 摘要🎄
本文介绍了深度可分离卷积和空间可分离卷积的概念及其在计算复杂性和效率上的优势。深度可分离卷积通过通道内卷积和1x1卷积减少参数量,而空间可分离卷积将3x3卷积分解为3x1和1x3卷积以降低计算复杂性。代码示例展示了这两种卷积在实际网络结构中的应用,并通过比较计算量表明深度可分离卷积具有更高的效率。
2. 定义🎄
空间可分离卷积:空间可分离卷积是一种将传统卷积操作分解为两个更小操作的方法,通常分为逐行卷积和逐列卷积两个步骤。首先,使用一维卷积核对输入特征图的每一行进行卷积,生成中间特征图;然后,对中间特征图的每一列进行卷积,得到最终输出特征图。这种卷积方式可以减少计算量和参数数量,但并非所有的卷积核都适合进行空间上的分离,因此在深度学习中的应用不如深度可分离卷积广泛。 最常见的情况是将3x3的卷积核划分为3x1和1x3的卷积核,如下所示:
现在,我们不是用9次乘法进行一次卷积,而是进行两次卷积,每次3次乘法(总共6次),以达到相同的效果。 乘法较少,计算复杂性下降,网络运行速度更快。
import torch
import torch.nn as nn
class SpatiallySeparableConv2d(nn.Module):
def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0):
super(SpatiallySeparableConv2d, self).__init__()
# 检查卷积核大小是否为奇数
assert kernel_size % 2 == 1, "Kernel size must be odd for spatially separable convolution"
self.depthwise_row_conv = nn.Conv2d(in_channels, in_channels, kernel_size=(kernel_size, 1),
stride=(stride, 1), padding=((padding//2), 0), groups=in_channels)
self.depthwise_col_conv = nn.Conv2d(in_channels, out_channels, kernel_size=(1, kernel_size),
stride=(1, stride), padding=(0, (padding//2)), groups=in_channels)
def forward(self, x):
x = self.depthwise_row_conv(x)
x = self.depthwise_col_conv(x)
return x
# 示例使用
input_tensor = torch.randn(1, 3, 32, 32) # (batch_size, channels, height, width)
conv = SpatiallySeparableConv2d(in_channels=3, out_channels=8, kernel_size=3, stride=1, padding=1)
output = conv(input_tensor)
print(output.shape) # 输出的形状
深度可分离卷积:深度可分离卷积(Depthwise Separable Convolution)是一种高效的卷积操作,广泛应用于轻量级神经网络中,如MobileNet和Xception。它将标准卷积操作分解为两个较小的操作:深度卷积(Depthwise Convolution)和逐点卷积(Pointwise Convolution)。
深度卷积:在这一步中,每个输入通道独立地应用一个卷积核,这样只捕获空间特征而不混合通道信息。
逐点卷积:此步骤使用1x1的卷积核对深度卷积的输出进行卷积,目的是组合来自不同通道的特征。
这种分解方法显著减少了计算量和模型参数,同时保持了网络的性能。深度可分离卷积特别适合于计算资源有限的环境,如移动设备和嵌入式系统,深度可分离卷积步骤如下图所示。
import torch
import torch.nn as nn
class DepthwiseSeparableConv2d(nn.Module):
def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0):
super(DepthwiseSeparableConv2d, self).__init__()
self.depthwise = nn.Conv2d(
in_channels, in_channels,
kernel_size=kernel_size,
stride=stride,
padding=padding,
groups=in_channels
)
self.pointwise = nn.Conv2d(
in_channels, out_channels,
kernel_size=1,
stride=1,
padding=0
)
def forward(self, x):
x = self.depthwise(x)
x = self.pointwise(x)
return x
# 示例使用
input_tensor = torch.randn(1, 3, 32, 32) # (batch_size, channels, height, width)
dconv = DepthwiseSeparableConv2d(in_channels=3, out_channels=8, kernel_size=3, stride=1, padding=1)
output = dconv(input_tensor)
print(output.shape) # 输出的形状
3. 具体代码分析🎄
import torch.nn as nn
from Module.activation import act_layers
class ConvBNReLU(nn.Sequential):
def __init__(self,i,o,k,s,p,dilation=(1,1),groups=1,bias=False,activation='ReLU'):
super(ConvBNReLU, self).__init__()
self.CBA = nn.Sequential(
nn.Conv2d(i,o,kernel_size=k,stride=s,padding=p,dilation=dilation,groups=groups,bias=bias),
nn.BatchNorm2d(o),
act_layers(activation)
)
def forward(self, input):
x = self.CBA(input)
return x
class Param(nn.Module):
def __init__(self,activation='ReLU'):
super(Param, self).__init__()
self.conv1 = nn.Sequential(
nn.Conv2d(3, 24, kernel_size=3, stride=2, padding=1, bias=False),
nn.BatchNorm2d(24),
act_layers(activation)
)
# 普通卷积
self.conv2 = nn.Sequential(
nn.Conv2d(24, 24, kernel_size=3, stride=1, padding=1, bias=False),
nn.BatchNorm2d(24),
act_layers(activation)
)
# 深度可分离
self.conv3 = nn.Sequential(
self.depthwise_conv(24, 24, kernel_s=3, stride=1, padding=1),
nn.Conv2d(24, 24, 1),
nn.BatchNorm2d(24),
act_layers(activation)
)
# 空间可分离
self.D3x1_D1x3 = nn.Sequential(
ConvBNReLU(24,24,(3,1),1,p=(1,0),dilation=(1,1),groups=1),
ConvBNReLU(24,24,(1,3),1,p=(0,1),dilation=(1,1),groups=1)
)
@staticmethod
def depthwise_conv(input_c: int,
output_c: int,
kernel_s: int,
stride: int = 1,
padding: int = 0,
bias: bool = False) -> nn.Conv2d:
return nn.Conv2d(in_channels=input_c, out_channels=output_c, kernel_size=kernel_s,
stride=stride, padding=padding, bias=bias, groups=input_c)
def forward(self, x):
x = self.conv1(x)
# 普通卷积 258.55M 5.93k
x = self.conv2(x)
# 深度可分离 69.57M 1.56K
x = self.conv3(x)
# 空间可分离 186.9M 4.25K
x = self.D3x1_D1x3(x)
return x
很明显深度可分离卷积参数量和计算复杂度都最小。