轻量级网络——EfficientNet

简介: 轻量级网络——EfficientNet

paper核心思想:通过固定比例去缩放网络的宽度,深度,输入图像的分辨率来提高网络性能


1.EfficientNet介绍


扩大卷积神经网络被广泛用于提高精度。然而,扩大卷积神经网络的过程从来没有被很好地理解,目前有许多方法可以做到这一点。最常见的方法是通过深度或宽度(其实也就是增加channels)来放大卷积网。另一种不太常见但越来越流行的方法是根据图像分辨率放大模型。在以前的工作中,通常只缩放三个维度中的一个——深度、宽度和图像大小。虽然可以任意伸缩两个或三个维度,但任意伸缩需要繁琐的手动调优,而且通常仍会产生次优的精度和效率。


在EfficientNet中,从作者的研究证明平衡网络宽度/深度/分辨率的所有维度是至关重要的,令人惊讶的是,这种平衡可以通过固定比例缩放每个维度来实现。与传统的任意缩放这些因素不同,我们的方法统一缩放网络的宽度,深度,以及分辨率,一组固定的比例系数来解决问题。

image.png

传统的方法大多将卷积神经网络的规模分为以下几个维度之一:


  • Depth ( d ):更深层次的ConvNet能够捕获更丰富、更复杂的特性,并能很好地概括新任务。然而,由于梯度消失问题,更深的网络也更难以训练。虽然skip connections(和批处理归一化等技术缓解了训练问题,但是非常深的网络的准确率提高降低
  • Width ( w ):更广泛的网络往往能够捕获更多的细粒度特征,更容易训练。然而,极宽但较浅的网络往往难以捕获更高层次的特征。当网络变得更宽时,随着w的增大,准确率很快饱和
  • Resolution ( r ):增加输入网络的图像分辨率能够潜在得获得更高细粒度的特征模板,但对于非常高的输入分辨率,准确率的增益也会减小。并且大分辨率图像会增加计算量。


结论一:


  • 放大网络宽度、深度或分辨率的任何维度都能提高精度,但对于更大的模型,精度增益会减小。

根据经验,我们观察到不同的尺度并不是独立的。直观地说,对于更高分辨率的图像,我们应该增加网络深度,这样更大的接受域可以帮助捕获更大图像中包含更多像素的相似特征。相应地,当分辨率较高时,我们也应该增加网络宽度,为了在高分辨率图像中捕获更多像素的更细粒度的模式。这些直觉表明,我们需要协调和平衡不同的尺度,而不是传统的一维尺度。


结论二:


  • 为了追求更好的准确性和效率,在ConvNet缩放过程中,平衡网络宽度、深度和分辨率的所有维度是至关重要的。


2.EfficientNet参数选择


针对以上所提出的问题,现在所需要的求得在有限的资源里面如何确定平衡这网络的深度d,通道的宽度c,还有输入图像的分辨率r。


为了能够处理这个问题,作者将问题进行了抽象:

image.png



在实践中,ConvNet层往往被划分为多个阶段,每个阶段的所有层都具有相同的架构。例如,ResNet有五个阶段,每个阶段的所有层都具有相同的卷积类型,除了第一层执行降采样(通过stride进行改变shape)。因此,我们可以将卷积网络定义为:

image.png


下面这一段感觉可能难以理解:常规的ConvNet设计主要关注于寻找最佳的层架构Fi,而模型缩放试图在不改变基线网络的Fi预定义的情况下扩展网络长度(Li)、宽度(Ci)和/或分辨率(Hi, Wi)。我理解的应该就是不改变网络快结构,只是改变深度,宽度与输入的分辨率。通过修正F i FiFi,模型缩放简化了新的资源约束下的设计问题,但仍有很大的设计空间去探索不同的L i , C i , H i , W i 每一层。为了进一步减少设计空间,我们限制所有层必须按恒定比例均匀缩放。我们的目标是在任何给定的资源约束下最大化模型的精度。

image.png


而为了能够统一w i d t h , d e p t h , r e s o l u t i o n, width,depth,resolution的参数,作者使用了混合缩放方法 ( compound scaling method) ,利用一个混合因子φ 去统一缩放这三者,具体的计算公式如下:

image.png


接下来作者在基准网络EfficientNetB-0使用NAS来搜索α , β , γ 这三个参数

image.png

image.png

  • input_size:代表训练网络时输入网络的图像大小
  • width_coefficient:代表channel维度上的倍率因子,比如在 EfficientNetB0中Stage1的3x3卷积层所使用的卷积核个数是32,那么在B6中就是 32 × 1.8 = 57.6接着取整到离它最近的8的整数倍即56,其它Stage同理
  • depth_coefficient:代表depth维度上的倍率因子( 仅针对Stage2 到Stage8),比如在EfficientNetB0中Stage7的L = 4 ,那么在B6中就是 4 × 2.6 = 10.4,接着向上取整即11
  • drop_connect_rate:是在MBConv结构中dropout层使用的drop_rate,在官方keras模块的实现中MBConv结构的drop_rate是从0递增到drop_connect_rate的(具体实现可以看下官方源码,注意,在源码实现中只有使用shortcut的时候才有Dropout层)。还需要注意的是,这里的Dropout层是Stochastic Depth,即会随机丢掉整个block的主分支(只剩捷径分支,相当于直接跳过了这个block)也可以理解为减少了网络的深度。
  • dropout_rate:是最后一个全连接层前的dropout层(在stage9的Pooling与FC之间)的dropout_rate。


3.EfficientNet网络结构


下表为EfficientNet-B0的网络框架(B1-B7就是在B0的基础上修改Resolution,Channels以及Layers),可以看出网络总共分成了9个Stage,第一个Stage就是一个卷积核大小为3x3步距为2的普通卷积层(包含BN和激活函数Swish),Stage2~Stage8都是在重复堆叠MBConv结构(最后一列的Layers表示该Stage重复MBConv结构多少次),而Stage9由一个普通的1x1的卷积层(包含BN和激活函数Swish)一个平均池化层和一个全连接层组成。表格中每个MBConv后会跟一个数字1或6,这里的1或6就是倍率因子n即MBConv中第一个1x1的卷积层会将输入特征矩阵的channels扩充为n倍,其中k3x3或k5x5表示MBConv中Depthwise Conv所采用的卷积核大小。Channels表示通过该Stage后输出特征矩阵的Channels。

image.png

MBConv结构

image.png

MBConv其实就是MobileNetV3网络中的InvertedResidualBlock,但也有些许区别。一个是采用的激活函数不一样(EfficientNet的MBConv中使用的都是Swish激活函数),另一个是在每个MBConv中都加入了SE(Squeeze-and-Excitation)模块。下图是引用参考资料的MBConv结构。


如图所示,MBConv结构主要由一个1x1的普通卷积(升维作用,包含BN和Swish),一个kxk的Depthwise Conv卷积(包含BN和Swish)k的具体值可看EfficientNet-B0的网络框架主要有3x3和5x5两种情况,一个SE模块,一个1x1的普通卷积(降维作用,包含BN),一个Droupout层构成。搭建过程中还需要注意几点:


  • 第一个升维的1x1卷积层,它的卷积核个数是输入特征矩阵channel的n倍n ∈ { 1 , 6 }
  • 当n = 1 时,不要第一个升维的1x1卷积层,即Stage2中的MBConv结构都没有第一个升维的1x1卷积层(这和MobileNetV3网络类似)
  • 关于shortcut连接,仅当输入MBConv结构的特征矩阵与输出的特征矩阵shape相同时才存在(代码中可通过stride == 1 and inputc_channels == output_channels条件来判断)
  • SE模块如下所示,由一个全局平均池化,两个全连接层组成。第一个全连接层的节点个数是输入该MBConv特征矩阵channels的 1 /4 ,且使用Swish激活函数。第二个全连接层的节点个数等于Depthwise Conv层输出的特征矩阵channels,且使用Sigmoid激活函数。

image.png

EfficientNet(B0-B7)参数

image.png

4.EfficientNet性能统计


EfficientNet-B7超过了现有最好的GPipe精度,但使用的参数少了8.4倍,推理速度快了6.1倍。与广泛使用的ResNet-50 相比,EfficientNet-B4在类似FLOPS的情况下,将top-1的准确率从76.3%提高到了83.0%(+6.7%)。在8个广泛使用的数据集中有5个具有最先进的精度,同时比现有的ConvNets减少至多21倍的参数。

20210625205043107.png

EfficientNet-B7实现了新的最先进的84.3%的顶级精度,但比GPipe小8.4倍,快6.1倍。EfficientNet-B1比ResNet-152小7.6倍,快5.7倍。

image.png


5.EfficientNet的pytorch实现


参考github代码:


import math
import torch
import torch.nn as nn
# 激活函数需要继承nn.Module,然后进行正向传播
class Swish(nn.Module):
    def forward(self, x):
        return x * torch.sigmoid(x)
# 3x3 DW卷积  其中'//'是取整运算符,eg: 3//2=1,
# 其中stride默认为1,也就是一般不需要进行尺寸减半操作,而通过padding扩充使得对于kernel_size为3/5均可自动补充
def ConvBNAct(in_channels,out_channels,kernel_size=3, stride=1, groups=1):
    return nn.Sequential(
            nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size, stride=stride, padding=kernel_size//2, groups=groups),
            nn.BatchNorm2d(out_channels),
            Swish()
        )
# pw卷积含激活函数
def Conv1x1BNAct(in_channels,out_channels):
    return nn.Sequential(
            nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=1, stride=1),
            nn.BatchNorm2d(out_channels),
            Swish()
        )
