温故而知新,可以为师矣!
一、参考资料
详细且通俗讲解轻量级神经网络——MobileNets【V1、V2、V3】
深度学习中常用的几种卷积(下篇):膨胀卷积、可分离卷积(深度可分离、空间可分离)、分组卷积(附Pytorch测试代码)
二、深度可分离卷积(DSConv)的相关介绍
1. 标准卷积
标准卷积,利用若干个多通道卷积核对输入的多通道图像进行处理,输出的feature map既提取了通道特征,又提取了空间特征。
如下图所示,假设输入层为一个大小为64×64像素、3通道彩色图片。经过一个包含4个Filter的卷积层,最终输出4个Feature Map,且尺寸与输入层相同。此时,卷积层共4个Filter,每个Filter包含了3个Kernel,每个Kernel的大小为3×3。因此,卷积层的参数量为:
N
s
t
d
=
4
×
3
×
3
×
3
=
108
N_{std} = 4 × 3 × 3 × 3 = 108
Nstd=4×3×3×3=108。
用数学公式表达标准卷积,假设卷积核大小为 D K ∗ D K D_K*D_K DK∗DK,输入通道为M,输出通道为N,输出的特征图尺寸为 D F ∗ D F D_F*D_F DF∗DF,则通过标准卷积之后,可以计算:
参数量为: D K ∗ D K ∗ M ∗ N D_K*D_K*M*N DK∗DK∗M∗N;
计算量为: D K ∗ D K ∗ M ∗ N ∗ D F ∗ D F D_K*D_K*M*N*D_F*D_F DK∗DK∗M∗N∗DF∗DF。
2. 逐深度卷积(Depthwise Convolution)
简单理解,逐深度卷积就是深度(channel)维度不变,改变H/W。
逐深度卷积(Depthwise convolution,DWConv)与标准卷积的区别在于,深度卷积的卷积核为单通道模式,需要对输入的每一个通道进行卷积,这样就会得到和输入特征图通道数一致的输出特征图。即有输入特征图通道数=卷积核个数=输出特征图个数。
假设,一个大小为64×64像素、3通道彩色图片,3个单通道卷积核分别进行卷积计算,输出3个单通道的特征图。所以,一个3通道的图像经过运算后生成了3个Feature map,如下图所示。其中一个Filter只包含一个大小为3×3的Kernel,卷积部分的参数量为:
N
d
e
p
t
h
w
i
s
e
=
3
×
3
×
3
=
27
N_{depthwise} = 3 × 3 × 3 = 27
Ndepthwise=3×3×3=27。
再比如,输入12x12x3 的特征图,3个5x5x1的卷积核,经过逐深度卷积输出8x8x3的特征图。因此,卷积层的参数量为:
N
p
o
i
n
t
w
i
s
e
=
5
×
5
×
3
=
45
N_{pointwise} = 5 × 5 × 3 = 45
Npointwise=5×5×3=45。
3. 逐点卷积(Pointwise Convolution)
简单理解,逐点卷积就是W/H维度不变,改变channel。
根据深度卷积可知,输入特征图通道数=卷积核个数=输出特征图个数,这样会导致输出的特征图个数过少(或者说输出特征图的通道数过少,可看成是输出特征图个数为1,通道数为3),从而可能影响信息的有效性。此时,就需要进行逐点卷积。
逐点卷积(Pointwise Convolution,PWConv)实质上是用1x1的卷积核进行升维。在GoogleNet中大量使用1x1的卷积核,那里主要是用来降维。1x1的卷积核主要作用是对特征图进行升维和降维。
如下图所示,从深度卷积得到的3个单通道特征图,经过4个大小为1x1x3卷积核的卷积计算,输出4个特征图,而输出特征图的个数取决于Filter的个数。因此,卷积层的参数量为:
N
p
o
i
n
t
w
i
s
e
=
1
×
1
×
3
×
4
=
12
N_{pointwise} = 1 × 1 × 3 × 4 = 12
Npointwise=1×1×3×4=12。
再比如,经过逐深度卷积得到 8x8x3 的特征图,256个1x1x3的卷积核,经过逐点卷积输出8x8x256的特征图。因此,卷积层的参数量为:
N
p
o
i
n
t
w
i
s
e
=
1
×
1
×
3
×
256
=
768
N_{pointwise} = 1 × 1 × 3 × 256 = 768
Npointwise=1×1×3×256=768。
4. 深度可分离卷积(Depthwise Separable Convolution)
深度可分离卷积(Depthwise separable convolution, DSC)由逐深度卷积和逐点卷积组成,深度卷积用于提取空间特征,逐点卷积用于提取通道特征。深度可分离卷积在特征维度上分组卷积,对每个channel进行独立的逐深度卷积(depthwise convolution),并在输出前使用一个1x1卷积(pointwise convolution)将所有通道进行聚合。
深度可分离卷积,先对每个channel进行DWConv,然后再通过 PWConv合并所有channels为输出特征图,从而达到减小计算量、提升计算效率的目的。
4.1 参数量
逐深度卷积:深度卷积的卷积核尺寸 D K ∗ D K ∗ 1 D_K * D_K * 1 DK∗DK∗1,卷积核个数为M,所以参数量为: D K ∗ D K ∗ M D_K * D_K * M DK∗DK∗M。
逐点卷积:逐点卷积的卷积核尺寸为 1 ∗ 1 ∗ M 1 * 1 * M 1∗1∗M,卷积核个数为N,所以参数量为: M ∗ N M * N M∗N。
因此,深度可分离卷积的参数量为: D K ∗ D K ∗ M + M ∗ N D_K * D_K * M + M * N DK∗DK∗M+M∗N。
4.2 计算量
深度卷积:深度卷积的卷积核尺寸 D K ∗ D K ∗ 1 D_K * D_K * 1 DK∗DK∗1,卷积核个数为M,每个都要做 D F ∗ D F D_F * D_F DF∗DF次乘加运算,所以计算量为: D K ∗ D K ∗ M ∗ D F ∗ D F D_K * D_K * M * D_F * D_F DK∗DK∗M∗DF∗DF。
逐点卷积:逐点卷积的卷积核尺寸为 1 ∗ 1 ∗ M 1 * 1 * M 1∗1∗M,卷积核个数为N,每个都要做 D F ∗ D F D_F * D_F DF∗DF次乘加运算,所以计算量为: M ∗ N ∗ D F ∗ D F M*N * D_F * D_F M∗N∗DF∗DF。
因此,深度可分离卷积的计算量为: D K ∗ D K ∗ M ∗ D F ∗ D F + M ∗ N ∗ D F ∗ D F D_K * D_K * M*D_F * D_F + M*N*D_F * D_F DK∗DK∗M∗DF∗DF+M∗N∗DF∗DF。
4.3 与标准卷积对比
4.3.1 结构对比
深度可分离卷积的每个块构成:首先是一个3x3的深度卷积,其次是BN、Relu层,接下来是1x1的逐点卷积,最后又是BN和Relu层。
4.3.2 计算量和参数量对比
参数量比值: 深度可分离卷积 标准卷积 = D K × D K × M + M × N D K × D K × M × N = 1 N + 1 D K 2 \frac{深度可分离卷积}{标准卷积} = \frac{D_K\times D_K\times M+M\times N}{D_K\times D_K\times M\times N} = \frac{1}{N}+\frac{1}{{D_{K}}^{2}} 标准卷积深度可分离卷积=DK×DK×M×NDK×DK×M+M×N=N1+DK21;
计算量比值:
深度可分离卷积
标准卷积
=
D
K
×
D
K
×
M
×
D
F
×
D
F
+
M
×
N
×
D
F
×
D
F
D
K
×
D
K
×
M
×
N
×
D
F
×
D
F
=
1
N
+
1
D
K
2
\frac{深度可分离卷积}{标准卷积} = \frac{D_{K}\times D_{K}\times M\times D_{F}\times D_{F}+M\times N\times D_{F}\times D_{F}}{D_{K}\times D_{K}\times M\times N\times D_{F}\times D_{F}} = \frac{1}{N}+\frac{1}{{D_{K}}^{2}}
标准卷积深度可分离卷积=DK×DK×M×N×DF×DFDK×DK×M×DF×DF+M×N×DF×DF=N1+DK21。
一般的,N较大, 1 N \frac{1}{N} N1 可忽略不计, D K D_K DK 表示卷积核的大小,若 D K = 3 D_K =3 DK=3, 1 D k 2 = 1 9 \frac{1}{D_k^2}=\frac{1}{9} Dk21=91 。也就是说,如果我们使用常见的3×3的卷积核,那么使用深度可分离卷积的参数量和计算量下降到原来的九分之一左右。
4.4 深度可分离卷积的优势
相比于传统的卷积神经网络,深度可分离卷积的显著优势在于:
- 更少的参数:可减少输入通道数量,从而有效地减少卷积层所需的参数。
- 更快的速度:运行速度比传统卷积快。
- 更加易于移植:计算量更小,更易于实现和部署在不同的平台上。
- 更加精简:能够精简计算模型,从而在较小的设备上实现高精度的运算。
5. (PyTorch)代码实现
torch.nn.Conv2d
可分离卷积(Separable convolution)详解
5.1 函数原型
torch.nn.Conv2d(in_channels,
out_channels,
kernel_size,
stride=1,
padding=0,
dilation=1,
groups=1,
bias=True,
padding_mode='zeros',
device=None,
dtype=None)
参数 groups
用来表示卷积的分组,in_channels
和 out_channels
都要被 groups
整除。当groups
设置不同时,可以区分出分组卷积或深度可分离卷积:
- 当
groups=1
时,表示标准卷积; - 当
groups<in_channels
时,表示普通的分组卷积。例如,当group=2
时,该分组卷积有两组并列的卷积,每组看到一半的输入通道,并产生一半的输出通道,最后将两组的结果连接起来。 - 当
groups=in_channels
时,表示深度可分离卷积,每个通道都有一组自己的滤波器。
5.2 标准卷积
import torch.nn as nn
import torch
from torchsummary import summary
class Conv_test(nn.Module):
def __init__(self, in_ch, out_ch, kernel_size, padding, groups):
super(Conv_test, self).__init__()
self.conv = nn.Conv2d(
in_channels=in_ch,
out_channels=out_ch,
kernel_size=kernel_size,
stride=(1, 1),
padding=padding,
groups=groups,
bias=False
)
def forward(self, input):
out = self.conv(input)
return out
# 标准卷积,(3, 64, 64) -> (4, 64, 64)
# 参数量: in_ch * (k*k) * out_ch,则3x(3x3)x4 = 108
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
conv = Conv_test(3, 4, 3, 1, 1).to(device)
print(summary(conv, input_size=(3, 64, 64)))
输出结果
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Conv2d-1 [-1, 4, 64, 64] 108
================================================================
Total params: 108
Trainable params: 108
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.05
Forward/backward pass size (MB): 0.12
Params size (MB): 0.00
Estimated Total Size (MB): 0.17
----------------------------------------------------------------
None
5.3 分组卷积
# 分组卷积层,(4, 64, 64) -> (6, 64, 64)
# 参数量: groups * (in_ch//groups) * (k*k) * (out_ch//groups),则2x2x(3x3)x3 = 108
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
conv = Conv_test(4, 6, 3, padding=1, groups=2).to(device)
print(summary(conv, input_size=(4, 64, 64)))
输出结果
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Conv2d-1 [-1, 6, 64, 64] 108
================================================================
Total params: 108
Trainable params: 108
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.06
Forward/backward pass size (MB): 0.19
Params size (MB): 0.00
Estimated Total Size (MB): 0.25
----------------------------------------------------------------
None
Process finished with exit code 0
5.4 逐深度卷积
# 逐深度卷积,(3, 64, 64) -> (3, 64, 64)
# 参数量:1x(3x3)x3=27
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
conv = Conv_test(3, 3, 3, padding=1, groups=3).to(device)
print(summary(conv, input_size=(3, 64, 64)))
输出结果
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Conv2d-1 [-1, 3, 64, 64] 27
================================================================
Total params: 27
Trainable params: 27
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.05
Forward/backward pass size (MB): 0.09
Params size (MB): 0.00
Estimated Total Size (MB): 0.14
----------------------------------------------------------------
None
Process finished with exit code 0
5.5 逐点卷积
# 逐点卷积,输入即逐深度卷积的输出大小,目标输出也是4个feature map (3, 64, 64) -> (4, 64, 64)
# 参数量: 3x(1x1)x4=12
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
conv = Conv_test(3, 4, kernel_size=1, padding=0, groups=1).to(device)
print(summary(conv, input_size=(3, 64, 64)))
输出结果
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Conv2d-1 [-1, 4, 64, 64] 12
================================================================
Total params: 12
Trainable params: 12
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.05
Forward/backward pass size (MB): 0.12
Params size (MB): 0.00
Estimated Total Size (MB): 0.17
----------------------------------------------------------------
None
Process finished with exit code 0
5.6 深度可分离卷积
import torch.nn as nn
class myModel(nn.Module):
def __init__(self):
super(myModel, self).__init__()
self.dwconv = nn.Sequential(
nn.Conv2d(3, 3, kernel_size=3, stride=2, padding=1, groups=3, bias=False),
nn.BatchNorm2d(3),
nn.ReLU(inplace=True),
nn.Conv2d(3, 9, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(9),
nn.ReLU(inplace=True),
)
5.7 yolo模型中的深度可分离卷积
yolo模型使用深度可分离卷积的代码如下:
import torch.nn as nn
class DWConv(nn.Module):
"""Depthwise Conv + Conv"""
def __init__(self, in_channels, out_channels, ksize, stride=1, act="silu"):
super().__init__()
self.dconv = BaseConv(
in_channels, in_channels, ksize=ksize,
stride=stride, groups=in_channels, act=act
)
self.pconv = BaseConv(
in_channels, out_channels, ksize=1,
stride=1, groups=1, act=act
)
def forward(self, x):
x = self.dconv(x)
return self.pconv(x)