图像分类学习笔记(七)——MobileNet

news2024/12/23 14:38:20

 一、MobileNetV1

传统的神经网络,内存需求大、运算量大,导致无法在移动设备以及嵌入式设备上运行。之前的VGG16模型权重大小大概有490M,ResNet模型权重大小大概有644M。MobileNet网络是由google团队在2017年提出的,专注于移动端或者嵌入式设备中轻量级CNN网络。相比于传统卷积神经网络,在准确率小幅降低的前提下大大减少模型参数与运算量(相比VGG16准确率减少了0.9%,但模型参数只有VGG的1/32)

(一)要点

  • Depthwise Convolution(大大减少运算量和参数数量)

  • 增加超参数α,β(α是控制卷积层卷积核个数的倍率,β是控制输入图像的大小),这两个参数是人为设定的,不是学习到的。
    • 将卷积核的个数减少后,能保证准确率小幅下降的情况下,减少运算量
    • 适当减少输入图像的大小,能保证准确率小幅下降的情况下,减少运算量
    • 可以根据项目需求,适当调整α和β

(二) 传统卷积、DW卷积、PW卷积

 传统卷积

  • 卷积核channel=输入特征矩阵channel
  • 输出特征矩阵channel=卷积核个数

 DW卷积/深度卷积(Depthwise Convolution)

  • 卷积核channel=1
  • 输入特征矩阵channel=卷积核个数=输出特征矩阵channel

即DW卷积中的每一个卷积核,只会和输入特征矩阵的一个channel进行卷积计算,所以输出的特征矩阵就等于输入的特征矩阵

 PW卷积/逐点卷积(Pointwise Conv)

  • 普通卷积,只不过卷积核大小为 1×1,进行升维

 (三)Depthwise Separable Conv深度可分卷积

一般来说,DW卷积核PW卷积是放在一起操作的,共同组成深度可分卷积操作。普通卷积和深度可分卷积的参数量和计算量的对比:

参数量和计算量:

  •  参数量是指网络中需要多少参数,对于卷积来说,就是卷积核里所有的值的个数,它往往和空间使用情况有关;
  • 计算量是指网络中我们进行了多少次乘加运算,对于卷积来说,我们得到的特征图都是进行一系列的乘加运算得到的,计算公式就是卷积核的尺寸DK x DK×M、卷积核个数N、及输出特征图尺寸DF x DF的乘积,计算量往往和时间消耗有关。

 普通卷积

  • 参数量:DK × DK × M × N
  • 计算量:DK × DK × M × N × DF × DF

深度可分卷积

  • 参数量:DK × DK × M + M × N
  • 计算量:DK × DK × M × DF × DF + M × N × DF × DF

 一般的,N较大,\frac{1}{N}可忽略不计,DK 表示卷积核的大小,若DK =3, \frac{1}{D_k^2}=\frac{1}{9},即我们若使用常见的3×3的卷积核,那么使用深度可分离卷积的参数量和计算量下降到原来的九分之一左右。(理论上普通卷积计算量是深度可分卷积的8倍到9倍)

 (四)MobileNetV1网络结构

 对于DW卷积,训练完之后会出现部分卷积核会费掉的问题,即卷积核参数大部分为零,也就是表示其实DW卷积没有起到多大的作用。对于这个问题,MobileNetV2有一定的改善。

二、MobileNetV2

MobileNet V2网络是由google团队在2018年提出的,相比于MobileNet V1网络,准确率更高,模型更小。

(一)要点

  • Inverted Residuals(倒残差结构)
  • Linear Bottlenecks(线性瓶颈结构)

(二)Inverted Residuals(倒残差结构)

ResNet的传统残差结构(Residual block):

  • 1×1卷积降维,3×3卷积处理,1×1卷积升维
  • 看图可知两头大,中间小
  • 激活函数为ReLU

 倒残差结构(Inverted Residuals):

  • 1×1卷积升维,3×3卷积DW,1×1卷积降维
  • 看图可知两头小,中间大
  • 激活函数为ReLU(6)