# pw卷积不含激活函数
def Conv1x1BN(in_channels,out_channels):
    return nn.Sequential(
            nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=1, stride=1),
            nn.BatchNorm2d(out_channels)
        )
def Conv1(in_planes, places, stride=2):
    return nn.Sequential(
        nn.Conv2d(in_channels=in_planes,out_channels=places,kernel_size=7,stride=stride,padding=3, bias=False),
        nn.BatchNorm2d(places),
        Swish(),
        nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
    )
# 打平操作
class Flatten(nn.Module):
    def forward(self, x):
        return x.view(x.shape[0], -1)
# SE结构
class SEBlock(nn.Module):
    def __init__(self, channels, ratio=16):
        super().__init__()
        # 这里压缩的倍数的16倍,而在MobileNetv3中压缩的倍数是4倍
        mid_channels = channels // ratio
        # 基于通道的注意力
        self.se = nn.Sequential(
            nn.AdaptiveAvgPool2d(1),
            nn.Conv2d(channels, mid_channels, kernel_size=1, stride=1, padding=0, bias=True),
            Swish(),
            nn.Conv2d(mid_channels, channels, kernel_size=1, stride=1, padding=0, bias=True),
        )
    def forward(self, x):
        return x * torch.sigmoid(self.se(x))
# MobileNetv3中的结构块,所以是MBBlock,需要注意,channels的扩充有因子expansion_factor=6
class MBConvBlock(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size=3, stride=1, expansion_factor=6):
        super(MBConvBlock, self).__init__()
        self.stride = stride
        self.expansion_factor = expansion_factor
        mid_channels = (in_channels * expansion_factor)
        self.bottleneck = nn.Sequential(
            # 首先PW卷积升维,扩充倍数是6倍
            Conv1x1BNAct(in_channels, mid_channels),
            # 然后DW卷积,维度不变,并且通过这一步将尺寸缩放
            ConvBNAct(mid_channels, mid_channels, kernel_size, stride, groups=mid_channels),
            # 添加一个注意力模块
            SEBlock(mid_channels),
            # 最后PW卷积降维操作,变回原来的channels数
            Conv1x1BN(mid_channels, out_channels)
        )
        # 残差相加
        if self.stride == 1:
            self.shortcut = Conv1x1BN(in_channels, out_channels)
    def forward(self, x):
        out = self.bottleneck(x)
        out = (out + self.shortcut(x)) if self.stride==1 else out
        return out
