先看一下nn.Conv2d()代碼:
輸入:(N,C_in,H_in,W_in)
輸出:(N,C_out,H_out,W_out)
dilation:空洞卷積率,控制卷積膨脹間隔
groups:分組卷積來控制輸入和輸出的連接配接方式,in_channels和out_channels都要被groups整除。
groups設定不同時,可區分為分組卷積或深度可分離卷積:
- 當groups=1時,表示普通卷積
- 當groups<in_channels時,表示普通的分組卷積
- 當groups=in_channels時,表示深度可分離卷積,每個通道都有一組自己的濾波器,大小為:out_channels/in_channels
可分離卷積
可分離卷積:空間可分離卷積和深度可分離卷積
假設feature的size為[channel,height,width]
- 空間也就是指:[height,width]這兩個次元組成的
- 深度也就是指:channel這一次元
1、空間可分離卷積
空間可分離卷積:将n×n的卷積分成1×n和n×1兩步計算
- 普通的3×3卷積在一個5×5的feature map上的計算方式如下圖,需9×9次乘法。
- 空間可分離卷積計算方式如下圖,(1)先使用3×1的filter,所需計算量為15×3;(2)使用過1×3的filter,所需計算量為9×3;總共需要72次,小于普通卷積的81次乘法。
2、深度可分離卷積
-
Conventional Convolution
假設輸入層為64×64像素的RGB圖像。
正常卷積:每個卷積核是同時操作輸入圖檔的每個通道。
卷積層的參數數量為4×3×3×3
-
Depthwise Convolution
Depthwise Convolution:一個卷積核負責一個通道,卷積核的數量與上一層的通道數相同(通道和卷積核一一對應)。
卷積層參數數量為3×3×3
缺點:
(1)無法擴充Feature map的通道數。
(2)未有效利用不同通道在相同空間位置上的feature資訊。
-
Pointwise Convolution
Pointwise Convolution:與正常卷積運算相似,但卷積核尺寸為 1×1×M,M為上一層的通道數。
将上一步的map在深度方向上進行權重組合,生成新的Feature map。有幾個卷積核就有幾個輸出Feature map。
卷積層參數數量為1×1×3×4
綜上,我們比對一下:正常卷積的參數個數為108;深度可分離卷積的參數個數為39,參數個數是正常卷積的約1/3。 下面我們用代碼來驗證一下!
代碼測試
1、普通卷積、深度可分離卷積對比:
-
整體對比:
假設存在這樣一個場景,上一層有一個64×64大小,3通道的特征圖,需要經過卷積操作,輸出4個通道的特征圖,并且要求尺寸不改變。我們可以對比一下采用正常卷積和深度可分離卷積參數量各是多少。
import torch.nn as nn
from torchsummary import summary
class normal_conv(nn.Module):
def __init__(self, in_channels, out_channels):
super(normal_conv, self).__init__()
self.conv = nn.Conv2d(in_channels,
out_channels,
kernel_size=3,
stride=1,
padding=1,
bias=True)
def forward(self, x):
return self.conv(x)
class sep_conv(nn.Module):
def __init__(self, in_channels, out_channels):
super(sep_conv, self).__init__()
self.deepthwise_conv = nn.Conv2d(in_channels,
in_channels,
kernel_size=3,
stride=1,
padding=1,
bias=True,
groups=in_channels)
self.pointwise_conv = nn.Conv2d(in_channels,
out_channels,
kernel_size=1,
stride=1,
padding=0,
bias=True,
groups=1)
def forward(self,x):
d = self.deepthwise_conv(x)
p = self.pointwise_conv(d)
return p
input_size = (3,64,64)
conv1 = normal_conv(3,4)
conv2 = sep_conv(3,4)
print("使用正常卷積所需要的參數:")
print(summary(conv1,input_size,batch_size=1))
print("使用深度可分離卷積所需要的參數:")
print(summary(conv2,input_size,batch_size=1))
輸出結果:
使用正常卷積所需要的參數:
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Conv2d-1 [1, 4, 64, 64] 112
================================================================
Total params: 112
Trainable params: 112
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
使用深度可分離卷積所需要的參數:
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Conv2d-1 [1, 3, 64, 64] 30
Conv2d-2 [1, 4, 64, 64] 16
================================================================
Total params: 46
Trainable params: 46
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.05
Forward/backward pass size (MB): 0.22
Params size (MB): 0.00
Estimated Total Size (MB): 0.27
----------------------------------------------------------------
None
- 各部分對比:
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,
padding=padding,
groups=groups,
bias=False
)
def forward(self, input):
out = self.conv(input)
return out
#标準的卷積層,輸入的是3x64x64,目标輸出4個feature map
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
# 逐深度卷積層,輸入同上
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
# 逐點卷積層,輸入即逐深度卷積的輸出大小,目标輸出也是4個feature map
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
2、分組卷積、深度可分離卷積對比:
- 普通卷積:總參數量是 4x8x3x3=288。
-
分組卷積:假設輸入層為一個大小為64×64像素的彩色圖檔、in_channels=4,out_channels=8,經過2組卷積層,最終輸出8個Feature
Map,我們可以計算出卷積層的參數數量是 2x8x3x3=144。
- 深度可分離卷積:逐深度卷積的卷積數量是 4x3x3=36, 逐點卷積卷積數量是 1x1x4x8=32,總參數量為68。
#普通卷積層
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
conv = Conv_test(4, 8, 3, padding=1, groups=1).to(device)
print(summary(conv, input_size=(4, 64, 64)))
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Conv2d-1 [-1, 8, 64, 64] 288
================================================================
Total params: 288
Trainable params: 288
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.06
Forward/backward pass size (MB): 0.25
Params size (MB): 0.00
Estimated Total Size (MB): 0.31
----------------------------------------------------------------
None
# 分組卷積層,輸入的是4x64x64,目标輸出8個feature map
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
conv = Conv_test(4, 8, 3, padding=1, groups=2).to(device)
print(summary(conv, input_size=(4, 64, 64)))
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Conv2d-1 [-1, 8, 64, 64] 144
================================================================
Total params: 144
Trainable params: 144
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.06
Forward/backward pass size (MB): 0.25
Params size (MB): 0.00
Estimated Total Size (MB): 0.31
----------------------------------------------------------------
None
# 逐深度卷積層,輸入同上
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
conv = Conv_test(4, 4, 3, padding=1, groups=4).to(device)
print(summary(conv, input_size=(4, 64, 64)))
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Conv2d-1 [-1, 4, 64, 64] 36
================================================================
Total params: 36
Trainable params: 36
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.06
Forward/backward pass size (MB): 0.12
Params size (MB): 0.00
Estimated Total Size (MB): 0.19
----------------------------------------------------------------
None
# 逐點卷積層,輸入即逐深度卷積的輸出大小,目标輸出也是8個feature map
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
conv = Conv_test(4, 8, kernel_size=1, padding=0, groups=1).to(device)
print(summary(conv, input_size=(4, 64, 64)))
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Conv2d-1 [-1, 8, 64, 64] 32
================================================================
Total params: 32
Trainable params: 32
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.06
Forward/backward pass size (MB): 0.25
Params size (MB): 0.00
Estimated Total Size (MB): 0.31
----------------------------------------------------------------
None