tensorflow2.2实现MobilenetV2

MobilenetV2介绍

网络设计是基于MobileNetV1。它保持了简单性,同时显著提高了精度,在移动应用的多图像分类和检测任务上达到了最新的水平。主要贡献是一个新的层模块:具有线性瓶颈的倒置残差。该模块将输入的低维压缩表示首先扩展到高维并用轻量级深度卷积进行过滤。随后用线性卷积将特征投影回低维表示。

MobilenetV2网络结构

在介绍MobilenetV2网络结构之前需要先了解一下网络内部的细节。

1. Depthwise Separable Convolutions

深度可分卷积这是一种分解卷积的形式,它将一个标准卷积分解为深度卷积和一个1×1卷积,1x1卷积又叫称为点卷积,下图是MobilenetV1中论文的图。
在这里插入图片描述
标准的卷积是输入一个 D F D_{F} DF x D F D_{F} DF x M M M 的特征图,输出一个 D F D_{F} DF x D F D_{F} DF x N N N 的特征图。

标准的卷积层的参数是 D K D_{K} DK x D K D_{K} DK x M M M x N N N ,其中 D K D_{K} DK是卷积核的小大, M M M是输入通道数, N N N是输出通道数。

所以标准的卷积计算量如下:
D K D_{K} DK x D K D_{K} DK x M M M x N N N x D F D_{F} DF x D F D_{F} DF

深度可分离卷积由两层组成:深度卷积和点卷积。

我们使用深度卷积来为每个输入通道(输入深度)应用一个过滤器。然后使用点态卷积(一个简单的1×1卷积)创建深度层输出的线性组合。

其计算量为: D K D_{K} DK x D K D_{K} DK x M M M x D F D_{F} DF x D F D_{F} DF

相对于标准卷积,深度卷积是非常有效的。然而,它只过滤输入通道,并没有将它们组合起来创建新的功能。因此,为了生成这些新特征,需要一个额外的层,通过1 × 1的卷积计算深度卷积输出的线性组合。

其计算量为: M M M x N N N x D F D_{F} DF x D F D_{F} DF

例子:
标准卷积:
假设有一个56 x 56 x 16的特征图,其卷积核是3 x 3大小,输出通道为32,计算量大小就是56 x56 x16 x 32 x 3 x 3 = 14450688
深度可分离卷积:
假设有一个56 x 56 x 16的特征图,输出通道为32,先用16个3 x 3大小的卷积核进行卷积,接着用32个1 x 1大小的卷积核分别对这用16个3 x 3大小的卷积核进行卷积之后的特征图进行卷积,计算量为3 x 3 x 16 x 56 x 56 + 16 x 32 x 56 x 56 = 2057216

从而可见计算量少了许多。

2. Linear Bottlenecks

在论文中,实验者们发现,使用线性层是至关重要的,因为它可以防止非线性破坏太多的信息,在瓶颈中使用非线性层确实会使性能降低几个百分点,如下图。线性瓶颈模型的严格来说比非线性模型要弱一些,因为激活总是可以在线性状态下进行,并对偏差和缩放进行适当的修改。然而,我们在图a中展示的实验表明,线性瓶颈改善了性能,为非线性破坏低维空间中的信息提供了支持。
在这里插入图片描述

3. Inverted residuals

倒残差瓶颈块与残差块类似,其中每个块包含一个输入,然后是几个瓶颈,然后是扩展,下图是论文中给大家展示的残差块与倒残差瓶颈块的区别。
在这里插入图片描述

主要区别就是残差块先进行降维再升维,而倒残差瓶颈块是先进行升维再降维,结构如下:
在这里插入图片描述
倒残差瓶颈块从 k k k转换为 k ′ k′ k个通道,步长为 s s s,扩展系数为 t t t

4. Model Architecture

在这里插入图片描述
网络使用使用ReLU6作为非线性,因为用于低精度计算时它的鲁棒性。

其中:

  • n n n代表重复次数。
  • c c c代表有相同的通道数。
  • s s s代表步长。
  • t t t代表扩展系数。

代码实现

from os import name
import numpy as np
import tensorflow as tf
from tensorflow.keras.layers import (
    Activation, BatchNormalization, Conv2D, DepthwiseConv2D, Dropout,ZeroPadding2D, Add, Dense,
    GlobalAveragePooling2D, Input, Reshape
)
from tensorflow.keras.models import Model
from plot_model import plot_model
from tensorflow.keras import backend as K

def correct_pad(inputs, kernel_size):
    img_dim = 1
    input_size = K.int_shape(inputs)[img_dim:(img_dim + 2)]

    if isinstance(kernel_size, int):
        kernel_size = (kernel_size, kernel_size)

    if input_size[0] is None:
        adjust = (1, 1)
    else:
        adjust = (1 - input_size[0] % 2, 1 - input_size[1] % 2)

    correct = (kernel_size[0] // 2, kernel_size[1] // 2)

    return ((correct[0] - adjust[0], correct[0]),
            (correct[1] - adjust[1], correct[1]))

# 保证特征层为8得倍数
def _make_divisible(v, divisor, min_value=None):
    if min_value is None:
        min_value = divisor
    new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)
    if new_v < 0.9 * v:
        new_v += divisor
    return new_v