# 主网络
class EfficientNet(nn.Module):
    params = {
        'efficientnet_b0': (1.0, 1.0, 224, 0.2),
        'efficientnet_b1': (1.0, 1.1, 240, 0.2),
        'efficientnet_b2': (1.1, 1.2, 260, 0.3),
        'efficientnet_b3': (1.2, 1.4, 300, 0.3),
        'efficientnet_b4': (1.4, 1.8, 380, 0.4),
        'efficientnet_b5': (1.6, 2.2, 456, 0.4),
        'efficientnet_b6': (1.8, 2.6, 528, 0.5),
        'efficientnet_b7': (2.0, 3.1, 600, 0.5),
    }
    def __init__(self, subtype = 'efficientnet_b0', num_classes = 5):
        super(EfficientNet, self).__init__()
        self.width_coeff = self.params[subtype][0]      # 元组的第一个参数: 宽度的扩充因子 width_coefficient
        self.depth_coeff = self.params[subtype][1]      # 元组的第二个参数: 深度的扩充因子 depth_coefficient
        self.dropout_rate = self.params[subtype][3]     # 元组的第四个参数: 随机丢弃比例 最后一个全连接层前的dropout层
        self.depth_div = 8
        # 对于b0层: 输出的channels为32,特征尺寸需要减半处理,所以stride=2
        self.stage1 = ConvBNAct(3, self._calculate_width(32), kernel_size=3, stride=2)
        # 此处stride=1,所以尺寸不变
        self.stage2 = self.make_layer(self._calculate_width(32), self._calculate_width(16), kernel_size=3, stride=1, block=self._calculate_depth(1))
        self.stage3 = self.make_layer(self._calculate_width(16), self._calculate_width(24), kernel_size=3, stride=2, block=self._calculate_depth(2))
        self.stage4 = self.make_layer(self._calculate_width(24), self._calculate_width(40), kernel_size=5, stride=2, block=self._calculate_depth(2))
        self.stage5 = self.make_layer(self._calculate_width(40), self._calculate_width(80), kernel_size=3, stride=2, block=self._calculate_depth(3))
        # 此处stride=1,所以尺寸不变
        self.stage6 = self.make_layer(self._calculate_width(80), self._calculate_width(112), kernel_size=5, stride=1, block=self._calculate_depth(3))
        self.stage7 = self.make_layer(self._calculate_width(112), self._calculate_width(192), kernel_size=5, stride=2, block=self._calculate_depth(4))
        self.stage8 = self.make_layer(self._calculate_width(192), self._calculate_width(320), kernel_size=3, stride=1, block=self._calculate_depth(1))
        # 池化操作
        self.pooling = nn.Sequential(
            Conv1x1BNAct(self._calculate_width(320), self._calculate_width(1280)),
            nn.AdaptiveAvgPool2d(1),
            nn.Dropout2d(0.2),
        )
        # 全连接层
        self.fc = nn.Sequential(
            Flatten(),
            nn.Linear(self._calculate_width(1280), num_classes)
        )
        self.init_weights()
    # 参数初始化
    def init_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode="fan_out")
            elif isinstance(m, nn.Linear):
                init_range = 1.0 / math.sqrt(m.weight.shape[1])
                nn.init.uniform_(m.weight, -init_range, init_range)
    # width_coefficient因子取整到离它最近的8的整数倍
    def _calculate_width(self, x):
        # 对于输入的channels,需要乘上宽度的扩展因子
        x *= self.width_coeff
        # 当x偏向8整数倍的下方,通过加上4来判断
        new_x = max(self.depth_div, int(x + self.depth_div / 2) // self.depth_div * self.depth_div)
        if new_x < 0.9 * x:
            new_x += self.depth_div
        return int(new_x)
    # depth_coefficient因子得到小数后向上取整
    def _calculate_depth(self, x):
        return int(math.ceil(x * self.depth_coeff))     # ceil向上取整函数 eg: ceil(4.01)=5
    def make_layer(self, in_places, places, kernel_size, stride, block):
        layers = []
        # 通过块结构的第一层改变尺寸,所以需要传入stride
        layers.append(MBConvBlock(in_places, places, kernel_size, stride))
        # 块结构的其他层不改变channels。注意,此处的in_channels = out_channels = places
        for i in range(1, block):
            layers.append(MBConvBlock(places, places, kernel_size))
        return nn.Sequential(*layers)
    # 以基本结构b0为例:
    # input: torch.Size([1, 3, 224, 224])
    def forward(self, x):
        x = self.stage1(x)      # torch.Size([1, 32, 112, 112])
        x = self.stage2(x)      # torch.Size([1, 16, 112, 112])
        x = self.stage3(x)      # torch.Size([1, 24, 56, 56])
        x = self.stage4(x)      # torch.Size([1, 40, 28, 28])
        x = self.stage5(x)      # torch.Size([1, 80, 14, 14])
        x = self.stage6(x)      # torch.Size([1, 112, 14, 14])
        x = self.stage7(x)      # torch.Size([1, 192, 7, 7])
        x = self.stage8(x)      # torch.Size([1, 320, 7, 7])
        x = self.pooling(x)     # torch.Size([1, 1280, 1, 1])
        x = self.fc(x)          # torch.Size([1, 5])
        return x
# 最基本结构
def EfficientNet_b0():
    return EfficientNet('efficientnet_b0')
# 以下结构为按比例的扩充模型
def EfficientNet_b1():
    return EfficientNet('efficientnet_b1')
def EfficientNet_b2():
    return EfficientNet('efficientnet_b2')
def EfficientNet_b3():
    return EfficientNet('efficientnet_b3')
def EfficientNet_b4():
    return EfficientNet('efficientnet_b4')
def EfficientNet_b5():
    return EfficientNet('efficientnet_b5')
def EfficientNet_b6():
    return EfficientNet('efficientnet_b6')
def EfficientNet_b7():
    return EfficientNet('efficientnet_b7')
if __name__== '__main__':
    # model = EfficientNet_b0()
    # model = EfficientNet_b1()
    # model = EfficientNet_b2()
    # model = EfficientNet_b3()
    # model = EfficientNet_b4()
    # model = EfficientNet_b5()
    # model = EfficientNet_b6()
    model = EfficientNet_b7()
    # print(model)
    input = torch.randn(1, 3, 224, 224)
    out = model(input)
    print(out.shape)
    torch.save(model.state_dict(), 'EfficientNet_b7.mdl')


模型的大小如图所示:

image.png

可见,模型不算很小,所以这也是EfficientNetV2改进的一点。


参考资料:

https://blog.csdn.net/qq_37541097/article/details/114434046

https://www.bilibili.com/video/BV1XK4y1U7PX


目录
相关文章
|
5月前
|
机器学习/深度学习
YOLOv5改进 | 主干篇 | 轻量级网络ShuffleNetV1(附代码+修改教程)
YOLOv5改进 | 主干篇 | 轻量级网络ShuffleNetV1(附代码+修改教程)
124 1
|
5月前
|
机器学习/深度学习 计算机视觉 知识图谱
【YOLOv8改进】MobileViT 更换主干网络: 轻量级、通用且适合移动设备的视觉变压器 (论文笔记+引入代码)
MobileViT是针对移动设备的轻量级视觉Transformer网络,结合CNN的局部特征、Transformer的全局注意力和ViT的表示学习。在ImageNet-1k上,它以600万参数实现78.4%的top-1准确率,超越MobileNetv3和DeiT。MobileViT不仅适用于图像分类,还在目标检测等任务中表现出色,且优化简单,代码已开源。YOLOv8引入了MobileViT块,整合卷积和Transformer结构,提升模型性能。更多详情可参考相关专栏和链接。
|
5月前
|
机器学习/深度学习 编解码 算法
YOLOv5改进 | 主干网络 | 用EfficientNet卷积替换backbone【教程+代码 】
在YOLOv5的GFLOPs计算量中,卷积占了其中大多数的比列,为了减少计算量,研究人员提出了用EfficientNet代替backbone。本文给大家带来的教程是**将原来的主干网络替换为EfficientNet。文章在介绍主要的原理后,将手把手教学如何进行模块的代码添加和修改,并将修改后的完整代码放在文章的最后,方便大家一键运行,小白也可轻松上手实践。以帮助您更好地学习深度学习目标检测YOLO系列的挑战。
|
27天前
|
编解码 人工智能 文件存储
卷积神经网络架构:EfficientNet结构的特点
EfficientNet是一种高效的卷积神经网络架构,它通过系统化的方法来提升模型的性能和效率。
26 1
|
2月前
|
数据采集 资源调度 JavaScript
Node.js 适合做高并发、I/O密集型项目、轻量级实时应用、前端构建工具、命令行工具以及网络爬虫和数据处理等项目
【8月更文挑战第4天】Node.js 适合做高并发、I/O密集型项目、轻量级实时应用、前端构建工具、命令行工具以及网络爬虫和数据处理等项目
38 5
|
3月前
|
机器学习/深度学习 计算机视觉 网络架构
【YOLOv8改进- Backbone主干】YOLOv8 更换主干网络之 PP-LCNet,轻量级CPU卷积神经网络,降低参数量
YOLO目标检测专栏介绍了PP-LCNet,一种基于MKLDNN加速的轻量级CPU网络,提升了模型在多任务中的性能。PP-LCNet利用H-Swish、大核卷积、SE模块和全局平均池化后的全连接层,实现低延迟下的高准确性。代码和预训练模型可在PaddlePaddle的PaddleClas找到。文章提供了网络结构、核心代码及性能提升的详细信息。更多实战案例和YOLO改进见相关链接。
|
3月前
|
机器学习/深度学习 编解码 TensorFlow
【YOLOv8改进- Backbone主干】YOLOv8 更换主干网络之EfficientNet,高效的卷积神经网络,降低参数量
YOLOv8专栏探讨了目标检测的创新改进,包括模型扩展和神经架构搜索。提出的新方法使用复合系数平衡网络的深度、宽度和分辨率,产生了EfficientNets系列,其在准确性和效率上超越了先前的ConvNets。EfficientNet-B7在ImageNet上达到84.3%的顶级准确率,同时保持较小的模型大小和更快的推理速度。文章提供了论文和代码链接,以及核心的EfficientNet模型构建Python代码。
|
5月前
|
机器学习/深度学习 测试技术 Ruby
YOLOv5改进 | 主干篇 | 反向残差块网络EMO一种轻量级的CNN架构(附完整代码 + 修改教程)
YOLOv5改进 | 主干篇 | 反向残差块网络EMO一种轻量级的CNN架构(附完整代码 + 修改教程)
235 2
|
4月前
|
机器学习/深度学习 计算机视觉 网络架构
【YOLOv8改进-卷积Conv】DualConv( Dual Convolutional):用于轻量级深度神经网络的双卷积核
**摘要:** 我们提出DualConv,一种融合$3\times3$和$1\times1$卷积的轻量级DNN技术,适用于资源有限的系统。它通过组卷积结合两种卷积核,减少计算和参数量,同时增强准确性。在MobileNetV2上,参数减少54%,CIFAR-100精度仅降0.68%。在YOLOv3中,DualConv提升检测速度并增4.4%的PASCAL VOC准确性。论文及代码已开源。
|
4月前
|
机器学习/深度学习 PyTorch 算法框架/工具
【YOLOv8改进 - 注意力机制】SimAM:轻量级注意力机制,解锁卷积神经网络新潜力
YOLO目标检测专栏介绍了SimAM,一种无参数的CNN注意力模块,基于神经科学理论优化能量函数,提升模型表现。SimAM通过计算3D注意力权重增强特征表示,无需额外参数。文章提供论文链接、Pytorch实现代码及详细配置,展示了如何在目标检测任务中应用该模块。
下一篇
无影云桌面