(三)Linear Bottlenecks

Linear Bottlenecks是针对倒残差结构最后一个1×1的卷积层,使用了线性的激活函数,而不是ReLU激活函数。

理由:

作者做了一个实验,输入一个二维的矩阵,channel为1,分别采用不同的矩阵T将其变换到更高的维度上,再使用ReLU激活函数得到输出值,再使用T矩阵的逆将其还原回二维的特征矩阵。在输入维度是2、3时,最后输出和输入相比丢失了较多信息;但是在输入维度是15到30时,最后输出则保留了输入的较多信息。也就是ReLU激活函数对低维特征信息造成大量损失 ,而对高维特征信息造成的损失比较小,又因为倒残差结构是两头小,中间大的结构,所以输出是低维的特征矩阵,所以需要线性的激活函数来替代ReLU激活函数。

因此这里可以有两种思路:

  • 一是把Relu激活函数替换成别的
  • 二是通过升维将输入的维度变高

倒残差结构图

 其中shortcut连接只有当stride=1并且输入特征矩阵与输出特征矩阵shape相同时才有。stride=1保证了输出特征矩阵宽高不变,因此shape相同特指输入输出特征矩阵的深度k = k ′

表中 t 为扩展因子,第一个1 x 1的卷积核个数为tk;第二层dw卷积s(stride为给定的),输出长宽变成1/s倍,深度不变;第三层1 x 1的卷积,降维操作,宽高不变,深度变为k’。

(四)MobileNetV2网络结构

  •  t是扩展因子(倍率)
  • c是输出特征矩阵channel,即k'
  • n是bottleneck的重复次数
  • s是步距(一个block中只针对第一个bottleneck,后面的bottleneck的步距都为1)
  • 表格中的第二行t=1,也就是该bottleneck的第一层卷积层没有对输入特征矩阵的深度进行调整,在pytorch和tensorflow的实现中,没有使用这层的1×1卷积的,而是直接使用了DW卷积。因为第一层卷积层既没有起到升维作用,又没有起到降维作用,所以其实是可以不需要的。
  •  其中shortcut连接只有当stride=1并且输入特征矩阵与输出特征矩阵shape相同时才有。在表格的第六行中有三个bottleneck,s=1,但是并没有shortcut,这是因为输入深度64,输出深度为96,无法进行相加。(shortcut连接只有当stride=1并且输入特征矩阵与输出特征矩阵shape相同时才有)
  • 最后的一个卷积层相当于一个全连接层,k代表分类的类别个数。

(五)性能对比

分类任务:

 目标检测任务:

三、MobileNetV3

(一)要点

  • 更新Block(bneck)
    • 加入SE(Squeeze-and-Excitation)模块(通道的注意力机制模块)
    • 更新了激活函数
  • 使用NAS(Neural Architecture Search)搜索参数
  • 重新设计耗时层结构

性能比较:

(二)更新Block

1、加入SE模块

针对得到的特征矩阵,对其每个channel进行池化处理,channel有多少得到的一维向量就有多少元素,再通过两个全连接层得到输出的特征向量。对于第一个全连接层,它的全连接层节点个数是输入特征矩阵channel的四分之一,第二个全连接层节点个数是和最开始输入特征矩阵channel保持一致的。对于最后输出的向量可以理解为对最开始输入特征矩阵的每个channel得出了一个权重关系,对于比较重要的channel就赋予更大的权重。

举例:

2、更新了激活函数

  •  图中的NL表示的就是非线性激活函数,因为在每一层所使用的非线性激活函数都不一样,所以没有明确标出具体是哪个激活函数。
  • 最后一层1×1卷积核用来降维的后面没有使用激活函数

 swish函数确实能够提高网络的准确率,但是计算、求导复杂,对量化过程不友好,针对这两个问题,作者提出了h-swish激活函数。

(三)重新设置耗时层结构

