MobileNetV2详细总结以及代码讲解

本文深入解析MobileNetV2模型结构,强调其通过升维降维减少参数量的创新,详细介绍了模型网络代码实现过程,并展示了如何进行图片预测。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

模型介绍

特点:1.相比于MobileNetV1,先进行了1x1的卷积进行升维,目的在于获得更多特征,然后用3x3的空间卷积,最后再用1x1降维。核心思想是升维再降维,参数量更少。
2.为了避免Relu对特征的破坏,在在3x3网络结构前利用1x1卷积升维,在3x3网络结构后,再利用1x1卷积降维后,不再进行Relu6层,直接进行残差网络的加法。
PS:Relu对于负的输入,输出全为零;而本来特征就已经被“压缩”,再经过Relu的话,又要“损失”一部分特征,因此这里不采用Relu。

在这里插入图片描述

模型网络

t:1x1升维扩张的倍数。
c:输出通道数。
n:进行多少次。
s:步长。
在这里插入图片描述
如下图是bottleneck的结构,这里我们需要注意的是,步长维1和2的情况是不同的。stride=2时,不采用shortcut。因为维度变化,无法相加。
在这里插入图片描述

代码实现+图片预测

我们先将代码块分开说明。

首先,我们来看一下MoblieNet的更新点吧。

从上面我们知道:
1. (1,1)的pointwise卷积,提高通道数,结果Relu
2. (3,3)的depthwise卷积,提特征,结果Relu
3. (1,1)的pointwise卷积,单纯降维,不Relu
4.输入与输出的shortcut
所以在步长(2,2)的时候,要注意使用correct_pad填充特征图,
避免卷积出小数宽高特征图的情况。

这就是第一步啦,1x1扩张,relu6。

    if block_id:
        # Expand
        x = layers.Conv2D(expansion * in_channels,
                          kernel_size=1,
                          padding='same',
                          use_bias=False,
                          activation=None,
                          name=prefix + 'expand')(x)
        x = layers.BatchNormalization(axis=channel_axis,
                                      epsilon=1e-3,
                                      momentum=0.999,
                                      name=prefix + 'expand_BN')(x)
        x = layers.ReLU(6., name=prefix + 'expand_relu')(x)
    else:
        prefix = 'expanded_conv_'

在这里我们进行填充0的操作,为的是在3x3卷积后,长宽缩短到1/2,correct_pad是计算padding。

if stride == 2:
        x = ZeroPadding2D(padding=correct_pad(x, 3),
                          name=prefix + 'pad')(x)

然后就是可分离卷积了。

 # part2 可分离卷积
    x = DepthwiseConv2D(kernel_size=3,
                        strides=stride,
                        activation=None,
                        use_bias=False,
                        padding='same' if stride == 1 else 'valid',
                        name=prefix + 'depthwise')(x)
    x = BatchNormalization(epsilon=1e-3,
                           momentum=0.999,
                           name=prefix + 'depthwise_BN')(x)

    x = Activation(relu6, name=prefix + 'depthwise_relu')(x)

最后就是进行降维,这里不用relu。当stride=1以及输入输出通道数相同时,进行shortcut。


    # part3压缩特征,而且不使用relu函数,保证特征不被破坏
    x = Conv2D(pointwise_filters,
               kernel_size=1,
               padding='same',
               use_bias=False,
               activation=None,
               name=prefix + 'project')(x)

    x = BatchNormalization(epsilon=1e-3,
                           momentum=0.999,
                           name=prefix + 'project_BN')(x)

    if in_channels == pointwise_filters and stride == 1:
        return Add(name=prefix + 'add')([inputs, x])
    return x
下面是代码整合
#-------------------------------------------------------------#
#   MobileNetV2的网络部分
#-------------------------------------------------------------#
import math
import numpy as np
import tensorflow as tf
from tensorflow.keras import backend
from keras import backend as K
from keras.preprocessing import image
from keras.models import Model
from keras.layers.normalization import BatchNormalization
from keras.layers import Conv2D, Add, ZeroPadding2D, GlobalAveragePooling2D, Dropout, Dense
from keras.layers import MaxPooling2D,Activation,DepthwiseConv2D,Input,GlobalMaxPooling2D
from keras.applications import imagenet_utils
from keras.applications.imagenet_utils import decode_predictions
from keras.utils.data_utils import get_file


