MobileNet_v2个人理解

MobileNet_v2:《Inverted Residuals and Linear Bottlenecks: Mobile Networks for Classification, Detection and Segmentation》

下载地址:https://arxiv.org/abs/1801.04381

目录

论文要解决什么问题?

用什么方法解决的(创新点)?

Inverted Residuals

Linear Bottlenecks

实验效果怎么样?

代码部分

参考博客


论文要解决什么问题?

在MobileNet_v2论文中指出,实验中发现DW卷积中有大量的卷积核为0,即有许多卷积核未参与实际运算。最后发现是ReLU非线性激活函数惹的祸。

MobileNet_v2个人理解_第1张图片

具体请参考下面的Linear Bottlenecks。

 

用什么方法解决的(创新点)?

MobileNetV2 中的创新点:

  • Inverted Residuals
  • Linear Bottlenecks

Inverted Residuals

下图的a为残差网络中标准的Residual block,b为本文的一创新点---Inverted residual block。

Residual block先通过1×1的Conv进行降维,再用3×3的Conv保持通道不变,最后再用一层1×1的Conv进行升维(升维后的维度和初始进入该block的维度保持一致),执行addition操作,每个卷积后面都使用了ReLU激活函数。

Inverted residual block先用1×1的Conv进行升维,激活函数使用ReLU6。再进行3×3的DW卷积(分步长为1和2),维度不变,激活函数还是使用ReLU6。最后再使用1×1的Conv进行降维,激活函数使用线性激活,不再使用ReLU,因为论文表述从高维向低维转换,使用ReLU非线性激活函数可能会造成信息丢失或破坏。

MobileNet_v2个人理解_第2张图片

 举个例子,假设输入到Inverted Residual的输入为56×56×24,则先经过1×1的Conv进行升维,扩展因子 \large t 为6,维度变为144。然后经过3×3的DW卷积,维度不变。最后再使用1×1的Conv进行降维,维度变为24.

MobileNet_v2个人理解_第3张图片

Inverted Residual block详细构造图如下(\large s=1 才有shortcut分支):

MobileNet_v2个人理解_第4张图片

 下表是论文中给出的倒残差基本结构,\large t 为维度扩展因子。在3×3 的DW卷积中,\large s控制步长,通过\large s = 2来实现下采样。\large k' 为输出通道数,即论文中的c。

MobileNet_v2个人理解_第5张图片

 另外,需注意的一个点是\large s=1 且输入特征矩阵的channels和输出特征矩阵的channels相等才会有shortccut分支

MobileNet_v2个人理解_第6张图片

Linear Bottlenecks

论文中称网络层中的激活特征为兴趣流形(mainfold of interest),如果当前激活空间内兴趣流形完整度较高,经过ReLU,可能会让激活空间坍塌,不可避免的会丢失信息。

作者的思路是对一个n维空间的一个特征信息做ReLU运算,然后对比ReLU之后的结果与Input的结果相差有多大。

如上图所示,Input是一个2维数据,其中兴趣流形是其中的蓝色螺旋线。本例使用矩阵 \large T 将数据嵌入到n维空间中,后接ReLU,再使用 \large T^{-1} 将其投影回2D平面。可以看到设置n=2,3时信息丢失严重,中心点坍塌掉了。当n=15~30之间,恢复的信息明显多了。这也说明了channels少的feature map后面不应该接ReLU,否则会破坏feature map,信息丢失严重 ,而channels多(>15)的feature map可以保留近乎完整的信息。

针对这个问题,既然是ReLU导致的信息损耗,就将ReLU替换成线性激活函数。

 

输入为224×224×3的MobileNetV2网络结构
Input(输入尺寸) 操作 深度扩张倍数 t 输出通道数 c 倒残差重复次数 n 第一层bottleneck的步长 s
224×224×3 conv2d - 32 1 2
112×112×32 bottleneck 1 16 1 1
112×112×16 bottleneck 6 24 2 2
56×56×24 bottleneck 6 32 3 2
28×28×32 bottleneck 6 64 4 2
14×14×64 bottleneck 6 96 3 1
14×14×96 bottleneck 6 160 3 2
7×7×160 bottleneck 6 320 1 1
7×7×320 conv2d 1×1 - 1280 1 1
7×7×1280 avgpool 7×7 - - 1 -
1×1×1280 conv2d 1×1 - k -  

 

实验效果怎么样?

  • ImageNet Classification

在ImageNet数据集对比了MobileNetV1、ShuffleNet、MobileNetV2 三个模型的Top1精度、Params和CPU(Google Pixel 1 phone)执行时间。MobileNetV2 运行时间149ms,参数6.9M,Top1精度74.7。 在ImageNet数据集,依 top-1而论,比ResNet-34,VGG19精度高,比ResNet-50精度低。

MobileNet_v2个人理解_第7张图片

  • Object Detection

论文以MobileNetV2为基本分类网络,实现MNet V2 + SSDLite,耗时200ms,mAP 22.1,参数只有4.3M,相比之下YOLOv2 mAP 21.6,参数50.7M。模型的精度比SSD300和SSD512略低。

MobileNet_v2个人理解_第8张图片

  • Semantic Segmentation

Mobilenet v2那个时候,Semantic Segmentation性能最高的架构是DeepLabv3,论文在MobileNetV2基础上实现DeepLabv3,同时与基于ResNet-101的架构做对比,实验效果显示MNet V2 mIOU 75.32,参数2.11M,而ResNet-101 mIOU80.49,参数58.16M,明显MNet V2 在实时性方面具有优势,具体细节还请参考论文。

MobileNet_v2个人理解_第9张图片

 

代码部分

定义卷积块:

def conv_bn(inp, oup, s):
    # 传统的3*3卷积 CBR
    return nn.Sequential(
        nn.Conv2d(inp, oup, kernel_size=3, stride=s, padding=1, bias=False),
        nn.BatchNorm2d(oup),
        nn.ReLU6(inplace=True)
    )


def conv_1x1_bn(inp, oup):
    # 传统的1*1卷积 CBR
    return nn.Sequential(
        nn.Conv2d(inp, oup, kernel_size=1, stride=1, padding=0, bias=False),
        nn.BatchNorm2d(oup),
        nn.ReLU6(inplace=True)
    )

 定义倒残差结构:

class InvertedResidual(nn.Module):

    def __init__(self, inp, oup, s, expand_ratio):
        '''
        :param inp: 输入通道
        :param oup: 输出通道
        :param s: 步长
        :param expand_ratio: 进入3*3dw卷积之前,对输入通道进行扩展的倍数,就是下面的t
        '''
        super(InvertedResidual, self).__init__()

        self.stride = s
        assert s in [1, 2]

        # round(x):返回浮点数x的四舍五入值
        hidden_dim = round(inp * expand_ratio)
        # shortcut分支存在条件:s=1 且 输入特征矩阵和输出特征矩阵的channels相等
        self.use_res_connect = self.stride == 1 and inp == oup

        if expand_ratio == 1:
            self.conv = nn.Sequential(
                # dw
                nn.Conv2d(hidden_dim, hidden_dim, kernel_size=3, stride=s, padding=1, groups=hidden_dim, bias=False),
                nn.BatchNorm2d(hidden_dim),
                nn.ReLU6(inplace=True),
                # pw
                nn.Conv2d(hidden_dim, oup, kernel_size=1, stride=1, padding=0, bias=False),
                nn.BatchNorm2d(oup),
            )
        else:
            self.conv = nn.Sequential(
                # pw
                nn.Conv2d(inp, hidden_dim, 1, 1, 0, bias=False),
                nn.BatchNorm2d(hidden_dim),
                nn.ReLU6(inplace=True),
                # dw
                nn.Conv2d(hidden_dim, hidden_dim, 3, s, 1, groups=hidden_dim, bias=False),
                nn.BatchNorm2d(hidden_dim),
                nn.ReLU6(inplace=True),
                # pw-linear
                nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
                nn.BatchNorm2d(oup),
            )

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

定义Mobilenet_v2网络结构:

class MobileNetV2(nn.Module):
    def __init__(self, num_classes=1000, input_size=224, width_mult=1., dropout_ratio=0.2):
        super(MobileNetV2, self).__init__()

        input_channel = 32
        last_channel = 1280
        '''
            t:第一层1*1 conv2d的卷积核深度的扩展倍数,如h*w*k --> h*w*tk
            c:输出特征矩阵的深度
            n:倒残差结构重复次数
            s:第一层bottleneck的步长,其他层为1
        '''
        interverted_residual_setting = [

            # t,  c, n, s
            [1, 16, 1, 1],
            [6, 24, 2, 2],
            [6, 32, 3, 2],
            [6, 64, 4, 2],
            [6, 96, 3, 1],
            [6, 160, 3, 2],
            [6, 320, 1, 1],
        ]
        # building first layer
        assert input_size % 32 == 0
        input_channel = int(input_channel * width_mult)
        self.last_channel = int(last_channel * width_mult) if width_mult > 1.0 else last_channel

        # 第一层卷积,普通conv2d卷积,(3, 224, 224)-->(32, 112, 112)
        self.features = [conv_bn(3, input_channel, 2)]

        # 循环构造7个大bottleneck块,每个大bottleneck块中的倒残差堆叠数不相等
        for t, c, n, s in interverted_residual_setting:

            output_channel = int(c * width_mult)
            # 重复构造n层倒残差结构
            for i in range(n):
                if i == 0:
                    # 每次起始第一层倒残差步长为列表中的s
                    self.features.append(InvertedResidual(input_channel, output_channel, s,
                                               expand_ratio=t))
                else:
                    # 除了第一层,其他层的倒残差步长全为1
                    self.features.append(InvertedResidual(input_channel, output_channel, 1,
                                               expand_ratio=t))
                # 将本层输出通道赋给下层的输入通道
                input_channel = output_channel

        # 上面的输出为7*7*320,再进行普通的1*1 Conv2d, 输出通道数为last_channel:1280
        self.features.append(conv_1x1_bn(input_channel, self.last_channel))

        # 在__init__中 self.features = nn.Sequential(…), 在forward()中只需要使用self.features(x)就可
        # Sequential的输入也可以是list,然后输入的时候用*来引用
        self.features = nn.Sequential(*self.features)

        #
        self.classifier = nn.Sequential(
            nn.Dropout(dropout_ratio),
            nn.Linear(self.last_channel, num_classes),
        )

        self._initialize_weights()

    def forward(self, x):
        x = self.features(x)
        x = x.mean(3).mean(2)
        x = self.classifier(x)
        return x
    
    # 初始化参数
    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
                m.weight.data.normal_(0, math.sqrt(2. / n))
                if m.bias is not None:
                    m.bias.data.zero_()
            elif isinstance(m, nn.BatchNorm2d):
                m.weight.data.fill_(1)
                m.bias.data.zero_()
            elif isinstance(m, nn.Linear):
                n = m.weight.size(1)
                m.weight.data.normal_(0, 0.01)
                m.bias.data.zero_()

打印模型结构:

if __name__ == '__main__':
    model = MobileNetV2()
    model.cuda()
    summary(model, (3, 224, 224))

参考博客

https://zhuanlan.zhihu.com/p/67872001

图像分类中的网路结构(6)MobileNet v2

 

 

你可能感兴趣的:(轻量级网络,深度学习,计算机视觉,卷积)