MobileNet(二)

2023-10-30

MobileNets

MobileNetv1

深度可分离卷积

深度学习的经典网络模型,如ResNet、VGG、GooogleNet等已经达到了不错的效果,但存在一个问题,即模型庞大,参数较多,计算量较大,在一些实际的场景如移动或嵌入式设备中很难被应用。此时就出现了针对这些移动场景的轻量级的网络—MobileNets。

MobileNet模型是基于深度可分离卷积,这是一种分解卷积的形式。这种形式将标准卷积分解为一个深度卷积和一个逐点卷积的1x1的卷积。

深度卷积只处理长宽方向的空间信息

逐点卷积只处理跨通道方向的信息

深度卷积(dw)

深度卷积与标准卷积的区别在于深度卷积的卷积核为单通道模式,需要对输入的每一个通道进行卷积,以此得到和输入特征图通道数一致的输出特征图。

深度卷积中一个卷积核只处理一个通道,即每个卷积核只处理自己对应的通道。输入特征图有多少个通道就有多少个卷积核。将每个卷积核处理后的特征图堆叠在一起。输入和输出特征图的通道数相同。

输入特征图的通道上为3,由于采用的卷积核是单通道模式,所以要三个单通道卷积核才可和输入图像完全一一对应的卷积,以此得到了输出的特征图,输出特征图的个数为3。

深度卷积的输入特征图通道数 = 卷积核个数 = 输出特征图个数

由于只处理长宽方向的信息会导致丢失跨通道信息,为了将跨通道的信息补充回来,需要进行逐点卷积。

逐点卷积

通过深度卷积可得到特征图,但输入特征图通道数=卷积核个数=输出特征图个数,导致输出的特征图的个数太少了【或者可以说输出特征图的通道数太少了】,从而会影响信息的有效性。

此时就要进行逐点卷积,其实质就是用1x1的卷积核进行升维。【1x1的卷积核的主要作用就是对特征图进行升维和降维。】

从上一步得到了3个特征图【或者说成是一个有三个通道的特征图】,卷积核大小为1x1x3,卷积核个数为4,这样卷积后就可以得到4个特征图。

参数量和计算量

标准卷积

参数量:标准卷积的参数量是DK x DK × M x N
计算量:假设输出的特征图尺寸也是DF x DF,标准卷积的计算量是DK x DK × M x N x DF x DF。
参数量指网络中需要多少参数,对于卷积来说,就是卷积核里所有的值的个数,其往往和空间使用情况有关;

计算量指网络中进行了多少次乘加运算,对于卷积来说,得到的特征图都是进行一系列的乘加运算得到的,计算公式就是卷积核的尺寸DK x DK × M、卷积核个数N、及输出特征图尺寸DF x DF的乘积,计算量往往和时间消耗有关。】

深度可分离卷积

参数量

深度卷积:参数量为DK x DK × M
逐点卷积:参数量为M x N
深度可分离卷积的参数量为DK x DK × M + M x N

计算量

深度卷积:计算量为DK x DK × M x DF x DF
逐点卷积:计算量为M x N x DF x DF
深度可分离卷积的参数量为DK x DK × M x DF x DF+MxNxDF x DF

综上,得到了标准卷积和深度可分离卷积的参数量和计算量,用它们的比值进行比较:一般N较大,\frac{1}{N} 可忽略不计,DK 表示卷积核的大小,若DK =3,\frac{1}{D_k^2}=\frac{1}{9} 。若使用常见的 3 × 3 卷积核,那么深度可分离卷积的参数量和计算量下降到原来的九分之一左右。

MobileNetv1网络结构

MobileNetv2

在MobileNetv1的训练中,深度卷积时卷积核容易废掉,即训练完后很多卷积核是空的。

MobileNetv2认为造成的原因是由于Relu函数造成的,在输入维度是2,3时,输出和输入相比丢失了较多信息;但在输入维度是15到30时,输出则保留了输入的较多信息。

这说明在使用Relu函数时,当输入的维度较低时,会丢失较多信息,因此可以想到两种思路,一是把Relu激活函数替换成别的,二是通过升维将输入的维度变高。MobileNetv2就是这么做的:Linear Bottlenecks 和 Inverted Residuals。

MobileNetV2 使用了逆转残差模块。

输入图像,使用1x1卷积提升通道数 -> 在高维空间下使用深度卷积 -> 使用1x1卷积下降通道数,降维时采用线性激活函数(y=x)。

        当步长等于1且输入和输出特征图的shape相同时,使用残差连接输入和输出;

        当步长=2(下采样阶段)直接输出降维后的特征图。

