【LeYOLO】嵌入式和移动端的轻量级YOLO模型

代码地址:https://github.com/LilianHollard/LeYOLO

论文地址:https://arxiv.org/pdf/2406.14239

在深度神经网络中,计算效率对于目标检测至关重要,尤其是在新模型更注重速度而非有效计算(FLOP)的情况下。这一演变某种程度上忽视了嵌入式和面向移动端设备的AI目标检测应用。

在本文中,作者重点关注基于FLOP的高效目标检测计算的神经网络架构设计选择,并提出几项优化措施来提高基于YOLO模型的效率。

1、首先,作者引入了一种受倒置瓶颈(inverted bottlenecks)和来自信息瓶颈(the Information Bottleneck)原理的理论所启发的有效 Backbone 网络缩放方法。

2、其次,作者提出了快速金字塔架构网络(FPAN),旨在促进快速多尺度特征共享同时减少计算资源。

3、最后,作者提出了一个解耦的DNiN检测Head,旨在为分类和回归任务提供快速且轻量级的计算。

在这些优化措施的基础上,并利用更有效的 Backbone 网络,本文为目标检测和以YOLO为中心的模型贡献了一个新的范式,称为LeYOLO。作者的贡献在多种资源限制下始终优于现有模型,实现了前所未有的准确性和FLOP比率。

值得注意的是,LeYOLO-Small在仅使用4.5 FLOP(G)的情况下,在COCO val 上达到了38.2%的竞争性mAP分数,与最新的YOLOv9-Tiny模型相比,计算负载减少了42%,同时保持了相似的准确性。

作者新颖的模型系列实现了前所未有的FLOP到准确性的比率,提供了从超低神经网络配置(< 1 GFLOP)到高效且要求严格的目标检测设置(> 4 GFLOPs)的可扩展性,对于 0.66, 1.47, 2.53, 4.51, 5.8和8.4 FLOP(G)分别达到了25.2, 31.3, 35.2, 38.2, 39.3和41 mAP


本文贡献:

1、Lightweight: 在每个FLOP比率的准确率方面,LeYOLO相比于轻量级目标检测的最先进神经网络(0.5到8 FLOP(G)之间)实现了最佳的准确率。

2、Scaling:LeYOLO为工业、边缘和嵌入式设备提供了使用轻量级YOLO模型与最先进的扩展效率的新机会。

3、Bags of specials:尽管作者的研究专注于精度与计算成本的最佳比例,基于该研究作者也提出了不同的替代方案。

4、New architecture:作者提出在LeYOLO中使用计算效率更高的块,并通过实验提供证据。

5、High reproducibility:作者的研究重点是改进深度神经网络的架构。作者使用ultralytics API的可重用训练方法实现结果,不使用ImageNet预训练。


nc: 80  # number of classes
scales:
  # [depth, width, max_channels]
  n: [1.0, 1.33, 576]  

# LeYOLO Small backbone
backbone:
  # [from, repeats, module, args]
  - [-1, 1, mn_conv, [16, 3, 2, "SI"]]  # 0-P1/2
  - [-1, 1, mn_conv, [16, 1, 1, "SI"]]
  - [-1, 1, MobileNetV3_BLOCK, [16, 3, 16, False, "SI", 2, False]]  # 2-P2/4
  - [-1, 1, MobileNetV3_BLOCK, [32, 3, 96, False, "SI", 2]]  # 3-P3/8
  - [-1, 1, MobileNetV3_BLOCK, [32, 3, 96, False, "SI", 1]]
  - [-1, 1, MobileNetV3_BLOCK, [64, 5, 96, True, "SI", 2]]  # 5-P4/16
  - [-1, 1, MobileNetV3_BLOCK, [64, 5, 192, True, "SI", 1]]
  - [-1, 1, MobileNetV3_BLOCK, [64, 5, 192, True, "SI", 1]]
  - [-1, 1, MobileNetV3_BLOCK, [64, 5, 192, True, "SI", 1]]
  - [-1, 1, MobileNetV3_BLOCK, [64, 5, 192, True, "SI", 1]]
  - [-1, 1, MobileNetV3_BLOCK, [96, 5, 576, True, "SI", 2]] # 10-P5/32
  - [-1, 1, MobileNetV3_BLOCK, [96, 5, 576, True, "SI", 1]]
  - [-1, 1, MobileNetV3_BLOCK, [96, 5, 576, True, "SI", 1]]
  - [-1, 1, MobileNetV3_BLOCK, [96, 5, 576, True, "SI", 1]]
  - [-1, 1, SPPF, [96, 5]]  # 14
  
