2018 轻量化网络Mobilnet v2

0. MobileNet介绍

MobileNet是M为移动和嵌入式设备提出的高效模型。MobileNet基于流线型(streamlined) 架构,使用深度可分离卷积(depthwise separable convolutions, 即Xception变体结构, 详细请参考干巴他爹–Depthwise卷积与Pointwise卷积)来构建轻量级深度神经网络。

论文验证了MobileNet在目标检测,人脸识别,大规模地理定位等领域中使用的有效性。

1. MobileNet (v1, v2) 模型说明

Mobilenet V1: MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications
是Google的Andrew G. Howard, Menglong Zhu, Bo Chen等人于2017年发明的网络结构.

Mobilenet V2: Inverted Residuals and Linear Bottlenecks Mobile Networks for Classification, Detection and Segmentation

这里我们主要介绍Mobilenet V2, 其主要创新点体现在2块:

下面内容转载自 作者:小麦草 来源:CSDN 原文:https://blog.csdn.net/kangdi7547/article/details/81431572

  • 1、Inverted Residual Block
    MobileNet V1没有很好的利用Residual Connection,而Residual Connection通常情况下总是好的,所以MobileNet V2加上。先看看原始的ResNet Block长什么样,下图左边:
    在这里插入图片描述

① 先用1x1降通道过ReLU,再3x3空间卷积过ReLU,再用1x1卷积过ReLU恢复通道,并和输入相加。之所以要1x1卷积降通道,是为了减少计算量,不然中间的3x3空间卷积计算量太大。所以Residual block是沙漏形两边宽中间窄

但是,现在我们中间的3x3卷积变为了Depthwise的了,计算量很少了,所以通道可以多一点,效果更好,所以通过1x1卷积先提升通道数,再Depthwise的3x3空间卷积,再用1x1卷积降低维度。两端的通道数都很小,所以1x1卷积升通道或降通道计算量都并不大,而中间通道数虽然多,但是Depthwise 的卷积计算量也不大。作者称之为Inverted Residual Block,两边窄中间宽,像柳叶较小的计算量得到较好的性能

  • 2、ReLU6

首先说明一下 ReLU6,卷积之后通常会接一个 ReLU 非线性激活,在 MobileNet V1 里面使用 ReLU6,ReLU6 就是普通的ReLU但是限制最大输出值为 6,这是为了在移动端设备使用 float16/int8 的低精度的时候,也能有很好的数值分辨率,如果对 ReLU 的激活范围不加限制,输出范围为0到正无穷,如果激活值非常大,分布在一个很大的范围内,则低精度的float16/int8无法很好地精确描述如此大范围的数值,带来精度损失。

本文提出,最后输出的 ReLU6 去掉,直接线性输出,理由是:ReLU 变换后保留非0区域对应于一个线性变换,仅当输入低维时ReLU 能保留所有完整信息。

Xception中的实验证明了 Depthwise 卷积后再加ReLU 效果会变差,作者猜想可能是 Depthwise 输出太浅了, 应用 ReLU会带来信息丢失,而 MobileNet V1还引用了 Xception 的论文,但是在 Depthwise 卷积后面还是加了ReLU。在 MobileNet V2 这个 ReLU终于去掉了,并用了大量的篇幅来说明为什么要去掉。

总之,去掉最后那个 ReLU,效果更好

2. 环境说明

  • keras 2.2.4
  • tensorflow-gpu 1.12.0
  • pydot
  • opencv 4.0

3. 基于keras的mobilenet v2代码展示

3.1 首先, 我们需要引入一系列实现mobilenet v2所需要的keras模块.
from keras.models import Model
from keras.layers import Input, Conv2D, GlobalAveragePooling2D, DepthwiseConv2D, Dropout
from keras.layers import Activation, BatchNormalization, add, Reshape, ReLU
from keras.utils.vis_utils import plot_model

from keras import backend as K
3.2 Mobilenet v2定义.
"""
	@date: 2019/04/15
	@author: samuel ko
	MobileNet v2模型定义.
"""

