怎么做钓鱼网站生成,网站建设选超速云建站,wordpress 多网站吗,哪个网站查食品建设好MobileNet 系列
在本文会介绍 MobileNet 系列#xff0c;重点在于其模型结构的轻量化设计#xff0c;主要介绍详细的轻量化设计原则#xff0c;基于这原则#xff0c;MobileNetV1 是如何设计成一个小型#xff0c;低延迟#xff0c;低功耗的参数化模型#xff0c;可以满…MobileNet 系列
在本文会介绍 MobileNet 系列重点在于其模型结构的轻量化设计主要介绍详细的轻量化设计原则基于这原则MobileNetV1 是如何设计成一个小型低延迟低功耗的参数化模型可以满足各种用例的资源约束。可以更方便的实现分类检测嵌入和分割等功能。会结合目前较流行的深度学习技术在 V1 的基础会分别讲解 V2V3V4 做出的改进升级让读者们更深入了解轻量级网络结构的设计思路与过程。
MobileNet V1 模型
MobileNet V1 是一种体积较小、计算量较少、适用于移动设备的卷积神经网络。mobileNet V1 的主要创新点是用深度可分离卷积(depthwise separable convolution)代替普通的卷积并使用宽度乘数(width multiply)减少参数量在 ImageNet 图像分类、Stanford Dog 细粒度图像分类、目标检测、人脸属性识别、人脸编码、以图搜地等计算机视觉任务上结合知识蒸馏进行评估MobileNet 表现出极致的轻量化和速度性能。
逐通道卷积
逐通道卷积Depthwise Convolution的一个卷积核只有一个通道输入信息的一个通道只被一个卷积核卷积这个过程产生的 feature map 通道数和输入的通道数完全一样如下图所示 深度分离卷积把输入特征图的所有通道进行分离每个通道对应的一个卷积核对该通道的特征图进行单独的卷积操作(也就是说第 m 个深度卷积核作用在输入的第 m 个通道上得到输出结果的第 m 个通道)。在深度分离卷积中每个卷积核的深度固定为 1。
参数量: D k × D k × M D_{k}\times D_{k}\times M Dk×Dk×M
计算量 : D k × D k × M × D f × D f D_{k}\times D_{k}\times M\times D_{f}\times D_{f} Dk×Dk×M×Df×Df
其中 D k D_{k} Dk 为卷积核尺寸 D f D_{f} Df 为特征图尺寸M 为输入通道数输出通道数为 1。
逐点卷积
逐点卷积Pointwise Convolution的本质就是 1 × 1 1\times 1 1×1 的卷积它的卷积核的尺寸为 1 × 1 × M 1\times 1\times M 1×1×MM 为上一层输出信息的通道数。所以这里 Pointwise Convolution 的每个卷积核会将上一步的特征图在通道方向上进行加权组合生成新的特征图如下图所示
参数量: 1 × 1 × M × N 1\times 1\times M\times N 1×1×M×N
计算量: 1 × 1 × M × N × D f × D f 1\times 1\times M\times N\times D_{f}\times D_{f} 1×1×M×N×Df×Df
其中卷积核尺寸是 1 × 1 1\times1 1×1 D f D_{f} Df 为特征图尺寸 M M M 为输入通道数 N N N 为输出通道数。
因此计算一次深度可分离卷积的总体计算量为: D k ⋅ D k ⋅ M ⋅ D F ⋅ D F M ⋅ N ⋅ D F ⋅ D F D_{k}\cdot D_{k}\cdot M\cdot D_{F}\cdot D_{F} M\cdot N\cdot D_{F}\cdot D_{F} Dk⋅Dk⋅M⋅DF⋅DFM⋅N⋅DF⋅DF
它们减少计算量的比例(参数量比例同计算量)为: D k ⋅ D k ⋅ M ⋅ D F ⋅ D F M ⋅ N ⋅ D F ⋅ D F D k ⋅ D k ⋅ M ⋅ N ⋅ D F ⋅ D F 1 N 1 D k 2 \frac{D_{k}\cdot D_{k}\cdot M\cdot D_{F}\cdot D_{F} M\cdot N\cdot D_{F}\cdot D_{F}}{D_{k}\cdot D_{k}\cdot M\cdot N\cdot D_{F}\cdot D_{F}}\frac{1}{N}\frac{1}{D_{k}^{2}} Dk⋅Dk⋅M⋅N⋅DF⋅DFDk⋅Dk⋅M⋅DF⋅DFM⋅N⋅DF⋅DFN1Dk21
# 定义 DW、PW 卷积模块
def conv_dw(inp, oup, stride):return nn.Sequential(# DW DW 卷积的卷积核输入与输出的数量一致且等于分组数nn.Conv2d(inp, inp, 3, stride, 1, groupsinp, biasFalse), nn.BatchNorm2d(inp),nn.ReLU(inplaceTrue),# PWnn.Conv2d(inp, oup, 1, 1, 0, biasFalse),nn.BatchNorm2d(oup),nn.ReLU(inplaceTrue),)宽度分辨率乘子
宽度和分辨率调整系数用于调整模型的大小和计算复杂性。 宽度系数α宽度系数是一个介于 0 和 1 之间的比例因子。通过降低每个卷积层的通道数可以减少模型中的参数数量和计算量从而使模型更轻量化。 分辨率系数ρ分辨率系数是一个介于 0 和 1 之间的比例因子。通过降低输入图像的分辨率可以减少卷积操作的计算量和内存消耗。
计算量: D k ⋅ D k ⋅ α M ⋅ ρ D F ⋅ ρ D F α M ⋅ α N ⋅ ρ D F ⋅ ρ D F D_{k}\cdot D_{k} \cdot αM\cdotρD_{F}\cdotρD_{F} αM\cdot αN\cdotρD_{F}\cdotρD_{F} Dk⋅Dk⋅αM⋅ρDF⋅ρDFαM⋅αN⋅ρDF⋅ρDF
计算量减少了: D k ⋅ D k ⋅ α M ⋅ ρ D F ⋅ ρ D F α M ⋅ α N ⋅ ρ D F ⋅ ρ D F D k ⋅ D k ⋅ M ⋅ N ⋅ D F ⋅ D F α ρ N α 2 ρ 2 D k 2 \frac{D_{k}\cdot D_{k} \cdot αM\cdotρD_{F}\cdotρD_{F} αM\cdot αN\cdotρD_{F}\cdotρD_{F}}{D_{k}\cdot D_{k} \cdot M\cdot N\cdot D_{F}\cdot D_{F}} \frac{αρ}{N}\frac{α^{2}ρ^{2}}{D_{k}^{2}} Dk⋅Dk⋅M⋅N⋅DF⋅DFDk⋅Dk⋅αM⋅ρDF⋅ρDFαM⋅αN⋅ρDF⋅ρDFNαρDk2α2ρ2
网络结构实现
在 V1 结构中会加入 BN并使用 ReLU 激活函数所以 depthwise separable convolution 的基本结构如下图右面所示, 左面是正常的 conv import torch.nn as nn
import torch
# 定义普通卷积、BN、激活模块
def conv_bn(inp, oup, stride):return nn.Sequential(nn.Conv2d(inp, oup, 3, stride, 1, biasFalse),nn.BatchNorm2d(oup),nn.ReLU(inplaceTrue))
# 定义 DW、PW 卷积模块
def conv_dw(inp, oup, stride):return nn.Sequential(# dwnn.Conv2d(inp, inp, 3, stride, 1, groupsinp, biasFalse), # DW 卷积的卷积核输入与输出的数量一致且等于分组数nn.BatchNorm2d(inp),nn.ReLU(inplaceTrue),# pwnn.Conv2d(inp, oup, 1, 1, 0, biasFalse),nn.BatchNorm2d(oup),nn.ReLU(inplaceTrue),)整体网络就是通过不断堆叠 MBconv 组件组成的,首先是一个 3x3 的标准卷积然后后面就是堆积 depthwise separable convolution并且可以看到其中的部分 depthwise convolution 会通过 strides2 进行 down sampling。经过 卷积提取特征后再采用 average pooling 将 feature 变成 1x1根据预测类别大小加上全连接层最后是一个 softmax 层。
import torch.nn as nn
import torchclass MobileNetV1(nn.Module):def __init__(self, ch_in, n_classes):super(MobileNetV1, self).__init__()self.model nn.Sequential(conv_bn(ch_in, 32, 2), #普通卷积conv_dw(32, 64, 1), #DW 卷积conv_dw(64, 128, 2),conv_dw(128, 128, 1),conv_dw(128, 256, 2),conv_dw(256, 256, 1),conv_dw(256, 512, 2),conv_dw(512, 512, 1),conv_dw(512, 512, 1),conv_dw(512, 512, 1),conv_dw(512, 512, 1),conv_dw(512, 512, 1),conv_dw(512, 1024, 2),conv_dw(1024, 1024, 1),nn.AdaptiveAvgPool2d(1) #自适应平均池化)self.fc nn.Linear(1024, n_classes) #线性层def forward(self, x):x self.model(x)x x.view(-1, 1024)x self.fc(x)return xMobileNet V2 模型
MobileNet-V2 的主要思想就是在 v1 的基础上引入了线性瓶颈 (Linear Bottleneck)和逆残差 (Inverted Residual)来提高网络的表征能力同样也是一种轻量级的卷积神经网络。
Linear Bottlenecks
MobileNetV1 中引入α参数来做模型通道的缩减相当于给模型“瘦身”这样特征信息就能更集中在缩减后的通道中。但研究人员发现深度可分离卷积中有大量卷积核为 0即有很多卷积核没有参与实际计算。研究后发现是 ReLU 激活函数的问题认为 ReLU 这个激活函数在低维空间运算中会损失很多信息而在高维空间中会保留较多有用信息 。ReLU 会对维度较低的张量造成较大的信息损耗。
维度越低损失信息越多。如下图 2 和 3 已经没有螺旋的样子了维度越高损失信息越少当原始输入维度数增加到 15 以后再加 ReLU基本不会丢失太多的信息接近输入。 如果manifold of interest兴趣流形都为非零值则经过 ReLU 相当于只做了一个线性变换没有信息丢失维度足够多时ReLU 能够保留manifold of interest兴趣流形的完整信息。
论文针对这个问题在 Bottleneck 末尾使用 Linear Bottleneck(即不使用 ReLU 激活做了线性变换)来代替原本的非线性激活变换。具体到 V2 网络中就是将最后的 Point-Wise 卷积的 ReLU6 都换成线性函数。
实验证据表明使用线性层至关重要因为它可以防止非线性破坏太多的信息。
可分离卷积演化
可分离卷积块的演化如下图所示
标准卷积一个大方块深度可分离卷积(Depthwise convolutionPointwise Convolution薄片片方块块)linear bottleneck(高维后)relu6-dw-relu6-pw降维-升维-和图©等效(线性激活后)pw 升维-relu6-dw-relu6-pw降维-线性激活 反向残差引入
反向残差Inverted residuals 如下图所示 Original residual blockreduce – transfer – expand 中间窄两头宽
Residual block 先用 1 × 1 1 \times 1 1×1 卷积降通道过 ReLU再 3 × 3 3 \times 3 3×3 卷积过 ReLU最后再用 1 × 1 1 \times 1 1×1 卷积过 ReLU 恢复通道并和输入相加。之所以要 1 × 1 1 \times 1 1×1 卷积降通道是为了减少计算量不然中间的 $ 3 \times 3$ 卷积计算量太大。所以 Residual block 是中间窄两头宽。
Inverted residual blockexpand – transfer – reduce 中间宽两头窄
在 Inverted Residual block 中$ 3 \times 3$ 卷积变成 Depthwise 了计算量很少了所以通道数可以多一点效果更好所以通过 1 × 1 1 \times 1 1×1 卷积先提升通道数再 Depthwise $ 3 \times 3$ 卷积最后用 1 × 1 1 \times 1 1×1 卷积降低通道数。两端的通道数都很小所以 1 × 1 1 \times 1 1×1 卷积升通道和降通道计算量都并不大而中间的通道数虽然多但是 Depthwise 的卷积计算量也不。
# 定义 mobile 网络基本结构--即到残差结构
class InvertedResidual(nn.Module):def __init__(self, in_channel, out_channel, stride, expand_ratio):super(InvertedResidual, self).__init__()hidden_channel in_channel * expand_ratioself.use_shortcut stride 1 and in_channel out_channel # stride 1 and in_channel out_channel保证输入矩阵与输出矩阵的 shape 一致且通道数也一致这样才可以进行 shurtcutlayers []if expand_ratio ! 1: # 表示如果扩展因子不为 1 时则使用 1x1 的卷积层即对输入特征矩阵的深度进行扩充# 1x1 pointwise convlayers.append(ConvBNReLU(in_channel, hidden_channel, kernel_size1))layers.extend([# 3x3 depthwise conv# 在 pytorch 中如果设置的 group1 的话就为普通卷积如果设置的值为输入特征矩阵的深度的话即 in_channel则为深度卷积deptwise conv并且 Dw 卷积的输出特征矩阵的深度等于输入特征矩阵的深度ConvBNReLU(hidden_channel, hidden_channel, stridestride, groupshidden_channel),# 1x1 pointwise conv(linear) 因为其后跟随的是线性激活函数即 yx所以其后面不在跟随激活函数nn.Conv2d(hidden_channel, out_channel, kernel_size1, biasFalse),nn.BatchNorm2d(out_channel),])self.conv nn.Sequential(*layers)def forward(self, x):if self.use_shortcut:return x self.conv(x)else:return self.conv(x)ReLU6 激活
卷积之后通常会接一个 ReLU 非线性激活在 MobileNet 中使用 ReLU6。ReLU6 在普通的 ReLU 基础上限制最大输出为 6这是为了在移动端设备 float16/int8 的低精度的时候也能有很好的数值分辨率。
如果对 ReLU 的激活范围不加限制输出范围为 0 到正无穷如果激活值非常大分布在一个很大的范围内则低精度的 float16/int8 无法很好地精确描述如此大范围的数值带来精度损失。
# 定义普通卷积、BN 结构
class ConvBNReLU(nn.Sequential):def __init__(self, in_channel, out_channel, kernel_size3, stride1, groups1):padding (kernel_size - 1) // 2 # padding 的设置根据 kernel_size 来定如果 kernel_size 为 3则 padding 设置为 1如果 kernel_size 为 1为 padding 为 0super(ConvBNReLU, self).__init__(# 在 pytorch 中如果设置的 group1 的话就为普通卷积如果设置的值为输入特征矩阵的深度的话即 in_channel则为深度卷积deptwise conv并且 Dw 卷积的输出特征矩阵的深度等于输入特征矩阵的深度nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groupsgroups, biasFalse), # groups1示普通的卷积因为接下来要使用的是 BN 层此处的偏置不起任何作用所以设置为 1nn.BatchNorm2d(out_channel),nn.ReLU6(inplaceTrue) # 此处使用的是 Relu6 激活函数)
网络结构实现
V2 的加入了 1 × 1 1 \times 1 1×1 升维引入 Shortcut 并且去掉了最后的 ReLU改为 Linear。步长为 1 时先进行 1 × 1 1 \times 1 1×1 卷积升维再进行深度卷积提取特征再通过 Linear 的逐点卷积降维。
将 input 与 output 相加形成残差结构。步长为 2 时因为 input 与 output 的尺寸不符因此不添加 shortcut 结构。整个结构由 V2 block 堆叠而成。 from torch import nn
import torchdef _make_divisible(ch, divisor8, min_chNone):将输入的通道数(ch)调整到 divisor 的整数倍方便硬件加速This function is taken from the original tf repo.It ensures that all layers have a channel number that is divisible by 8It can be seen here:if min_ch is None:min_ch divisornew_ch max(min_ch, int(ch divisor / 2) // divisor * divisor)# Make sure that round down does not go down by more than 10%.if new_ch 0.9 * ch:new_ch divisorreturn new_ch# 定义 mobileNetV2 网络
class MobileNetV2(nn.Module):def __init__(self, num_classes1000, alpha1.0, round_nearest8):super(MobileNetV2, self).__init__()block InvertedResidualinput_channel _make_divisible(32 * alpha, round_nearest) # 将卷积核的个数调整为 8 的整数倍last_channel _make_divisible(1280 * alpha, round_nearest)inverted_residual_setting [# t, c, n, s[1, 16, 1, 1],[6, 24, 2, 2],[6, 32, 3, 2],[6, 64, 4, 2],[6, 96, 3, 1],[6, 160, 3, 2],[6, 320, 1, 1],]features []# conv1 layerfeatures.append(ConvBNReLU(3, input_channel, stride2)) # 添加第一层普通卷积层# building inverted residual residual blockesfor t, c, n, s in inverted_residual_setting:output_channel _make_divisible(c * alpha, round_nearest) # 根据 alpha 因子调整卷积核的个数for i in range(n): # 循环添加倒残差模块stride s if i 0 else 1 # s 表示的是倒残差模块结构中第一层卷积对应的步距剩余层都是 1features.append(block(input_channel, output_channel, stride, expand_ratiot)) # 添加一系列倒残差结构input_channel output_channel# building last several layersfeatures.append(ConvBNReLU(input_channel, last_channel, 1)) # 构建最后一层卷积层# combine feature layersself.features nn.Sequential(*features)# building classifierself.avgpool nn.AdaptiveAvgPool2d((1, 1)) # 采用自适应平均采样层self.classifier nn.Sequential(nn.Dropout(0.2),nn.Linear(last_channel, num_classes))# weight initialization 初始化全只能怪for m in self.modules():if isinstance(m, nn.Conv2d):nn.init.kaiming_normal_(m.weight, modefan_out)if m.bias is not None:nn.init.zeros_(m.bias)elif isinstance(m, nn.BatchNorm2d):nn.init.ones_(m.weight)nn.init.zeros_(m.bias)elif isinstance(m, nn.Linear):nn.init.normal_(m.weight, 0, 0.01) # 初始化为正态分布的函数均值为 0方差为 0.01nn.init.zeros_(m.bias)def forward(self, x):x self.features(x)x self.avgpool(x)x torch.flatten(x, 1)x self.classifier(x)return xMobileNet V3 模型
MobileNetV3 是由谷歌团队在 2019 年提出的轻量化网络模型传统的卷积神经网络内容需求大运算量大无法再移动设备以及嵌入式设备上运行为了解决这一问题MobileNet V3 网络应运而生。在移动端图像分类、目标检测、语义分割等任务上均取得了优秀的表现。MobileNetV3 采用了很多新的技术包括针对通道注意力的 Squeeze-and-Excitation 模块、NAS 搜索方法等这些方法都有利于进一步提升网络的性能。
重新设计耗时层结构
首先减少网络第一个卷积层的卷积核个数从 32 减到 16然后精简了最后的 Stage将原来搜索到的最后阶段的人工精简删除了多余的卷积层将延迟较少了 7 毫秒将近全部运行时间的 11%并减少了 3000 万的乘加操作次数几乎没有损失准确性。 重新设计激活函数
引入新的非线性激活函数h-swish。swish 公式 S w i s h x x ∗ α ( x ) Swish x x*α(x) Swishxx∗α(x)
这里σ(x)是 sigmoid 函数。swish 虽然提高了精度但 sigmoid 函数计算是极为昂贵的在嵌入式移动端不适合它的存在因此MobileNet_V3 提出了计算更为简便的 h-swish 函数其定义如下 h − s w i s h [ x ] x R e L U 6 ( x 3 ) 6 h-swish[x] x\frac{ReLU6(x3)}{6} h−swish[x]x6ReLU6(x3)
替换前后能够对 swish 进行一个很好的近似如下图所示: 在网络结构搜索中作者结合两种技术资源受限的 NASplatform-aware NAS与 NetAdapt前者用于在计算和参数量受限的前提下搜索网络的各个模块所以称之为模块级的搜索Block-wise Search后者用于对各个模块确定之后网络层的微调。
NAS 搜索全局结构
采用 NSA 方法来搜寻全局网络结构另外需要针对轻量模型进行优化用一个多目标奖励。 A C C ( m ) x [ L A T ( m ) / T A R ] w {ACC(m)x[LAT(m)/TAR]}^{w} ACC(m)x[LAT(m)/TAR]w
来近似 pareto 最优解根据目标延迟 TAR 为每个模型 m 平衡模型精度 ACC(m)和延迟 LAT(m)。用较小的权重因子 w -0.15 来弥补不同的延迟的更大精度变化。从头训练了一个新的架构搜索找到了初始的 seed 模型然后应用 NetAdapt 和其他优化来获得最终的 MobilenetV3-Small 模型。
NetAdapt 搜索层结构 Δ A C C Δ L a t e n c y \frac{ΔACC}{ΔLatency} ΔLatencyΔACC
给定一个 K conv and FC layers 的网络 Net在每一步的结构更改中需要减少一个给定个值 deltaR然后调整每层的卷积核数生成一个 Net_simp 集合从中找到目标延时的网络。保持循环直到满足给定条件后 finetune 网络。V3 用了两种减少延迟的方法来产生网络 减少 expansion layer 的 size。 减少所有共享相同 bottleneck size 模块的瓶颈。
SE 结构
首先使用一个全局池化层将每个通道变成一个具体的数值然后接两个全连接层最后通过一个 H-Sigmoid 函数获取最终的权重赋值给最初的特征图。 # 注意力机制模块SE 模块即两个全连接层
# 该模块的基本流程是
# 先进行自适应平均池化(1x1)———1x1 的卷积层———relu 激活层———1x1 的卷积池化———hardsigmoid()激活函数激活
class SqueezeExcitation(nn.Module):def __init__(self, input_c: int, squeeze_factor: int 4):super(SqueezeExcitation, self).__init__()squeeze_c _make_divisible(input_c // squeeze_factor, 8) # 获得距离该数最近的 8 的整数倍的数字self.fc1 nn.Conv2d(input_c, squeeze_c, 1) # 该卷积的输出的 squeeze_c 是输入 input_c 的 1/4 其作用与全连接层一样self.fc2 nn.Conv2d(squeeze_c, input_c, 1)def forward(self, x: Tensor) - Tensor:scale F.adaptive_avg_pool2d(x, output_size(1, 1)) # 将特征矩阵每一个 channel 上的数据给平均池化到 1x1 的大小scale self.fc1(scale)scale F.relu(scale, inplaceTrue)scale self.fc2(scale)scale F.hardsigmoid(scale, inplaceTrue) # 激活函数return scale * x # 将得到的数据与传入的对应 channel 数据进行相乘反向残差结构
相对于 MobileNets_V2MobileNets_V3 的反向残差结构发生改变在 MobileNets_V2 的反向残差结构基础上加入了 SE 模块。
# 定义 block 的配置类
class InvertedResidualConfig:def __init__(self,input_c: int, # block 模块中的第一个 1x1 卷积层的输入 channel 数kernel: int, # depthwise 卷积的卷积核大小expanded_c: int, # block 模块中的第一个 1x1 卷积层的输出 channel 数out_c: int, # 经过 block 模块中第二个 1x1 卷积层处理过后得到的 channel 数use_se: bool, # 是否使用注意力机制模块activation: str, # 激活方式stride: int, # 步长width_multi: float): # width_multi调节每个卷积层所使用 channel 的倍率因子self.input_c self.adjust_channels(input_c, width_multi)self.kernel kernelself.expanded_c self.adjust_channels(expanded_c, width_multi)self.out_c self.adjust_channels(out_c, width_multi)self.use_se use_seself.use_hs activation HS # whether using h-swish activationself.stride stridestaticmethoddef adjust_channels(channels: int, width_multi: float):return _make_divisible(channels * width_multi, 8)# 此为 block 模块其包含第一个 1x1 卷积层、DeptWis 卷积层、SE 注意力机制层判断是否需求、第二个 1x1 卷积层、激活函数需要判断是否是非线性激活,ConvBNActivation 为普通卷积块卷积层
class InvertedResidual(nn.Module):def __init__(self,cnf: InvertedResidualConfig, # cnf:配置类参数norm_layer: Callable[..., nn.Module]): # norm_layer# BN 层super(InvertedResidual, self).__init__()if cnf.stride not in [1, 2]: # 判断某一层的配置文件其步长是否满足条件raise ValueError(illegal stride value.)# 判断是否进行短连接self.use_res_connect (cnf.stride 1 and cnf.input_c cnf.out_c) # 只有当步长为 1并且输入通道等于输出通道数layers: List[nn.Module] []activation_layer nn.Hardswish if cnf.use_hs else nn.ReLU # 判断当前的激活函数类型# expand# 判断是否相等如果相等则不适用 1x1 的卷积层增加 channel 维度不相等的话才使用该层进行升维度if cnf.expanded_c ! cnf.input_c:layers.append(ConvBNActivation(cnf.input_c,cnf.expanded_c,kernel_size1,norm_layernorm_layer,activation_layeractivation_layer))# depthwise layers.append(ConvBNActivation(cnf.expanded_c,cnf.expanded_c,kernel_sizecnf.kernel, # depthwise 卷积的卷积核大小stridecnf.stride,groupscnf.expanded_c, # 深度 DW 卷积norm_layernorm_layer, # BN 层activation_layeractivation_layer))# 判断是否需要添加 SE 模块if cnf.use_se:layers.append(SqueezeExcitation(cnf.expanded_c))# projectlayers.append(ConvBNActivation(cnf.expanded_c,cnf.out_c,kernel_size1,norm_layernorm_layer, # BN 层activation_layernn.Identity)) # 此层的 activation_layer 就是进行里普通的线性激活没有做任何的处理self.block nn.Sequential(*layers)self.out_channels cnf.out_cself.is_strided cnf.stride 1def forward(self, x: Tensor) - Tensor:result self.block(x)if self.use_res_connect:result x # 进行 shortcut 连接return result模型结构与实现
核心模块也是网络的基本模块。主要实现了通道可分离卷积SE 通道注意力机制残差连接。结构图如下 from typing import Callable, List, Optionalimport torch
from torch import nn, Tensor
from torch.nn import functional as F
from functools import partial# MobileNetV3 网络结构基础框架其包括模型的第一层卷积层———nx【bneckBlock 模块】———1x1 的卷积层———自适应平均池化层———全连接层———全连接层
class MobileNetV3(nn.Module):def __init__(self,inverted_residual_setting: List[InvertedResidualConfig], # beneckBlock 结构一系列参数列表last_channel: int, # 对应的是倒数第二个全连接层输出节点数 1280num_classes: int 1000, # 类别个数block: Optional[Callable[..., nn.Module]] None, # InvertedResidual 核心模块norm_layer: Optional[Callable[..., nn.Module]] None):super(MobileNetV3, self).__init__()if not inverted_residual_setting:raise ValueError(The inverted_residual_setting should not be empty.)elif not (isinstance(inverted_residual_setting, List) andall([isinstance(s, InvertedResidualConfig) for s in inverted_residual_setting])):raise TypeError(The inverted_residual_setting should be List[InvertedResidualConfig])if block is None:block InvertedResidual # block 类if norm_layer is None:norm_layer partial(nn.BatchNorm2d, eps0.001, momentum0.01) # partial()为 python 方法即为 nn.BatchNorm2d 传入默认的两个参数layers: List[nn.Module] []# building first layer# 构建第一层卷积结构firstconv_output_c inverted_residual_setting[0].input_c # 表示第一个卷积层输出的 channel 数layers.append(ConvBNActivation(3, # 输入图像数据的 channel 数firstconv_output_c, # 输出 channelkernel_size3,stride2,norm_layernorm_layer,activation_layernn.Hardswish))# building inverted residual blocks# 利用循环的方式添加 block 模块将每层的配置文件传给 blockfor cnf in inverted_residual_setting:layers.append(block(cnf, norm_layer))# building last several layerslastconv_input_c inverted_residual_setting[-1].out_c # 最后的 bneckblock 的输出 channellastconv_output_c 6 * lastconv_input_c # lastconv_output_c 与最后的 bneckblock 的输出 channel 数是六倍的关系# 定义最后一层的卷积层layers.append(ConvBNActivation(lastconv_input_c, # 最后的 bneckblock 的输出 channel 数lastconv_output_c, # lastconv_output_c 与最后的 bneckblock 的输出 channel 数是六倍的关系kernel_size1,norm_layernorm_layer,activation_layernn.Hardswish))self.features nn.Sequential(*layers)self.avgpool nn.AdaptiveAvgPool2d(1)self.classifier nn.Sequential(nn.Linear(lastconv_output_c, last_channel),nn.Hardswish(inplaceTrue),nn.Dropout(p0.2, inplaceTrue),nn.Linear(last_channel, num_classes))# initial weightsfor m in self.modules():if isinstance(m, nn.Conv2d):nn.init.kaiming_normal_(m.weight, modefan_out)if m.bias is not None:nn.init.zeros_(m.bias)elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):nn.init.ones_(m.weight)nn.init.zeros_(m.bias)elif isinstance(m, nn.Linear):nn.init.normal_(m.weight, 0, 0.01)nn.init.zeros_(m.bias)def _forward_impl(self, x: Tensor) - Tensor:x self.features(x)x self.avgpool(x)x torch.flatten(x, 1)x self.classifier(x)return xdef forward(self, x: Tensor) - Tensor:return self._forward_impl(x)MobileNet V4 模型
MobileNetV4:主要是针对移动设备设计的通用高效架构。在其核心部分引入了通用倒瓶颈UIB搜索块Mobile MQA带来了 39%速度提升。同时还带来了一种优化的神经架构搜索NAS方法它提高了 MobileNetV4 搜索的有效性。同时 UIB、Mobile MQA 以及精细化的 NAS 方法的整合使得 MNv4 模型系列在移动 CPU、DSP、GPU 以及像苹果神经引擎和谷歌 Pixel EdgeTPU 这样的专用加速器上几乎达到了帕累托最优——这是其他测试模型所不具备的特性。最后为了进一步提升准确度引入了一种新颖的蒸馏技术。借助这项技术MNv4-Hybrid-Large 模型在 ImageNet-1K 上的准确度达到了 87%在 Pixel 8 EdgeTPU 上的运行时间仅为 3.8ms。
设计原则
MobileNetV4 在平衡 MACs 和内存带宽方面进行了投资旨在以最低的成本获得最大的回报特别关注网络的起始和结束部分。
在网络的开头MobileNetV4 使用大而昂贵的前几层来显著提高模型的容量和下游准确性。这些初始层主要由大量的 MACs 组成因此它们仅在低 Ridge Point 硬件上成本较高。在网络的末端所有 MobileNetV4 变体使用相同大小的最终全连接(FC)层以最大化准确性尽管这导致较小尺寸的 MNV4 变体在高 Ridge Point 硬件上遭受更高的 FC 延迟。
由于大的初始卷积层在低 RP 硬件上成本高但在高 RP 硬件上并不昂贵而最终的全连接层在高 Ridge Point 硬件上成本高在低 Ridge Point 硬件上却不贵MobileNetV4 模型不会同时遭受这两种减速。
换句话说MNV4 模型能够使用提高准确性的昂贵层但不会同时承受这些层的组合成本从而在所有脊点上都实现了几乎是最优的 Pareto 性能。经过一系列的研究MobileNet V4 提出了以下的设计原则 : 多路径效率问题组卷积和类似的多路径设计尽管具有更低的浮点运算次数FLOPcounts但由于内存访问复杂度可能效率较低。 硬件支持很重要像 SqueezeandExciteSE这样的高级模块 GELU、LayerNorm 在 DSPs 上的支持并不好LayerNorm 的速度也比 BatchNorm 慢而 SE 在加速器上的表现也比较慢。 简洁的力量深度卷积和逐点卷积、ReLU、批量归一化以及简单的注意力机制例如MHSA——展示了卓越的效率和硬件兼容性。
MobileNet V4 具有以下原则: 标准组件优先考虑广泛支持的元素以实现无缝部署和硬件效率。 灵活的 UIB 模块作者新颖的可搜索 UIB 构建块支持可适应的空间和通道混合接收场调整以及最大化的计算利用率通过网络架构搜索NAS促进了效率和准确性之间的平衡妥协。 采用直接注意力机制作者的 MobileMQA 机制为了最佳性能而优先考虑简单性。
这些原则使得 MobileNetV4 在所有评估的硬件上大多数情况下都是帕累托最优的。
硬件无关 Pareto 效率
屋顶线模型为了使一个模型在普遍情况下都高效它必须能够在其理论的计算复杂度与实际硬件性能之间找到平衡。
在硬件目标上表现出色这些硬件目标的瓶颈极大地限制了模型性能这些瓶颈主要由硬件的最高计算吞吐量和最高内存带宽决定。
为此作者使用了屋顶线模型该模型估计给定工作负载的性能并预测它是受内存瓶颈还是计算瓶颈的限制。简而言之它忽略了特定的硬件细节只考虑工作负载的操作强度 LayerMACs/(WeightBytes ActivationBytes) 与硬件处理器和内存系统的理论极限之间的关系。
内存和计算操作大致是并行发生的因此两个中较慢的那个大约决定了延迟瓶颈。为了将屋顶线模型应用于以为索引的神经网络层作者可以以下述方式计算模型推理延迟 M A C T i m e i L a y e r M A C s i P e a k M A s MACTime_{i}\frac{LayerMAC_{s_{i}}}{PeakMA_{s}} MACTimeiPeakMAsLayerMACsi M o d e l T i m e ∑ i m a x ( M A C T i m e i , M e m T i m e i ) ModelTime\sum_{i}max(MACTime_{i},MemTime_{i}) ModelTimei∑max(MACTimei,MemTimei) M e m T i m e i W e i g h t B y t e s i A t i v a t i o n B y t e s i P e a k M e m m B W MemTime_{i}\frac{WeightBytes_{i}AtivationBytes_{i}}{PeakMemmBW} MemTimeiPeakMemmBWWeightBytesiAtivationBytesi
在屋顶线模型中硬件行为由_脊点_RP来总结即硬件的峰值 MACs 与峰值 MemBW 之比。也就是说这是实现最大性能所需的最小操作强度。
如下图 2,3 所示作者从 RP 预期的最低值0MAC/字节扫描到最高值500MACs/字节。屋顶线模型仅依赖于数据传输与计算的比率因此具有相同 RP 的所有硬件通过延迟对工作负载的排名是相同的。3 这意味着如果新目标的 RP 包含在扫描范围内那么扫描-RP 的屋顶线分析见下一段同样适用于未来的硬件和软件。 脊点扫描分析如图 2 和图 3 所示屋顶线模型揭示了 MobileNetV4 模型如何与其他卷积 MobileNets 相比实现硬件独立的几乎帕累托最优性能。在低脊点硬件例如 CPU上模型更可能受计算限制而非内存限制。
因此为了提高延迟即使以增加内存复杂度为代价如 MobileNetV3Large-1.5x也要最小化总的 MAC 数量。
在高脊点硬件上数据移动是瓶颈所以 MAC 不会显著减慢模型速度但可以增加模型容量如 MobileNetV1-1.5x。因此为低脊点优化的模型在高脊点上运行缓慢因为内存密集和低 MAC 的全连接FC层受到内存带宽的限制不能利用高可用的峰值 MAC。
通用反向 Bottlenecks
作者提出了通用逆瓶颈UniversalInvertedBottleneck, UIB模块这是一个适用于高效网络设计的可适应构建块它具有灵活地适应各种优化目标的能力而不会使搜索复杂度爆炸性增长。
倒瓶颈IB模块由 MobileNetV2 提出已成为高效网络的标准化构建模块。
基于最成功的 MobileNet 要素——可分离的深度卷积DW和点式PW扩展及倒瓶颈结构本文引入了一种新的构建块——通用倒瓶颈UIB块如下图所示。其结构相当简单。
作者在倒瓶颈块中引入了两个可选的 DW一个在扩展层之前另一个在扩展层和投影层之间。这些 DW 的存在与否是神经网络架构搜索NAS优化过程的一部分从而产生新的架构。尽管这种修改很简单但作者的新构建块很好地统一了几个重要现有块包括原始的 IB 块、ConvNext 块以及 ViT 中的 FFN 块。此外UIB 还引入了一种新的变体额外的深度卷积 IBExtraDW块。 除了在神经网络架构搜索NAS过程中允许灵活的中间层IB结构外作者还避免了任何人为设计的缩放规则比如在 EfficientNet 中使用的那种而是为每个模型大小单独优化结构。
为了防止 NAS 超级网络的大小爆炸性增长作者共享了通用组件逐点扩展和投影并简单地将深度可分离卷积DWs作为额外的搜索选项添加进去。结合基于超级网络的网络架构搜索算法这种方法使得大多数参数大于 95%可以在不同的实例之间共享使得 NAS 变得极其高效。
倒瓶颈IB- 在扩展的特征激活上进行空间混合以增加成本为代价提供更大的模型容量。
ConvNext 通过在扩展之前执行空间混合实现了使用更大核尺寸进行更廉价的空间混合。
ExtraDW 是本文提出的一种新变体它允许廉价地增加网络的深度和感受野。它提供了以下几点优势结合 ConvNext 与 IB.4 的优势。
FFN 是由两个 1x1 的点状卷积PW堆叠而成并在它们之间加入激活和标准化层。PW 是最受加速器友好的操作之一但最好与其他模块一起使用。
#倒瓶颈层实现
class UniversalInvertedBottleneckBlock(nn.Module):def __init__(self, inp, oup, start_dw_kernel_size, middle_dw_kernel_size, middle_dw_downsample,stride,expand_ratio):super().__init__()# Starting depthwise conv.self.start_dw_kernel_size start_dw_kernel_sizeif self.start_dw_kernel_size: stride_ stride if not middle_dw_downsample else 1self._start_dw_ conv_2d(inp, inp, kernel_sizestart_dw_kernel_size, stridestride_, groupsinp, actFalse)# Expansion with 1x1 convs.expand_filters make_divisible(inp * expand_ratio, 8)self._expand_conv conv_2d(inp, expand_filters, kernel_size1)# Middle depthwise conv.self.middle_dw_kernel_size middle_dw_kernel_sizeif self.middle_dw_kernel_size:stride_ stride if middle_dw_downsample else 1self._middle_dw conv_2d(expand_filters, expand_filters, kernel_sizemiddle_dw_kernel_size, stridestride_, groupsexpand_filters)# Projection with 1x1 convs.self._proj_conv conv_2d(expand_filters, oup, kernel_size1, stride1, actFalse)def forward(self, x):if self.start_dw_kernel_size:x self._start_dw_(x)x self._expand_conv(x)if self.middle_dw_kernel_size:x self._middle_dw(x)x self._proj_conv(x)return xMobileMQA
在本节中作者介绍了 MobileMQA这是一个专门为加速器优化的新型注意力块它能提供超过 39%的推理速度提升。
操作强度的重要性近期在视觉模型的研究中人们大多致力于减少算术运算MACs以提高效率。然而在移动加速器上性能的真正瓶颈往往不是计算而是内存访问。这是因为加速器提供的计算能力远大于内存带宽。因此仅仅最小化 MACs 可能并不会导致更好的性能。相反作者必须考虑操作强度即算术运算与内存访问的比率。
MQA 在混合模型中是高效的MHSA 将 Query、键和值投影到多个空间以捕捉信息的不同方面。多 Query 注意力MQA通过在所有头之间使用共享的键和值简化了这一点。尽管多个 Query 头是必要的但大型语言模型可以有效共享单个键和值的头而不会牺牲准确度。对于键和值使用一个共享的头当批处理的 Token 数量相对于特征维度较小时可以大大减少内存访问需求从而显著提高操作强度。
这对于面向移动应用的混合视觉模型通常是这种情况在这种情况下仅在具有高特征维度的低分辨率后期阶段使用注意力并且批大小通常为 1。作者的实验证实了 MQA 在混合模型中的优势。如表 1 所示与 MHSA 相比MQA 在 EdgeTPUs 和三星 S23GPU 上实现了超过 39%的加速而质量损失可以忽略不计-0.03%。MQA 还将 MACs 和模型参数减少了 25%以上。据作者所知作者是第一个在移动视觉中使用 MQA 的。
采用非对称空间下采样受到 MQA 的启发它在 Query、键和值之间使用非对称计算作者将空间缩减注意力SRA融合到作者优化的 MQA 模块中以降低键和值的分辨率同时保持高分辨率 Query。这一策略是由混合模型中空间相邻标记之间的观察到的相关性所启发的这归因于早期层中的空间混合卷积滤波器。
通过非对称空间下采样作者在输入和输出之间保持了相同的标记数量保持了注意力的高分辨率并显著提高了效率。与不同作者的方法用步长为 2 的 3x3 深度卷积替换了 AvgPooling为提高模型容量提供了一种成本效益高的方式。
移动 MQA 这里作者提出了作者的移动 MQA 模块 M o b i l e M Q A ( X ) C o n c a t ( a t t e n t i o n 1 , . . . , a t t e n t i o n n ) W o Mobile_MQA(X)Concat(attention_{1},...,attention_{n})W^{o} MobileMQA(X)Concat(attention1,...,attentionn)Wo w h e r e a t t e n t i o n j s o f t m a x ( ( X W Q j ) ( S R ( X ) W K ) T d k ) ( S R ( X ) W V ) where attention_{j}softmax(\frac{(XW^{Q_{j}})(SR(X)W^K)^T}{\sqrt{d_{k}}})(SR(X)W^{V}) whereattentionjsoftmax(dk (XWQj)(SR(X)WK)T)(SR(X)WV)
其中 SR 表示空间缩减在作者设计中是指步长为 2 的深度可分离卷积DW或者在未使用空间缩减的情况下的恒等函数。结合非对称空间下采样可以在极小的精度损失-0.06%情况下带来超过 20%的效率提升。
NAS 优化与结构增强
为了有效地实例化 UIB 块作者采用了针对性能改进定制的 TuNAS。
增强搜索策略作者的方法通过实施两阶段搜索减轻了 TuNAS 因参数共享而偏向于较小滤波器和扩展因子的偏见。这种策略解决了 UIB 的深度层与其他搜索选项之间参数数量方差的问题。
粗粒度搜索起初作者专注于确定最优的滤波器大小同时保持参数固定一个默认扩展因子为 4 的反向瓶颈块和一个 3x3 的深度可分核。
细粒度搜索在初步搜索结果的基础上作者搜索 UIB 的两个深度可分层的配置包括它们的存在以及 3x3 或 5x5 的核大小同时保持扩展因子恒定为 4。
增强 TuNAS 的鲁棒训练 TuNAS 的成功取决于对架构质量的准确评估这对于奖励计算和政策学习至关重要。最初TuNAS 利用 ImageNet-1k 来训练超级网络以便进行架构评估。然而这种做法忽略了实际应用中网络可能遇到的噪声和扰动。为了解决这个问题作者建议在训练过程中加入鲁棒性训练。具体来说作者在训练集中引入了多种数据增强和对抗样本以此来增强模型的鲁棒性。
通过这种方式TuNAS 可以更好地评估架构在嘈杂环境下的性能从而提高最终学到的网络架构的质量。然而模型在 ImageNet 上的性能显著受到数据增强、正则化以及超参数选择的影响。鉴于 TuNAS 的架构样本在不断发展变化找到一个稳定的超参数集合是具有挑战性的。
作者通过一个离线的蒸馏数据集来解决这一问题这样就不需要额外的增强方法并减少了对正则化和优化设置的敏感性。最后作者将 TuNAS 的训练扩展到 750 个周期产生了更深、质量更高的模型。
关于 NAS作者方法如下:
搜索空间构建 固定初始层首先在第一阶段使用了一个 Conv2D 层3x3 核步长 2以便快速降低分辨率随后在第二阶段采用了 NAS 优化的融合 IB 块步长 2以平衡效率和准确性。 NAS 驱动的优化NAS 过程精确地确定了在剩余四个阶段中 UIB 块的数量和参数实例化确保了性能的最优结构。 固定 Head 层作者使用了与 MobileNetV3 相同的 Head 层配置。
观察到在 UIB 块内的点式卷积在高分辨率下往往表现出较低的运算强度作者优先在初始层中使用计算密度更高的操作以平衡效率和准确度。
优化目标 MNv4-Conv-S双重目标——285MMACs 和 0.2ms 延迟Pixel6EdgeTPU224px 输入。 MNv4-Conv-M0.6 毫秒延迟Pixel6EdgeTPU256 像素输入。 MNv4-Conv-L针对 384px 输入双重延迟目标为 2.3msPixel6EdgeTPU和 2.0msPixel7EdgeTPU。
需要注意的是通过将作者的搜索空间限制在跨设备具有良好相关成本模型的组件上作者发现 EdgeTPU 延迟优化可以直接产生普遍高效的模型。
from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple, Unionimport torch
import torch.nn as nnfrom mobilenet.model_config import MODEL_SPECS #通过配置参数构建 Mobile v4 block
def build_blocks(layer_spec):if not layer_spec.get(block_name):return nn.Sequential()block_names layer_spec[block_name]layers nn.Sequential()if block_names convbn:schema_ [inp, oup, kernel_size, stride]args {}for i in range(layer_spec[num_blocks]):args dict(zip(schema_, layer_spec[block_specs][i]))layers.add_module(fconvbn_{i}, conv_2d(**args))elif block_names uib:schema_ [inp, oup, start_dw_kernel_size, middle_dw_kernel_size, middle_dw_downsample, stride, expand_ratio]args {}for i in range(layer_spec[num_blocks]):args dict(zip(schema_, layer_spec[block_specs][i]))layers.add_module(fuib_{i}, UniversalInvertedBottleneckBlock(**args))elif block_names fused_ib:schema_ [inp, oup, stride, expand_ratio, act]args {}for i in range(layer_spec[num_blocks]):args dict(zip(schema_, layer_spec[block_specs][i]))layers.add_module(ffused_ib_{i}, InvertedResidual(**args))else:raise NotImplementedErrorreturn layers#构建 Mobilenet v4
class MobileNetV4(nn.Module):def __init__(self, model):# MobileNetV4ConvSmall MobileNetV4ConvMedium MobileNetV4ConvLarge# MobileNetV4HybridMedium MobileNetV4HybridLargeParams to initiate MobilenNetV4Args:model : support 5 types of models as indicated in super().__init__()assert model in MODEL_SPECS.keys()self.model modelself.spec MODEL_SPECS[self.model]# conv0self.conv0 build_blocks(self.spec[conv0])# layer1self.layer1 build_blocks(self.spec[layer1])# layer2self.layer2 build_blocks(self.spec[layer2])# layer3self.layer3 build_blocks(self.spec[layer3])# layer4self.layer4 build_blocks(self.spec[layer4])# layer5 self.layer5 build_blocks(self.spec[layer5]) def forward(self, x):x0 self.conv0(x)x1 self.layer1(x0)x2 self.layer2(x1)x3 self.layer3(x2)x4 self.layer4(x3)x5 self.layer5(x4)x5 nn.functional.adaptive_avg_pool2d(x5, 1 )return [x1, x2, x3, x4, x5]如果您想了解更多AI知识与AI专业人士交流请立即访问昇腾社区官方网站https://www.hiascend.com/或者深入研读《AI系统原理与架构》一书这里汇聚了海量的AI学习资源和实践课程为您的AI技术成长提供强劲动力。不仅如此您还有机会投身于全国昇腾AI创新大赛和昇腾AI开发者创享日等盛事发现AI世界的无限奥秘~