1、减少第一个卷积层的卷积核个数(32->16)

 将卷积核的个数从32减少为16后,准确率和原来的一样,那么使用更少的卷积核计算量会更少,大概节省了2ms。

2、精简Last Stage

 (四)MobileNetV3网络结构

MobileNetV3-Small

 MobileNetV3-Large

  •  exp size指用来升维的1×1卷积核的个数
  • #out 降维后(输出)的维度

四、使用Pytorch搭建MobileNetV2网络结构

from torch import nn
import torch


def _make_divisible(ch, divisor=8, min_ch=None):
    """
    This function is taken from the original tf repo.
    It ensures that all layers have a channel number that is divisible by 8
    It can be seen here:
    https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py
    """
    if min_ch is None:
        min_ch = divisor
    # int(ch + divisor / 2) // divisor类似于四舍五入的操作
    new_ch = max(min_ch, int(ch + divisor / 2) // divisor * divisor)
    # Make sure that round down does not go down by more than 10%.
    # 确保向下取整时不会减少超过10%
    if new_ch < 0.9 * ch:
        new_ch += divisor
    return new_ch

# 这个继承nn.Sequential而不是nn.Module这是根据pytorch官方实现样例来的
# 因为后续训练需要使用pytorch官方提供的网络预训练权重,所以按照官网所给的方式搭建
class ConvBNReLU(nn.Sequential):
    # 注意这里的groups,因为DW卷积还是用的是nn.Conv2d实现,如果groups=1就是普通卷积,如果groups设置为输入特征矩阵的深度就是DW卷积
    def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1):
        padding = (kernel_size - 1) // 2
        super(ConvBNReLU, self).__init__(
            nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False),
            nn.BatchNorm2d(out_channel),
            nn.ReLU6(inplace=True)
        )


class InvertedResidual(nn.Module):
    def __init__(self, in_channel, out_channel, stride, expand_ratio):
        super(InvertedResidual, self).__init__()
        hidden_channel = in_channel * expand_ratio
        # 当stride=1且输入特征矩阵与输出特征矩阵shape相同时才有shortcut连接
        self.use_shortcut = stride == 1 and in_channel == out_channel # 是个布尔变量

        layers = []
        if expand_ratio != 1:
            # 1x1 pointwise conv
            layers.append(ConvBNReLU(in_channel, hidden_channel, kernel_size=1))
        layers.extend([# extend和append的区别在于extend可以一次性插入很多个
            # 3x3 depthwise conv
            ConvBNReLU(hidden_channel, hidden_channel, stride=stride, groups=hidden_channel),
            # 1x1 pointwise conv(linear)
            nn.Conv2d(hidden_channel, out_channel, kernel_size=1, bias=False),
            nn.BatchNorm2d(out_channel),
            # 因为线性激活函数的表达式就是y=x,不对输入作任何处理,所以就不需要再额外添加激活函数
        ])

        self.conv = nn.Sequential(*layers)

    def forward(self, x):
        if self.use_shortcut:
            return x + self.conv(x)
        else:
            return self.conv(x)


class MobileNetV2(nn.Module):
    def __init__(self, num_classes=1000, alpha=1.0, round_nearest=8):
        super(MobileNetV2, self).__init__()
        block = InvertedResidual
        # 将卷积核个数(即输出通道数)调整为round_nearest的整数倍,更好的调用硬件设备
        input_channel = _make_divisible(32 * alpha, round_nearest)
        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 layer
        features.append(ConvBNReLU(3, input_channel, stride=2))
        # building inverted residual residual blockes
        for t, c, n, s in inverted_residual_setting:
            output_channel = _make_divisible(c * alpha, round_nearest)
            for i in range(n):
                stride = s if i == 0 else 1
                features.append(block(input_channel, output_channel, stride, expand_ratio=t))
                input_channel = output_channel
        # building last several layers
        features.append(ConvBNReLU(input_channel, last_channel, 1))
        # combine feature layers
        self.features = nn.Sequential(*features)

        # building classifier
        self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) # 自适应的平均池化下采样,给出输出矩阵的高和宽是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, mode='fan_out')
                if m.bias is not None: # 如果存在偏置,则偏置置为0
                    nn.init.zeros_(m.bias)
            elif isinstance(m, nn.BatchNorm2d): # 如果是BN层,方差设置为1.均值设置为0
                nn.init.ones_(m.weight)
                nn.init.zeros_(m.bias)
            elif isinstance(m, nn.Linear): # 如果是全连接层,对它的权重进行初始化(正态分布,均值为0,方差为0.01),偏置置为0
                nn.init.normal_(m.weight, 0, 0.01)
                nn.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 x

