MobileNet V2 随笔

MobileNet V2

Linear Bottlenecks and Inverted Residuals

下图是可分离卷积:

请注意到由于 MobileNet 使用 Batch Normalization ,而 Batch Normalization 的 beta 项含有均值,因此整个 MobileNet 并没有使用 BiasAdd 操作。其次,我们要注意到 MobileNetV1 无论是 Depthwise Conv 还是 Pointwise Conv ,它们后面会加入一个 ReLu6 操作,作者在 MobileNetV2 花了很大篇幅来讨论这个操作的影响。

下面两张图是 MobileNet V1 的模块结构:

下图是 MobileNet V2 的主要模块结构(Linear Bottlenecks and Inverted Residuals):

不难发现 MobileNet V2 的主要结构,只是在 MobileNet V1 的基础上引入了,残差连接和瓶颈结构,以及把 Pointwise 的激活改成了 Linear 。

MobileNet V2 网络结构

下图是 MobileNet V2 的网络架构:

下图是 MobileNet V1 的网络架构:

对比 MobileNet 的 V1 和 V2 两版网络架构,我们发现两版都没有 Pooling 层,而是直接把 stride 设成 2 ,直接卷积缩放。而且最近有些观点表示,Pooling 层产生的的特征丢失不可逆转,所以 MobileNet 一直就没有 Pooling 层。另外,大致一眼过去,貌似 V2 宽度比 V1 小很多。然而,我们挑出其中一层,比如 56×56 56 × 56 来算一下,会发现 V2 中间该模块中间有两层 feature map 的维度达到 192 ,而 V1 仅有128。这么看来,V2 的特征信息传输更高效,对数据带宽利用率更好。再就是,尽管 v2 比 v1 更深,但是 v2 的 parameters 未必就比 v1 多。因为 MobileNet 的参数重量,主要不在 depthwise 上,而是在 poinwise ,所以瓶颈压缩部分减少了不少 pointwise 的参数。

Memory Efficient Trick

这个内存高效利用技巧,我用下面的草稿解释。

需要注意,源码这部分的实现逻辑可能是有问题的,貌似与原文精神不一致。

