0
点赞
收藏
分享

微信扫一扫

轻量级网络结构mobilenetv1、v2、v3学习笔记

今天是自己做一下mobilenet的简单笔记,怕自己后面忘记了mobilenet的相关内容,首先我们要知道mobilenet是为了能够在嵌入式设备上使用的一种模型框架,因此它最大的优点就是参数量少,并且还是要保证对于计算机视觉相关任务的精确度不能降得太低。下面就是mobilenet的各版本情况 。

mobilenetv1网络结构图

那么对于mobilenetv1来说,最大的优点或者说是改进就是选择了深度可分离卷积块来减少一般卷积的参数量,我们可以通过下面的图可以看出。(图片来源水印的地方,大家可以去看该大佬的bilibili视频,非常棒!)

 对于传统的卷积机制:假设输入的是3x3xc维度的特征图,通过3x3xd的卷积块进行卷积,得到的是1x1xd的特诊图,那么计算量为3x3xcxdx3x3=81cd.

对于深度可分离卷积来说,假设输入的是3x3xc维度的特征图,首先通过3x3xc的进行深度可分离卷积,就是对每个通道进行卷积,得到的是1x1xc的特征图,计算量为1x1x3x3xc,在经过1x1xd的卷积块进行卷积得到的结果是1x1xd,计算量为1x1xdx1x1xc,那么总的计算量1x1x3x3xc+1x1xdx1x1xc=9c+cd

通过上下进行对比,能够很明显看出差距。

下面就是mobilenetv1的整体架构图,其实原论文还提出了两个超参数,一个是α一个是β。α参数是一个倍率因子,用来调整卷积核的个数,这个卷积核个数是每个深度可分离卷积结束后通过1x1xd的卷积块的通道数改变,希望大家能够明白,β是控制输入网络的图像尺寸参数,但是深度可分离卷积也有下面图中所显示的缺点就是容易使卷积核参数变成0.至此才会出现mobilenetv2模型。

mobilenetv2网络结构图

 下面是mobilenetv2的block,可以从中大致看出就像一个残差块一样,没错就是那么简单,他就是继承了mobilenetv1的深度可分离卷积,然后由进行了一个创新,就是先升通道数再降通道数,为什么这么做呢,因为论文中说,激活函数relu对低维度的特征会造成更多的信息丢失,对于高维度的特征的丢失会少一些,所以就有了这个创新。整体步骤就是,先通过一般卷积进行升维,再通过深度可分离卷积操作,再通过一般卷积进行降维,最后再进行残差相加(特征图大小必须是一样的,相信大家这是知道的),具体看代码所示.而对于一般的残差块是1x1卷积降维->3x3卷积->1x1卷积升维。这就是两者的区别。

       mobilenetv2的block

class InvertedResidual(nn.Module):
    def __init__(self, inp, oup, stride, expand_ratio):
        super(InvertedResidual, self).__init__()
        self.stride = stride
        assert stride in [1, 2]#stride=2就是进行了下采样操作。

        hidden_dim = int(round(inp * expand_ratio))
        self.use_res_connect = self.stride == 1 and inp == oup,判断输出的通道数和输出的是否一样。如果不进行想加,那么就是进行了下采样操作。

        layers = []
        if expand_ratio != 1:
            layers.append(ConvBNReLU(inp, hidden_dim, kernel_size=1))
        layers.extend([
            ConvBNReLU(hidden_dim, hidden_dim, stride=stride, groups=hidden_dim),
            nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
            nn.BatchNorm2d(oup),
        ])
        self.conv = nn.Sequential(*layers)

    def forward(self, x):
        if self.use_res_connect:
            return x + self.conv(x)#这是相加,并不是通道数拼接,要看懂
        else:
            return self.conv(x)

 下面就是mobilenetv2的整体结构图。这个结构图的解读是这样的:

Input 代表输入的特征图大小 从第二个开始也代表是上一个卷积块的输出结果(这个相信大家能看懂)

Operator 代表的是执行代码块,除了第一个和后三个,都是组成mobilenetv2最终要的卷积块,也就是上面代码所示的那样。

t 代表的是扩展因子,也就是在每个block中,升维的通道数是输入通道数的t倍。

n 代表的是block需要执行多少次

s代表的是步长   但是这里需要注意的是,这里的步长只要是大于1的都是对于第一个block来说的,举个例子,在n=2时,s=2,那么只有第一个block的stride是2,另一个block的s还是1.

mobilenetv3网络结构图

 对于mobilenetv3来说,又有一个大的改进就是应用通道注意力机制。就是将输入的特征图在进行池化操作,变成1x1xc的大小,然后再进行展平----->全连接------->全连接------->sigmoid()------->得到的是1x1xc的大小,然后再与输入的特征图在通道上进行相乘,从而达到通道注意力机制的作用。具体看下面的代码。

 mobilenetv3的block

class InvertedResidual(nn.Module):
    def __init__(self, inp, hidden_dim, oup, kernel_size, stride, use_se, use_hs):
        super(InvertedResidual, self).__init__()
        assert stride in [1, 2]

        self.identity = stride == 1 and inp == oup

        if inp == hidden_dim:
            self.conv = nn.Sequential(
                # dw
                nn.Conv2d(hidden_dim, hidden_dim, kernel_size, stride, (kernel_size - 1) // 2, groups=hidden_dim, bias=False),
                nn.BatchNorm2d(hidden_dim),
                h_swish() if use_hs else nn.ReLU(inplace=True),
                # Squeeze-and-Excite
                SELayer(hidden_dim) if use_se else nn.Identity(),
                # pw-linear
                nn.Conv2d(hidden_dim, oup, 1, 1, 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),
                h_swish() if use_hs else nn.ReLU(inplace=True),
                # dw
                nn.Conv2d(hidden_dim, hidden_dim, kernel_size, stride, (kernel_size - 1) // 2, groups=hidden_dim, bias=False),
                nn.BatchNorm2d(hidden_dim),
                # Squeeze-and-Excite
                SELayer(hidden_dim) if use_se else nn.Identity(),
                h_swish() if use_hs else nn.ReLU(inplace=True),
                # pw-linear
                nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
                nn.BatchNorm2d(oup),
            )

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

通道注意力机制代码如下图所示:

# class SqueezeExcitation(nn.Module):#注意力机制
#     def __init__(self, input_c: int, squeeze_factor: int = 4):
#         super(SqueezeExcitation, self).__init__()
#         squeeze_c = _make_divisible(input_c // squeeze_factor, 8)#这个作用是为了将输入的通道数变成离8最近的8的倍数。这是一个自己弄的函数。
#         self.fc1 = nn.Conv2d(input_c, squeeze_c, 1)
#         self.fc2 = nn.Conv2d(squeeze_c, input_c, 1)
#
#     def forward(self, x):
#         scale = F.adaptive_avg_pool2d(x, output_size=(1, 1))
#         scale = self.fc1(scale)
#         scale = F.relu(scale, inplace=True)
#         scale = self.fc2(scale)
#         scale = F.hardsigmoid(scale, inplace=True)
#         return scale * x

 下面是mobilenetv3的模型架构图,和v2的差不多,就是多了SE(是否使用注意力机制模块)和NL(三种激活函数的使用),exp_size也是扩展因子,但是这是扩展后的结果,并不是倍数。

 最后说明一下,本博客的代码来自:

原文链接:睿智的目标检测49——Pytorch 利用mobilenet系列(v1,v2,v3)搭建yolov4目标检测平台_Bubbliiiing的学习小课堂-CSDN博客

举报

相关推荐

0 条评论