1. 程式人生 > 實用技巧 >關於深度可分離卷積的理解

關於深度可分離卷積的理解

常規卷積

常規卷積中,連線的上一層一般具有多個通道(這裡假設為n個通道),因此在做卷積時,一個濾波器(filter)必須具有n個卷積核(kernel)來與之對應。一個濾波器完成一次卷積,實際上是多個卷積核與上一層對應通道的特徵圖進行卷積後,再進行相加,從而輸出下一層的一個通道特徵圖。在下一層中,若需要得到多個通道的特徵圖(這裡假設為m個通道),那麼對應的濾波器就需要m個。

用通俗的話來概括卷積,他起到的作用就是兩個:一個是對上一層的特徵圖進行尺寸調整,另一個是則是對上一層的特徵圖數量進行調整,也就是通道數的調整。

這裡不理解的可以看吳恩達有關三維卷積的講解視訊:

深度可分離卷積

深度可分離卷積,其實只對常規卷積做了一個很小的改動,但是帶來的確實引數量的下降,這無疑為網路的輕量化帶來了好處。

對於來自上一層的多通道特徵圖,首先將其全部拆分為單個通道的特徵圖,分別對他們進行單通道卷積,然後重新堆疊到一起。這被稱之為逐通道卷積(Deepthwise Convolution)。這個拆分的動作十分關鍵,在這一步裡,它只對來自上一層的特徵圖做了尺寸的調整,而通道數沒有發生變化。於是將前面得到的特徵圖進行第二次卷積,這是採取的卷積核都是1×1大小的,濾波器包含了與上一層通道數一樣數量的卷積核。一個濾波器輸出一張特徵圖,因此多個通道,則需要多個濾波器。這又被稱之為逐點卷積(Pointwise Convolution)。

引數量對比

假設存在這樣一個場景,上一層有一個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

可以看到,引數由112下降到了46,通道越多這種效果越明顯。

參考文獻