对比 ResNet 的残差结构。

输入图像,使用1x1卷积下降通道数 -> 在低维空间下使用标准卷积 -> 使用1x1卷积上升通道数,激活函数都是ReLU函数。

        当步长等于1且输入和输出特征图的shape相同时,使用残差连接输入和输出;

        当步长=2(下采样阶段)直接输出降维后的特征图。

Linear Bottlenecks

深度可分离卷积的每个块构成:首先是一个3x3的深度卷积,其次是BN、Relu层,接下来是1x1的逐点卷积,最后又是BN和Relu层。

前文说要把Relu激活函数替换,MobileNetv2将Relu替换成线性激活函数。但不是将所有的Relu激活都换成了线性激活,而是将上图中标黄的Relu变成了线性激活函数。变换后的块在文章中称为Linear Bottlenecks,结构如下图所示:

Inverted Residuals

Inverted Residuals中文是倒残差结构

左侧为ResNet中的残差结构,其结构为1x1卷积降维 -> 3x3卷积 -> 1x1卷积升维;

右侧为MobileNetv2中的倒残差结构,其结构为1x1卷积升维 -> 3x3 DW卷积 -> 1x1卷积降维。MobileNetv2先使用1x1进行升维的原因是前面所说的高维信息通过ReLU激活函数后丢失的信息更少。

MobileNetv3

注:NAS(网络结构搜索),如VGG、ResNet、MobileNetv1、MobileNetv2等网络结构都是手动去设计的,像网络的层数、卷积核大小、步长等参数。而NAS是通过计算机来实现最优的参数设定,通过比较不同参数的网络模型效果,从而选择最优的参数设置。

新增SE模块

下图上半部分是MobileNetv2的block,下半部分是MobileNetv3的block。

MobileNetv3的block和MobileNetv2基本一致,主要是加入了SE模块(图中黄色框部分)。SE模块先将特征图的每个通道都进行平均池化,然后进行两个全连接层得到输出结果,这个结果会和原始的特征图进行相乘,得到新的特征图。

【注:第一个全连接层的输出设置为原来通道数的1/4,第二个全连接层输出设置为通道数】

SE注意力机制

  1. 将特征图进行全局平均池化,特征图有多少通道,那么池化结果(一维向量)就有多少元素,[h, w, c]==>[None, c]。
  2. 经过两个全连接层得到输出向量。第一个全连接层的输出通道数 = 原输入特征图通道数的1/4;第二个全连接层的输出通道数 = 原输入特征图的通道数:先降维后升维。
  3. 全连接层的输出向量可理解为,向量的每个元素是对每张特征图进行分析得出的权重关系。比较重要的特征图就会赋予更大的权重,即该特征图对应的向量元素值较大。反之,不太重要的特征图对应的权重值较小。
  4. 第一个全连接层使用ReLU激活函数,第二个全连接层使用 hard_sigmoid 激活函数
  5. 经过两个全连接层得到一个由channel个元素组成的向量,每个元素是针对每个通道的权重,将权重和原特征图的对应相乘,得到新的特征图数据

举例说明:下图左上角表示为两个通道的特质图,经平均池化后得到左下角的图;再次经过两次全连接层后,转化成了右下角的图,最后用右下角的0.5、0.6分别乘原始的特质图,则得到最终的右上角的图。

SE组件的作用:通过显示地建模通道之间的相互依存关系来增强通道级的特征响应(即学习一组权重,将权重赋予到每个通道来改善特征表示),使得重要特征得到加强,非重要特征得到弱化
具体来说,就是通过学习的方式来自动获取到每个特征通道的重要程度,然后依照这个重要程度去提升有用的特征,并抑制对当前任务用处不大的特征 

重新设计耗时层结构

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

        减少卷积核数量,使用ReLU或swish,准确率几乎相同。

  • 精简Last Stage

        Original Last Stage是通过NAS算出来的,但实际测试发现Efficient Last Stage结构可在不损失精度情况下去除一些多余的层。

重新设计激活函数

在MobileNetv1和MobileNetv2版本中使用的Relu激活函数是Relu6激活函数。

MobileNetv3版本使用的激活函数为h-swish。

MobileNetv3的网络结构

更改YOLOv5

搭建核心模块

①激活函数

定义hard_sigmoid激活函数和hard_swish激活函数