def _conv_block(inputs, filters, kernel, strides):
    """Convolution Block
    卷积模块
    """

    channel_axis = 1 if K.image_data_format() == 'channels_first' else -1

    x = Conv2D(filters, kernel, padding='same', strides=strides)(inputs)
    x = BatchNormalization(axis=channel_axis)(x)
    x = ReLU(6.)(x)
    return x


def _bottleneck(inputs, filters, kernel, t, s, r=False):
    """Bottleneck
    此函数定义了一个基本的Resnet的bottleneck模块, 或者说inverted_residual_block.
    (将里面的卷积用深度可分离卷积替代正常卷积.)
    
    步骤:  
        1. 先通过_conv_block(..., kernel=(1,1), stride=(1,1))来增加输入图片的通道数(channels). (输入通道数 * t)
        2. 接着接上Depthwise卷积(depthwise+pointwise的混合形式), 混合通道间信息, 并扩大特征图的通道数(channels).
        3. 再用Conv2D(kernerl=(1,1))的1x1卷积, 融合通道信息, 降低通道维度.
        4. 并使用残差结构, 使用concatenate将特征图按通道进行拼接.
    """

    channel_axis = 1 if K.image_data_format() == 'channels_first' else -1
    tchannel = K.int_shape(inputs)[channel_axis] * t

    x = _conv_block(inputs, tchannel, (1, 1), (1, 1))

    x = DepthwiseConv2D(kernel, strides=(s, s), depth_multiplier=1, padding='same')(x)
    x = BatchNormalization(axis=channel_axis)(x)
    x = ReLU(6.)(x)

    x = Conv2D(filters, (1, 1), strides=(1, 1), padding='same')(x)
    x = BatchNormalization(axis=channel_axis)(x)

    if r:
        x = add([x, inputs])
    return x


def _inverted_residual_block(inputs, filters, kernel, t, strides, n):
    """Inverted Residual Block
    Bottleneck堆叠结构.
    """

    x = _bottleneck(inputs, filters, kernel, t, strides)

    for i in range(1, n):
        x = _bottleneck(x, filters, kernel, t, 1, True)

    return x


def MobileNetv2(input_shape, n_classes):
    """MobileNetv2
    定义了MobileNet v2的整体结构.
    # Arguments
        input_shape: 输入图片的size, 此处为 (224, 224, 3).
        n_classes: 类别数量, 对CIFAR100为例, n_classes=100.
    # Returns
        MobileNetv2 model.
    """

    inputs = Input(shape=input_shape)
    x = _conv_block(inputs, 32, (3, 3), strides=(2, 2))

    x = _inverted_residual_block(x, 16, (3, 3), t=1, strides=1, n=1)
    x = _inverted_residual_block(x, 24, (3, 3), t=6, strides=2, n=2)
    x = _inverted_residual_block(x, 32, (3, 3), t=6, strides=2, n=3)
    x = _inverted_residual_block(x, 64, (3, 3), t=6, strides=2, n=4)
    x = _inverted_residual_block(x, 96, (3, 3), t=6, strides=1, n=3)
    x = _inverted_residual_block(x, 160, (3, 3), t=6, strides=2, n=3)
    x = _inverted_residual_block(x, 320, (3, 3), t=6, strides=1, n=1)

    x = _conv_block(x, 1280, (1, 1), strides=(1, 1))
    x = GlobalAveragePooling2D()(x)
    x = Reshape((1, 1, 1280))(x)
    x = Dropout(0.3, name='Dropout')(x)
    x = Conv2D(n_classes, (1, 1), padding='same')(x)

    x = Activation('softmax', name='softmax')(x)
    output = Reshape((n_classes,))(x)

    model = Model(inputs, output)
    plot_model(model, to_file='MobileNetv2.png', show_shapes=True)

    return model


if __name__ == '__main__':
    MobileNetv2((224, 224, 3), 100)
3.3 Mobilenet v2训练.