五、使用Pytorch搭建MobileNetV3网络结构

from typing import Callable, List, Optional

import torch
from torch import nn, Tensor
from torch.nn import functional as F
from functools import partial


def _make_divisible(ch, divisor=8, min_ch=None):
    """
    This function is taken from the original tf repo.
    It ensures that all layers have a channel number that is divisible by 8
    It can be seen here:
    https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py
    """
    if min_ch is None:
        min_ch = divisor
    new_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 += divisor
    return new_ch


class ConvBNActivation(nn.Sequential):
    def __init__(self,
                 in_planes: int,
                 out_planes: int,
                 kernel_size: int = 3,
                 stride: int = 1,
                 groups: int = 1,
                 norm_layer: Optional[Callable[..., nn.Module]] = None,
                 activation_layer: Optional[Callable[..., nn.Module]] = None):
        padding = (kernel_size - 1) // 2
        if norm_layer is None:
            norm_layer = nn.BatchNorm2d
        if activation_layer is None:
            activation_layer = nn.ReLU6
        super(ConvBNActivation, self).__init__(nn.Conv2d(in_channels=in_planes,
                                                         out_channels=out_planes,
                                                         kernel_size=kernel_size,
                                                         stride=stride,
                                                         padding=padding,
                                                         groups=groups,
                                                         bias=False),
                                               norm_layer(out_planes),
                                               activation_layer(inplace=True))


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)
        self.fc1 = nn.Conv2d(input_c, squeeze_c, 1)  # 和全连接层起到相同的作用
        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))
        scale = self.fc1(scale)
        scale = F.relu(scale, inplace=True)
        scale = self.fc2(scale)
        scale = F.hardsigmoid(scale, inplace=True)
        return scale * x

# 对应的是MobileNetV3中的每一个bneck结构的参数配置
class InvertedResidualConfig:
    def __init__(self,
                 input_c: int,
                 kernel: int,
                 expanded_c: int,
                 out_c: int,
                 use_se: bool,
                 activation: str,
                 stride: int,
                 width_multi: float): # width_multi就是相当于alpha参数,倍率因子
        self.input_c = self.adjust_channels(input_c, width_multi)
        self.kernel = kernel
        self.expanded_c = self.adjust_channels(expanded_c, width_multi)
        self.out_c = self.adjust_channels(out_c, width_multi)
        self.use_se = use_se
        self.use_hs = activation == "HS"  # whether using h-swish activation
        self.stride = stride

    @staticmethod
    def adjust_channels(channels: int, width_multi: float):
        return _make_divisible(channels * width_multi, 8)