def split_conv(input_tensor,
               num_outputs,
               num_ways,
               scope,
               divisible_by=8,
               **kwargs):
    """Creates a split convolution.
    Split convolution splits the input and output into
    'num_blocks' blocks of approximately the same size each,
    and only connects $i$-th input to $i$ output.
    Args:
        input_tensor: input tensor
        num_outputs: number of output filters
        num_ways: num blocks to split by.
        scope: scope for all the operators.
        divisible_by: make sure that every part is divisiable by this.
        **kwargs: will be passed directly into conv2d operator
    Returns:
        tensor
    """
    b = input_tensor.get_shape().as_list()[3]

    if num_ways == 1 or min(b // num_ways,
                            num_outputs // num_ways) < divisible_by:
        # Don't do any splitting if we end up with less than 8 filters
        # on either side.
        return slim.conv2d(input_tensor, num_outputs, [1, 1], scope=scope, **kwargs)

    outs = []
    input_splits = _split_divisible(b, num_ways, divisible_by=divisible_by) # 这句貌似不应该拆分输入
    output_splits = _split_divisible(
        num_outputs, num_ways, divisible_by=divisible_by)
    inputs = tf.split(input_tensor, input_splits, axis=3, name='split_' + scope)
    base = scope
    for i, (input_tensor, out_size) in enumerate(zip(inputs, output_splits)):
        scope = base + '_part_%d' % (i,)
        n = slim.conv2d(input_tensor, out_size, [1, 1], scope=scope, **kwargs)
        n = tf.identity(n, scope + '_output')
        outs.append(n)
    return tf.concat(outs, 3, name=scope + '_concat')

上面源码这段,我认为是有问题的,输入输出一般不可能是同时进行 split 的,甚至大多数情况都不会去尝试拆输入。

下面附录一张我的原始推导笔记:

关于 ReLu6 的讨论

我认为 MobileNet V2 的关键论点,可能并不是 Linear Bottleneck 和 Inverted Residuals 本身的形式,它们只是一个研究结果而已。而作者主要的论点应该是,以 ReLu6 为例的非线性激活映射,对特征信息空间的有害影响。在部分的讨论中,作者有一个观点,我认为比较有趣。这个观点是:

In ReLu(Bx) R e L u ( B x ) , which B B is the linear classifier to ensure which are the non-zero volume part of output space

这也就是说,每个卷积层都能看作 Feature Map 分类器,而 ReLu 决定了一些特征的去留。大于零的算做重要特征被留下,小于等于零作为冗余特征被舍弃。同时,我们很清楚世上并不存在完美分类器,从不误判的分类器。因此这些分类器只要出现误判,那么特征信息就会出现损失,而且作为符合条件概率的链式响应,这个信息损失应该相当严重,精度应该很低,那为何现在 CNN 在某些领域,依然表现出色呢?这是因为 B 会把 x 的信息,散落到比 x 更高的维度空间上,保持了一定的信息冗余。这样就能在有信息损失的推理过程中,利用剩下的特征信息推理出正确的结果。因此,有了 Linear Bottleneck 和 Inverted Residuals 设计,既能逐步筛选有效的抽象特征,而又不在 bottleneck 时,丢失重要的特征信息。

下面的实验现象是:

S2=(x1(t),x2(t))t线Tn×2R2RnnT2×n1ReLu6(Tn×2S2)S2
我们可以看到 ReLu 非线性激活对原始信息损害,因此在 bottleneck module 后面加 ReLu6 操作,并不一定是个恰当的选择。

下面图的实验,将 Linear bottleneck 的猜想用到 MobileNet 的表现,还顺带讨论了 shortcut 对 expansion module 的影响。

这里,我也借机表达个人观点。首先,对于 Inverted residuals 的设计,作者很诚实告诉我们这是一个无心插柳柳成荫的故事。他最初的动机,只是打算借助 classical residual connections 在梯度传播上的优势,提高网络推理的准确性。然而,classical residual connections 是 shortcut between expansions 的,这对前面 Memory Efficinet Trick 在实现上很不友好,甚至相当麻烦。因此,补充了一个 Shortcut between bottleneck 的实验对照,本来只要效果过得去,就谢天谢地了。没想到,这效果竟然比传统的要好,所以他也把这个点作为 MobileNet V2 的其中一个亮点放在了标题。

对 Linear bottleneck Inverted residual block ,作者尝试从信息流动的角度进行解释。他直观地把这种 block 拆分成两个阶段,一个是 expansion ,再就是 bottleck 。他认为 expansion layers 可以看作相应层的 capacity ,这好比是一种特征池。而 bottleneck 则是 expressiveness ,对众多特征反应结果的综合概括。这仿佛就是人类对事物的认知过程,先从众多维度,各个方面,分析事物的不同特性,这是一种发散型分析,expansion 的过程。然后,对这些不同的特性进行总结,从而归纳出该事物的一个简单规律,是事物的抽象而完整表述,bottlenect 的过程。基于作者的这解析角度,我们可以不难猜测 Inverted residuals 成功背后的原理是,事物的抽象表达更完整了,而且更容易保全事物的信息。用 Shortcut between Expansions 保留事物的重要信息,犹如在茫茫人海中,确认眼神找对象一样,是个大海捞针的过程,稍不留神 bottleneck 就把信息丢了,丢了再 shortcut 也只是徒劳。Expansion 空间维度那么高,事情反而不好学了。

总结

当初笔者粗读原文时,MobileNet V2 给我的感觉就是并没有创意,改几个连接,做几个尝试性实验出来的成果。随着对作者观点的深入,虽然现在我还没感受 MobileNet V2 的重大突破,但是我觉着作者的想法和观点很大胆,很独特,很新颖,很有趣。主要原因是作者看待 bottleneck 的方式,是跟原来相反的。原来人们都爱把 bottleneck 放在两个胖子中间,比如 ResNeXt 就是这种观点,而作者却把 bottlenecks 看作一个胖子的两端,这么想的话,其实这个胖子还有不少的瘦身空间,比如对胖子引入 ResNeXt 的 Cardinality 参数等。也正因为作者很多这种“反过来想”的研究观点,丰富了我对轻量网络结构设计的观点,所以这篇论文更适合耐心品尝,浮躁很难感受到这篇文章的真实亮点。

  • 13
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值