# LeYOLO Small head
head:
  - [-1, 1, nn.Upsample, [None, 2, 'nearest']]
  - [[-1, 9], 1, Concat, [1]]  # 16 cat backbone P4
  - [-1, 1, MobileNetV3_BLOCK, [64, 5, 128, True, "SI", 1]]
  - [-1, 1, MobileNetV3_BLOCK, [64, 5, 128, True, "SI", 1]]
  - [-1, 1, MobileNetV3_BLOCK, [64, 5, 128, True, "SI", 1]]

  - [-1, 1, nn.Upsample, [None, 2, 'nearest']]
  - [[-1, 4], 1, Concat, [1]]  # 21 cat backbone P3
  - [-1, 1, MobileNetV3_BLOCK, [32, 3, None, True, "SI", 1, False]] 
  - [-1, 1, MobileNetV3_BLOCK, [32, 3, 64, True, "SI", 1]] 
  - [-1, 1, MobileNetV3_BLOCK, [32, 3, 64, True, "SI", 1]] 

  - [-1, 1, mn_conv, [64, 3, 2, "SI"]]
  - [[-1, 19], 1, Concat, [1]]  # 26 cat head P4
  - [-1, 1, MobileNetV3_BLOCK, [64, 5, 128, True, "SI", 1]] 
  - [-1, 1, MobileNetV3_BLOCK, [64, 5, 128, True, "SI", 1]] 
  - [-1, 1, MobileNetV3_BLOCK, [64, 5, 128, True, "SI", 1]] 

  - [-1, 1, mn_conv, [96, 3, 2, "SI"]]
  - [[-1, 14], 1, Concat, [1]]  # 31 cat head P5
  - [-1, 1, MobileNetV3_BLOCK, [96, 5, 192, True, "SI", 1]] 
  - [-1, 1, MobileNetV3_BLOCK, [96, 5, 192, True, "SI", 1]] 
  - [-1, 1, MobileNetV3_BLOCK, [96, 5, 192, True, "SI", 1]] 

  - [[24, 29, 34], 1, Detect, [nc]]  # Detect(P3, P4, P5)

Leyolo-small的配置文件


Base building block

class MobileNetV3_BLOCK(nn.Module):
    def __init__(self, c1, c2, k=3, e=None, sa="None", act="RE", stride=1, pw=True):
        #input_channels, output_channels, repetition, stride, expension ratio
        super().__init__()
        #act = nn.ReLU6(inplace=True) if NL=="RE" else nn.Hardswish()
        c_mid = e if e != None else c1
        self.residual = c1 == c2 and stride == 1

        features = [mn_conv(c1, c_mid, act=act)] if pw else [] #if c_mid != c1 else []
        features.extend([mn_conv(c_mid, c_mid, k, stride, g=c_mid, act=act),
                         #attn,
                         nn.Conv2d(c_mid, c2, 1),
                         nn.BatchNorm2d(c2),
                         #nn.SiLU(),
                         ])
        self.layers = nn.Sequential(*features)
    def forward(self, x):
        #print(x.shape)
        if self.residual:
            return x + self.layers(x)
        else:
            return self.layers(x)


class mn_conv(nn.Module):
    def __init__(self, c1, c2, k=1, s=1, act="RE", p=None, g=1, d=1):
        super().__init__()
        padding = 0 if k==s else autopad(k,p,d)
        self.c = nn.Conv2d(c1, c2, k, s, padding, groups=g)
        self.bn = nn.BatchNorm2d(c2)
        self.act = activation_function(act)#nn.ReLU6(inplace=True) if act=="RE" else nn.Hardswish()
    
    def forward(self, x):
        return self.act(self.bn(self.c(x)))

