本文介绍 深度可分离卷积的具体理解:MobileNetV2设计灵感

深度可分离卷积的具体理解:MobileNetV2设计灵感

This article was original written by Jin Tian, welcome re-post, first come with https://jinfagang.github.io . but please keep this copyright info, thanks, any question could be asked via wechat: jintianiloveu

Depthwise separable convolution

在对比不同网络的结构的时候,很多网络它背后的设计思想却很容易被人忽视。其中MobileNet中使用的深度可分离的卷积结构就比较难以理解。

简单来说,就是,为什么这种结构被人认为是可以减少计算量的?它和直接的卷积结构有和区别?需要为它自定义单独的层去实现吗?

对比两个例子:

假设有一个3x3大小的卷积层,输入通道是16, 输出通道是32,那么既然输出通道是32, 就可以使用32个3x3的卷积核去遍历输入,这里面要用到的参数是 16x32x3x3=4608 个

上面是通常的卷积运算方式。但是如果将里面的操作分为两部进行,也同时达到需要的输出维度可以这么做:

先用 16个 3x3 的卷积核遍历16个通道里面的数据,得到16个特征图谱,在用32个1x1的卷积核去遍历这16个特征图谱,进行相加融合,这个过程使用的参数是 16x3x3 + 16x32x1x1 = 656 个参数。

同样是使用的通道变化了,为什么后面这个操作所用到的参数更少了呢??第一种方式也就是常规方法是直接一步到位,用输出channel以及对应的卷积核去操作,那结果自然也是一步到位。而第二种方法很巧妙的使用了两步计算,第一步先把数据遍历一遍,尺寸变换一下; 接着第二部用了1x1卷积的特性,不改变形状之改变通道,从而通道也变化了。

这样的方式,便是将传统的卷积分为了两步计算,但是时间是原来的1/7,同时参数是原来的1/9. 可以说是非常成功的trick了。

看看使用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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class InvertedResidual(nn.Module):

    def __init__(self, x, output, stride, expand_ratio):
        """
        this is the core of MobileNet, something just like ResNet
        but not very much alike.

        it is called Inverted Residual, the opposite residual operation,
        how does it operation anyway?

        Only when stride == 1 && input == output, using residual connect
        other wise normal convolution

        what does this expand_ratio for? this value is the middle expand ratio when you transfer
        input channel to output channel ( you will get a middle value right? so there it is)

        :param x:
        :param output:
        :param stride:
        :param expand_ratio:
        """
        super(InvertedResidual, self).__init__()
        self.stride = stride
        assert stride in [1, 2], 'InsertedResidual stride must be 1 or 2, can not be changed'
        self.user_res_connect = self.stride == 1 and x == output

        # this convolution is the what we called Depth wise separable convolution
        # consist of pw and dw process, which is transfer channel and transfer shape in 2 steps
        self.conv = nn.Sequential(
            # pw
            nn.Conv2d(x, x * expand_ratio, 1, 1, 0, bias=False),
            nn.BatchNorm2d(x * expand_ratio),
            nn.ReLU6(inplace=True),
            # dw
            nn.Conv2d(x * expand_ratio, x * expand_ratio, 3, stride, 1, groups=x*expand_ratio, bias=False),
            nn.BatchNorm2d(x*expand_ratio),
            nn.ReLU6(inplace=True),
            # pw linear
            nn.Conv2d(x*expand_ratio, output, 1, 1, 0, bias=False),
            nn.BatchNorm2d(output),
        )

    def forward(self, x):
        if self.user_res_connect:
            return x + self.conv(x)
        else:
            return self.conv(x)

其实十分简单。