# TODO Change path to v1.1
BASE_WEIGHT_PATH = ('https://github.com/JonathanCMitchell/mobilenet_v2_keras/'
                    'releases/download/v1.1/')

# relu6!
def relu6(x):
    return K.relu(x, max_value=6)
    
# 用于计算padding的大小
def correct_pad(inputs, kernel_size):
    img_dim = 1
    input_size = backend.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 MobileNetV2(input_shape=[224,224,3],
                alpha=1.0,
                include_top=True,
                weights='imagenet',
                classes=1000):

    rows = input_shape[0]

    img_input = Input(shape=input_shape)

    # stem部分
    # 224,224,3 -> 112,112,32
    first_block_filters = _make_divisible(32 * alpha, 8)
    x = ZeroPadding2D(padding=correct_pad(img_input, 3),
                             name='Conv1_pad')(img_input)
    x = Conv2D(first_block_filters,
                      kernel_size=3,
                      strides=(2, 2),
                      padding='valid',
                      use_bias=False,
                      name='Conv1')(x)
    x = BatchNormalization(epsilon=1e-3,
                                  momentum=0.999,
                                  name='bn_Conv1')(x)
    x = Activation(relu6, name='Conv1_relu')(x)

    # 112,112,32 -> 112,112,16
    x = _inverted_res_block(x, filters=16, alpha=alpha, stride=1,
                            expansion=1, block_id=0)

    # 112,112,16 -> 56,56,24
    x = _inverted_res_block(x, filters=24, alpha=alpha, stride=2,
                            expansion=6, block_id=1)
    x = _inverted_res_block(x, filters=24, alpha=alpha, stride=1,
                            expansion=6, block_id=2)

    # 56,56,24 -> 28,28,32
    x = _inverted_res_block(x, filters=32, alpha=alpha, stride=2,
                            expansion=6, block_id=3)
    x = _inverted_res_block(x, filters=32, alpha=alpha, stride=1,
                            expansion=6, block_id=4)
    x = _inverted_res_block(x, filters=32, alpha=alpha, stride=1,
                            expansion=6, block_id=5)

    # 28,28,32 -> 14,14,64
    x = _inverted_res_block(x, filters=64, alpha=alpha, stride=2,
                            expansion=6, block_id=6)
    x = _inverted_res_block(x, filters=64, alpha=alpha, stride=1,
                            expansion=6, block_id=7)
    x = _inverted_res_block(x, filters=64, alpha=alpha, stride=1,
                            expansion=6, block_id=8)
    x = _inverted_res_block(x, filters=64, alpha=alpha, stride=1,
                            expansion=6, block_id=9)

    # 14,14,64 -> 14,14,96
    x = _inverted_res_block(x, filters=96, alpha=alpha, stride=1,
                            expansion=6, block_id=10)
    x = _inverted_res_block(x, filters=96, alpha=alpha, stride=1,
                            expansion=6, block_id=11)
    x = _inverted_res_block(x, filters=96, alpha=alpha, stride=1,
                            expansion=6, block_id=12)
    # 14,14,96 -> 7,7,160
    x = _inverted_res_block(x, filters=160, alpha=alpha, stride=2,
                            expansion=6, block_id=13)
    x = _inverted_res_block(x, filters=160, alpha=alpha, stride=1,
                            expansion=6, block_id=14)
    x = _inverted_res_block(x, filters=160, alpha=alpha, stride=1,
                            expansion=6, block_id=15)

    # 7,7,160 -> 7,7,320
    x = _inverted_res_block(x, filters=320, alpha=alpha, stride=1,
                            expansion=6, block_id=16)

    if alpha > 1.0:
        last_block_filters = _make_divisible(1280 * alpha, 8)
    else:
        last_block_filters = 1280

    # 7,7,320 -> 7,7,1280
    x = Conv2D(last_block_filters,
                      kernel_size=1,
                      use_bias=False,
                      name='Conv_1')(x)
    x = BatchNormalization(epsilon=1e-3,
                                  momentum=0.999,
                                  name='Conv_1_bn')(x)
    x = Activation(relu6, name='out_relu')(x)


    x = GlobalAveragePooling2D()(x)
    x = Dense(classes, activation='softmax',
                        use_bias=True, name='Logits')(x)

    inputs = img_input

    model = Model(inputs, x, name='mobilenetv2_%0.2f_%s' % (alpha, rows))

    # Load weights.
    if weights == 'imagenet':
        if include_top:
            model_name = ('mobilenet_v2_weights_tf_dim_ordering_tf_kernels_' +
                          str(alpha) + '_' + str(rows) + '.h5')
            weight_path = BASE_WEIGHT_PATH + model_name
            weights_path = get_file(
                model_name, weight_path, cache_subdir='models')
        else:
            model_name = ('mobilenet_v2_weights_tf_dim_ordering_tf_kernels_' +
                          str(alpha) + '_' + str(rows) + '_no_top' + '.h5')
            weight_path = BASE_WEIGHT_PATH + model_name
            weights_path = get_file(
                model_name, weight_path, cache_subdir='models')
        model.load_weights(weights_path)
    elif weights is not None:
        model.load_weights(weights)

    return model


