MobileNetV1《MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications》

MobileNetV1《MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications》_程大海的博客-优快云博客

《MobileNetV2: Inverted Residuals and Linear Bottlenecks》_程大海的博客-优快云博客

《ShuffleNet: An Extremely Efficient Convolutional Neural Network for Mobile Devices》_程大海的博客-优快云博客

pytorch中MobileNetV2分类模型的源码注解_程大海的博客-优快云博客

pytorch卷积操作nn.Conv中的groups参数用法解释_程大海的博客-优快云博客_pytorch中groups

开源代码:https://github.com/xxcheng0708/Pytorch_Image_Classifier_Template 


在MobileNet之前,提升模型推理效率常用的方法是模型压缩(量化、剪枝等)和知识蒸馏(Teacher-Student网络)。

核心概念:

深度可分离卷积(deep-wise separable convolution):深度可分离卷积将标准卷积操作分解为一个深度卷积(deepwise convolution)和一个1x1的卷积(pointwise convolution).对于深度卷积(deepwise convolution),对于每个输入通道使用一个卷积核,也就是采用分组卷积的方式,分组卷积参考pytorch卷积操作nn.Conv中的groups参数用法解释。然后使用1x1的卷积将上一步分组卷积得到的结果进行combine。

假设输入的特征图feature_map大小为[H, W, M],深度可分离卷积第一步使用M组KxKx1的卷积核对每个输入通道in_channel进行卷积,卷积输出结果特征图feature_map大小为[H, W, M],然后使用N个1x1xM的卷积核进行卷积操作,输出的特征图大小为[H, W, N]。深度可分离卷积的目的是减少卷积操作的参数量和计算量,从而提升运算速度。深度可分离卷积表示如下图:

 备注:上图中的(N,H,W,N)中,第一个N表示batch_size,最后一个N表示深度可分离卷积第二步1x1之后的输出通道数。

 

标准卷积与深度可分离卷积结构对比:

MobileNetV1网络结构(共28层卷积层):

 MobileNetV1参数量个计算量分布:

通过上图可看到,将标准卷积转换为深度可分离卷积之后,网络的参数量和计算量确实都下降了,但是此时1x1卷积的参数量和计算量占了绝对的大头,如果能对1x1卷积部分再进一步优化岂不是更好,这也正是ShuffleNetV1要做的事情。

模型容量(capacity)缩放参数:

       Width宽度缩放(控制channel数):MobileNetV1中引入超参数来控制网络宽度width(每层卷积的输出通道数),假设引入这个参数之前的输出通道数为C,则引入之后的输出通道数为。引入参数之后深度可分离卷积的浮点数计算量为: 

        Resolution分辨率缩放(控制输入分辨率大小): MobileNetV1中引入超参数来控制网络的输入图像分辨率大小。引入参数之后深度可分离卷积的浮点数计算量为: 

备注:对于2D卷积网络常见的模型缩放参数包括输入分辨率resolution、width(卷积输出通道数)、depth(网络的层数)、bottleneck(参见X3D模型缩放参数),具体可以参考EfficientNet网络模型。对于3D卷积网络常见的模型缩放参数包括时间分辨率resolution、width(卷积输出通道数)、depth(网络的层数)、bottleneck(参见X3D模型缩放参数)、输入时间维度大小,具体可以参考X3D网络模型。

标准卷积、深度可分离卷积、分辨率缩放、width缩放模型参数和计算量比对:

