卷积神经网络在图像处理中的地位已然毋庸置疑。卷积运算具备强大的特征提取能力、相比全连接又消耗更少的参数,应用在图像这样的二维结构数据中有着先天优势。
1. 常规卷积运算
假设输入层为一个大小为64×64像素、三通道彩色图片。经过一个包含4个Filter的卷积层,最终输出4个Feature Map,且尺寸与输入层相同。整个过程可以用下图来概括。
input:$(N, C_{\text{in}}, H, W)$,output:$(N, C_{\text{out}}, H_{\text{out}}, W_{\text{out}})$
图片卷积输出大小计算公式
输入图像大小$(C_{in}, W_{in},H_{in})$,卷积核Filter大小:$(F,F)$,步长:$S$,填充$padding$:$P$:
参数量
假设一个卷积核的大小为$(K,K)$,输入的特征图为$(C_{in}, H, W)$,输入为$(C_{out}, H_{out},W_{out})$,不考虑偏置,不补0的卷积。参数量为(即卷积核个数*卷积核参数+偏置数):
计算量
MAC(Multiply Accumulate),需要考虑输出map的大小,1个MAC算两次操作
示例
- 输入维度:(6, 64, 64),卷积核:(3, 3),padding=1,stride=1;
- 输出shape:(3, 64, 64);
- 参数:$params = 6\times 3\times 3\times 3 = 162$
- 计算量:$flops=3\times 3\times 64\times 64\times 6\times 3=663552$
用Pytorch展示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26import torch
import torch.nn as nn
import torchsummary
import thop
class BaseNet(nn.Module):
def __init__(self, in_ch, out_ch, group=1):
super().__init__()
self.conv1 = nn.Conv2d(in_channels=in_ch, out_channels=out_ch,
groups=group, kernel_size=3, stride=1,
padding=1, bias=False)
def forward(self, x):
x = self.conv1(x)
return x
model = BaseNet(6, 3)
input_tensor = torch.rand(1,6,64,64)
input_size = tuple(input_tensor.shape[1:])
torchsummary.summary(model, input_size=input_size, batch_size=1)
print("thop:")
flops, params = thop.profile(model=model, inputs=(input_tensor,))
print(flops, params)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Conv2d-1 [1, 3, 64, 64] 162
================================================================
Total params: 162
Trainable params: 162
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.09
Forward/backward pass size (MB): 0.09
Params size (MB): 0.00
Estimated Total Size (MB): 0.19
----------------------------------------------------------------
thop:
[INFO] Register count_convNd() for <class 'torch.nn.modules.conv.Conv2d'>.
[WARN] Cannot find rule for <class '__main__.BaseNet'>. Treat it as zero Macs and zero Params.
663552.0 162.0
2. 分组卷积
分组卷积(Group Convolution):常规卷积的计算结果中,特征图的每个通道和输入特征图的所有通道都有关。下方左图是普通卷积的示意图,下放右图是分组卷积的示意图,差别就非常明显了。分组卷积的输出特征图的每个通道,只和输入特征图的一部分通道有关,而这部分通道,就是一个分组(Group)。
依旧假设输入特征图的尺寸为$C_{in} \times H \times W$,分为3组进行分组卷积,那么,对于每一组,输出特征图的通道数都是$C_{out}/3$,卷积核大小变为$C_{in} \times K \times K$,最后只需要将各个分组的计算结果按照通道进行连接(Cat)即可。
分组卷积可以很大程度上减少卷积所需的参数量,相同的输入输出特征图,分组卷积所需的参数量为:
即,分组卷积可将参数量减少为原来的$1/G$,$G$为分组数量。
分组卷积最早出现在AlexNet中,如下图所示。在CNN发展初期,GPU资源不足以满足训练任务的要求,因此,Hinton采用了多GPU训练的策略,每个GPU完成一部分卷积,最后把多个GPU的卷积结果进行融合。
代码实例
- 输入维度:(6, 64, 64),卷积核:(3, 3),padding=1,stride=1, group=3;
- 输出shape:(3, 64, 64);
- 参数:$params = (6\times 3\times 3\times 3) /3= 54$
- 计算量:$flops=(3\times 3\times 64\times 64\times 6\times 3)/3=221184$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class BaseNet(nn.Module):
def __init__(self, in_ch, out_ch, group=1):
super().__init__()
self.conv1 = nn.Conv2d(in_channels=in_ch, out_channels=out_ch,
groups=group, kernel_size=3, stride=1,
padding=1, bias=False)
def forward(self, x):
x = self.conv1(x)
return x
model = BaseNet(6, 3, 3)
input_tensor = torch.rand(1,6,64,64)
input_size = tuple(input_tensor.shape[1:])
torchsummary.summary(model, input_size=input_size, batch_size=1)
print("thop:")
flops, params = thop.profile(model=model, inputs=(input_tensor,))
print(flops, params)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Conv2d-1 [1, 3, 64, 64] 54
================================================================
Total params: 54
Trainable params: 54
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.09
Forward/backward pass size (MB): 0.09
Params size (MB): 0.00
Estimated Total Size (MB): 0.19
----------------------------------------------------------------
thop:
[INFO] Register count_convNd() for <class 'torch.nn.modules.conv.Conv2d'>.
[WARN] Cannot find rule for <class '__main__.BaseNet'>. Treat it as zero Macs and zero Params.
221184.0 54.0
3. 深度可分离卷积
深度可分离卷积(DepthwiseSeparable Convolution),在Google的Xception
以及MobileNet
论文中均有描述。它的核心思想是将一个完整的卷积运算分解为两步进行,分别为Depthwise Convolution
与Pointwise Convolution
。
Depthwise Convolution
当分组卷积(Group Convolution)的group等于输入map维度时,分组卷积就变成了depthwise卷积:
Pointwise Convolution
Pointwise Convolution的运算与常规卷积运算非常相似,它的卷积核的尺寸为1×1×M,M为上一层的通道数。所以这里的卷积运算会将上一步的map在深度方向上进行加权组合,生成新的Feature map。有几个卷积核就有几个输出Feature map。如下图所示:
代码实例
- 输入维度:(6, 64, 64),卷积核:(3, 3),padding=1,stride=1, group=3;
- 输出shape:(3, 64, 64);
- 参数:$params = (6\times 3\times 3)+(6\times3)= 72$
计算量:$flops=294912$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25class BaseNet_DWCONV_sp(nn.Module):
def __init__(self, in_ch, out_ch, group):
super().__init__()
# depthwise
self.dwconv = nn.Conv2d(in_channels=in_ch, out_channels=in_ch,
groups=in_ch, kernel_size=3,
stride=1, padding=1, bias=False)
# pointwise
self.poconv = nn.Conv2d(in_channels=in_ch, out_channels=out_ch,
kernel_size=1, stride=1, bias=False)
def forward(self, x):
x = self.dwconv(x)
x = self.poconv(x)
return x
model = BaseNet_DWCONV_sp(6, 3, 3)
input_tensor = torch.rand(1,6,64,64)
input_size = tuple(input_tensor.shape[1:])
torchsummary.summary(model, input_size=input_size, batch_size=1)
print("thop:")
flops, params = thop.profile(model=model, inputs=(input_tensor,))
print(flops, params)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Conv2d-1 [1, 6, 64, 64] 54
Conv2d-2 [1, 3, 64, 64] 18
================================================================
Total params: 72
Trainable params: 72
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.09
Forward/backward pass size (MB): 0.28
Params size (MB): 0.00
Estimated Total Size (MB): 0.38
----------------------------------------------------------------
thop:
[INFO] Register count_convNd() for <class 'torch.nn.modules.conv.Conv2d'>.
[WARN] Cannot find rule for <class '__main__.BaseNet_DWCONV_sp'>. Treat it as zero Macs and zero Params.
294912.0 72.0
4. 空洞卷积
这部分待完善…
空洞卷积(atrous convolutions)又名扩张卷积(dilated convolutions),向卷积层引入了一个称为 “扩张率(dilation rate)”的新参数,该参数定义了卷积核处理数据时各值的间距。
空洞卷积(Dilated/Atrous Convolution),广泛应用于语义分割与目标检测等任务中,语义分割中经典的deeplab系列与DUC对空洞卷积进行了深入的思考。目标检测中SSD与RFBNet,同样使用了空洞卷积。
在相同的计算条件下,空洞卷积提供了更大的感受野。空洞卷积经常用在实时图像分割中。当网络层需要较大的感受野,但计算资源有限而无法提高卷积核数量或大小时,可以考虑空洞卷积。
- 空洞卷积的作用
- 扩大感受野:在deep net中为了增加感受野且降低计算量,总要进行降采样(pooling或s2/conv),这样虽然可以增加感受野,但空间分辨率降低了。为了能不丢失分辨率,且仍然扩大感受野,可以使用空洞卷积。这在检测,分割任务中十分有用。一方面感受野大了可以检测分割大目标,另一方面分辨率高了可以精确定位目标。
- 捕获多尺度上下文信息:空洞卷积有一个参数可以设置dilation rate,具体含义就是在卷积核中填充dilation rate-1个0,因此,当设置不同dilation rate时,感受野就会不一样,也即获取了多尺度信息。多尺度信息在视觉任务中相当重要啊。
参考
Group Convolution分组卷积,以及Depthwise Convolution和Global Depthwise Convolution
卷积网络基础知识—-Depthwise Convolution && Pointwise Convolution && Separable Convolution