def _inverted_res_block(inputs, expansion, stride, alpha, filters, block_id):
    in_channels = backend.int_shape(inputs)[-1]
    pointwise_conv_filters = int(filters * alpha)
    pointwise_filters = _make_divisible(pointwise_conv_filters, 8)
    x = inputs
    prefix = 'block_{}_'.format(block_id)

    # part1 数据扩张
    if block_id:
        # Expand
        x = Conv2D(expansion * in_channels,
                          kernel_size=1,
                          padding='same',
                          use_bias=False,
                          activation=None,
                          name=prefix + 'expand')(x)
        x = BatchNormalization(epsilon=1e-3,
                                      momentum=0.999,
                                      name=prefix + 'expand_BN')(x)
        x = Activation(relu6, name=prefix + 'expand_relu')(x)
    else:
        prefix = 'expanded_conv_'

    if stride == 2:
        x = ZeroPadding2D(padding=correct_pad(x, 3),
                                 name=prefix + 'pad')(x)
    
    # part2 可分离卷积
    x = DepthwiseConv2D(kernel_size=3,
                               strides=stride,
                               activation=None,
                               use_bias=False,
                               padding='same' if stride == 1 else 'valid',
                               name=prefix + 'depthwise')(x)
    x = BatchNormalization(epsilon=1e-3,
                                  momentum=0.999,
                                  name=prefix + 'depthwise_BN')(x)

    x = Activation(relu6, name=prefix + 'depthwise_relu')(x)

    # part3压缩特征,而且不使用relu函数,保证特征不被破坏
    x = Conv2D(pointwise_filters,
                      kernel_size=1,
                      padding='same',
                      use_bias=False,
                      activation=None,
                      name=prefix + 'project')(x)

    x = BatchNormalization(epsilon=1e-3,
                                  momentum=0.999,
                                  name=prefix + 'project_BN')(x)

    if in_channels == pointwise_filters and stride == 1:
        return Add(name=prefix + 'add')([inputs, x])
    return x
def preprocess_input(x):
    x /= 255.
    x -= 0.5
    x *= 2.
    return x

if __name__ == '__main__':
    model = MobileNetV2(input_shape=(224, 224, 3))
    model.summary()
    img_path = 'elephant.jpg'
    img = image.load_img(img_path, target_size=(224, 224))
    x = image.img_to_array(img)
    x = np.expand_dims(x, axis=0)
    x = preprocess_input(x)
    print('Input image shape:', x.shape)

    preds = model.predict(x)
    print(np.argmax(preds))
    print('Predicted:', decode_predictions(preds, 1))

