特征提取网络之mobilenet

前言

MobileNet网络是由google团队在2017年提出的,专注于移动端或者嵌入 式设备中的轻量级CNN网络。相比传统卷积神经网络,在准确率小幅降低的前提下大大减少模型参数与运算量。(相比VGG16准确率减少了0.9%, 但模型参数只有VGG的1/32)。主要创新点在于深度可分离卷积,而整个网络实际上也是深度可分离模块的堆叠。
mobilenetv1论文地址:https://arxiv.org/abs/1704.04861
mobilenetv2论文地址:https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=8578572

1.mobilenetV1解析

网络结构

在这里插入图片描述

DW(Depthwise Convolution)卷积:对输入特征层,每个通道对应一个卷积核且每个卷积核的通道数仅为1个通道。这样对每一通道进行逐通道卷积的操作,就得到了和输入特征图通道数一致的输出特征图。
PW(Pointwise Conv)卷积:对输入特征层,进行每个像素点卷积(1X1)卷积。卷积核大小为1X1,通道数为输出所需的通道数。

普通卷积修改为深度可分离卷积

在这里插入图片描述

为什么深度可分离卷积会比普通的卷积推理时间要快。

我们看看普通卷积的计算量:
对于输入为SxSxM大小的特征层,使用KxK大小的卷积核,要使输出变为PxPxN。计算量为:

计算量大小:KxKxMxNxPxP

再看看使用深度分离卷积的计算量:

使用DW:KxKx1xMxPxP	# 仅使用一个卷积核通道,并且进行DW卷积输出通道数不变
使用PW:1x1XMxNxPxP	# 仅使用卷积核大小为1X1的卷积,并且进行PW卷积输出通道数会变
DW+PW:k*k*M*P*P+M*N*P*P

比较两者的关系

(k*k*M*P*P+M*N*P*P)/K*K*M*N*P*P=1/N+1/k^2
所以如果卷积核大小为3x3,那么理论上普通卷积计算量是DW+PW的89倍。
这就是为什么深度可分离卷积会比普通的卷积运行时间要快

mobilenetv1还设置了两个超参数α和β来控制网络结构的宽度和输入分辨率大小
在这里插入图片描述
通道数和分辨率的不同选择对应的结果。

3.mobilenetV2解析

网络结构

在这里插入图片描述
t是通道扩展因子(升维和降维几倍) ,c是输出特征矩阵通道数channel ,n是bottleneck的重复次数 ,s是步距(针对第一个残差层,其他为1)

主要提出了倒残差结构
在这里插入图片描述

Resnet里的残差结构是先1x1卷积降维后,3X3卷积,再1X1卷积升维,两边大,中间小。这里是先升维,DW卷积后,1X1卷积降维。直接1X1卷积降维再经过深度可分离卷积(卷积核通道仅为1)后,提取的特征会更少,所以采用先升维丰富特征信息的思想。这里每层的激活函数由原来的BN+Relu变成了Relu6,最后由高维变低维输出没有用relu6了的是线性层。如下图所示,并且只有输入和输出分辨率和通道一致的时候才采用残差连接。
在这里插入图片描述

为什么选择使用有限制的relu6作为激活函数。

因为mobilenet主要是用在移动端设备,如果限制权值范围,使得整个区间被好好的利用起来,这样的话权值能够被更加均匀的映射到0-255区间上,使得权值信息更多地被保留,从而减少了量化误差。
https://blog.csdn.net/tangshopping/article/details/112979152

为什么最后输出不用relu激活函数

在这里插入图片描述
因为作者发现,relu激活函数在变为低纬度的输出会损失很多信息,变为高维用可以。因为最后输出是由高维变为低维,就直接用1X1线性层了(这里我们在压缩通道操作的时候要不要考虑下呢)。

代码解析

mobilenetv2的forward函数

 def forward(self, x):
        """Forward function."""
        x = self.conv1(x)
        outs = []
        for i, layer_name in enumerate(self.layers):
            layer = getattr(self, layer_name)
            x = layer(x)
            if i in self.out_indices:
                outs.append(x)
        return tuple(outs)

conv1:是一个卷积核k=3,stride = 2,padding=1的3X3卷积。
layer层

for i, layer_cfg in enumerate(self.arch_settings):
    expand_ratio, channel, num_blocks, stride = layer_cfg	# 每个layer层的配置
    out_channels = make_divisible(channel * widen_factor, 8)	# 输出通道调整为8的整数倍
    inverted_res_layer = self.make_layer(	# 创建倒残差块
        out_channels=out_channels,
        num_blocks=num_blocks,
        stride=stride,
        expand_ratio=expand_ratio)
    layer_name = f'layer{i + 1}'
    self.add_module(layer_name, inverted_res_layer)
    self.layers.append(layer_name)

if widen_factor > 1.0:
    self.out_channel = int(1280 * widen_factor)
else:
    self.out_channel = 1280

make_divisible

def make_divisible(value, divisor, min_value=None, min_ratio=0.9):
    if min_value is None:
        min_value = divisor
    new_value = max(min_value, int(value + divisor / 2) // divisor * divisor)	# 注意// divisor * divisor不会抵消,是向下取整后再乘
    # Make sure that round down does not go down by more than (1-min_ratio).
    if new_value < min_ratio * value:	# 确保不会减少10%
        new_value += divisor
    return new_value

构建倒残差块

def make_layer(self, out_channels, num_blocks, stride, expand_ratio):
"""Stack InvertedResidual blocks to build a layer for MobileNetV2.

        Args:
            out_channels (int): out_channels of block.
            num_blocks (int): number of blocks.
            stride (int): stride of the first block. Default: 1
            expand_ratio (int): Expand the number of channels of the
                hidden layer in InvertedResidual by this ratio. Default: 6.
        """
        layers = []
        for i in range(num_blocks):
            if i >= 1:
                stride = 1	# stride 只在堆叠的残差块的第一个残差块会变化,其他的都为1
            layers.append(
                InvertedResidual(
                    self.in_channels,	
                    out_channels,	
                    mid_channels=int(round(self.in_channels * expand_ratio)),	# 中间升维后的通带数
                    stride=stride,
                    with_expand_conv=expand_ratio != 1,
                    conv_cfg=self.conv_cfg,
                    norm_cfg=self.norm_cfg,
                    act_cfg=self.act_cfg,
                    with_cp=self.with_cp))
            self.in_channels = out_channels	 # 最后输出通道数等于输入通道数

        return nn.Sequential(*layers)
  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AI、明察秋毫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值