def relu6(x):
    return K.relu(x, max_value=6)

def Conv2D_block(inputs, filters, kernel_size=(3, 3), strides=(1, 1)):
    x = Conv2D(
        filters=filters, kernel_size=kernel_size, padding='valid',
        use_bias=False, strides=strides
    )(inputs)
    x = BatchNormalization(epsilon=1e-3,
                                  momentum=0.999)(x)
    x = Activation(relu6)(x)

    return x

def bottleneck(inputs, expansion, stride, alpha, filters):
    in_channels = K.int_shape(inputs)[-1]
    pointwise_conv_filters = int(filters * alpha)
    pointwise_filters = _make_divisible(pointwise_conv_filters, 8)
    x = inputs
    # 数据扩充
    x = Conv2D(expansion * in_channels,
                          kernel_size=1,
                          padding='same',
                          use_bias=False,
                          activation=None)(x)
    x = BatchNormalization(epsilon=1e-3,
                                    momentum=0.999)(x)
    x = Activation(relu6)(x)
    
    if stride == 2:
        x = ZeroPadding2D(padding=correct_pad(x, 3))(x)
        
    # 深度卷积
    x = DepthwiseConv2D(kernel_size=3,
                               strides=stride,
                               activation=None,
                               use_bias=False,
                               padding='same' if stride == 1 else 'valid')(x)
    x = BatchNormalization(epsilon=1e-3,
                                  momentum=0.999)(x)

    x = Activation(relu6)(x)
    
    # 1x1卷积用于改变通道数
    x = Conv2D(pointwise_filters,
                      kernel_size=1,
                      padding='same',
                      use_bias=False,
                      activation=None)(x)

    x = BatchNormalization(epsilon=1e-3,
                                  momentum=0.999)(x)
    
    if (in_channels == pointwise_filters) and stride == 1:
        return Add()([inputs, x])
    return x

    
def MobilenetV2(inputs, alpha=0.35, dropout=1e-3, classes=17):
    
    first_block_filters = _make_divisible(32 * alpha, 8)
    x = ZeroPadding2D(padding=correct_pad(inputs, 3))(inputs)
    x = Conv2D_block(x, filters=first_block_filters, kernel_size=3, strides=(2, 2))
    
    x = bottleneck(x, filters=16, alpha=alpha, stride=1, expansion=1)
    
    x = bottleneck(x, filters=24, alpha=alpha, stride=2, expansion=6)
    x = bottleneck(x, filters=24, alpha=alpha, stride=1, expansion=6)
    
    x = bottleneck(x, filters=32, alpha=alpha, stride=2, expansion=6)
    x = bottleneck(x, filters=32, alpha=alpha, stride=1, expansion=6)
    x = bottleneck(x, filters=32, alpha=alpha, stride=1, expansion=6)
    
    x = bottleneck(x, filters=64, alpha=alpha, stride=2, expansion=6)
    x = bottleneck(x, filters=64, alpha=alpha, stride=1, expansion=6)
    x = bottleneck(x, filters=64, alpha=alpha, stride=1, expansion=6)
    x = bottleneck(x, filters=64, alpha=alpha, stride=1, expansion=6)
    
    x = bottleneck(x, filters=96, alpha=alpha, stride=1, expansion=6)
    x = bottleneck(x, filters=96, alpha=alpha, stride=1, expansion=6)
    x = bottleneck(x, filters=96, alpha=alpha, stride=1, expansion=6)
    
    x = bottleneck(x, filters=160, alpha=alpha, stride=2, expansion=6)
    x = bottleneck(x, filters=160, alpha=alpha, stride=1, expansion=6)
    x = bottleneck(x, filters=160, alpha=alpha, stride=1, expansion=6)
    
    x = bottleneck(x, filters=320, alpha=alpha, stride=1, expansion=6)
    
    if alpha > 1.0:
        last_block_filters = _make_divisible(1280 * alpha, 8)
    else:
        last_block_filters = 1280
        
    x = Conv2D_block(x, filters=last_block_filters, kernel_size=1, strides=(1, 1))
    
    x = GlobalAveragePooling2D()(x)
    shape = (1, 1, int(last_block_filters))

    x = Reshape(shape, name='reshape_1')(x)
    x = Dropout(dropout, name='dropout')(x)

    x = Conv2D(classes, (1, 1),padding='same', name='conv_preds')(x)
    x = Activation('softmax', name='act_softmax')(x)
    x = Reshape((classes,), name='reshape_2')(x)
    return x
    

if __name__ == '__main__':
    is_show_picture = False
    inputs = Input(shape=(224,224,3))
    classes = 1000
    model = Model(inputs=inputs, outputs=MobilenetV2(inputs=inputs, classes=classes))
    
    model.summary()
    print(len(model.layers))
    # for i in range(len(model.layers)):
       # print(i, model.layers[i])
    if is_show_picture:
        plot_model(model,
           to_file='./nets_picture/MobilenetV2.png',
           )
        print("plot_model------------------------>")
    
  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值