<think>好的,我需要详细解释MobileNetV2的技术架构和工作原理。首先,根据用户提供的引用内容,MobileNetV2的核心创新是倒残差结构(InvertedResiduals)和线性瓶颈(LinearBottlenecks)。我需要结合这些点展开。 用户引用了多个资料,其中引用[1]提到倒残差结构和线性瓶颈,引用[2]也提到V2引入倒残差模块,引用[3]对比了不同模型的性能,引用[4]讨论了alpha值调整对模型的影响。这些都是关键点。首先,倒残差结构与传统的残差结构不同,传统的是两头窄中间宽,而倒残差是中间扩展后再压缩。这可能涉及扩张层和投影层的具体参数,比如扩张系数t。需要解释清楚这个结构的设计目的,比如保持信息的同时减少计算量。然后是线性瓶颈,引用[1]和[2]都提到,在瓶颈层使用线性激活函数(去掉ReLU),防止低维空间的信息丢失。这一点需要详细说明,为什么在低维时ReLU会有问题,比如ReLU导致神经元死亡,破坏特征信息。 接下来是网络结构,MobileNetV2由多个倒残差块堆叠而成,每个块的具体结构,比如1x1卷积升维,深度可分离卷积,再1x1卷积降维。需要分步骤说明每个部分的作用,比如深度可分离卷积如何减少参数量。工作原理部分需要结合上述结构,解释如何通过轻量化设计适应移动设备,比如使用更少的参数和计算量,同时保持准确率。可能还要提到alpha参数调整宽度,引用[4]中的不同alpha值对应的模型大小和准确率,说明模型的灵活性。另外,要确保使用正确的LaTeX格式,行内公式用$...$,独立公式用$$...$$。例如,扩张系数t可以写成$t$,参数计算可能涉及公式如输入通道数$C_{in}$,输出$C_{out}$等。需要生成相关问题,可能涉及与其他轻量级模型的对比,倒残差结构的具体优势,或者实际应用中的调整方法等。最后检查是否符合用户要求,结构清晰,引用正确,比如引用[1]到[4]在适当的位置标注,确保回答真实可靠,覆盖所有关键点。</think>### MobileNetV2 深度学习模型架构详解 #### 一、核心技术特点 1. **倒残差结构(Inverted Residuals)** - 与传统残差模块(ResNet)的“宽-窄-宽”结构相反,倒残差采用“窄-宽-窄”设计(图1)。输入先通过1x1卷积**升维**(扩张系数$t$),再经过深度可分离卷积提取特征,最后用1x1卷积**降维**。这一设计在减少计算量的同时保留更多特征信息[^1][^2]。 - 公式描述扩张过程: $$C_{out} = t \cdot C_{in}$$ 其中$t$通常取6,将输入通道数扩展6倍后再进行深度卷积。 2. **线性瓶颈(Linear Bottlenecks)** - 在降维的1x1卷积层**移除ReLU激活函数**,改用线性输出。实验表明,低维空间中使用ReLU会导致特征信息丢失,而线性层能缓解这一问题[^1][^2]。 - 例如,当通道数从64压缩到16时,最后一层使用线性变换而非ReLU。 3. **轻量化设计** - 全网络由多个倒残差块堆叠而成,结合**深度可分离卷积**(Depthwise Separable Convolution),将标准卷积拆分为深度卷积(逐通道)和逐点卷积(1x1),大幅减少参数量。 - 计算量对比: 标准卷积:$H \cdot W \cdot C_{in} \cdot C_{out} \cdot K^2$ 深度可分离卷积:$H \cdot W \cdot C_{in} \cdot K^2 + H \cdot W \cdot C_{in} \cdot C_{out}$ 其中$K$为卷积核大小,$H/W$为特征图尺寸。 #### 二、网络结构细节 MobileNetV2包含以下核心模块(以alpha=1.0为例): 1. **初始卷积层**:3x3标准卷积,步长2,输出通道322. **倒残差块序列**: - 每个块结构:1x1卷积升维 → ReLU6 → 3x3深度卷积 → ReLU6 → 1x1线性降维。 - 不同阶段通道数逐步增加(32→16→24→32→64→96→160→320),总层数约53层[^4]。 3. **尾部结构**:1x1卷积进一步扩展通道至1280,接全局平均池化和全连接分类层。 #### 三、工作原理 1. **特征复用**:倒残差结构通过中间的高维空间(扩展层)增强特征表达能力,降维时保留关键信息。 2. **计算效率**:深度可分离卷积减少约3~5倍计算量,使模型在移动端实时推理成为可能(如224x224输入仅需300M FLOPs)[^2]。 3. **参数调节**:通过alpha系数(0.35~1.4)控制网络宽度,平衡精度与速度。例如alpha=1.0时Top-1准确率28.66%,模型大小仅3.5MB[^4]。 #### 四、性能对比 | 模型 | Top-1 Acc. | 参数量 | FLOPs | |---------------|------------|---------|---------| | MobileNetV1 | 29.4% | 4.2M | 569M | | MobileNetV2 | 28.6% | 3.5M | 300M | | ShuffleNet v1 | 26.3% | 3.4M | 292M | (数据来源:论文及引用[3][^3]) --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

江小皮不皮

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

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

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

打赏作者

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

抵扣说明:

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

余额充值