class InvertedResidual(nn.Module):
    def __init__(self,
                 cnf: InvertedResidualConfig,
                 norm_layer: Callable[..., nn.Module]):
        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)

        layers: List[nn.Module] = []
        # 要使用Hardswish必须将pytorch更新到1.7或1.7以上
        activation_layer = nn.Hardswish if cnf.use_hs else nn.ReLU

        # expand
        if cnf.expanded_c != cnf.input_c:
            layers.append(ConvBNActivation(cnf.input_c,
                                           cnf.expanded_c,
                                           kernel_size=1,
                                           norm_layer=norm_layer,
                                           activation_layer=activation_layer))

        # depthwise
        layers.append(ConvBNActivation(cnf.expanded_c,
                                       cnf.expanded_c,
                                       kernel_size=cnf.kernel,
                                       stride=cnf.stride,
                                       groups=cnf.expanded_c,
                                       norm_layer=norm_layer,
                                       activation_layer=activation_layer))

        if cnf.use_se:
            layers.append(SqueezeExcitation(cnf.expanded_c))

        # project
        layers.append(ConvBNActivation(cnf.expanded_c,
                                       cnf.out_c,
                                       kernel_size=1,
                                       norm_layer=norm_layer,
                                       activation_layer=nn.Identity)) # nn.Identity就是线性激活,没有做任何处理

        self.block = nn.Sequential(*layers)
        self.out_channels = cnf.out_c
        self.is_strided = cnf.stride > 1

    def forward(self, x: Tensor) -> Tensor:
        result = self.block(x)
        if self.use_res_connect:
            result += x

        return result