"""
swish: x / e^-x
h_sigmoid: ReLU6(x+3) / 6  ReLU6: min(max(x,0),6)
h_swish: x * ReLU6(x+3) / 6
"""
class h_sigmoid(nn.Module):
    def __init__(self, inplace=True):
        super(h_sigmoid, self).__init__()
        self.relu = nn.ReLU6(inplace=inplace)  # inplace为True,将会改变输入的数据;否则不改变原输入,只产生新的输出

    def forward(self, x):
        return self.relu(x + 3) / 6


class h_swish(nn.Module):
    def __init__(self, inplace=True):
        super(h_swish, self).__init__()
        self.sigmoid = h_sigmoid(inplace=inplace)

    def forward(self, x):
        return x * self.sigmoid(x)

②SE注意力机制

SE注意力机制由 全局平均池化 + 全连接层降维 + 全连接层升维 + 对应权重相乘 组成。

class SELayer(nn.Module):
    """
    对特征图进行全局平均池化,特征图有多少通道,那么池化结果(一维向量)就有多少元素,[h, w, c]==>[None, c]
    经过两个全连接层得到输出向量
        第一个全连接层的输出通道数 = 原输入特征图通道数的1/4; 第一个全连接层使用ReLU激活函数
        第二个全连接层的输出通道数 = 原输入特征图的通道数; 第二个全连接层使用 hard_sigmoid 激活函数
    经过两个全连接层得到一个由channel个元素组成的向量,每个元素是针对每个通道的权重,将权重和原特征图的对应相乘
    """
    def __init__(self, channel, reduction=4):
        super(SELayer, self).__init__()
        # Squeeze操作, 对tensor变量进行维度压缩, 获取全局信息
        self.avg_pool = nn.AdaptiveAvgPool2d(1)  # 全局平均池化, [b,h,w,c]==>[b,c]==>[b,1,1,c]   
        # Excitation操作(FC + ReLU + FC + Sigmoid), 经过一系列激活操作, 但不改变操作前后的大小和通道数
        self.fc = nn.Sequential(
            nn.Linear(channel, channel // reduction),  # 第一个全连接层的输出通道数=原输入特征图通道数的1/4
            nn.ReLU(inplace=True),
            nn.Linear(channel // reduction, channel),  # 第二个全连接层的输出通道数=原输入特征图通道数
            h_sigmoid()
        )

    def forward(self, x):
        b, c, _, _ = x.size()
        y = self.avg_pool(x)
        y = y.view(b, c)
        y = self.fc(y).view(b, c, 1, 1)  # 学习到的每一channel的权重
        return x * y  # 将权重和原特征图的对应相乘

③标准卷积块

标准卷积块是由 普通卷积 + 批标准化 + 激活函数 组成

class conv_bn_hswish(nn.Module):
    """
    This equals to
    def conv_3x3_bn(inp, oup, stride):
        return nn.Sequential(
            nn.Conv2d(inp, oup, 3, stride, 1, bias=False),
            nn.BatchNorm2d(oup),
            h_swish()
        )
    """

    def __init__(self, c1, c2, stride):
        super(conv_bn_hswish, self).__init__()
        # Conv2d(in_channels,out_channels,kernel_size,stride,padding,dilation,groups,bias)
        self.conv = nn.Conv2d(c1, c2, 3, stride, 1, bias=False)
        self.bn = nn.BatchNorm2d(c2)
        self.act = h_swish()

    def forward(self, x):
        return self.act(self.bn(self.conv(x)))

    def fuseforward(self, x):
        return self.act(self.conv(x))

④逆残差模块

class MobileNet_Block(nn.Module):
    def __init__(self, inp, oup, hidden_dim, kernel_size, stride, use_se, use_hs):
        """
        MobileNet_Block: [int_ch, out_ch, hidden_ch, kernel_size, stride, use_se, use_hs]
        :param hidden_dim: 表示在 Inverted residuals 中的扩张通道数
                           Inverted residuals block: ①1×1卷积升维  ②3×3卷积  ③1×1卷积降维
        :param use_se: 表示是否使用 SELayer
        :param use_hs: 表示使用 h_swish 还是 ReLU
        """
        super(MobileNet_Block, self).__init__()
        assert stride in [1, 2]

        self.identity = stride == 1 and inp == oup

        # 输入通道数 = 扩张通道数, 不进行通道扩张
        if inp == hidden_dim:
            self.conv = nn.Sequential(
                # dw, 深度卷积
                nn.Conv2d(hidden_dim, hidden_dim, kernel_size, stride,
                          (kernel_size - 1) // 2, groups=hidden_dim, bias=False),
                nn.BatchNorm2d(hidden_dim),
                h_swish() if use_hs else nn.ReLU(inplace=True),
                # Squeeze-and-Excite, SE模块
                SELayer(hidden_dim) if use_se else nn.Sequential(),
                # pw-linear, 逐点卷积
                nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
                nn.BatchNorm2d(oup),
            )
        else:
            # 输入通道数 ≠ 扩张通道数, 进行通道扩张
            self.conv = nn.Sequential(
                # pw
                nn.Conv2d(inp, hidden_dim, 1, 1, 0, bias=False),
                nn.BatchNorm2d(hidden_dim),
                h_swish() if use_hs else nn.ReLU(inplace=True),
                # dw
                nn.Conv2d(hidden_dim, hidden_dim, kernel_size, stride,
                          (kernel_size - 1) // 2, groups=hidden_dim,bias=False),
                nn.BatchNorm2d(hidden_dim),
                # Squeeze-and-Excite
                SELayer(hidden_dim) if use_se else nn.Sequential(),
                h_swish() if use_hs else nn.ReLU(inplace=True),
                # pw-linear
                nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
                nn.BatchNorm2d(oup),
            )

    def forward(self, x):
        y = self.conv(x)
        if self.identity:
            return x + y
        else:
            return y

加入YOLOv5

  • common.py文件修改,加入如下代码
# ---------------------------- MobileBlock start -------------------------------
from torch import nn

"""
swish: x / e^-x
h_sigmoid: ReLU6(x+3) / 6  ReLU6: min(max(x,0),6)
h_swish: x * ReLU6(x+3) / 6
"""
class h_sigmoid(nn.Module):
    def __init__(self, inplace=True):
        super(h_sigmoid, self).__init__()
        self.relu = nn.ReLU6(inplace=inplace)  

    def forward(self, x):
        return self.relu(x + 3) / 6


class h_swish(nn.Module):
    def __init__(self, inplace=True):
        super(h_swish, self).__init__()
        self.sigmoid = h_sigmoid(inplace=inplace)

    def forward(self, x):
        return x * self.sigmoid(x)


class SELayer(nn.Module):
    """
    对特征图进行全局平均池化,特征图有多少通道,那么池化结果(一维向量)就有多少元素,[h, w, c]==>[None, c]
    经过两个全连接层得到输出向量
        第一个全连接层的输出通道数 = 原输入特征图通道数的1/4; 第一个全连接层使用ReLU激活函数
        第二个全连接层的输出通道数 = 原输入特征图的通道数; 第二个全连接层使用 hard_sigmoid 激活函数
    经过两个全连接层得到一个由channel个元素组成的向量,每个元素是针对每个通道的权重,将权重和原特征图的对应相乘
    """
    def __init__(self, channel, reduction=4):
        super(SELayer, self).__init__()
        # Squeeze操作, 对tensor变量进行维度压缩, 获取全局信息
        self.avg_pool = nn.AdaptiveAvgPool2d(1)  # 全局平均池化, [b,h,w,c]==>[b,c]==>[b,1,1,c]
        # Excitation操作(FC + ReLU + FC + Sigmoid), 经过一系列激活操作, 但不改变操作前后的大小和通道数
        self.fc = nn.Sequential(
            nn.Linear(channel, channel // reduction),  # 第一个全连接层的输出通道数=原输入特征图通道数的1/4
            nn.ReLU(inplace=True),
            nn.Linear(channel // reduction, channel),  # 第二个全连接层的输出通道数=原输入特征图通道数
            h_sigmoid()
        )

    def forward(self, x):
        b, c, _, _ = x.size()
        y = self.avg_pool(x)
        y = y.view(b, c)
        y = self.fc(y).view(b, c, 1, 1)  # 学习到的每一channel的权重
        return x * y  # 将权重和原特征图的对应相乘


class conv_bn_hswish(nn.Module):
    def __init__(self, c1, c2, stride):
        super(conv_bn_hswish, self).__init__()
        # Conv2d(in_channels,out_channels,kernel_size,stride,padding,dilation,groups,bias)
        self.conv = nn.Conv2d(c1, c2, 3, stride, 1, bias=False)
        self.bn = nn.BatchNorm2d(c2)
        self.act = h_swish()

    def forward(self, x):
        return self.act(self.bn(self.conv(x)))

    def fuseforward(self, x):
        return self.act(self.conv(x))


class MobileNet_Block(nn.Module):
    def __init__(self, inp, oup, hidden_dim, kernel_size, stride, use_se, use_hs):
        """
        :param hidden_dim: 表示在 Inverted residuals 中的扩张通道数
                           Inverted residuals block: ①1×1卷积升维  ②3×3卷积  ③1×1卷积降维
        :param use_se: 表示是否使用 SELayer
        :param use_hs: 表示使用 h_swish 还是 ReLU
        """
        super(MobileNet_Block, self).__init__()
        assert stride in [1, 2]

        self.identity = stride == 1 and inp == oup

        # 输入通道数 = 扩张通道数, 不进行通道扩张
        if inp == hidden_dim:
            self.conv = nn.Sequential(
                # dw, 深度卷积
                nn.Conv2d(hidden_dim, hidden_dim, kernel_size, stride,
                          (kernel_size - 1) // 2, groups=hidden_dim, bias=False),
                nn.BatchNorm2d(hidden_dim),
                h_swish() if use_hs else nn.ReLU(inplace=True),
                # Squeeze-and-Excite, SE模块
                SELayer(hidden_dim) if use_se else nn.Sequential(),
                # pw-linear, 逐点卷积
                nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
                nn.BatchNorm2d(oup),
            )
        else:
            # 输入通道数 ≠ 扩张通道数, 进行通道扩张
            self.conv = nn.Sequential(
                # pw
                nn.Conv2d(inp, hidden_dim, 1, 1, 0, bias=False),
                nn.BatchNorm2d(hidden_dim),
                h_swish() if use_hs else nn.ReLU(inplace=True),
                # dw
                nn.Conv2d(hidden_dim, hidden_dim, kernel_size, stride,
                          (kernel_size - 1) // 2, groups=hidden_dim,bias=False),
                nn.BatchNorm2d(hidden_dim),
                # Squeeze-and-Excite
                SELayer(hidden_dim) if use_se else nn.Sequential(),
                h_swish() if use_hs else nn.ReLU(inplace=True),
                # pw-linear
                nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
                nn.BatchNorm2d(oup),
            )

    def forward(self, x):
        y = self.conv(x)
        if self.identity:
            return x + y
        else:
            return y
# ---------------------------- MobileBlock end ---------------------------------
  • yolo.py文件修改:在yolo.py的parse_model函数中,加入h_sigmoid, h_swish, SELayer, conv_bn_hswish, MobileNet_Block五个模块

  • 新建yaml文件:在model文件下新建yolov5-mobilenetv3-small.yaml文件,添加以下代码,自行更换small与large

  • # YOLOv5 									
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

MobileNet(二) 的相关文章

随机推荐

  • promQL语法

    文章目录 一 表达式语言数据类型 二 字面量 一 字符串 二 标量 三 时间序列过滤器 一 瞬时向量过滤器 二 区间向量过滤器 三 时间位移操作 四 操作符 一 二元运算符 1 算术二元运算符 2 布尔运算符 3 集合运算符 二 匹配模式
  • 过零检测电路原理与作用 可控整流

    原文地址 http blog 163 com liuyunqian yeah blog static 7039584320104185634579 使用光耦 Multisim仿真电路 网友建议 电容代替那只47K的电阻 330欧电阻直接短路
  • xftp无法链接Linux

    报错信息 SFTP子系统申请已拒绝 请确保SSH连接SFTP子系统设置有效 无法与192 168 0 102 解决方法 编辑 etc ssh sshd config文件 将 Subsystem sftp usr local libexec
  • 6.2、Flink数据写入到Kafka

    目录 1 添加POM依赖 2 API使用说明 3 序列化器 3 1 使用预定义的序列化器 3 2 使用自定义的序列化器 4 容错保证级别 4 1 至少一次 的配置 4 2 精确一次 的配置 5 这是一个完整的入门案例 1 添加POM依赖 A
  • Lua : 函数封装好,用起来就是爽

    目录 1 Lua函数的格式 2 Lua可变参数函数 3 多个返回值的函数 Lua中函数比较简单 只是相比较c c 来说 lua函数的返回值比较特殊 他可以返回一个或者多个值 且值的类型可不相同 1 Lua函数的格式 Lua 函数语法格式 f
  • 刷个宇宙题:剑指 Offer II 006. 排序数组中两个数字之和、 007. 数组中和为 0 的三个数

    题目 006 排序数组中两个数字之和 方法1 哈希表的方式 class Solution public vector
  • 内存函数详细解析 - C语言

    文章目录 前言 1 memcpy 内存拷贝 2 memmove 内存移动 3 memcmp 内存比较 4 memset 内存设置 5 函数代码 5 1memmove代码 5 2memcpy代码 5 3memcmp代码 5 4memset代码
  • 可综合的ROM芯片设计实现-verilog代码

    文章目录 1 基本单元 1 1 最基本cell 1 2 两个存储单位 1 3 八个存储单位 1 4 十六个存储单位 2 使用和测试 2 1 使用 2 2 仿真 本文实现可以综合的ROM模块 由verilog实现 该方法可以用于芯片固化程序的
  • 关于Android Studio中adb识别不了设备问题。

    文章目录 前情提要 第一种 杀进程方式 第二种 复制文件在Windows目录下 第三种 修改adb的端口号 第四种 重启电脑 前情提要 在windows系统中安装adb后 adb connect ip 5555 总是出现5037端口被占用的
  • MATLAB 批量读取NC文件并转为TIF文件

    因为课题处理30年的降雨和蒸发的遥感资料 NC格式 而想要在Arcgis中处理要求的是raster格式的 所以需要批量转化为tif文件 所以在此分享自己改编之后的代码 可以简洁明了的实现这个过程 我所参考和借鉴的文章的链接如下 MATLAB
  • 免费快捷一键生成SSL证书

    https freessl cn https www pianyissl com https certbot eff org 我只用过第一个 因为太强了 差不多五分钟搞定 下面两个是别人推荐的 记录一下使用过程 先注册登录https fre
  • ESP32/ESP8266 WIFI接入通过HTTP响应远程控制(附可用源码)

    1 esp32 esp8266相关介绍 1 1ESP WIFI工作模式 ESP共有三种工作模式 分别是无线接入点模式 AP 无线终端模式STA Wireless Station 以及混合模式 以上两种模式的混合 2 网络连接 ESP的WiF
  • Linux 上Docker的安装与使用入门

    1 安装Docker yum install docker x86 64 2 启动Docker service docker start把Docker 加入到开机自启动 chkconfig docker on 3 去docker 仓库查找i
  • Pycharm安装go插件,开始go之旅

    在PyCharm Idea一样 装GO插件 相当容易 PyCharm左上角 File gt Settings gt Plugins 点击 Browse repositories 输入go查询 看右侧说明确认是正确的go插件即可安装 PyCh
  • Java常见异常总结

    1 java lang NullPointerException 空指针异常 调用了未经初始化的对象或者是不存在的对象 经常出现在创建图片 调用数组这些操作中 比如图片未经初始化 或者图片创建时的路径错误等等 对数组操作中出现空指针 即把数
  • 移动通信关键技术-多址技术和复用技术

    移动通信网络简介 现在是2017年 4G已经使用很多年了 那么回顾一下移动通信的历史发展 1G 以AMPS ATCS为代表的模拟通信系统 主要技术是FDMA 主要业务是语音业务 2G 以GSM为代表的数字通信 主要技术TDMA是 IS 95
  • android任意函数绘制_Android布局优化技巧大盘点

    欢迎关注专栏 里面定期分享Android和Flutter架构技术知识点及解析 还会不断更新的BATJ面试专题 欢迎大家前来探讨交流 如有好的文章也欢迎投稿 Android高级进阶 zhuanlan zhihu com 开始 继上一篇卡顿优化
  • mysql leave iterator_c++迭代器(iterator)详解

    1 迭代器 iterator 是一中检查容器内元素并遍历元素的数据类型 1 每种容器类型都定义了自己的迭代器类型 如vector vector iterator iter 这条语句定义了一个名为iter的变量 它的数据类型是由vector定
  • 自作JavaScript飞机大战小游戏

    不会canvas画板标签小朋友们的福音来啦 用标签也能制作简单小游戏哦 飞机大战GitHub源码链接 https github com shunyue1320 fjdz git 做游戏就要有素材的啦 以下是在爱给网找到的素材 首先3个页面的
  • MobileNet(二)

    MobileNets MobileNetv1 深度可分离卷积 深度学习的经典网络模型 如ResNet VGG GooogleNet等已经达到了不错的效果 但存在一个问题 即模型庞大 参数较多 计算量较大 在一些实际的场景如移动或嵌入式设备中
Powered by Hwhale