倒置瓶颈(inverted bottleneck)结构,最初由MobileNetV2提出,是许多新的最先进模型的核心,因其轻量级计算和简洁性而受到重视。在FLOP计算方面,要超越深度可分离卷积(depthwise convolutions)达到一定有效性水平是比较困难的。点卷积(pointwise convolutions)解决了通道间相关性的缺失问题。然而,在作者对倒置瓶颈块的实验中,作者观察到优化通道数量可以有效地减少计算需求,特别是在大特征图尺寸上。

实际上,如果块的扩展比率为1,或者由于拼接效果,输入通道C_{in}等于计算出的扩展层数C_{mid},那么在作者的块中就没有必要使用第一个点卷积。只要输入C_{in}和输出C_{out}的张量相等,如图2和公式(1)所示,即使第一个pointwise卷积不存在,作者也总是使用残差连接。

“即使第一个pointwise卷积不存在,作者也总是使用残差连接”,即:

- [-1, 1, mn_conv, [16, 1, 1, "SI"]]

- [-1, 1, MobileNetV3_BLOCK, [16, 3, 16, False, "SI", 2, False]] # 2-P2/4  最后一个False表示第一个pointwise卷积不存在的情况

作者在图2(c)中描述了LeYOLO的基础构建块,突出了经典瓶颈(图2(a))、倒置瓶颈(图2(b))以及作者提出的方法(图2(c))之间的区别。

作者用\otimes表示两个值之间的卷积。对于F_{in} \in \mathbb{R}^{1,1,C_{in},C_{mid}}F_{out} \in \mathbb{R}^{1,1,C_{mid},C_{out}}F_{mid} \in \mathbb{R}^{k,k,1,C_{mid}},这里的卷积具有个C_{in}输入通道,C_{mid}个在倒置瓶颈中扩展的通道,以及C_{out}个输出通道


Stride strategy

一些模型在倒置瓶颈结构中包含了步长。然而,作者采用了一种特定的通道扩展策略

每个语义信息 Level  P_{i}在其所有隐藏层中都具有输入通道数C_{in}P_{i}、输出通道数C_{out}P_{i}和扩展通道数C_{mid}P_{i}。作者的目标是丰富从语义信息 Level P_{i}的隐藏层h_{i}到后续隐藏层h_{i+1}的信息流,通过按比例增加通道C_{in}P_{i},以扩展到预期的C_{mid}P_{i+1}通道。

倒置瓶颈采取以下步骤:

具有步长的倒置瓶颈中的语义信息表示为P如下——图3:

因此,步长大于一的通道扩展策略可能类似于以下形式:

作者本可以简单地为每个块应用丰富的扩展比例,不仅仅是那些步长大于一的块,但这样做会显著增加整个网络的计算成本。虽然这种策略不是整个模型所必需的,但在某些节点上这样做是有意义的,比如在stem,从昂贵的特征图空间大小中最大限度地扩展信息,特别是使用最终的逐点卷积。

            if m in (InvertedBottleneck,MobileNetV3_BLOCK):
              if isinstance(args[3],int): #might use "None"
                args[3] = make_divisible(min(args[3], max_channels) * width, 8)

STEM

作者经常用“STEM”这个术语来描述最初几层,这些层直接处理输入图像和低语义信息,以快速有效地减少空间尺寸,并将初始信息通道数(通常是3,代表红、绿、蓝三个颜色通道)激发到一个更高的通道数。主要的好处是减少了计算成本,因为如果处理的层在空间上过大,对于目标检测的总成本会迅速爆炸。

一些最先进的YOLO模型,只有YOLOv6和YOLOv8是具有低计算资源STEM的好例子,当将通道数和层缩放到1/4时,它们的总成本都是0.32 GFLOP。这两个模型迅速将特征图缩小到160x160p,以补偿在过高空间尺寸上的滑动卷积的成本。

为了在大特征图尺寸上有效地使用卷积,作者在整个STEM中使用了pointwisestandard卷积,从P0(640x640)缩小到P2(160x160)过程中将通道数限制在一个严格的低数值内 - 表1。


Efficient backbone feature extractor