下面是训练过程, 我这里训练的是10分类的任务, 输入图像大小为224 x 224. 关于数据集形式如下:

  • 训练数据路径:
    在这里插入图片描述

  • 训练数据中每一类的样本情况:
    在这里插入图片描述

    ptrain = 'data/train'
    pval = 'data/val'
    """
        ptrain/pval: 训练集/验证集目录, 其形式如下:
    |
    |---0 (类别1)
    |    |----A1.jpg(后缀为.png都可以, 需要自己适配修改一下即可.)  
    |    |----A2.jpg
    |    |----...
    |---1 (类别2)
    |    |----B1.jpg
    |    |----B2.jpg
    |    |----...
    |---2
    | .....
"""
Train the MobileNet V2 model
"""
import os
import sys
import pandas as pd

from keras.optimizers import Adam
from keras.preprocessing.image import ImageDataGenerator
from keras.callbacks import EarlyStopping
from keras.layers import Conv2D, Reshape, Activation
from keras.models import Model


def generate(batch, size):
    """数据生成器以及数据增广.
    # Arguments
        batch: Integer, batch size.
        size: Integer, image size.
    # Returns
        train_generator: train set generator
        validation_generator: validation set generator
        count1: Integer, number of train set.
        count2: Integer, number of test set.
    """
    
    ptrain = 'data/train'
    pval = 'data/val'
    """
        ptrain/pval: 训练集/验证集目录, 其形式如下:
    |
    |---0 (类别1)
    |    |----A1.jpg(后缀为.png都可以, 需要自己适配修改一下即可.)  
    |    |----A2.jpg
    |    |----...
    |---1 (类别2)
    |    |----B1.jpg
    |    |----B2.jpg
    |    |----...
    |---2
    | .....
    """
    # 用生成器的方式组织数据集.
    datagen1 = ImageDataGenerator(
        rescale=1. / 255,
        shear_range=0.2,
        zoom_range=0.2,
        rotation_range=90,
        width_shift_range=0.2,
        height_shift_range=0.2,
        horizontal_flip=True)

    datagen2 = ImageDataGenerator(rescale=1. / 255)

    train_generator = datagen1.flow_from_directory(
        ptrain,
        target_size=(size, size),
        batch_size=batch,
        class_mode='categorical')

    validation_generator = datagen2.flow_from_directory(
        pval,
        target_size=(size, size),
        batch_size=batch,
        class_mode='categorical')

    count1 = 0
    for root, dirs, files in os.walk(ptrain):
        for each in files:
            count1 += 1

    count2 = 0
    for root, dirs, files in os.walk(pval):
        for each in files:
            count2 += 1

    return train_generator, validation_generator, count1, count2


def train(batch, epochs, num_classes=10, size=224):
    """Train the model.
    # Arguments
        batch: Integer.
        epochs: Integer.
        num_classes, Integer, 数据集的类别数.
        size: Integer, 图像的尺寸(这里, 我们规定图片的长和宽都相等).
    """

    train_generator, validation_generator, count1, count2 = generate(batch, size)
    print("训练集样本个数", count1)
    print("验证集样本个数", count2)
    
    # 加载模型.
    model = MobileNetv2((size, size, 3), num_classes)

    opt = Adam()
    # 设计early stopping机制.
    earlystop = EarlyStopping(monitor='val_acc', patience=30, verbose=0, mode='auto')
    model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=['accuracy'])

    hist = model.fit_generator(
        train_generator,
        validation_data=validation_generator,
        steps_per_epoch=count1 // batch,
        validation_steps=count2 // batch,
        epochs=epochs,
        callbacks=[earlystop])

    if not os.path.exists('model'):
        os.makedirs('model')
    
    # 保存模型和训练历史记录.
    df = pd.DataFrame.from_dict(hist.history)
    df.to_csv('hist.csv', encoding='utf-8', index=False)
    model.save_weights('weights.h5')


if __name__ == '__main__':
    train(32, 100, num_classes=10, size=224)

如果正常跑起来的话, 你的屏幕上应该有如下显示:

在这里插入图片描述

4.总结

到此, 一个10分类的基于MobileNet v2的网络就训完了, 测试的方法比较简单, 就像之前使用的例子一样, 这里不再详细写出. 这篇教程的目的, 主要在于通过MobileNet v2的网络设计, 了解和使用DepthwiseConv2d以及配合构造残差模块, 并且对 如何设计一个适用于移动端的模型(计算量/参数量小)有一个基本的概念.

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: Age of Ai 设计师:meimeiellie 返回首页