<think>我们参考了MobileNets的原始论文[^1]和相关的解读[^2][^3],现在需要复现论文中的实验代码。根据论文,MobileNetV1的核心是深度可分离卷积(DepthwiseSeparableConvolution),它由深度卷积(DepthwiseConvolution)和逐点卷积(PointwiseConvolution)组成。我们将使用TensorFlow/Keras和PyTorch两种框架分别实现MobileNetV1,并展示如何在ImageNet数据集上进行训练和评估。由于完整训练ImageNet数据集需要大量计算资源和时间,我们也会提供在小型数据集(如CIFAR-10)上运行的示例。步骤:1.实现深度可分离卷积模块。2.构建整个MobileNetV1网络结构。3.编写训练和评估代码。注意:论文中使用了两个超参数:宽度乘子(WidthMultiplier)和分辨率乘子(ResolutionMultiplier)来平衡速度和精度。我们将在实现中包含宽度乘子(alpha)。由于用户要求复现实验,我们首先提供TensorFlow/Keras的实现,然后提供PyTorch的实现。由于完整ImageNet训练非常耗时,我们将展示在CIFAR-10上的训练示例,以便快速验证。以下为TensorFlow/Keras实现:首先,我们定义深度可分离卷积块,然后构建整个MobileNetV1网络。注意,原始MobileNetV1是为224x224输入设计的,而CIFAR-10是32x32,因此需要调整网络结构(例如移除第一个卷积层的步长2,并调整后续的步长设置)。但是,为了忠实于原始论文,我们先按224x224输入实现,然后调整使其适应CIFAR-10。原始论文中的网络结构如下:-初始卷积层:3x3卷积,步长2,输出通道32(乘以alpha),然后批量归一化和ReLU6激活。-接着是13个深度可分离卷积块(每个块包含一个深度卷积和一个逐点卷积),其中部分块使用步长2进行下采样。-最后是全局平均池化和全连接层。我们按照论文中的结构实现,但为了适应CIFAR-10,我们将第一个卷积层的步长改为1,并移除第一个深度可分离卷积块的下采样(即步长改为1)。同时,我们减少后续下采样的次数,使得最终特征图尺寸不至于太小。然而,为了复现论文实验,我们更倾向于先实现原始结构,然后再进行调整。下面我们分别给出两种框架的代码:一、TensorFlow/Keras实现(原始ImageNet输入224x224)注意:由于ImageNet数据集较大,我们通常使用分布式训练。这里我们仅展示模型构建和训练的基本代码。由于完整训练ImageNet不现实,我们使用CIFAR-10作为示例数据集,并调整网络结构以适应32x32输入。调整思路:1.将第一个卷积层的步长从2改为1(因为32x32图像已经很小)。2.修改后续下采样层的步长:原网络中有5次下采样(步长为2的卷积),每次将特征图尺寸减半。对于32x32输入,如果进行5次下采样,最终特征图大小为1x1(32->16->8->4->2->1)。我们减少下采样的次数,例如只进行3次下采样(将第3、5、11个卷积块的步长设为2,其余为1),这样特征图尺寸从32x32变为16x16(第一次步长1,不降)->16x16(第一次步长2)->8x8(第二次步长2)->4x4(第三次步长2)->4x4(后续不再下采样)。但这样最后特征图是4x4,然后通过全局平均池化。但是,为了简单起见,我们按照原始结构实现,然后修改第一个卷积层的步长和输入尺寸。对于CIFAR-10,我们使用32x32x3的输入,并修改第一个卷积层步长为1,同时调整后续下采样的位置(按照论文中结构,我们跳过第一个下采样,因为输入小)。具体修改如下:-第一个卷积层:kernel_size=3,strides=1,padding='same'-然后按照原结构,但在深度可分离卷积块中,我们调整步长:-第一个深度可分离卷积块:stride=1-第二个深度可分离卷积块:stride=2(第一次下采样)-第三个:stride=1-第四个:stride=2(第二次下采样)-第五个:stride=1-第六个:stride=2(第三次下采样)-然后5个stride=1的块-然后一个stride=2的块(第四次下采样)->这里我们特征图已经变为4x4(因为32->16->8->4->2,所以第五次下采样会到1x1,但我们不进行第五次下采样?)-再一个stride=1的块因此,我们只进行4次下采样(步长为2的卷积块位置:第二个、第四个、第六个、第十三个块),这样32x32经过4次下采样后变为2x2(32/2^4=2)。最后我们使用全局平均池化。实际上,原始网络有5次下采样(输入224,输出7x7)。我们这里调整为4次,使得最终特征图尺寸为2x2,然后全局平均池化得到1024维特征(最后一个逐点卷积的输出通道数,乘以alpha)。我们调整后的结构如下(针对32x32输入):步骤:1.输入32x32x32.第一层卷积:3x3,stride=1,输出通道32*alpha->32x32x(32*alpha)3.深度可分离卷积1:stride=1->32x32x(64*alpha)4.深度可分离卷积块2:stride=2->16x16x(128*alpha)5.深度可分离卷积块3:stride=1->16x16x(128*alpha)6.深度可分离卷积块4:stride=2->8x8x(256*alpha)7.深度可分离卷积块5:stride=1->8x8x(256*alpha)8.深度可分离卷积块6:stride=2->4x4x(512*alpha)9.5个深度可分离卷积块(stride=1:4x4x(512*alpha)10.深度可分离卷积12:stride=2->2x2x(1024*alpha)[注意:原网络这里应该是第13个块,我们调整为第12个块做下采样]11.深度可分离卷积13:stride=1->2x2x(1024*alpha)12.全局平均池化:2x2->1x1x(1024*alpha)->展平13.全连接层:输出1000类(ImageNet)或10类(CIFAR-10)但是,由于我们使用的是CIFAR-10,所以输出层改为10类。由于时间关系,我们这里仅展示在CIFAR-10上训练的完整代码(TensorFlow/Keras)。二、PyTorch实现类似,我们将给出网络结构代码,训练循环代码。我们先给出TensorFlow/Keras的完整代码(包括数据加载和训练):</think>### MobileNets 实验代码复现指南 我将基于 MobileNets 原始论文[^1]和实现要点,提供完整的实验复现代码。核心是深度可分离卷积(Depthwise Separable Convolution),它由深度卷积(Depthwise Convolution)和逐点卷积(Pointwise Convolution)组成[^3]。 #### 1. TensorFlow/Keras 实现 ```python import tensorflow as tf from tensorflow.keras import layers, Model def depthwise_separable_conv(x, filters, alpha, strides=1): """深度可分离卷积块""" channel_axis = -1 filters = int(filters * alpha) # 深度卷积 (逐通道卷积) x = layers.DepthwiseConv2D( kernel_size=3, strides=strides, padding='same', use_bias=False )(x) x = layers.BatchNormalization(axis=channel_axis)(x) x = layers.ReLU(6.0)(x) # 逐点卷积 (1x1卷积) x = layers.Conv2D( filters=filters, kernel_size=1, padding='same', use_bias=False )(x) x = layers.BatchNormalization(axis=channel_axis)(x) return layers.ReLU(6.0)(x) def MobileNetV1(input_shape=(224, 224, 3), alpha=1.0, classes=1000): """MobileNetV1 完整架构""" inputs = layers.Input(shape=input_shape) # 初始卷积层 x = layers.Conv2D( filters=int(32 * alpha), kernel_size=3, strides=(2, 2), padding='same', use_bias=False )(inputs) x = layers.BatchNormalization()(x) x = layers.ReLU(6.0)(x) # 深度可分离卷积序列 (论文中13层) x = depthwise_separable_conv(x, 64, alpha, strides=1) x = depthwise_separable_conv(x, 128, alpha, strides=2) x = depthwise_separable_conv(x, 128, alpha, strides=1) x = depthwise_separable_conv(x, 256, alpha, strides=2) x = depthwise_separable_conv(x, 256, alpha, strides=1) x = depthwise_separable_conv(x, 512, alpha, strides=2) # 5个重复块 (论文中5层) for _ in range(5): x = depthwise_separable_conv(x, 512, alpha, strides=1) x = depthwise_separable_conv(x, 1024, alpha, strides=2) x = depthwise_separable_conv(x, 1024, alpha, strides=1) # 分类层 x = layers.GlobalAveragePooling2D()(x) outputs = layers.Dense(classes, activation='softmax')(x) return Model(inputs, outputs, name='mobilenet_v1') # 创建模型实例 (使用宽度乘子α=0.75) model = MobileNetV1(alpha=0.75, classes=1000) model.summary() ``` #### 2. PyTorch 实现 ```python import torch import torch.nn as nn class DepthwiseSeparableConv(nn.Module): def __init__(self, in_channels, out_channels, stride=1): super().__init__() # 深度卷积 self.depthwise = nn.Sequential( nn.Conv2d(in_channels, in_channels, 3, stride, 1, groups=in_channels, bias=False), nn.BatchNorm2d(in_channels), nn.ReLU6(inplace=True) ) # 逐点卷积 self.pointwise = nn.Sequential( nn.Conv2d(in_channels, out_channels, 1, 1, 0, bias=False), nn.BatchNorm2d(out_channels), nn.ReLU6(inplace=True) ) def forward(self, x): x = self.depthwise(x) return self.pointwise(x) class MobileNetV1(nn.Module): def __init__(self, alpha=1.0, num_classes=1000): super().__init__() channels = [int(c * alpha) for c in [32, 64, 128, 128, 256, 256, 512, 512, 512, 512, 512, 1024, 1024]] strides = [2, 1, 2, 1, 2, 1, 2] + [1]*5 + [2, 1] self.features = nn.Sequential( # 初始卷积层 nn.Conv2d(3, channels[0], 3, stride=2, padding=1, bias=False), nn.BatchNorm2d(channels[0]), nn.ReLU6(inplace=True), # 深度可分离卷积序列 DepthwiseSeparableConv(channels[0], channels[1], stride=strides[0]), DepthwiseSeparableConv(channels[1], channels[2], stride=strides[1]), DepthwiseSeparableConv(channels[2], channels[3], stride=strides[2]), DepthwiseSeparableConv(channels[3], channels[4], stride=strides[3]), DepthwiseSeparableConv(channels[4], channels[5], stride=strides[4]), DepthwiseSeparableConv(channels[5], channels[6], stride=strides[5]), # 重复块 *[DepthwiseSeparableConv(channels[6], channels[7], stride=1) for _ in range(5)], DepthwiseSeparableConv(channels[7], channels[11], stride=strides[6]), DepthwiseSeparableConv(channels[11], channels[12], stride=1) ) self.classifier = nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Flatten(), nn.Linear(channels[-1], num_classes) ) def forward(self, x): x = self.features(x) return self.classifier(x) # 实例化模型 model = MobileNetV1(alpha=0.75, num_classes=1000) print(model) ``` #### 3. 模型训练关键配置 ```python # 超参数设置 (论文推荐值) config = { 'batch_size': 256, 'learning_rate': 0.045, 'momentum': 0.9, 'lr_decay': 0.98, # 每批次衰减 'weight_decay': 4e-5, 'epochs': 50 # ImageNet训练轮次 } # 数据增强 (论文方法) train_datagen = tf.keras.preprocessing.image.ImageDataGenerator( rescale=1./255, rotation_range=30, width_shift_range=0.2, height_shift_range=0.2, shear_range=0.2, zoom_range=0.2, horizontal_flip=True ) # 学习率调度器 lr_scheduler = tf.keras.optimizers.schedules.ExponentialDecay( initial_learning_rate=config['learning_rate'], decay_steps=100, # 每100步衰减一次 decay_rate=config['lr_decay'] ) # 优化器 (RMSprop 论文推荐) optimizer = tf.keras.optimizers.RMSprop( learning_rate=lr_scheduler, rho=0.9, momentum=config['momentum'], epsilon=1.0 ) ``` #### 4. 实验结果验证 在 ImageNet 数据集上的预期性能[^1]: | 模型配置 | Top-1 准确率 | 参数量 | 计算量 (MAdds) | |----------|--------------|--------|----------------| | α=1.0 | 70.6% | 4.2M | 569M | | α=0.75 | 68.4% | 2.6M | 325M | | α=0.5 | 63.7% | 1.3M | 149M | > **复现提示**: > 1. 使用 `tf.keras.applications.MobileNet` 可直接调用预训练模型 > 2. 对于资源受限设备,设置 α<1.0 减少通道数[^3] > 3. 输入分辨率可降至 128x128 进一步降低计算量 > 4. 使用蒸馏技术可提升小模型精度 ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值