作者认识到深度神经网络(DNNs)并不完全符合马尔可夫链X\rightarrow \widetilde{X}\rightarrow Y,其中X\widetilde{X}Y分别是输入、从X提取的最小充分统计量和输出。因此,为了得到\widetilde{X}作为提取对Y有意义特征的最小充分统计量,DNN需要学习如何使用最小充分统计量提取特征,并采用尽可能紧凑的结构

其次,因为DNN只处理前一层h_{i-1}的输入,一个直接的后果可能是丢失后续层无法恢复的信息。如公式(5)

诸如[5; 21]中提出的列式网络等昂贵的解决方案通过在每个块之间进行密集的特征共享来解决这一问题,通过结合密集的训练块或在信息分割的关键点添加额外的检测Head,正如最近在YOLOv9中看到的那样。由于在上述方程中实现公平是可行的,[66]建议每一层应尽可能最大化自身的信息I(Y;h_{i}),同时最小化层间信息交换I(h_{i-1};h_{i})。因此,作者没有像[71; 73; 21; 5]那样增加模型的计算复杂性,而是选择更高效地扩展它,整合Dangyoon等人的倒瓶颈理论。

作者的实现包括,通过确保输入/输出通道的数量不超过第一隐藏层到最后一层通道的差比,以形式I(h_{0};h_{n})最小化层间信息交换,其中n等于神经网络的最后一个隐藏层。隐藏层通道的数量应保持在由输入通道P1和输出通道P5定义的范围内,比小于6,以形式I(h_{1};h_{n})最小化I(h_{i-1};h_{i})

相反,Dangyonn等人的倒瓶颈通道扩展实验表明,扩展或缩减比例不应超过6。因此,作者通过在整个网络中扩展3倍来最大化I(Y;h_{i})。同时,在采用pointwise通道扩展策略的间隔倒瓶颈中,通过总共扩展6倍进一步激发信息,最大化(P_{i};P_{i+1})之间的I(Y;h_{i})。然而,作者从P4到P5的倒瓶颈进一步激发信息,通过扩展9倍最大化(P_{4};P_{5})I(Y;h_{i})(与扩展6倍相比,增加了+0.5 mAP)。

块之间残差连接的实施有助于通过提供前一层h_{i-1}的信息来最小化I(h_{i-1};h_{i})。密集连接可能增强模型,但它们需要额外的内存。


Neck

在目标检测中,作者将模型中聚合多个语义信息层次的部分称为“neck”,它将更远层的提取层次共享到第一层。

从历史上看,研究者使用PANet或FPN有效地共享特征图,通过将几个语义信息P_{i}链接到PANet及其各自的输出,如图4(a)所示,实现了多个检测层次。

在本文中,作者主要关注两个算法:BiFPN和YOLOF的SiSO。BiFPN与作者模型的中心思想相同:使用低计算成本的层(连接和加法,深度卷积和逐点卷积)。然而,BiFPN需要太多的语义信息和太多的阻塞状态(等待前一层,复杂的图),这使得难以保持快速执行速度。

SiSO中,可以看到YOLOF的作者决定为模型“neck”使用单一的输入和输出。与YOLOF论文中提出的其他解决方案相比,作者观察到多输出“neck”(单入多出-SiMO)与单输出“neck”(单入单出-SiSO)之间存在显著退化。作者特别感兴趣的是他们关于SiMO潜在效率的工作,证明仅通过优化单一丰富输入的语义信息流,就可以改进YOLO模型“neck”的第一层。

受到PAN和FPNnet的启发,作者提出了一个快速PANnet(FPANet),该网络具有更少的卷积层,较低的通道数和更有效的语义信息共享。作者的方法与YOLOv8中的“neck”概念相似。作者减少了post-backbone和 Head 之间的P3和P5的计算流,直接强化了P4的语义信息层次,如图4(b)所示。此外,作者简化了“neck”,最小化了锁和等待时间。而且,如图4(c)所示,作者优化了通道数量以减少P3中的计算,其中初始逐点步骤是不必要的,因为从P4和backbone来的P3信息的自底向上的路径的连接与PAN在P3处的倒置瓶颈中扩展的通道相匹配,即C_{out}P_{4} + C_{in}P_{3} = C_{mid}P_{3}

