天天看點

可分離卷積

先看一下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
           

繼續閱讀