class MobileNetV3(nn.Module):
    def __init__(self,
                 inverted_residual_setting: List[InvertedResidualConfig],
                 last_channel: int,
                 num_classes: int = 1000,
                 block: Optional[Callable[..., nn.Module]] = None,
                 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) and
                  all([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

        if norm_layer is None:
            # partial()是为BatchNorm2d传入了两个默认的参数eps和momentum
            norm_layer = partial(nn.BatchNorm2d, eps=0.001, momentum=0.01)

        layers: List[nn.Module] = []

        # building first layer
        firstconv_output_c = inverted_residual_setting[0].input_c
        layers.append(ConvBNActivation(3,
                                       firstconv_output_c,
                                       kernel_size=3,
                                       stride=2,
                                       norm_layer=norm_layer,
                                       activation_layer=nn.Hardswish))
        # building inverted residual blocks
        for cnf in inverted_residual_setting:
            layers.append(block(cnf, norm_layer))

        # building last several layers
        lastconv_input_c = inverted_residual_setting[-1].out_c
        lastconv_output_c = 6 * lastconv_input_c
        layers.append(ConvBNActivation(lastconv_input_c,
                                       lastconv_output_c,
                                       kernel_size=1,
                                       norm_layer=norm_layer,
                                       activation_layer=nn.Hardswish))
        self.features = nn.Sequential(*layers)
        self.avgpool = nn.AdaptiveAvgPool2d(1)
        self.classifier = nn.Sequential(nn.Linear(lastconv_output_c, last_channel),
                                        nn.Hardswish(inplace=True),
                                        nn.Dropout(p=0.2, inplace=True),
                                        nn.Linear(last_channel, num_classes))

        # initial weights
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode="fan_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 x

    def forward(self, x: Tensor) -> Tensor:
        return self._forward_impl(x)


def mobilenet_v3_large(num_classes: int = 1000,
                       reduced_tail: bool = False) -> MobileNetV3:
    """
    Constructs a large MobileNetV3 architecture from
    "Searching for MobileNetV3" <https://arxiv.org/abs/1905.02244>.

    weights_link:
    https://download.pytorch.org/models/mobilenet_v3_large-8738ca79.pth

    Args:
        num_classes (int): number of classes
        reduced_tail (bool): If True, reduces the channel counts of all feature layers
            between C4 and C5 by 2. It is used to reduce the channel redundancy in the
            backbone for Detection and Segmentation.
    """
    width_multi = 1.0
    bneck_conf = partial(InvertedResidualConfig, width_multi=width_multi)
    adjust_channels = partial(InvertedResidualConfig.adjust_channels, width_multi=width_multi)

    reduce_divider = 2 if reduced_tail else 1

    inverted_residual_setting = [
        # input_c, kernel, expanded_c, out_c, use_se, activation, stride
        bneck_conf(16, 3, 16, 16, False, "RE", 1),
        bneck_conf(16, 3, 64, 24, False, "RE", 2),  # C1
        bneck_conf(24, 3, 72, 24, False, "RE", 1),
        bneck_conf(24, 5, 72, 40, True, "RE", 2),  # C2
        bneck_conf(40, 5, 120, 40, True, "RE", 1),
        bneck_conf(40, 5, 120, 40, True, "RE", 1),
        bneck_conf(40, 3, 240, 80, False, "HS", 2),  # C3
        bneck_conf(80, 3, 200, 80, False, "HS", 1),
        bneck_conf(80, 3, 184, 80, False, "HS", 1),
        bneck_conf(80, 3, 184, 80, False, "HS", 1),
        bneck_conf(80, 3, 480, 112, True, "HS", 1),
        bneck_conf(112, 3, 672, 112, True, "HS", 1),
        bneck_conf(112, 5, 672, 160 // reduce_divider, True, "HS", 2),  # C4
        bneck_conf(160 // reduce_divider, 5, 960 // reduce_divider, 160 // reduce_divider, True, "HS", 1),
        bneck_conf(160 // reduce_divider, 5, 960 // reduce_divider, 160 // reduce_divider, True, "HS", 1),
    ]
    last_channel = adjust_channels(1280 // reduce_divider)  # C5

    return MobileNetV3(inverted_residual_setting=inverted_residual_setting,
                       last_channel=last_channel,
                       num_classes=num_classes)


def mobilenet_v3_small(num_classes: int = 1000,
                       reduced_tail: bool = False) -> MobileNetV3:
    """
    Constructs a large MobileNetV3 architecture from
    "Searching for MobileNetV3" <https://arxiv.org/abs/1905.02244>.

    weights_link:
    https://download.pytorch.org/models/mobilenet_v3_small-047dcff4.pth

    Args:
        num_classes (int): number of classes
        reduced_tail (bool): If True, reduces the channel counts of all feature layers
            between C4 and C5 by 2. It is used to reduce the channel redundancy in the
            backbone for Detection and Segmentation.
    """
    width_multi = 1.0
    bneck_conf = partial(InvertedResidualConfig, width_multi=width_multi)
    adjust_channels = partial(InvertedResidualConfig.adjust_channels, width_multi=width_multi)

    reduce_divider = 2 if reduced_tail else 1

    inverted_residual_setting = [
        # input_c, kernel, expanded_c, out_c, use_se, activation, stride
        bneck_conf(16, 3, 16, 16, True, "RE", 2),  # C1
        bneck_conf(16, 3, 72, 24, False, "RE", 2),  # C2
        bneck_conf(24, 3, 88, 24, False, "RE", 1),
        bneck_conf(24, 5, 96, 40, True, "HS", 2),  # C3
        bneck_conf(40, 5, 240, 40, True, "HS", 1),
        bneck_conf(40, 5, 240, 40, True, "HS", 1),
        bneck_conf(40, 5, 120, 48, True, "HS", 1),
        bneck_conf(48, 5, 144, 48, True, "HS", 1),
        bneck_conf(48, 5, 288, 96 // reduce_divider, True, "HS", 2),  # C4
        bneck_conf(96 // reduce_divider, 5, 576 // reduce_divider, 96 // reduce_divider, True, "HS", 1),
        bneck_conf(96 // reduce_divider, 5, 576 // reduce_divider, 96 // reduce_divider, True, "HS", 1)
    ]
    last_channel = adjust_channels(1024 // reduce_divider)  # C5

    return MobileNetV3(inverted_residual_setting=inverted_residual_setting,
                       last_channel=last_channel,
                       num_classes=num_classes)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/954415.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

分析三维模型OBJ格式轻量化在网络传输中的重要性

分析三维模型OBJ格式轻量化在网络传输中的重要性 三维模型的OBJ格式轻量化在网络传输中扮演着重要的角色。随着互联网的快速发展和普及&#xff0c;越来越多的三维模型需要通过网络进行传输&#xff0c;涉及到下载、上传、共享等场景。而原始的三维模型文件往往较大&#xff0c…

正中优配:巨头深夜发声:向全社会开放!历史新高,万亿AI龙头火了!

当地时间8月30日周三&#xff0c;美股三大股指团体收涨&#xff0c;接连第四日收高。美股8月买卖进入尾声&#xff0c;出资者重视通胀与工作方面的一些重要经济数据。万亿AI巨子英伟达收涨0.98%&#xff0c;股价再创前史新高。 据百度官方微信大众号8月31日0时音讯&#xff0c…

vue3渲染函数h的简单使用——定义局部组件

vue3渲染函数h的简单使用 基本用法 创建 Vnodes Vue 提供了一个 h() 函数用于创建 vnodes&#xff1a; import { h } from vueconst vnode h(div, // type{ id: foo, class: bar }, // props[/* children */] )更多用法 详情查看官方文档 在SFC中定义局部组件使用 h函数…

【Qt学习】10 利用QSharedMemory实现单例运行

问题 让应用程序只有一个运行实例 QSharedMemory除了可以完成进程间通信&#xff0c;还可以实现应用程序单例化。 解法 首先&#xff0c;看看QSharedMemory的几个函数&#xff1a; 1、QSharedMemory(const QString &key, QObject *parent Q_NULLPTR)构造函数 该构造函数…

Ceph的纠删码特性 EC(Erasure Code)代码流程

从GitHub上Clone Ceph项目&#xff0c;我是基于(ceph version 12.2.11 luminous 版本)的代码来分析的 一、EC&#xff08;Erasure Code&#xff09;是什么&#xff1f; Ceph的纠删码特性EC&#xff1a;将写入的数据分成N份原始数据&#xff0c;通过这N份原始数据计算出M份效验…

html2canvas 截图空白 或出现toDataURL‘ on ‘HTMLCanvasElement或img标签没截下来 的所有解决办法

1.如果截图空白&#xff1a; 1.1以下的参数是必须要有的。 width: shareContent.offsetWidth, //设置canvas尺寸与所截图尺寸相同&#xff0c;防止白边height: shareContent.offsetHeight, //防止白边logging: true,useCORS: true,x:0,y:0,2&#xff0c;如果出现了报错 toData…

go gin gorm连接postgres postgis输出geojson

go gin gorm连接postgres postgis输出geojson 1. 技术环境 go-gin-gorm postgres-postgis 2. 简单实现代码 思路就是&#xff1a;采用原生sql实现查询、更新等&#xff0c;采用gorm的raw来执行sql语句 package mainimport ("fmt""net/http""github.…

23HW-0Day(漏洞检测Tools)V1.7—批量漏洞检测

工具更新一、 更新如下漏洞利用&#xff1a; 1、用友移动管理系统appmanager文件上传 2、用友移动管理系统category文件上传 3、用友U8CRM文件上传 4、用友U8CRM文件读取 5、用友U8-Cloud文件上传 6、致远OAM1移动协同软件命令执行 工具更新二、 本次更新&#xff0c;工具支…

游戏测试和软件测试有什么区别?

针对手游而言&#xff0c;游戏测试的本质是APP&#xff0c;所以不少手游的测试方式与APP测试异曲同工&#xff0c;然而也有所不同。APP更多的是具有一种工具&#xff0c;一款APP好不好用不重要&#xff0c;关键点在于实用。而游戏则具有一种玩具属性&#xff0c;它并不见得实用…

基于mike平原河网水动力学模块建模教程

Mike 11是在河流模拟中应用非常广泛的优秀软件&#xff0c;在洪水演进、实时洪水预报、水库和水工建筑物优化调度、水工建筑物设计、桥梁的水力设计、溃坝过程模拟、河流泥沙输移和河道演变、河流及湿地的生态及水质评估、水污染对流和扩散、突发日常水质污染事故预警方面得到了…

像linux 一样清理Windows C盘

像 linux 有命令 du -sh 查看文件夹大小 但是windows 可就没有这个命令了&#xff0c;就算有命令&#xff0c;也不能扫描子目录里面的文件 但是windows 可以借助 软件来清理&#xff0c;和linux 一样 文件上面是目录&#xff0c;下面是文件所占用空间大小的图&#xff0c;咋…

【ES6】Promise.race的用法

Promise.race()方法同样是将多个 Promise 实例&#xff0c;包装成一个新的 Promise 实例。 const p Promise.race([p1, p2, p3]);上面代码中&#xff0c;只要p1、p2、p3之中有一个实例率先改变状态&#xff0c;p的状态就跟着改变。那个率先改变的 Promise 实例的返回值&#…

算法——排序

排序 下面的代码会用到宏定义&#xff0c;因为再C中没有swap交换函数&#xff0c;所以对于swap的宏定义代码如下&#xff1a; #define swap(a, b) {\__typeof(a) __a a; a b; b __a;\ } 稳定排序&#xff1a; 1.插入排序&#xff1a; 插入排序会将数组&#xff0c;分位两个部…

RS485/RS422 收发器(SIT3485和SIT3485E)

SIT3485 是一款 3.3V 供电、半双工、低功耗&#xff0c;功能完全满足 TIA/EIA-485 标准要求的 RS-485 收发器。 SIT3485 包括一个驱动器和一个接收器&#xff0c;两者均可独立使能与关闭。当两者均禁用时&#xff0c;驱动 器与接收器均输出高阻态。 SIT3485 具有 1…

手把手教你打造自己的AI聊天机器人程序(讯飞星火API)

案例背景 最近发现科大的讯飞星火大模型可以申请API试用了&#xff0c;我一直想用chatgpt的API&#xff0c;一是因为收费买不起&#xff0c;二是因为网络不方便..... 现在有了科大讯飞这个国内免费的&#xff0c;当然要试试。 目前讯飞星火可以申请试用他们的模型API&#x…

影视文化交流:如何通过Netflix了解和欣赏全球不同的影视文化

Netflix成立于1997年,是一家美国的在线流媒体平台。自推出以来,Netflix迅速发展壮大,目前已经成为全球领先的影视内容提供商之一。截至2022年底,Netflix在全球范围内拥有超过2.07亿的付费订阅用户,遍布190多个国家和地区。通过其便捷的平台和丰富的内容库,Netflix为全球观众带来…

Python怎么解决版本兼容性的问题? - 易智编译EaseEditing

Python的版本兼容性问题是在不同Python版本之间代码能否正常运行的问题。由于Python的语言和库在不同版本之间可能存在细微的差异&#xff0c;因此编写能够在多个Python版本上运行的代码是很重要的。 以下是一些解决Python版本兼容性问题的方法&#xff1a; 使用合适的语法&a…

R语言入门——line和lines的区别

目录 0 引言一、 line()二、 lines() 0 引言 首先&#xff0c;从直观上看&#xff0c;lines比line多了一个s&#xff0c;但它们还是有很大的区别的&#xff0c;下面将具体解释这个两个函数的区别。 一、 line() 从R语言的帮助文档中找到&#xff0c;line()的使用&#xff0c…

JS小知识点:在定义对象的时候,用变量值作为对象属性名

有没有一种情况&#xff0c;在定义一个对象时&#xff0c;对象的属性名由一个变量的值来决定的&#xff1f;而且是一步到位&#xff0c;简单实现的&#xff1f; 我们知道对象有时被称为关联数组&#xff0c;访问对象属性值的时候可以使用括号表示法&#xff08;比如 objectNam…

揭秘ChatGPT,如何打造自己的自定义指令 | 京东云技术团队

一、ChatGPT-0720更新 又在深夜&#xff0c;正要打开ChatGPT官网测试下pdf对话功能&#xff0c;发现ChatGPT又有更新。本次更新总结有2点&#xff1a; 1.对于Plus用户&#xff0c;GPT-4的使用限额从25条/3h提升至50条&#xff08;整整提升1倍~ $20的订阅费又更超值了&#xf…