通过用P3和P4的最小计算加强单一输入(P4)来指导“neck”信息,作者实现了介于SiMO和MiMO之间的方法,并显著减少了MiMO的变化。


Decoupled Network in Network Head

直到YOLOv5时期,分类和目标检测任务使用单一Head 。然而,自YOLOv6起,模型 Head 变成了一个更强大的工具,将块分成了两部分:一个用于分类的分支和一个用于目标回归的分支。尽管这种方法非常高效,但它意味着成本几乎翻倍,需要为分类和检测进行卷积。

作者理论地认为,除了通过轻量级的Depthwise卷积按通道细化 Backbone 和stem提取的特征外,无需添加空间信息。

从历史上看,YOLO模型作为一个网格工作,为每个网格像素通过 Anchor 框进行分类 Proposal 。Anchor 框提供了几种可能的检测大小,不仅仅是像素级的检测。

通过YOLO的点对点网格操作,作者认为可以使用pointwise卷积作为滑动多层感知机解决方案,逐像素地简化检测 Head ,类似于每个像素的分类 Proposal --几个Depthwise卷积用于仅空间方式,改进两个pointwise卷积之间的空间关系,每个像素进行分类和回归。

通过消融研究(见原文第B.4章),作者证明仅在模型 Head 使用pointwise卷积就能在LeYOLO-Nano@640尺度上取得令人印象深刻的33.4 mAP结果。在pointwise卷积之间使用Depthwise卷积细化空间信息,将模型性能提升到34.3 mAP

如图5所示,作者提出了DNiN Head,一种以pointwise卷积为中心的方法,具有两个单独的pointwise操作,用于每个网络 Proposal :分类和回归(边界框)。pointwise操作在目标检测中至关重要,在网络中网络框架内作为逐像素的分类器和回归器。Depthwise卷积被分成两个3x3卷积,以降低与单个5x5卷积相比的整体成本。

作者操作两个独立的pointwise卷积:一个专用于分类,另一个用于回归。这种区别源于分类和边界框提取之间不同的需求。作者提出的DNiN Head 在保持 Level P_{i}的空间尺寸的同时,扩展通道以匹配类数量。每个像素因此代表一个潜在的预测。


Results

### 部署运行轻量级YOLO模型 #### 准备环境 为了在树莓派上成功部署ncnn模型,如轻量级YOLO模型,需先准备好相应的开发环境。推荐使用带有4GB RAM及以上版本的树莓派Pi5作为设备平台,并安装官方Raspberry Pi OS操作系统[^1]。 #### 安装依赖项 确保已安装必要的软件包,包括但不限于CMake、g++ OpenCV等库,这些工具对于编译支持ncnn至关重要: ```bash sudo apt-get update && sudo apt-get upgrade -y sudo apt-get install cmake g++ libopencv-dev ``` #### 编译ncnn库 获取并编译ncnn源码至适合ARM架构的二进制文件。这一步骤是实现后续操作的基础: ```bash git clone https://github.com/Tencent/ncnn.git cd ncnn mkdir build && cd build cmake .. make -j$(nproc) sudo make install ``` #### 转换模型 将训练完成后的YOLO模型转换成适用于ncnn框架的形式。通常情况下,这意味着要从原始格式(例如PyTorch或Darknet)转为`.param`与`.bin`两个部分表示的结构化形式。 可以借助Python脚本辅助此过程;具体命令取决于所使用的初始模型类型及其对应的转换器。 #### 开发应用程序代码 编写一段简单的程序读取上述转换所得参数文件,并调用ncnn API执行前向传播计算得到最终输出结果。这里给出一个基于C++语言的例子片段: ```cpp #include "net.h" using namespace std; int main(int argc, char** argv){ // 加载模型 ncnn::Net yolo; yolo.load_param("yolov3-tiny.param"); yolo.load_model("yolov3-tiny.bin"); // 创建输入张量... } ``` #### 测试与优化 最后,在实际硬件平台上测试新创建的应用程序,评估其表现情况——特别是响应时间准确性方面。如果有必要的话,则进一步调整超参数设置或是尝试其他更高效的算法变体来提升整体效率[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

athrunsunny

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值