YOLO系列(v1~v3)的学习及YOLO-Fastest在海思平台的部署(上)

YOLO系列(v1~v3)的学习及YOLO-Fastest在海思平台的部署(上)
YOLO系列(v1~v3)的学习及YOLO-Fastest在海思平台的部署(中)
YOLO系列(v1~v3)的学习及YOLO-Fastest在海思平台的部署(下)

声明

本文由 凌然 编写。

当前版本R1.0(预发布)。

作者联系方式:E-mail: WindForest@yeah.net

本文仅为个人学习记录,其中难免存在客观事实的错谬或理解上的歪曲,因此望读者切勿“拿来主义”,由本文的错误造成的损失,作者概不负责。

因在发布期间可能对本文即时修改或校对,因此如非必要请勿转载本文,以免错误的内容在转载后无法得到更新从而对其他人造成误导或负面影响。

前言

本文记录了我个人的一段学习历程,可以算为笔记的性质。全文的内容安排如下:首先基于论文和Darknet源码学习了YOLOv1~v3的网络,对其思想有了大致的了解。在第二章中对目标任务的工程实现以及面临的限制进行了分析,对目标平台的深度学习网络模型部署例程进行了学习。第二章的内容为后续的学习方向提供了指导。第三章中,我试着熟悉了一下Caffe计算框架,以及对既有网络的基本训练流程。对Caffe的了解有助于最终的工程部署。在第四章我尝试理解一个可达到实时检测的新网络YOLO-Fastest,并对其基于Darknet的训练流程进行了学习,明确了在自定义数据集上进行训练的基本注意要点。一切准备就绪后,第五章给出了我部署时的工程构成说明以作抛砖引玉之用。

说实话,我不是很喜欢深度学习(或者是机器学习,或是AI什么的),也许它的神秘感和已有的实践结果让人感到惊叹,但那无法引起我的兴致。由于一些原因,我被迫入坑深度学习,并在这个过程中感到痛苦万分。但是我最终还是决定认真起来开始这段旅程,这不是一种消极的应付,也不是一种态度的转变——我依然讨厌深度学习,并无比期待从毕业的那天开始,永远不再接触这个东西。但是眼下这段时间我选择坚持下去,为了自己,也是为了别人:如果我现在的努力能够在未来的某一天帮助到其他人,那无论如何这份努力是有它的价值的。

如果未来某一天有人出于相似的原因,抱着和我一样的心情经历同样的过程,那请带上我的叮嘱:

生活也许会有痛苦,但快乐是需要自己主动选择的。——《J·K·C》

从个人的角度出发,我很愿意和未来的读者聊一聊,然而很可惜的是,当你看到这篇内容的时候,我可能早已将它忘记、转战其它的方向了。这诚然是一份无法弥补的遗憾,而我又不想把没有意义的闲话放在正文之内,因此只能在这里与你说一说(如果你愿意看的话)。

我知道自己并不适合做算法,因为没有扎实的数学基础,对公式推理也不怎么感兴趣,所以在选题初期就在想:如果能把理论和工程结合在一起,也许就不会那么枯燥了。(其实就算做算法也是要用代码去实现的,正如那一句:算法工程师,首先你得是个工程师。但这是后话了。)所以我主动地限制了自己的眼界,最终导致除了所做的这点东西之外,完全不了解行业发展到什么程度、有没有更好的算法、以及哪些地方可以借鉴或创新。希望刚刚踏上征程的你们能够因此受到警戒,不要为自己的可能性设限。

不要过于相信网上的资料和博客,包括你正在看的本文。总要带着批判的眼光逐行审视它们。除了正确性和准确性之外,还要注意你获得资料的时效性。如果无需花费太多时间,那无论是算法还是示例,总要试着上手运行一遍,这能让你在获得调试经验的同时,从工程实现的角度更好地理解它。

最后,一定一定要找到你为之努力的目标,并坚守这份初心。这能让你在面对繁星般的知识时不至迷失方向、在遇到困难的时候不会踌躇不前。

本文的目标和前期准备

在你看到这些文字数月甚至数年前,我陆陆续续敲下了这些内容,它们涵盖了我生命中的一个片段。阅读这些信息同样也需要你付出相当的时间,因此在开始之前,你需要知道本文记录了哪些内容,如果它们不是你需要的,那大可坦率道别。

  1. 一切的开始仅仅是因为我需要完成毕业条件而非科研创新,而我也并不打算未来在这条路上发展下去,因此对YOLO及深度学习不会讨论的非常深。如果你希望深入了解YOLO的每个方面,那我可能帮不到你;但如果你对YOLO有一些困惑,不妨看一看目录中有没有你所需要的东西。
  2. 最理想的状态下,我希望能在海思嵌入式平台上完成网络模型的移植,并希望将这个过程最简化。所以我不会尝试过多的旁支或与这个目标关系不大的工具。比如海思的文档中明确了仅支持Caffe1.x的框架,那么我不会去尝试Keras或Pytorch,即使它们更容易上手。
  3. 由于是毕设级别的,所以我不会很追求网络的精度和表现,而总是优先完成设计流程。因此在本文记录过程中若有纰漏、表现不佳或浅尝辄止之处,请务必原谅我。
  4. 虽然行文风格偏向笔记,但我还是尽量将它整理的顺畅,以便于前后有递进和逻辑。值得注意的是,笔记不是教程,因此我所尝试的步骤也许在你的环境上需要再次修改、一些地方的解释也许只提及了我认为的关键之处。另外请包容我在遣词造句上暴露出的低级文学造诣——相信我,我已经尽到当下而言最大的努力了!

我大概想了一下,列出了我开始进行尝试时的一些知识储备,这样也许对你清楚地定位自身有帮助。希望更厉害的大佬们看到这里可以无视我的鄙陋,因为您更清楚这些要求确是必须达到的:

  1. 了解卷积神经网络和深度学习。

    一般的学习路径可能是从机器学习到神经网络再到深度学习,网上很多的视频课程一类,这里不做评价(因为我基本没怎么看视频)。我只想推荐两本对我入门起了很大的帮助的书:斋藤康毅的《深度学习入门 基于Python的理论与实践》和弗朗索瓦·肖莱的《Python深度学习》。这两本书都有中文译本,前者是我学习深度学习的第一本书,后者虽然是基于Keras,但是对初窥门径很有帮助。

    [补充]

    Python被誉为深度学习第一语言的原因只是很多计算框架和库提供Python接口,极大地方便了学者开发和使用。有些框架如Darknet和Caffe就可以无需Python进行开发,但其局限性也是显而易见的。因此考虑到原型验证、算法验证、例程阅读以及制作一些小工具等场景,通常情况下建议读者学习Python的基本使用。

  2. 对YOLO之前的经典卷积神经网络及其各自的思想有所了解。

    YOLO的作者团队在设计网络时借鉴了不少其它网络的思想或做法,不了解在YOLO之前的网络会对理解YOLO产生一定的阻碍,本文不可能事无巨细反而更倾向于对某些关键点进行解释,因此建议至少先了解一下卷积神经网络的发展,以及一些经典的卷积神经网络例如LeNet(卷积神经网络发展先驱)、AlexNet(卷积网络发展转折点)、VGG16/19、GoogLeNet(1×1卷积)、ResNet(短连接)、SSD(图像金字塔)、R-CNN/Fast R-CNN/Faster R-CNN(区域建议网络)、FCN(全卷积网络)、MobileNet(深度可分离卷积)等,这些网络的旁支最好也要知道,如果能在心里建立起一个卷积神经网络发展的大树,那就再好不过了。

  3. 具有基本的Linux嵌入式编译经验,了解Makefile、条件编译以及make、gcc、Visual Studio等工具的作用/使用。

    说“编译”经验的原因是:嵌入式的范围实在是太广了,在新晋研究生的人群中,会单片机不代表会gcc;而Windows下IDE工具又太多。在这里我希望你有一定的嵌入式Linux程序开发经验,这些经验会帮助你解决网络模型移植过程中的大部分问题。举个例子,Makefile和VS使用的xml格式配置文件有异曲同工之妙;gcc的“-Werror”编译选项和VS的“将警告视为错误”则是同样的东西。

  4. 具有现代计算机使用经验。

    这个…你懂得,比如设置环境变量、使用命令提示符、查看设备信息、了解硬件-驱动-系统-软件之间的关系等。

本文的章节安排虽然有先后顺序,但我在学习的时候是前后穿插互补地进行的,因此章节之间可能存在部分内容或理解上的依赖。我尽可能地将这种依赖关系调整为“后面依赖前面”的顺序,难免疏漏之处还请读者自行查阅相关资料。因此对本文的阅读建议是:首先通览目录,而后从头到尾自己尝试一遍,这样可以达到最好的学习效果。

本文的尝试主要使用 CaffeDarknet 框架完成,网络的训练和测试均在 Windows 环境下进行。另外,希望你的计算机拥有至少2G以上显存、算力不小于5.0的 NVIDIA 显卡以便复现本文的基本操作。如果希望在自己的电脑上跑模型,那就要选择更大显存的显卡。显卡的算力表见:https://developer.nvidia.com/zh-cn/cuda-gpus

1 YOLO主线系列通读与了解

YOLO(You Only Look Once)是一种端到端的目标检测方法,它基于卷积神经网络,结合了多种网络优化策略,现已经在生产上被广泛应用。对于其英文全称,有人曾这样调侃:他们将YOLO解释为“You Only Live Once”。想必作者也会对这句话表示赞同吧,毕竟生命诚可贵。

YOLO这个名字代表着一系列的网络,各系列之间使用版本编号的方式加以区分。它们的论文如下(截至2021-2):

  1. You Only Look Once: Unified, Real-Time Object Detection
  2. YOLO9000: Better, Faster, Stronger
  3. YOLOv3: An Incremental Improvement
  4. YOLOv4: Optimal Speed and Accuracy of Object Detection

在阅读每一部分之前,我希望你多少先细读一遍对应的论文,而非将我(或者他人)的看法作为第一印象,虽然我主观上不想坑你,但是如果我的思维干扰到了你的判断,或者我无意中的错误耽误了你的进度,我会感到非常抱歉。

1.1 YOLOv1

YOLOv1是整个YOLO系列的开端,后续的论文和网络均是在此基础上做修改,因此从YOLOv1入手是必然也是必须的事情。YOLOv1的论文中提出了两种网络结构(Base YOLO和Fast YOLO)以分别完成不同需求的检测任务。Fast YOLO具有更少的卷积层和更快的速度。

论文中的Base YOLO网络结构

论文中的Base YOLO网络结构

1.1.1 YOLOv1网络算法

(1)网络是如何定义一个目标的

网络定义目标的方式即网络确定图像中一个目标时需要的描述参数。对此,论文在第二章叙述如下:

[论文引用]

“我们的(检测)系统将输入图像划分为 S × S S×S S×S的网格。如果目标的中心落入网格单元,则该网格单元负责检测该目标。

“每个网格单元(grid cell)预测边界框 B B B和这些框的置信度得分。这些置信度得分反映出该模型对边界框是否包含目标的信度(confident),以及它认为的边界框预测的准确性。这里我们正式将置信度定义为 P r ( O b j e c t ) ∗ I O U p r e d t r u t h Pr(Object)*{IOU}_{pred}^{truth} Pr(Object)IOUpredtruth。如果网格单元中没有目标(Object),则置信度分数应该为零。如若不然,我们希望置信度分数等于预测边界框与基准值(Ground Truth)之间的重叠度(Intersection Over Union, IOU)。

“每个边界框均包含5个预测: x , y , w , h x, y, w, h x,y,w,h和置信度。 ( x , y ) (x, y) (x,y)坐标表示边界框相对于网格单元边界的中心。宽高则相对于整个图像进行预测。最后,置信度预测(the confidence prediction)表示预测的边界框与任何基准值(Ground Truth)之间的IOU。

“每个网格单元还预测条件类别概率 C C C P r ⁡ ( C l a s s i ∣ O b j e c t ) Pr⁡({Class}_i |Object) Pr(ClassiObject)。这些概率以包含一个目标的网格单元为条件。无论边界框 B B B的数量是多少,我们只为每个网格单元预测一组类概率。

“在测试时,我们将条件类别概率和每个边界框的置信度预测相乘:
P r ⁡ ( C l a s s i │ O b j e c t ) ∗ P r ⁡ ( O b j e c t ) ∗ I O U p r e d t r u t h = P r ( C l a s s i ) ∗ I O U p r e d t r u t h {\large Pr⁡ \left ( {Class}_i│Object \right ) *Pr⁡(Object) *{IOU}_{pred}^{truth} = Pr \left ( {Class}_i \right ) *{IOU}_{pred}^{truth} } Pr(ClassiObject)Pr(Object)IOUpredtruth=Pr(Classi)IOUpredtruth
这为我们提供了每个边界框的特定类别置信度得分。这些分数既编码了该类别出现在边界框中的概率,也预测了当前边界框对目标的适配程度。”

S × S S×S S×S的网格是YOLO对图像进行预测的参照基准,如果使用OOP的编程思想解释,那么每个网格都是一个“预测器”对象,它会输出自身像素占据的多个目标的信息,每个目标的信息包括三个方面:它的位置和大小、它的置信度以及它的所属类别。

目标的位置大小由预测出的bbox(边界框)的 x , y , w , h x, y, w, h x,y,w,h决定, ( x , y ) (x, y) (x,y)坐标是目标的中心,这个中心坐标是相对于它所在网格而言的。举个例子,假设网格为 10 × 10 10×10 10×10的大小,那么预测出的目标中心的坐标范围应该为 ( 0 , 0 ) (0, 0) (0,0) ( 10 , 10 ) (10, 10) (10,10)。可以看出,想要得到目标在原图像中的真实坐标,需要在预测出的中心坐标的基础上加上预测出该目标的网格的自身位置偏移,此“操作”在YOLOv2论文中被得以具体叙述,但此处不表。

和中心坐标不同,描述目标大小的 w , h w, h w,h值是相对于图像整体而言的。可是这样一来当目标大小远超过网格大小时,其宽高值不就超出了网格范围了么,没错,但正如论文所说,每个目标是从整体去预测的,因此目标的信息来源并不是局限在网格范围内。与此同时,网络也确实存在预测出的宽高超出原图边界的可能,而这个问题只需要在绘制边界框时加以限制即可。

目标的置信度表示网络对本次检测结果的“自信程度”。在网络训练时,该参数的学习依赖IOU,GT(Ground Truth)基准值来自训练数据。在网络预测过程中,该值由网络直接得到,不再进行IOU计算(因为没有基准值)。类概率也是由网络直接学习得到,多类别预测中,每个类别将会被预测得到一个类概率值(源码中若某一类别的类概率小于阈值(实现中该值为0.2),则将其置0)。将类概率与置信度相乘即可得到该目标边界框的特定类别置信度得分(如示例程序输出结果所展示的那样)。

[补充]

在论文作者pjreddie版本代码实现中,上述叙述可围绕以下代码路径查看:

网络前向计算中最后一层DETECTION的实现:

main -> run_yolo -> test_yolo -> parse_network_cfg -> parse_detection -> make_detection_layer -> forward_detection_layer

DETECTION层在预测阶段的输出直接复制前一层CONNECTED层的输出。CONNECTED层的构建为:

main -> run_yolo -> test_yolo -> parse_network_cfg ->parse_connected -> make_connected_layer

在预测阶段对DETECTION层输出结果的处理:

main -> run_yolo -> test_yolo -> get_network_boxes -> fill_network_boxes -> get_detection_detections

[说明]

源码下载和执行可参考后续章节 1.1.2 Darknet(pjreddie版)源码编译和示例运行

同样地,我们也会考虑到另一个问题:如果目标很大,覆盖了很多个网格,那么这些网格岂不是都会预测出同一个目标?是的。这就需要NMS操作去除同一分类下检测到的目标框集中的问题。在代码实现中,若相邻的网格预测出的同类边界框重合度大于设定的阈值(实现中该值为0.4),则总是保留前一网格的预测结果。

(2)理解损失函数

目标检测的方法如此之多,每种方法由于解题思路完全不同导致其损失函数本身多种多样。损失函数是针对特定网络算法在预测特征的多个方面对预测结果做出判断和限制的一种定量化评估方法。论文中对网络训练阶段的描述翻译如下:

[论文引用]

我们(网络)的最后一层可以预测类概率和边界框坐标。我们通过图像的宽度和高度对边界框的宽度和高度进行归一化,使它们落在0和1之间。我们将边界框 x x x y y y坐标参数化为特定网格单元格位置的偏移量,因此它们也被限制在0和1之间。

我们对最后一层使用线性激活函数,所有其它的层均使用以下弱调整(leaky rectified)线性激活:

ϕ ( x ) = { x ,  if  x > 0 0.1 x ,  otherwise  {\large \phi (x) = \begin{cases} x , & \text{ if } x > 0 \\ 0.1x , & \text{ otherwise } \end{cases} } ϕ(x)=x,0.1x, if x>0 otherwise 
我们对模型输出中的平方和误差进行了优化。我们使用平方和误差的原因是它易于优化,但它并不完全符合我们实现平均精度最大化的目标。它对定位误差和分类误差的权重相等,这可不是个好主意。此外,在每幅图像中,许多网格单元都不包含任何目标。这会将这些网格单元的“置信度”得分推向零,通常会超过确实包含目标的网格单元的梯度。这可能导致模型不稳定,从而导致训练早期的发散(diverge)。

为了解决这个问题,我们针对不包含目标的边界框:增加了对坐标预测(coordinate predictions)的损失、减少了边界框置信度预测(confidence predictions)的损失。我们使用两个参数 λ c o o r d \lambda _{coord} λcoord λ n o o b j \lambda _{noobj} λnoobj来完成此操作。我们设置 λ c o o r d = 5 \lambda _{coord} = 5 λcoord=5 λ n o o b j = 0.5 \lambda _{noobj} = 0.5 λnoobj=0.5

平方和误差对大边界框和小边界框的权重也是相等的。而我们的误差度量标准应该反映出大边界框中的小偏差比小边界框中的小偏差更不重要(matter less than)。为了部分解决(address)这个问题,我们预测边界框宽度和高度的平方根,而不是直接预测宽度和高度。

YOLO会对每个网格单元预测出多个边界框。在训练时,对于每个(预测)目标,我们希望有一个唯一的预测器(predictor)对其负责。哪一个预测具有对于基准值(Ground Truth)最高的IOU,我们就将该预测器指定为对此目标“负责”。这导致边界框预测器之间的专用化。使得每个预测器可以更好地预测特定大小(certain sizes)、画幅(aspect ratios)或目标类别,从而改善整体召回率(recall)。

在训练过程中,我们优化了多部分(multi-part)(组合)的损失函数如下所示:

L o s s y o l o v 1 = λ c o o r d ∑ i = 0 s 2 ∑ j = 0 B 1 i j o b j [ ( x i − x ^ i ) 2 + ( y i − y ^ i ) 2 ] + λ c o o r d ∑ i = 0 s 2 ∑ j = 0 B 1 i j o b j [ ( ω i − ω ^ i ) 2 + ( h i − h ^ i ) 2 ] {\large Loss_{yolov1}= \lambda _{coord} \sum_{i=0}^{s^2} \sum_{j=0}^B \mathbb{1} _{i j}^{obj} \left [ \left( x_{i}-\hat{x}_{i} \right )^{2} + \left( y_{i}-\hat{y}_{i} \right )^{2} \right ] + \lambda _{coord} \sum_{i=0}^{s^2} \sum_{j=0}^B \mathbb{1} _{i j}^{obj} \left [ \left( \sqrt{\omega_{i}} -\sqrt{\hat{\omega}_i} \right)^{2} + \left ( \sqrt{h_{i}} -\sqrt{\hat{h}_i} \right)^{2} \right ] } Lossyolov1=λcoordi=0s2j=0B1ijobj[(xix^i)2+(yiy^i)2]+λcoordi=0s2j=0B1ijobj(ωi ω^i )2+(hi h^i )2

+ ∑ i = 0 s 2 ∑ j = 0 B 1 i j o b j ( C i − C ^ i ) 2 + λ n o o b j ∑ i = 0 s 2 ∑ j = 0 B 1 i j n o o b j ( C i − C ^ i ) 2 {\large + \sum_{i=0}^{s^2} \sum_{j=0}^B \mathbb{1}_{i j}^{obj} \left ( C_{i} -\hat{C}_i \right )^{2} + \lambda _{noobj} \sum_{i=0}^{s^2} \sum_{j=0}^B \mathbb{1}_{i j}^{noobj} \left ( C_{i} -\hat{C}_i \right )^{2} } +i=0s2j=0B1ijobj(CiC^i)2+λnoobji=0s2j=0B1ijnoobj(CiC^i)2

+ ∑ i = 0 s 2 1 i j o b j ∑ c ∈ c l a s s e s ( p i ( c ) − p i ^ ( c ) ) 2 {\large + \sum_{i=0}^{s^2} \mathbb{1}_{i j}^{obj} \sum_{c\in classes} \left ( p_{i}(c)-\hat{p_{i}}(c) \right )^{2} } +i=0s21ijobjcclasses(pi(c)pi^(c))2

YOLOv1损失函数

这里 1 i o b j \mathbb{1}_{i}^{obj} 1iobj表示目标是否出现在单元格(cell) i i i,而 1 i j o b j \mathbb{1}_{i j}^{obj} 1ijobj表示第 j j j个边界框预测器在单元格 i i i中对此预测“负责”。

根据论文中对损失函数构建的描述,可以看出公式中每个部分的含义、由来及其作用,三个部分分别对应坐标预测、置信度预测和类概率预测的误差度量,参数 λ c o o r d \lambda _{coord} λcoord λ n o o b j \lambda _{noobj} λnoobj用于提升或降低对应部分误差的影响。总而言之,损失函数的设计依赖于算法本身的同时又有具有一定的“弱关联性”,具体增删或强化/弱化哪一部分是由训练策略决定的。

[注意]

论文中给出的损失函数公式和具体的代码实现可能存在差异。

(3)算法是如何嵌入到网络结构中的

卷积神经网络是众多数据处理机制构造方法中的一种。如果算力和样本趋近于无限,则不排除全连接网络表现会更好的可能。和错综复杂的人脑不同,这种被高度裁剪和设计的数据处理器只能专注于我们希望它专注的那部分数据特征(在基于CNN的网络中,卷积层就是这些数据特征的提取器)。

也许你会感到疑惑,一个结构清晰的网络看似与理论论文中描述的算法完全是两回事,算法和网络是如何结合的呢?答案是网络的学习能力。网络被构建伊始,可以被认为是“空”的,或者是“包含未知参数的未知数据处理器”。反向传播为参数的更新提供了一种途径,使得在数据集上训练的网络的每一个预测都更加符合损失函数的要求。一个完美的网络学习到的不只是众多特征图,更是一个“以损失函数的要求为标准的数据变换模式”。

[补充]

在论文作者pjreddie版本代码实现中,网络损失相关可围绕以下代码路径查看:

训练过程中对每层网络执行损失计算:

main -> run_yolo -> train_yolo -> train_network -> train_network_datum -> forward_network -> calc_network_cost

该函数中判断每层网络描述结构体中cost成员(float *cost)是否有效,进而加和该值。在YOLOv1网络中,仅DETECTION层为该成员申请了空间(其它层该成员值为0(NULL)):

main -> run_yolo -> train_yolo -> load_network -> parse_network_cfg -> parse_detection -> make_detection_layer

[说明]

源码下载和执行可参考后续章节 1.1.2 Darknet(pjreddie版)源码编译和示例运行

在网络结构中找到记录前向计算损失的变量值后,监控该变量的读写就可以找到损失函数的实现。至于网络各预测输出的意义,自然已经被嵌入到损失函数的实现中了。考虑到时间安排和最终目标,YOLOv1的源码无需看得太过仔细,能够熟悉YOLO的基本思想即可。

1.1.2 Darknet(pjreddie版)源码编译和示例运行

YOLO的官方实现基于Darknet。Darknet和Tensorflow、Keras等一样,都只是一个框架,YOLO系列论文中多处提到的Darknet-53、Darknet-19之类的骨干网络是方法,它们名字中带了“Darknet”这个词,且仅此而已。

YOLO的源码可在Darknet的Github仓库上获取。Darknet基于C和CUDA编写,可以在Windows或者Linux下编译运行。

[说明]Darknet的版本

除了YOLO的作者的pjreddie版本的Darknet之外,还有一个AlexeyAB版本的Darknet

pjreddie版本至今已有两年以上没有维护了,因此在编译和运行上的效果不如AlexeyAB版本,接下来的叙述中你可以体会到这一点。AlexeyAB版本接过了原版Darknet的接力棒,至今仍在维护,因此对新版本YOLO的更新以及编译支持会有好很多。

此处从原版开始看起,仅仅是为了重走一边历史,但AlexeyAB版同样带有YOLOv1的网络结构配置。

  • Windows下的编译

    在Windows下编译需借助Cygwin或者MSYS2或使用VS构建工程。本章使用MSYS2环境执行编译,过程可参考:在window下搭建Darknet环境_CSDNdarknet YOLO 编译使用GPU_CSDN。编译需要注意几点:

    1. 首先执行以下语句完成更新:

      pacman -Sy
      pacman -Su
      pacman -S gcc
      pacman -S gdb
      pacman -S make
      
    2. 新版本的MSYS2不需要换源也可以很快完成包下载过程;

    3. 安装Cygwin或MSYS2后需要添加环境变量到PATH;

    4. 修改 .\include\darknet.h ,添加:

      #include <time.h>
      
    5. 虽然在命令提示符中也能够执行make、gcc等命令,但编译过程需要在MSYS2命令行窗口进行。

  • Linux下的编译

    本文未做尝试。

示例运行过程可参考YOLOv1官网示例,例如:

./darknet.exe yolo test cfg/yolov1.cfg yolov1.weights data/dog.jpg

[说明]该处使用的YOLOv1权重数据下载地址:http://pjreddie.com/media/files/yolov1/yolov1.weights

它的执行结果如下:

Linyar@DESKTOP-PNI4567 MSYS /g/darknet-master
$ ./darknet.exe yolo test cfg/yolov1.cfg yolov1.weights data/dog.jpg
layer     filters    size              input                output
    0 conv     64  7 x 7 / 2   448 x 448 x   3   ->   224 x 224 x  64  0.944 BFLOPs
    1 max          2 x 2 / 2   224 x 224 x  64   ->   112 x 112 x  64
    2 conv    192  3 x 3 / 1   112 x 112 x  64   ->   112 x 112 x 192  2.775 BFLOPs
    3 max          2 x 2 / 2   112 x 112 x 192   ->    56 x  56 x 192
    4 conv    128  1 x 1 / 1    56 x  56 x 192   ->    56 x  56 x 128  0.154 BFLOPs
    5 conv    256  3 x 3 / 1    56 x  56 x 128   ->    56 x  56 x 256  1.850 BFLOPs
    6 conv    256  1 x 1 / 1    56 x  56 x 256   ->    56 x  56 x 256  0.411 BFLOPs
    7 conv    512  3 x 3 / 1    56 x  56 x 256   ->    56 x  56 x 512  7.399 BFLOPs
    8 max          2 x 2 / 2    56 x  56 x 512   ->    28 x  28 x 512
    9 conv    256  1 x 1 / 1    28 x  28 x 512   ->    28 x  28 x 256  0.206 BFLOPs
   10 conv    512  3 x 3 / 1    28 x  28 x 256   ->    28 x  28 x 512  1.850 BFLOPs
   11 conv    256  1 x 1 / 1    28 x  28 x 512   ->    28 x  28 x 256  0.206 BFLOPs
   12 conv    512  3 x 3 / 1    28 x  28 x 256   ->    28 x  28 x 512  1.850 BFLOPs
   13 conv    256  1 x 1 / 1    28 x  28 x 512   ->    28 x  28 x 256  0.206 BFLOPs
   14 conv    512  3 x 3 / 1    28 x  28 x 256   ->    28 x  28 x 512  1.850 BFLOPs
   15 conv    256  1 x 1 / 1    28 x  28 x 512   ->    28 x  28 x 256  0.206 BFLOPs
   16 conv    512  3 x 3 / 1    28 x  28 x 256   ->    28 x  28 x 512  1.850 BFLOPs
   17 conv    512  1 x 1 / 1    28 x  28 x 512   ->    28 x  28 x 512  0.411 BFLOPs
   18 conv   1024  3 x 3 / 1    28 x  28 x 512   ->    28 x  28 x1024  7.399 BFLOPs
   19 max          2 x 2 / 2    28 x  28 x1024   ->    14 x  14 x1024
   20 conv    512  1 x 1 / 1    14 x  14 x1024   ->    14 x  14 x 512  0.206 BFLOPs
   21 conv   1024  3 x 3 / 1    14 x  14 x 512   ->    14 x  14 x1024  1.850 BFLOPs
   22 conv    512  1 x 1 / 1    14 x  14 x1024   ->    14 x  14 x 512  0.206 BFLOPs
   23 conv   1024  3 x 3 / 1    14 x  14 x 512   ->    14 x  14 x1024  1.850 BFLOPs
   24 conv   1024  3 x 3 / 1    14 x  14 x1024   ->    14 x  14 x1024  3.699 BFLOPs
   25 conv   1024  3 x 3 / 2    14 x  14 x1024   ->     7 x   7 x1024  0.925 BFLOPs
   26 conv   1024  3 x 3 / 1     7 x   7 x1024   ->     7 x   7 x1024  0.925 BFLOPs
   27 conv   1024  3 x 3 / 1     7 x   7 x1024   ->     7 x   7 x1024  0.925 BFLOPs
   28 Local Layer: 7 x 7 x 1024 image, 256 filters -> 7 x 7 x 256 image
   29 dropout       p = 0.50               12544  ->  12544
   30 connected                            12544  ->  1715
   31 Detection Layer
forced: Using default '0'
Loading weights from yolov1.weights...Done!
data/dog.jpg: Predicted in 7.844000 seconds.
dog: 26%
bicycle: 39%
car: 74%
Not compiled with OpenCV, saving to predictions.png instead

BaseYOLO预测示例

跑一下它框架中自带的示例吧!
上图就是论文中的那张,图片中的狗子可能永远不会知道自己将以这种方式流芳百世2333

在这里特别说明一下,上述程序在默认配置下编译得到的是CPU版本,因为此处我们只是通过示例跑一下网络模型, 暂不需要GPU版本 ,所以预测耗时达到将近8秒。如需编译GPU版本的Darknet,需在 .\Makefile 中打开GPU和CUDA编译开关,并配置CUDA和cuDNN的头文件和库的路径。

编译GPU版本之前,首先需要确保安装了正确版本的CUDAcuDNNVirtual Studio。这是针对Windows平台来说的。CUDA版本取决于你使用的显卡型号,不同型号支持的CUDA版本也有差异。cuDNN的版本则依赖于CUDA版本,在其下载页面已经有标注。CUDA代码的编译由nvcc负责,它依赖VS提供的cl工具。

[说明]查看Nvidia驱动支持的CUDA版本

如需查看Nvidia驱动支持的CUDA版本,可进入【NVIDIA控制面板】,选择【帮助】选项卡中的【系统信息】。在弹出的对话框中选择【组件】选项卡,查看【NVCUDA.DLL】的详细信息即可。如下图所示,当前驱动支持的最高CUDA版本为10。

查看显卡支持的CUDA版本

注意:CUDA具体版本选择需结合工程需求,在没有特殊要求的情况下才可使用支持的最高版本。

说明:驱动支持的CUDA版本与驱动程序的版本相关,可通过升级驱动程序获得对高版本CUDA的支持。

[问题记录]pjreddie版本Darknet过低导致CUDA宏缺失

当开启GPU+CUDA编译开关时,在我本机上使用CUDA10.1和cuDNN8.0.5.39编译会出现以下错误:

./src/convolutional_layer.c:153:13: error: ‘CUDNN_CONVOLUTION_FWD_SPECIFY_WORKSPACE_LIMIT’ undeclared (first use in this function)
153 |             CUDNN_CONVOLUTION_FWD_SPECIFY_WORKSPACE_LIMIT,
   |             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated due to -Wfatal-errors.
make: *** [Makefile:89: obj/convolutional_layer.o] Error 1

按照该链接的描述进行修改即可。

[说明]MSYS2环境下无法访问Windows环境变量,找不到nvcc等问题

按照该链接的说明,在Windows下增加环境变量MSYS2_PATH_TYPE,并设置其值为inherit,重启MSYS2即可。

[问题记录]由于路径问题,cl无法找到某些头文件

我没有尝试查找解决方法,因为我在这上面耗费了超出预期的时间。在屏蔽了一些cl无法识别的编译选项后,编译最终失败在这个地方:

编译GPU+CUDA版本的Darknet遇到的问题

在VS安装目录下并没有找到corecrt.h。当前使用的版本是VS2019 Community,这个问题也许和VS的版本也有关系。

总之,在经历过一系列的尝试之后,我还是放弃了对pjreddie版Darknet的GPU+CUDA版本的编译。以上叙述仅作为历史记录,留待后人填坑吧。>v<

1.1.3 网络结构

在获得Darknet的源码之后,可通过查看源码目录下的 .\cfg\yolov1.cfg.\cfg\yolov1-tiny.cfg 配置文件获得对论文中Base YOLO和Fast YOLO网络结构的描述,或者是像前文一样跑示例,在程序打印中也可以看到网络信息。

按照配置文件中给出的信息,结合源码实现,Base YOLO网络结构如下图所示。

[注意]

这里仅表示了每层的卷积层参数,实际上每个网络层由卷积层、BN和激活函数组成。

[说明]

*.cfg文件中卷积层 pad=1 表示启用填充,而非将填充值设为1。填充值为卷积核大小/2,取整。

[补充]

在论文作者pjreddie版本代码实现中,卷积层pad参数设置可围绕以下代码路径查看:

main -> run_yolo -> test_yolo -> load_network -> parse_network_cfg -> parse_convolutional 的参数处理部分

YOLOv1基本网络结构

YOLOv1网络结构

[说明]

f是filter的缩写,表示当前卷积层或池化层使用的核大小;p是pad的缩写,表示该层卷积操作的填充大小;s是stride的缩写,表示当前卷积层或池化层计算时的步长;n表示该层滤波器数量。

本文后续所有自绘制网络的参数简述均使用该缩写,若有修改或添加,会在对应位置另行标注。

其中,标红的卷积处理过程表示其中有未被处理到的pad区域,表现在计算上为:对于 n × m n\times m n×m的输入,卷积输出分辨率

O u t p u t = [ n + 2 p − f s + 1 ] × [ m + 2 p − f s + 1 ] {\large Output = \left [ \frac{n+2p-f}{s} + 1 \right ] \times \left [ \frac{m+2p-f}{s} + 1 \right ] } Output=[sn+2pf+1]×[sm+2pf+1]

的商不为整数。一般而言,卷积计算向下取整、池化计算向上取整,这个原则在卷积计算时的实现方式是:只计算卷积核滑动时完全覆盖的部分。网络训练时,使用上图中“Classification”标识的后级网络,而后通过更换后级网络为“Detection”标识的部分再训练以使网络执行检测任务。论文中对此的叙述如下:

[论文引用]

“我们在ImageNet 1000类竞赛数据集上对卷积层进行预训练。在预训练中,我们使用图3中的前20个卷积层,后接平均池化层和全连接层。我们对这个网络进行了大约一周的训练,在ImageNet 2012验证集上达到了88%的单一裁剪(single crop)TOP-5准确率,与GoogLeNet在Caffe’s Model Zoo(的表现)相当。

“然后我们将模型转换为执行检测任务。Ren等人证明了将卷积层和全连接层添加到预训练的网络中可以提高性能。按照他们的示例,我们添加了4个卷积层和两个全连接层,并将其赋予随机初始化的权重。检测(任务)通常需要细粒度的视觉信息,因此我们将网络的输入分辨率从 224 × 224 224×224 224×224增加到 448 × 448 448×448 448×448。”

1.1.4 源码实现中的网络输出

[论文引用]

“为了在PASCAL VOC上评估YOLO,我们使用 S = 7 , B = 2 S = 7, B = 2 S=7,B=2(的参数设置)。PASCAL VOC具有20个标记的类,因此 C = 20 C = 20 C=20。我们的最终预测是一个 7 × 7 × 30 7×7×30 7×7×30的张量。”

由上述叙述可以分析得知:该网络总共可以预测20个类别概率(因为训练时使用的VOC数据集只有20个类别)。每个网格单元只能预测两个框(bbox, bounding box),每个框包含5个预测量,并且只能具有一个类别。总共输出 7 × 7 × ( 2 × 5 + 20 ) = 1470 7×7×(2×5 + 20) = 1470 7×7×(2×5+20)=1470个张量(即每个类别上都会输出一个类别概率)。

对于每张图像,网络可以预测98个边界框以及每个框的类概率,然后根据阈值去除可能性比较低的目标窗口,再由NMS去除冗余窗口。由于前面所述,每个提供预测的网格单元只能预测出同一类别的两个框,因此对于一张图片,YOLO最大可检测目标数量为49个。

在现有的网络实现中,有些参数与论文中描述的略有不同,例如在网络结构打印信息中,全连接层的输出数量为1715。这是由于当前网络配置中,将每个单元格预测的边界框数量设置为3(即 B = 3 B = 3 B=3)所致,此时 1715 = 7 × 7 × ( 3 × 5 + 20 ) 1715 = 7×7×(3×5 + 20) 1715=7×7×(3×5+20)

和训练过程不同,预测过程的网络前向计算中,最后一个全连接层(Connected)层的输出即为网络输出,在内存中以一个float数组的形式存在,这片内存区域在构建网络时由calloc申请并存储在堆上。其数据存储格式如下图所示:

网络全连接层输出数据格式

单YOLOv1网络输出层数据存储格式

带着这幅图理解代码实现中对DETECTION层的处理逻辑会事半功倍。

1.2 YOLOv2

本节着重于YOLOv1和YOLOv2之间的差异。在我的计划中,同样不打算在这里花费太多的时间,作为更先进的YOLOv3的过渡,在前一版本的基础上理解YOLOv2的优化手段之后,我们直接向YOLOv3进发。

1.2.1 YOLOv2网络算法

(1)在YOLOv1基础上做的改动

针对YOLOv1会产生较多的定位错误这一问题,YOLOv2从网络结构到算法都做出了很多改进。论文对YOLOv1的修改分三个章节叙述,分别对应检测效果、检测速度和新训练方法。

  • 在检测效果(mAP)方面,YOLOv2相比于YOLO做出了如下修改:

    1. 使用了批标准化BN;
    2. 提高了分类器网络的输入分辨率;
    3. 使用卷积网络代替全连接层进行预测;
    4. 使用K-均值聚类自动生成先验框;
    5. 在先验框上直接预测边界框的中心位置;
    6. 在网络结构中添加直通层实现对细粒度特征的访问;
    7. 在训练时使用多尺度训练。
  • 在速度方面,YOLOv2使用了新的分类模型Darknet-19,使用GAP进行预测。

  • 在训练方法上,论文提出了一种基于WordTree的联合训练方法,使得训练过程可以使用标记为检测的图像来学习特定于检测的信息,论文据此基于ImageNet和COCO数据集训练了一种多类别检测网络YOLO9000。

    [说明]YOLOv2和YOLO9000的关系

    YOLO9000是基于YOLOv2特征提取骨干网络(Darknet-19)验证WordTree的网络。YOLO9000可使用分类数据集训练目标检测任务需要的信息,由于分类数据集中标签更加细致和复杂,因此网络使用基于WordNet思想设计的WordTree层次树。

下面尝试详细解释一下其中某些修改的具体细节。

(2)锚框(Anchor Boxes)、直接位置预测和维度聚类(Dimension Clusters)

锚框最初是在Faster R-CNN中提出,此后在SSD和YOLO等优秀的目标识别模型中得到了广泛的应用。目前anchor box的选择主要有三种方式:①人为经验选取;②K-均值聚类;③作为超参数进行学习。对锚框的总结可参考如:《锚框:Anchor box综述_知乎》和《目标检测中的Anchor_知乎》等。

使用锚框、并基于锚框进行直接位置预测使YOLOv2的网络预测任务与前一版本相比发生了很大的变化。所不变的是:预测依然基于网格单元(cell)进行,每个网格作为一个“预测器”的思想和YOLOv1是一致的。

在YOLOv1中,网络通过学习,使其中每个预测器(网格)直接输出目标的 B B B x , y , w , h x, y, w, h x,y,w,h和置信度。在YOLOv2论文中,每个网格将基于5个锚框(或作“先验框”,这个数值相当于YOLOv1参数中 B = 5 B = 5 B=5),为每个框预测出4个针对当前锚框的变换参数 t x t_{x} tx t y t_{y} ty t w t_{w} tw t h t_{h} th,以及目标置信度 t o t_{o} to

[论文引用]

“我们遵循YOLO的方法预测相对于网格单元位置的位置坐标,而不是预测偏移量。这将基准值(Ground Truth)限制在0到1之间。我们使用logistic激活函数将网络的预测限制在此范围内。

“网络对输出特征图的每个单元格预测5个边界框。网络为每个边界框预测了5个坐标(coordinates), t x t_{x} tx t y t_{y} ty t w t_{w} tw t h t_{h} th t o t_{o} to。如果网格单元对于图像左上角的偏移量为 ( c x , c y ) (c_{x}, c_{y}) (cx,cy),且先验边界框的宽度和高度分别为 p w , p h p_{w}, p_{h} pw,ph,则预测值为:”

b x = σ ( t x ) + c x {\Large b_{x} = \sigma ( t_{x} ) + c_{x} } bx=σ(tx)+cx

b y = σ ( t y ) + c y {\Large b_{y} = \sigma ( t_{y} ) + c_{y} } by=σ(ty)+cy

b w = p w + e t w {\Large b_{w} = p_{w} + e^{ t_{w} } } bw=pw+etw

b h = p h + e t h {\Large b_{h} = p_{h} + e^{ t_{h} } } bh=ph+eth

P r ⁡ ( o b j e c t ) ∗ I O U ( b , o b j e c t ) = σ ( t o ) {\Large Pr⁡(object) * IOU(b, object) = \sigma (t_{o}) } Pr(object)IOU(b,object)=σ(to)

YOLOv2基于先验框的网络预测

论文图3:带有尺寸先验(Dimension Priors)和边界预测的边界框

可以对此进行以下理解:

网格的 x , y x, y x,y输出表示对于当前网格(cell),用于“套住”目标的锚框的中心位置, ( x , y ) (x, y) (x,y)中心坐标依旧是相对于它所在网格而言的。网格的 w , h w, h w,h输出表示对于当前锚框而言,框的长和宽要进行多大的缩放。网格的置信度输出含义相同。

边界框的回归是通过预测先验框的“缩放量”实现的,因此在先验框选取较好的情况下网络会更容易做到“预测的边界框完美与目标边界契合”。

[论文引用]

“由于对位置预测进行了约束,因此参数(parametrization)更容易学习,从而使网络更加稳定。”

论文中使用的先验框获取方法是K-均值聚类。但在我的理解上,这意味着增强网络对数据集的依赖性,当数据集样本分布不均或样本容量太小时会造成先验框划分不合理的问题。当网络需移植到其它检测任务或监测场景上时,需要针对具体目标的样本集重新预测先验框。

[论文引用]

“**维度聚类(Dimension Clusters)。**将锚框和YOLO一起使用时,我们遇到了两个问题。首当其冲的是,框的尺寸是手工挑选的。网络(本身)可以学习以适当地调整框(的尺寸),但如果我们为网络选择更好的先验条件,则可以使网络更容易学习(如何)预测(出)良好的检测(结果)。

“我们不手动选择先验(框),而是在训练数据集的边界框上运行K-均值聚类(k-means clustering)以自动找到好的先验(框)。如果我们使用具有欧氏距离的标准K-均值,那么较大的框会比较小的框产生更多的误差。然而我们真正想要的是能够获得良好IOU分数的先验(值),这与盒子的大小无关。因此,对于我们的距离度量,我们使用:
d ( b o x , c e n t r o i d ) = 1 − I O U ( b o x , c e n t r o i d ) {\large d(box, centroid) = 1 - IOU(box, centroid) } d(box,centroid)=1IOU(box,centroid)
我们以各种K值运行K-均值(算法),并在最接近的形心(centroid)绘制平均IOU,见图2。我们选择 k = 5 k = 5 k=5作为模型复杂度和高召回率之间的一个很好的权衡点。聚类中心和手工挑选的锚框有明显的不同,(和手工挑选的相比,)又矮又宽的框更少,而又高又瘦的框更多。

论文-VOC和COCO上的聚类框尺寸

论文图2:VOC和COCO上的聚类框尺寸
我们在边界框的尺寸上运行K-均值聚类,以获得模型的良好先验。左图显示了在K的各种取值下获得的平均IOU。
我们发现k=5时在召回率和模型复杂度之间取得了较好的均衡。右图展示了VOC和COCO的相对形心。两组的先验方法都倾向于使用更高更瘦的框,而COCO的尺寸差异要大于VOC。
(3)细粒度特征(Fine-Grained Features)和直通层(Passthrough Layer)

这里不妨先瞄一眼 1.2.3 网络结构 章节中的配图,直观的了解一下“直通层”的样貌。和ResNet的“shortcut”一样,YOLOv2将前层网络的低级特征与后级网络学习到的高级特征进行融合以提升网络的特征辨识能力。

[论文引用]

“**细粒度特征(Fine-Grained Features)。**经过改进(modified)的YOLO可以在13×13的特征图上预测检测结果。尽管这(种特性)对于大型目标(的预测)来说已经足够,但是对于小目标的定位而言,更细粒度的特征可能更加有益。Faster R-CNN和SSD通过运行其建议网络获得各种分辨率的特征图。我们采用了另一种方法,只需要添加一个直通层(passthrough layer,源码中的reorg layer)就可以将26×26分辨率的特征带来(到当前层)。

“直通层通过将相邻的特征叠加到不同的通道而非空间位置中,从而将高分辨率特征和低分辨率特征连接起来,类似于ResNet中的特征映射。这样就将26×26×512的特征图转换为13×13×2048的特征图,可以将其与原始特征连接在一起。

“我们的检测器运行在这个扩展的特征图上,因此可以访问细粒度的特征。这会使性能略微提升1%。”

在后级预测器输入中引入前级特征的想法早已有之,这使得低级特征可以在全图的分类器上被复用,从而增加了网络对预测目标的特征组合和表达能力。

对于直通层的数据处理和实现请跳转到后续 1.2.4 源码实现中的直通层 章节。

(4)其它修改

对DarkNet-19的设计、使用和修改,论文中的叙述已经足够了。

[论文引用]

Darknet-19。我们提出了一种新的分类模型作为YOLOv2的基础。我们的模型建立在先前的网络设计以及该领域常识的基础上。与VGG模型类似,我们主要使用3×3滤波器,在每个池化步骤之后使通道数量翻倍。遵循Network in Network(NIN)的工作,我们使用全局平均池化进行预测,并使用1×1滤波器压缩3×3卷积之间的特征表示。我们使用BN来稳定训练、加快收敛速度以及对模型规范化。

“我们的最终的模型称为Darknet-19,它具有19个卷积层和5个最大池化层。相关的完整描述见表6。Darknet-19处理一幅图像仅需要55.8亿次操作,但在ImageNet上却达到了72.9%的TOP-1精度和91.2%的TOP-5精度。”

Darknet-19

论文表6:Dakrnet-19

“**为检测(任务)做训练。**我们对这个网络进行了修改,去掉了最后一个卷积层并代之以三个具有1024个过滤器的3×3卷积层,最后是具有我们需要检测的输出数量的1×1卷积层。对于VOC(数据集),我们预测5个框,每个框有5个坐标、20个类别,因此有125个滤波器。我们还在最后一个 3 × 3 × 512 3×3×512 3×3×512层到倒数第二个卷积层之间添加了一个直通层,以便我们的模型可以使用细粒度特征。”

[说明]

VOC数据集预测时,滤波器数量为:5个框*(5个坐标+20个类别)=125个输出,此时每个类别都会经由各自的1×1卷积输出置信度。

GAP(Global Average Pooling,全局平均池化)第一次出现在论文Network in Network(NIN)中,是对FC的优化,主要希望解决的问题是FC参数过多造成的问题,例如计算和存储困难、容易发生过拟合等。全局平均池化和字面意思一样,是对每个通道求出一个平均标量,而后连接到输出级的分类器上,即GAP层节点数和前级通道数一致,输出层节点数和分类数目一致,从而大大减少了参数的数量。

BN(Batch Normalization,批标准化)在2015年被提出。用于解决随着网络层数加深,每层数据分布偏移逐渐积累的问题(这在BN的文章中被称之为内部协变量偏移(internal covariate shift))。

[论文引用]《批量标准化:通过减少内部协变量偏移来加速深度网络训练》摘要翻译

“训练深度神经网络非常复杂,因为在训练过程中,随着先前各层的参数发生变化,各层输入的分布也会发生变化。由于要求较低的学习率和精细的参数初始化,这减慢了训练速度,并且众所周知,训练具有饱和非线性的模型非常困难。我们将此现象称为内部协变量偏移,并通过归一化层输入来解决该问题。我们的方法通过将归一化作为模型体系结构的一部分并针对每个训练小批量执行归一化来汲取其优势。批处理规范化使我们可以使用更高的学习率,而对初始化则不必那么小心。它还可以充当正则化器,在某些情况下,无需使用Dropout。批量归一化应用于最先进的图像分类模型,以较少的14倍训练步骤即可达到相同的准确性,并大大超越了原始模型。使用批标准化网络的集成,我们改进了ImageNet分类中最好的已发布结果:达到4.9%的top-5验证错误(和4.8%的测试错误),超过了人类评分的准确性。”

BN的好处有:①减轻了对参数初始化的依赖;②训练更快,可以使用更高的学习率;③一定程度上增加了泛化能力,可以替代dropout等技术。但由于BN依赖批处理大小,当batch值很小时,计算的均值和方差不稳定。故此BN不适合下面的场景:①Batch很小。比如训练资源有限无法应用较大的batch,也比如在线学习等使用单例进行模型参数更新的场景;②RNN。因为它是一个动态的网络结构,同一个Batch中训练实例有长有短,导致每一个时间步长必须维持各自的统计量,这使得BN并不能正确的使用。-参考来源-

关于BN的更详细的说明参见专题章节。

多尺度训练(Multi-Scale Training)是训练过程中的策略,这一思想可以使网络有动态的训练方法以适应不同的目标需求(即论文中说的“YOLOv2可以在速度和准确性之间进行权衡”),同样参考论文描述即可。

[论文引用]

多尺度训练(Multi-Scale Training)。原始YOLO的输入分辨率为448×448。添加锚框之后,我们将(输入)分辨率改为416×416。但是,由于我们的模型只使用卷积层和池化层,因此随时可以调整其(输入分辨率)的大小。我们希望YOLOv2能够在不同尺寸的图像上运行,因此我们将其(这种特性)训练到模型中。

“我们不固定输入图像的大小,而是每隔几次迭代就更改网络。我们的网络(在训练时),每10批将随机选择一个新的(输入)图像尺寸。由于我们的模型(对输入)下采样了32倍,因此我们可以从以下32的倍数中选择:{320, 352, …, 608}。最小的选项为320×320,最大的选项为608×608。(然后)我们将网络调整到该尺寸并继续训练。

“这种模式(regime)迫使网络学会在各种输入维度上进行良好的预测。这意味着一个网络可以在不同分辨率下进行结果预测。网络在较小的尺寸下运行时速度更快,因此YOLOv2可以在速度和准确性之间进行权衡(选择)。

“在低分辨率下,YOLOv2可以作为廉价(注:指计算开销小)且相当精准的检测器使用。在288×288的分辨率下,它的运行速度超过90FPS,几乎和Fast R-CNN一样好。这使它成为较小(算力)的GPU、高帧率视频或多视频流(应用)的理想选择。

“在高分辨率下,YOLOv2是在VOC 2007上可以实时运行的、具有78.6mAP的最先进的检测器。”

1.2.2 Darknet(AlexeyAB版)源码编译和示例运行

该版本Darknet的编译与pjreddie版本对VS等环境要求基本相似。此处依然以 Windows环境、CPU版本 为例。

[补充]

在Linux下的编译和使用步骤可参考:手把手教你用AlexeyAB版Darknet_知乎专栏

编译之前记得添加VS目录到系统PATH环境变量,否则无法找到cl.exe。默认路径为: C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin

[问题记录]在中文版Virtual Studio 2015环境下编译时提示D8000错误

编译过程中提示如下信息:

Linyar@潇菏 MSYS /g/darknet-master-AlexeyAB
$ make
...
cl: 命令行 warning D9025 :正在重写“/Os”(用“/Ot”)
cl: 命令行 warning D9002 :cl: 命令行 error D8000 :cl: 命令行 error D8000 :
...(此处省略众多相同内容)
cl: 命令行 error D8000 :cl: 命令行 error D8000 :C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\amd64\cl.exe 中有内部编译器错误。系统将会提示你稍后向 Microsoft 发送错误报告。
“C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\amd64\cl.exe”中的内部编译器错误
请选择 Visual C++
“帮助”菜单上的“技术支持”命令,或打开技术支持帮助文件来获得详细信息
convolutional_kernels.cu
make: *** [Makefile:186: obj/convolutional_kernels.o] Error 2

该链接下载VS2015的英文支持包即可。-vcpkg中rc.exe或mt.exe的问题-

仍旧在MSYS环境下运行Darknet,执行语句可参考YOLOv2官网示例,例如:

./darknet detect cfg/yolov2.cfg yolov2.weights data/dog.jpg

[说明]

该处使用的YOLOv2权重数据下载地址:https://pjreddie.com/media/files/yolov2.weights

这样运行的是基于COCO数据集的80分类检测器。若要运行基于VOC数据集的20分类检测器,可执行:

./darknet detector test cfg/voc.data cfg/yolov2-voc.cfg yolov2-voc.weights data/dog.jpg

[说明]

该处使用的YOLOv2-VOC权重数据下载地址:https://pjreddie.com/media/files/yolov2-voc.weights

YOLOv2-VOC检测的执行结果如下(进行了轻微的排版):

Linyar@潇菏 MSYS /g/darknet-master-AlexeyAB
$ ./darknet detector test cfg/voc.data cfg/yolov2-voc.cfg yolov2-voc.weights data/dog.jpg
 GPU isn't used
 OpenCV isn't used - data augmentation will be slow
mini_batch = 1, batch = 1, time_steps = 1, train = 0
   layer   filters  size/strd(dil)      input                output
   0 conv     32       3 x 3/ 1    416 x 416 x   3 ->  416 x 416 x  32 0.299 BF
   1 max               2 x 2/ 2    416 x 416 x  32 ->  208 x 208 x  32 0.006 BF
   2 conv     64       3 x 3/ 1    208 x 208 x  32 ->  208 x 208 x  64 1.595 BF
   3 max               2 x 2/ 2    208 x 208 x  64 ->  104 x 104 x  64 0.003 BF
   4 conv    128       3 x 3/ 1    104 x 104 x  64 ->  104 x 104 x 128 1.595 BF
   5 conv     64       1 x 1/ 1    104 x 104 x 128 ->  104 x 104 x  64 0.177 BF
   6 conv    128       3 x 3/ 1    104 x 104 x  64 ->  104 x 104 x 128 1.595 BF
   7 max               2 x 2/ 2    104 x 104 x 128 ->   52 x  52 x 128 0.001 BF
   8 conv    256       3 x 3/ 1     52 x  52 x 128 ->   52 x  52 x 256 1.595 BF
   9 conv    128       1 x 1/ 1     52 x  52 x 256 ->   52 x  52 x 128 0.177 BF
  10 conv    256       3 x 3/ 1     52 x  52 x 128 ->   52 x  52 x 256 1.595 BF
  11 max               2 x 2/ 2     52 x  52 x 256 ->   26 x  26 x 256 0.001 BF
  12 conv    512       3 x 3/ 1     26 x  26 x 256 ->   26 x  26 x 512 1.595 BF
  13 conv    256       1 x 1/ 1     26 x  26 x 512 ->   26 x  26 x 256 0.177 BF
  14 conv    512       3 x 3/ 1     26 x  26 x 256 ->   26 x  26 x 512 1.595 BF
  15 conv    256       1 x 1/ 1     26 x  26 x 512 ->   26 x  26 x 256 0.177 BF
  16 conv    512       3 x 3/ 1     26 x  26 x 256 ->   26 x  26 x 512 1.595 BF
  17 max               2 x 2/ 2     26 x  26 x 512 ->   13 x  13 x 512 0.000 BF
  18 conv   1024       3 x 3/ 1     13 x  13 x 512 ->   13 x  13 x1024 1.595 BF
  19 conv    512       1 x 1/ 1     13 x  13 x1024 ->   13 x  13 x 512 0.177 BF
  20 conv   1024       3 x 3/ 1     13 x  13 x 512 ->   13 x  13 x1024 1.595 BF
  21 conv    512       1 x 1/ 1     13 x  13 x1024 ->   13 x  13 x 512 0.177 BF
  22 conv   1024       3 x 3/ 1     13 x  13 x 512 ->   13 x  13 x1024 1.595 BF
  23 conv   1024       3 x 3/ 1     13 x  13 x1024 ->   13 x  13 x1024 3.190 BF
  24 conv   1024       3 x 3/ 1     13 x  13 x1024 ->   13 x  13 x1024 3.190 BF
  25 route  16                                     ->   26 x  26 x 512
  26 conv     64       1 x 1/ 1     26 x  26 x 512 ->   26 x  26 x  64 0.044 BF
  27 reorg_old reorg_old    / 2     26 x  26 x  64 ->   13 x  13 x 256
  28 route  27 24                                  ->   13 x  13 x1280
  29 conv   1024       3 x 3/ 1     13 x  13 x1280 ->   13 x  13 x1024 3.987 BF
  30 conv    125       1 x 1/ 1     13 x  13 x1024 ->   13 x  13 x 125 0.043 BF
  31 detection
mask_scale: Using default '1.000000'
Total BFLOPS 29.371
avg_outputs = 608368
Loading weights from yolov2-voc.weights...
 seen 64, trained: 5133 K-images (80 Kilo-batches_64)
Done! Loaded 32 layers from weights-file
 Detection layer: 31 - type = 27
data/dog.jpg: Predicted in 5504.591000 milli-seconds.
bicycle: 91%
dog: 84%
car: 85%
Not compiled with OpenCV, saving to predictions.png instead

YOLOv2-VOC网络运行示例

YOLOv2-VOC网络运行示例

出于对新版本YOLO的支持性和代码维护等考虑,后面YOLO的学习基于AlexeyAB版Darknet进行。

1.2.3 网络结构

可通过查看源码目录下的 ./cfg/yolov2.cfg./cfg/yolov2-voc.cfg 配置文件获得对论文中YOLOv2结构的描述,前者是官网示例使用的配置信息,后者则是在VOC数据集上的20分类模型。考虑到论文的层次递进以及其它原因,这里分析 YOLOv2-VOC 的网络结构,如下图所示。

[说明]

*.cfg文件中卷积层 pad=1 表示启用填充,而非将填充值设为1。填充值为卷积核大小/2,取整。

[补充]

在AlexeyAB版本代码实现中,卷积层pad参数设置可围绕以下代码路径查看:

main -> run_detector -> test_detector -> parse_network_cfg_custom -> parse_convolutional 的参数处理部分

YOLOv2-VOC网络结构

YOLOv2网络结构

上图中,对各层的标号排序是按照cfg文件中的顺序进行的。与前一版的结构构成相比,YOLOv2的特色在于增加了直通层(REORG)和路由层(ROUTE),以及不一样的输出层(REGION)。

1.2.4 源码实现中的直通层

分析直通层实现的目的是为了解决以下几个问题:

  1. 直通层输入的数据格式是怎样的?
  2. 直通层输出的数据格式是怎样的?
  3. 直通层是如何处理这些数据的?

[说明]

在AlexeyAB版本代码实现中,直通层构建可通过以下代码路径查看:

main -> run_detector -> test_detector -> parse_network_cfg_custom -> parse_reorg -> make_reorg_layer -> forward_reorg_layer -> reorg_cpu

直通层的源码实现在 blas.c 中,函数体如下:

void reorg_cpu(float *x, int out_w, int out_h, int out_c, int batch, int stride, int forward, float *out)
{
    int b,i,j,k;
    int in_c = out_c/(stride*stride);
    for(b = 0; b < batch; ++b){
        for(k = 0; k < out_c; ++k){
            for(j = 0; j < out_h; ++j){
                for(i = 0; i < out_w; ++i){
                    int in_index  = i + out_w*(j + out_h*(k + out_c*b));
                    int c2 = k % in_c;
                    int offset = k / in_c;
                    int w2 = i*stride + offset % stride;
                    int h2 = j*stride + offset / stride;
                    int out_index = w2 + out_w*stride*(h2 + out_h*stride*(c2 + in_c*b));
                    if(forward) out[out_index] = x[in_index];    // used by default for forward (i.e. forward = 0)
                    else out[in_index] = x[out_index];
                }
            }
        }
    }
}

[补充]函数传参释义如下:

  1. state_input :前向预测中上一级网络的输出。代码路径:main -> run_detector -> test_detector -> network_predict -> forward_network

  2. l.out_w :等于前级网络输出特征图的宽,此处为13。代码路径:main -> run_detector -> test_detector -> parse_network_cfg_custom

  3. l.out_h :等于前级网络输出特征图的高,此处为13。

  4. l.out_c :等于前级网络输出通道数×stride×stride,此处为256。代码路径:main -> run_detector -> test_detector -> parse_network_cfg_custom -> parse_reorg -> make_reorg_layer

  5. l.out_batch :网络训练的Batch大小。

  6. l.stride :下采样步长,此处为2。

  7. forward :确定计算方向为前项或后项,前向计算时为0。

  8. l.output :网络最终输出,为指向一片内存空间的指针,空间大小为l.out_h × l.out_w × l.out_c × batch

函数通过对输出通道数和下采样步长进行计算反推出输入通道数,而后在每个批(Batch)中依次处理各通道,在各通道中由列到行地处理每个像素,这意味着对每一张特征图中“每块单元”的遍历是从左到右、从上到下进行的,但“每块单元”中像素的遍历则是从上到下、从左到右进行的。in_index 变量命名有一定的误导性,它负责指示转换后的特征图上的每个像素的位置,而非输入特征图的像素位置。与此相对的,c2w2h2表示in_index所指示的、当前正在处理的像素在原特征图上的位置。

从网络结构图中可以看出,输入数据为64通道的26×26特征图,输出则为256通道的13×13特征图,辅以网络配置文件中 stride=2 推测:对于每张特征图,直通层对其进行了下采样处理,并最终得到stride×stride数量的缩放后的特征图。如果对代码进行逐行分析,那么需要的文字就太多了,不妨直接将转换过程用图表示出来,结合这这幅图理解上面那段程序就会容易许多了。

直通层输出示意图

直通层输出示意图

理解转换过程的关键在于:对于原数据的每个通道,stride=2使输出的通道数扩增了4倍,输出通道数的扩增是相对于原数据整体而非每张特征图而言的

我们不妨以一个较小的特征图输出作为例子,直观地展示一下直通层的操作(当然,程序的执行过程分析依旧省略)。现在假设输入特征图分辨率为10×10,数据批batch=1,输入通道数in_c=1,下采样步长stride=2,输出通道数out_c=4,则转换过程如下图所示:

直通层输出示例1

直通层输出示例(单通道输入)

当输入通道数in_c=2时,转换过程为:

直通层输出示例2

直通层输出示例(双通道输入)

网络中的处理不过是更换了上面例子中的特征图大小以及输入通道数,多批处理也只是对单批处理过程的拷贝而已。

对应于直通层,路由层的作用就是简单地将两层或多层网络的输出依次拷贝到一个内存空间中。

1.3 YOLOv3

1.3.1 YOLOv3网络算法

(1)算法的改进

论文中对以下几个方面进行了叙述:

  • 使用二元交叉熵损失函数代替平方和损失函数

    [论文引用]

    2.2 类别预测(Class Prediction)

    “每个框使用多标签分类预测边界框中可能包含的类别。我们没有使用Softmax,因为我们发现它对于良好的性能不是必要的,我们只使用独立的逻辑分类器。在训练中,我们使用二元交叉熵损失函数进行类别预测。

    “当我们转移到(move to)更复杂的领域(例如Open Images Dataset)时,这个公式(formulation)会有帮助。在这个数据集中,有许多重叠的标签(例如“女人”和“人”)。使用Softmax会假设每个边界框中只有一个类别,但事实并非如此。多标签的方法可以更好地对数据进行建模。”

  • 在各尺度上预测三个框;在COCO数据集上使用三种比例的9种先验框

    [论文引用]

    2.3 跨尺度预测(Predictions Across Scales)

    "YOLOv3预测三种不同尺寸的边界框。我们的系统使用类似于图像金字塔网络的概念从这些尺度(的图像)中提取特征。在我们的基本特征提取器中,我们添加了几个卷积层。这些(网络层)最后预测出一个三维张量的编码边界框、客观性(objectness)和类别预测。在我们用COCO进行的实验中,我们在每个尺度上预测了三个框,因此张量是 N × N × [ 3 ∗ ( 4 + 1 + 80 ) ] N × N × [3 * (4 + 1 + 80)] N×N×[3(4+1+80)],即4个边界框偏移、1个目标置信度(objectness)预测和80个类别预测(结果)。

    “接下来,我们从前两层中选取(take)特征图,并对其进行2倍上采样。我们还从网络中较早的地方获取了一个特征图,并使用并行连接(concatenation)将其与我们的上采样特征合并。这种方法使我们从上采样的特征中获取更有意义的语义信息,并从较早的特征图中获得细粒度(fine-grained)信息。然后,我们添加了更多的卷积层来处理这个组合的特征图,并最终预测出一个类似的张量,尽管现在它的大小是原来的两倍。

    “我们再次执行相同的设计从最终的尺度中预测出框。因此,我们对第3层的预测将受益于所有先前的计算以及网络早期的细粒度功能。

    “我们仍然使用K均值聚类来确定(determine)我们的边界框先验。我们只是随意(arbitrarily)选择了9个聚类(clusters)和3个比例(scales),然后将群集均匀地(evenly)分布(across)在各个尺度上。

    “在COCO数据集上,这9个聚类(clusters)为:(10×13)、(16×30)、(33×23)、(30×61)、(62×45)、(59×119)、(116×90)、(156×198)、(373×326)。”

    [说明]

    在Darknet源码目录下YOLOv3的网络配置文件中YOLO网络层参数处可找到论文中设置的这些先验框。

    有关网络输出的部分见 1.3.4-(4) YOLO网络层结果输出 章节。

  • 使用全新的特征提取网络Darknet-53

通过引入多尺度检测,使YOLO对小目标的检测能力得到了提升;但预测框和目标边界很难达到完全重合。

[论文引用]

“…当IOU阈值增加时,性能会随之显著下降,这表明YOLOv3很难使框与目标完全(perfectly)对齐。

“过去,YOLO一直在尽力处理小目标(检测)。但是现在我们看到了这种趋势出现了逆转。通过新的多尺度预测,我们看到YOLOv3具有相对较高的APS表现。然而,它在中型和大型对象上的性能相对较差。要深入了解这一点,还需要进行更多的调查。”

(2)理解损失函数

YOLOv3论文并没有直接给出网络的损失函数,因此只能从Darknet源码中推导,好在已经有前辈大佬们做过这项工作了,我们只需要照着学习即可。相关来源参考:官方DarkNet YOLO V3损失函数完结版_知乎专栏,不排除不同的实现会基于原版损失函数做修改的可能。

L o s s y o l o v 3 = − ∑ i = 0 s 2 ∑ j = 0 B 1 i j o b j ( 2 − ω ^ i × h ^ i ) [ x ^ i l o g ( x i ) + ( 1 − x ^ i ) l o g ( 1 − x i ) ] {\Large Loss_{yolov3}= - \sum_{i=0}^{s^2} \sum_{j=0}^B \mathbb{1} _{i j}^{obj} (2 - \hat{\omega}_{i} × \hat{h}_{i}) \left [ \hat{x}_{i} log(x_{i}) + (1 - \hat{x}_{i}) log(1 - x_{i}) \right ] } Lossyolov3=i=0s2j=0B1ijobj(2ω^i×h^i)[x^ilog(xi)+(1x^i)log(1xi)]

− ∑ i = 0 s 2 ∑ j = 0 B 1 i j o b j ( 2 − ω ^ i × h ^ i ) [ y ^ i l o g ( y i ) + ( 1 − y ^ i ) l o g ( 1 − y i ) ] {\Large - \sum_{i=0}^{s^2} \sum_{j=0}^B \mathbb{1} _{i j}^{obj} (2 - \hat{\omega}_{i} × \hat{h}_{i}) \left [ \hat{y}_{i} log(y_{i}) + (1 - \hat{y}_{i}) log(1 - y_{i}) \right ] } i=0s2j=0B1ijobj(2ω^i×h^i)[y^ilog(yi)+(1y^i)log(1yi)]

+ ∑ i = 0 s 2 ∑ j = 0 B 1 i j o b j ( 2 − ω ^ i × h ^ i ) [ ( ω i − ω ^ i ) 2 + ( h i − h ^ i ) 2 ] {\Large + \sum_{i=0}^{s^2} \sum_{j=0}^B \mathbb{1} _{i j}^{obj} (2 - \hat{\omega}_{i} × \hat{h}_{i}) \left [ \left( \omega_{i} - \hat{\omega}_{i} \right)^{2} + \left ( h_{i} - \hat{h}_{i} \right)^{2} \right ] } +i=0s2j=0B1ijobj(2ω^i×h^i)[(ωiω^i)2+(hih^i)2]

− ∑ i = 0 s 2 ∑ j = 0 B 1 i j o b j [ C ^ i l o g ( C i ) + ( 1 − C ^ i ) l o g ( 1 − C i ) ] {\Large - \sum_{i=0}^{s^2} \sum_{j=0}^B \mathbb{1}_{i j}^{obj} \left [ \hat{C}_{i} log(C_{i}) + (1 - \hat{C}_{i}) log(1 - C_{i}) \right ] } i=0s2j=0B1ijobj[C^ilog(Ci)+(1C^i)log(1Ci)]

− ∑ i = 0 s 2 ∑ j = 0 B 1 i j n o o b j [ C ^ i l o g ( C i ) + ( 1 − C ^ i ) l o g ( 1 − C i ) ] {\Large - \sum_{i=0}^{s^2} \sum_{j=0}^B \mathbb{1}_{i j}^{noobj} \left [ \hat{C}_{i} log(C_{i}) + (1 - \hat{C}_{i}) log(1 - C_{i}) \right ] } i=0s2j=0B1ijnoobj[C^ilog(Ci)+(1C^i)log(1Ci)]

− ∑ i = 0 s 2 ∑ j = 0 B 1 i j o b j ∑ c ∈ c l a s s e s [ p i ^ ( c ) l o g ( p i ( c ) ) + ( 1 − p i ^ ( c ) ) l o g ( 1 − p i ( c ) ) ] {\Large - \sum_{i=0}^{s^2} \sum_{j=0}^{B} \mathbb{1}_{i j}^{obj} \sum_{c \in classes} \left [ \hat{p_{i}} (c) log \left (p_{i}(c) \right ) + (1 - \hat{p_{i}} (c)) log \left (1 - p_{i}(c) \right ) \right ] } i=0s2j=0B1ijobjcclasses[pi^(c)log(pi(c))+(1pi^(c))log(1pi(c))]

YOLOv3损失函数

按照文章所述,YOLOv3的损失函数中,对 x , y x, y x,y、目标置信度和条件类概率使用二元交叉熵损失;对 ω , h \omega, h ω,h使用均方误差损失,但不再像YOLOv1一样对其平方根进行预测。

其中, x ^ i \hat{x}_{i} x^i y ^ i \hat{y}_{i} y^i ω ^ i \hat{\omega}_{i} ω^i h ^ i \hat{h}_{i} h^i来自标注数据,因此其下标仅由遍历的网格数确定,对于每个网格而言,GT值是相同的。 x i x_{i} xi y i y_{i} yi ω i \omega_{i} ωi h i h_{i} hi为预测值,虽然每个网格生成分配的anchor boxes数量的Bboxes,但 1 i j o b j \mathbb{1}_{i j}^{obj} 1ijobj的存在使损失函数总是获取并计算“负责该目标检测的网格”的损失值,因此下标为 i i i而非 i j ij ij

[注意]

这里的解释为个人参照链接文章的内容进行的理解,权且看看,正确性请读者自行辨析。

1.3.2 Darknet(AlexeyAB版)示例运行

遵循前面介绍YOLOv2的章节,仍旧在MSYS环境下运行Darknet,执行语句可参考YOLO官网示例,例如:

./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg

[说明]该处使用的YOLOv3权重数据下载地址:https://pjreddie.com/media/files/yolov3.weights

同样可以运行YOLOv3-Tiny模型:

./darknet detect cfg/yolov3-tiny.cfg yolov3-tiny.weights data/dog.jpg

[说明]该处使用的YOLOv3-Tiny权重数据下载地址:https://pjreddie.com/media/files/yolov3-tiny.weights

(1)YOLOv3示例预测

YOLOv3检测的执行结果如下:

Linyar@潇菏 MSYS /f/darknet-master-AlexeyAB
$ ./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg
 GPU isn't used
 OpenCV isn't used - data augmentation will be slow
mini_batch = 1, batch = 1, time_steps = 1, train = 0
   layer   filters  size/strd(dil)      input                output
   0 conv     32       3 x 3/ 1    416 x 416 x   3 ->  416 x 416 x  32 0.299 BF
   1 conv     64       3 x 3/ 2    416 x 416 x  32 ->  208 x 208 x  64 1.595 BF
   2 conv     32       1 x 1/ 1    208 x 208 x  64 ->  208 x 208 x  32 0.177 BF
   3 conv     64       3 x 3/ 1    208 x 208 x  32 ->  208 x 208 x  64 1.595 BF
   4 Shortcut Layer: 1,  wt = 0, wn = 0, outputs: 208 x 208 x  64 0.003 BF
   5 conv    128       3 x 3/ 2    208 x 208 x  64 ->  104 x 104 x 128 1.595 BF
   6 conv     64       1 x 1/ 1    104 x 104 x 128 ->  104 x 104 x  64 0.177 BF
   7 conv    128       3 x 3/ 1    104 x 104 x  64 ->  104 x 104 x 128 1.595 BF
   8 Shortcut Layer: 5,  wt = 0, wn = 0, outputs: 104 x 104 x 128 0.001 BF
   9 conv     64       1 x 1/ 1    104 x 104 x 128 ->  104 x 104 x  64 0.177 BF
  10 conv    128       3 x 3/ 1    104 x 104 x  64 ->  104 x 104 x 128 1.595 BF
  11 Shortcut Layer: 8,  wt = 0, wn = 0, outputs: 104 x 104 x 128 0.001 BF
  12 conv    256       3 x 3/ 2    104 x 104 x 128 ->   52 x  52 x 256 1.595 BF
  13 conv    128       1 x 1/ 1     52 x  52 x 256 ->   52 x  52 x 128 0.177 BF
  14 conv    256       3 x 3/ 1     52 x  52 x 128 ->   52 x  52 x 256 1.595 BF
  15 Shortcut Layer: 12,  wt = 0, wn = 0, outputs:  52 x  52 x 256 0.001 BF
  16 conv    128       1 x 1/ 1     52 x  52 x 256 ->   52 x  52 x 128 0.177 BF
  17 conv    256       3 x 3/ 1     52 x  52 x 128 ->   52 x  52 x 256 1.595 BF
  18 Shortcut Layer: 15,  wt = 0, wn = 0, outputs:  52 x  52 x 256 0.001 BF
  19 conv    128       1 x 1/ 1     52 x  52 x 256 ->   52 x  52 x 128 0.177 BF
  20 conv    256       3 x 3/ 1     52 x  52 x 128 ->   52 x  52 x 256 1.595 BF
  21 Shortcut Layer: 18,  wt = 0, wn = 0, outputs:  52 x  52 x 256 0.001 BF
  22 conv    128       1 x 1/ 1     52 x  52 x 256 ->   52 x  52 x 128 0.177 BF
  23 conv    256       3 x 3/ 1     52 x  52 x 128 ->   52 x  52 x 256 1.595 BF
  24 Shortcut Layer: 21,  wt = 0, wn = 0, outputs:  52 x  52 x 256 0.001 BF
  25 conv    128       1 x 1/ 1     52 x  52 x 256 ->   52 x  52 x 128 0.177 BF
  26 conv    256       3 x 3/ 1     52 x  52 x 128 ->   52 x  52 x 256 1.595 BF
  27 Shortcut Layer: 24,  wt = 0, wn = 0, outputs:  52 x  52 x 256 0.001 BF
  28 conv    128       1 x 1/ 1     52 x  52 x 256 ->   52 x  52 x 128 0.177 BF
  29 conv    256       3 x 3/ 1     52 x  52 x 128 ->   52 x  52 x 256 1.595 BF
  30 Shortcut Layer: 27,  wt = 0, wn = 0, outputs:  52 x  52 x 256 0.001 BF
  31 conv    128       1 x 1/ 1     52 x  52 x 256 ->   52 x  52 x 128 0.177 BF
  32 conv    256       3 x 3/ 1     52 x  52 x 128 ->   52 x  52 x 256 1.595 BF
  33 Shortcut Layer: 30,  wt = 0, wn = 0, outputs:  52 x  52 x 256 0.001 BF
  34 conv    128       1 x 1/ 1     52 x  52 x 256 ->   52 x  52 x 128 0.177 BF
  35 conv    256       3 x 3/ 1     52 x  52 x 128 ->   52 x  52 x 256 1.595 BF
  36 Shortcut Layer: 33,  wt = 0, wn = 0, outputs:  52 x  52 x 256 0.001 BF
  37 conv    512       3 x 3/ 2     52 x  52 x 256 ->   26 x  26 x 512 1.595 BF
  38 conv    256       1 x 1/ 1     26 x  26 x 512 ->   26 x  26 x 256 0.177 BF
  39 conv    512       3 x 3/ 1     26 x  26 x 256 ->   26 x  26 x 512 1.595 BF
  40 Shortcut Layer: 37,  wt = 0, wn = 0, outputs:  26 x  26 x 512 0.000 BF
  41 conv    256       1 x 1/ 1     26 x  26 x 512 ->   26 x  26 x 256 0.177 BF
  42 conv    512       3 x 3/ 1     26 x  26 x 256 ->   26 x  26 x 512 1.595 BF
  43 Shortcut Layer: 40,  wt = 0, wn = 0, outputs:  26 x  26 x 512 0.000 BF
  44 conv    256       1 x 1/ 1     26 x  26 x 512 ->   26 x  26 x 256 0.177 BF
  45 conv    512       3 x 3/ 1     26 x  26 x 256 ->   26 x  26 x 512 1.595 BF
  46 Shortcut Layer: 43,  wt = 0, wn = 0, outputs:  26 x  26 x 512 0.000 BF
  47 conv    256       1 x 1/ 1     26 x  26 x 512 ->   26 x  26 x 256 0.177 BF
  48 conv    512       3 x 3/ 1     26 x  26 x 256 ->   26 x  26 x 512 1.595 BF
  49 Shortcut Layer: 46,  wt = 0, wn = 0, outputs:  26 x  26 x 512 0.000 BF
  50 conv    256       1 x 1/ 1     26 x  26 x 512 ->   26 x  26 x 256 0.177 BF
  51 conv    512       3 x 3/ 1     26 x  26 x 256 ->   26 x  26 x 512 1.595 BF
  52 Shortcut Layer: 49,  wt = 0, wn = 0, outputs:  26 x  26 x 512 0.000 BF
  53 conv    256       1 x 1/ 1     26 x  26 x 512 ->   26 x  26 x 256 0.177 BF
  54 conv    512       3 x 3/ 1     26 x  26 x 256 ->   26 x  26 x 512 1.595 BF
  55 Shortcut Layer: 52,  wt = 0, wn = 0, outputs:  26 x  26 x 512 0.000 BF
  56 conv    256       1 x 1/ 1     26 x  26 x 512 ->   26 x  26 x 256 0.177 BF
  57 conv    512       3 x 3/ 1     26 x  26 x 256 ->   26 x  26 x 512 1.595 BF
  58 Shortcut Layer: 55,  wt = 0, wn = 0, outputs:  26 x  26 x 512 0.000 BF
  59 conv    256       1 x 1/ 1     26 x  26 x 512 ->   26 x  26 x 256 0.177 BF
  60 conv    512       3 x 3/ 1     26 x  26 x 256 ->   26 x  26 x 512 1.595 BF
  61 Shortcut Layer: 58,  wt = 0, wn = 0, outputs:  26 x  26 x 512 0.000 BF
  62 conv   1024       3 x 3/ 2     26 x  26 x 512 ->   13 x  13 x1024 1.595 BF
  63 conv    512       1 x 1/ 1     13 x  13 x1024 ->   13 x  13 x 512 0.177 BF
  64 conv   1024       3 x 3/ 1     13 x  13 x 512 ->   13 x  13 x1024 1.595 BF
  65 Shortcut Layer: 62,  wt = 0, wn = 0, outputs:  13 x  13 x1024 0.000 BF
  66 conv    512       1 x 1/ 1     13 x  13 x1024 ->   13 x  13 x 512 0.177 BF
  67 conv   1024       3 x 3/ 1     13 x  13 x 512 ->   13 x  13 x1024 1.595 BF
  68 Shortcut Layer: 65,  wt = 0, wn = 0, outputs:  13 x  13 x1024 0.000 BF
  69 conv    512       1 x 1/ 1     13 x  13 x1024 ->   13 x  13 x 512 0.177 BF
  70 conv   1024       3 x 3/ 1     13 x  13 x 512 ->   13 x  13 x1024 1.595 BF
  71 Shortcut Layer: 68,  wt = 0, wn = 0, outputs:  13 x  13 x1024 0.000 BF
  72 conv    512       1 x 1/ 1     13 x  13 x1024 ->   13 x  13 x 512 0.177 BF
  73 conv   1024       3 x 3/ 1     13 x  13 x 512 ->   13 x  13 x1024 1.595 BF
  74 Shortcut Layer: 71,  wt = 0, wn = 0, outputs:  13 x  13 x1024 0.000 BF
  75 conv    512       1 x 1/ 1     13 x  13 x1024 ->   13 x  13 x 512 0.177 BF
  76 conv   1024       3 x 3/ 1     13 x  13 x 512 ->   13 x  13 x1024 1.595 BF
  77 conv    512       1 x 1/ 1     13 x  13 x1024 ->   13 x  13 x 512 0.177 BF
  78 conv   1024       3 x 3/ 1     13 x  13 x 512 ->   13 x  13 x1024 1.595 BF
  79 conv    512       1 x 1/ 1     13 x  13 x1024 ->   13 x  13 x 512 0.177 BF
  80 conv   1024       3 x 3/ 1     13 x  13 x 512 ->   13 x  13 x1024 1.595 BF
  81 conv    255       1 x 1/ 1     13 x  13 x1024 ->   13 x  13 x 255 0.088 BF
  82 yolo
[yolo] params: iou loss: mse (2), iou_norm: 0.75, obj_norm: 1.00, cls_norm: 1.00, delta_norm: 1.00, scale_x_y: 1.00
  83 route  79                                     ->   13 x  13 x 512
  84 conv    256       1 x 1/ 1     13 x  13 x 512 ->   13 x  13 x 256 0.044 BF
  85 upsample                 2x    13 x  13 x 256 ->   26 x  26 x 256
  86 route  85 61                                  ->   26 x  26 x 768
  87 conv    256       1 x 1/ 1     26 x  26 x 768 ->   26 x  26 x 256 0.266 BF
  88 conv    512       3 x 3/ 1     26 x  26 x 256 ->   26 x  26 x 512 1.595 BF
  89 conv    256       1 x 1/ 1     26 x  26 x 512 ->   26 x  26 x 256 0.177 BF
  90 conv    512       3 x 3/ 1     26 x  26 x 256 ->   26 x  26 x 512 1.595 BF
  91 conv    256       1 x 1/ 1     26 x  26 x 512 ->   26 x  26 x 256 0.177 BF
  92 conv    512       3 x 3/ 1     26 x  26 x 256 ->   26 x  26 x 512 1.595 BF
  93 conv    255       1 x 1/ 1     26 x  26 x 512 ->   26 x  26 x 255 0.177 BF
  94 yolo
[yolo] params: iou loss: mse (2), iou_norm: 0.75, obj_norm: 1.00, cls_norm: 1.00, delta_norm: 1.00, scale_x_y: 1.00
  95 route  91                                     ->   26 x  26 x 256
  96 conv    128       1 x 1/ 1     26 x  26 x 256 ->   26 x  26 x 128 0.044 BF
  97 upsample                 2x    26 x  26 x 128 ->   52 x  52 x 128
  98 route  97 36                                  ->   52 x  52 x 384
  99 conv    128       1 x 1/ 1     52 x  52 x 384 ->   52 x  52 x 128 0.266 BF
 100 conv    256       3 x 3/ 1     52 x  52 x 128 ->   52 x  52 x 256 1.595 BF
 101 conv    128       1 x 1/ 1     52 x  52 x 256 ->   52 x  52 x 128 0.177 BF
 102 conv    256       3 x 3/ 1     52 x  52 x 128 ->   52 x  52 x 256 1.595 BF
 103 conv    128       1 x 1/ 1     52 x  52 x 256 ->   52 x  52 x 128 0.177 BF
 104 conv    256       3 x 3/ 1     52 x  52 x 128 ->   52 x  52 x 256 1.595 BF
 105 conv    255       1 x 1/ 1     52 x  52 x 256 ->   52 x  52 x 255 0.353 BF
 106 yolo
[yolo] params: iou loss: mse (2), iou_norm: 0.75, obj_norm: 1.00, cls_norm: 1.00, delta_norm: 1.00, scale_x_y: 1.00
Total BFLOPS 65.879
avg_outputs = 532444
Loading weights from yolov3.weights...
 seen 64, trained: 32013 K-images (500 Kilo-batches_64)
Done! Loaded 107 layers from weights-file
 Detection layer: 82 - type = 28
 Detection layer: 94 - type = 28
 Detection layer: 106 - type = 28
data/dog.jpg: Predicted in 12380.366000 milli-seconds.
bicycle: 99%
dog: 100%
truck: 93%
Not compiled with OpenCV, saving to predictions.png instead

YOLOv3运行示例

YOLOv3网络运行示例
(2)YOLOv3-Tiny示例预测

YOLOv3-Tiny检测的执行结果如下:

Linyar@潇菏 MSYS /f/darknet-master-AlexeyAB
$ ./darknet detect cfg/yolov3-tiny.cfg yolov3-tiny.weights data/dog.jpg
 GPU isn't used
 OpenCV isn't used - data augmentation will be slow
mini_batch = 1, batch = 1, time_steps = 1, train = 0
   layer   filters  size/strd(dil)      input                output
   0 conv     16       3 x 3/ 1    416 x 416 x   3 ->  416 x 416 x  16 0.150 BF
   1 max                2x 2/ 2    416 x 416 x  16 ->  208 x 208 x  16 0.003 BF
   2 conv     32       3 x 3/ 1    208 x 208 x  16 ->  208 x 208 x  32 0.399 BF
   3 max                2x 2/ 2    208 x 208 x  32 ->  104 x 104 x  32 0.001 BF
   4 conv     64       3 x 3/ 1    104 x 104 x  32 ->  104 x 104 x  64 0.399 BF
   5 max                2x 2/ 2    104 x 104 x  64 ->   52 x  52 x  64 0.001 BF
   6 conv    128       3 x 3/ 1     52 x  52 x  64 ->   52 x  52 x 128 0.399 BF
   7 max                2x 2/ 2     52 x  52 x 128 ->   26 x  26 x 128 0.000 BF
   8 conv    256       3 x 3/ 1     26 x  26 x 128 ->   26 x  26 x 256 0.399 BF
   9 max                2x 2/ 2     26 x  26 x 256 ->   13 x  13 x 256 0.000 BF
  10 conv    512       3 x 3/ 1     13 x  13 x 256 ->   13 x  13 x 512 0.399 BF
  11 max                2x 2/ 1     13 x  13 x 512 ->   13 x  13 x 512 0.000 BF
  12 conv   1024       3 x 3/ 1     13 x  13 x 512 ->   13 x  13 x1024 1.595 BF
  13 conv    256       1 x 1/ 1     13 x  13 x1024 ->   13 x  13 x 256 0.089 BF
  14 conv    512       3 x 3/ 1     13 x  13 x 256 ->   13 x  13 x 512 0.399 BF
  15 conv    255       1 x 1/ 1     13 x  13 x 512 ->   13 x  13 x 255 0.044 BF
  16 yolo
[yolo] params: iou loss: mse (2), iou_norm: 0.75, obj_norm: 1.00, cls_norm: 1.00, delta_norm: 1.00, scale_x_y: 1.00
  17 route  13                                     ->   13 x  13 x 256
  18 conv    128       1 x 1/ 1     13 x  13 x 256 ->   13 x  13 x 128 0.011 BF
  19 upsample                 2x    13 x  13 x 128 ->   26 x  26 x 128
  20 route  19 8                                   ->   26 x  26 x 384
  21 conv    256       3 x 3/ 1     26 x  26 x 384 ->   26 x  26 x 256 1.196 BF
  22 conv    255       1 x 1/ 1     26 x  26 x 256 ->   26 x  26 x 255 0.088 BF
  23 yolo
[yolo] params: iou loss: mse (2), iou_norm: 0.75, obj_norm: 1.00, cls_norm: 1.00, delta_norm: 1.00, scale_x_y: 1.00
Total BFLOPS 5.571
avg_outputs = 341534
Loading weights from yolov3-tiny.weights...
 seen 64, trained: 32013 K-images (500 Kilo-batches_64)
Done! Loaded 24 layers from weights-file
 Detection layer: 16 - type = 28
 Detection layer: 23 - type = 28
data/dog.jpg: Predicted in 943.792000 milli-seconds.
dog: 81%
bicycle: 38%
car: 71%
truck: 42%
truck: 62%
car: 40%
Not compiled with OpenCV, saving to predictions.png instead

YOLOv3-Tiny运行示例

YOLOv3-Tiny网络运行示例

1.3.3 网络结构

(1)YOLOv3网络结构

YOLOv3使用Darknet-53分类器的特征提取骨干网络,因此网络层数和规模均大于先前的版本。按照网络中不同部分进行合并和归纳的工作已经有人做过了,因此这里将整个网络的卷积参数画出来,以便对网络数据传递有更直观的感受。

[注意]

这里仅表示了每层的卷积层参数,实际上每个网络层由卷积层、BN和激活函数组成。

YOLOv3网络结构

YOLOv3网络结构

上图是根据Darknet配置文件和网络输出绘制而成的,虽然层数较多,但是仍然可以看出网络功能上模块化的痕迹(虚线框出来的部分)。Darknet-53残差网络与论文中给出的结构一致,只有输入图像分辨率不同的区别;特征提取网络之后分别外接了三个YOLO层(YOLOv3独有的输出层)分别预测不同尺度的目标。

Darknet-53网络结构

论文表1:Darknet-53
(2)YOLOv3-Tiny网络结构

遵循YOLOv3网络结构图的格式,我同样将YOLOv3-Tiny的网络结构绘制了一遍以供对比,由于其网络层数较少,因此这不会花费太多的时间。

YOLOv3-Tiny网络结构

YOLOv3-Tiny网络结构

对比来看,两者的网络构成思路是相似的,但由于各功能部分的网络深度较浅,因此预测结果存在不小的偏差(如示例所示)。

1.3.4 源码阅读

(1)Shortcut网络层

[说明]

在AlexeyAB版本代码实现中,直通层构建可通过以下代码路径查看:

main -> run_detector -> test_detector -> parse_network_cfg_custom -> parse_shortcut -> make_shortcut_layer -> forward_shortcut_layer

[注意]

本节内容仅为个人见解,未参考任何其它资料,因此难免错谬,细节之处还请读者推敲。

网络的shortcut层让我产生了一些困惑,按照当前网络参数设置,将shortcut的前向传播函数精简如下:

void forward_shortcut_layer(const layer l, network_state state)
{
    // 标记1
    int from_w = state.net.layers[l.index].w;
    int from_h = state.net.layers[l.index].h;
    int from_c = state.net.layers[l.index].c;
    
    //printf("nweights:%d n:%d from_w:%d from_h:%d from_c:%d ",l.nweights, l.n, from_w, from_h, from_c);
    //printf("l.w:%d l.h:%d l.c:%d\n",l.w, l.h, l.c);
    if (l.nweights == 0 && l.n == 1 && from_w == l.w && from_h == l.h && from_c == l.c)
    {// 分支1
        int size = l.batch * l.w * l.h * l.c;
        int i;
        for(i = 0; i < size; ++i)
            l.output[i] = state.input[i] + state.net.layers[l.index].output[i];
        //printf("[Turix-TEST-INFO]shortcut branch 1\n");
    }
    else
    {// 分支2
        shortcut_multilayer_cpu(...);
        //printf("[Turix-TEST-INFO]shortcut branch 2\n");
    }
    
    activate_array_cpu_custom(l.output, l.outputs*l.batch, l.activation);
}

可以看到,shortcut层将对比上层网络输出和待连接层的 输出 是否一致,若一致则将输出直接加和,否则执行shortcut_multilayer_cpu函数进行拼接。通过在判断分支中增加打印语句的方法,可以得到当前网络中各shortcut层执行分支的差异,在 1.3.3-(1)YOLOv3网络结构 节的 YOLOv3网络结构 图中已经有所体现。为了方便起见,这里也针对YOLOv3网络将shortcut_multilayer_cpu函数执行的动作精简如下所示:

void shortcut_multilayer_cpu(...)
{
    for (id = 0; id < l.outputs * l.batch; ++id)
    {
        int src_i;
        int src_b;
        int i;
        
        src_i = id % l.outputs;//当前batch中正在处理的顺序项
        src_b = id / l.outputs;//当前处理在循环中的batch偏移
        l.output[id] = state.input[id];//首先拷贝前层的输出

        for (i = 0; i < n; ++i)//对每个待连接层遍历以便加对输出加和,遍历数n=1
        {
            int add_outputs = l.input_sizes[i];//l.input_sizes[i]为待合并层的outputs属性
            if (src_i < add_outputs)
            {
                int add_index = add_outputs*src_b + src_i;
                int out_index = id;

                float *add = l.layers_output[i];
                l.output[out_index] += add[add_index];
            }
        }
    }
}

但是,这些被分支2处理的网络层似乎是执行了错误判断的结果?按照个人理解,forward_shortcut_layer函数中的判断应当对比的是当前层输出参数和待连接到此层的网络层的 输出 参数,但通过增加对判断过程的打印(如下所示)可以看出,实际上与当前层输出参数进行对比的是待连接到此层的网络层的 输入 参数。

[说明]

以下输出应结合完整的网络参数打印和网络结构图观看。

...
Loading weights from yolov3.weights...
 seen 64, trained: 32013 K-images (500 Kilo-batches_64)
Done! Loaded 107 layers from weights-file
 Detection layer: 82 - type = 28
 Detection layer: 94 - type = 28
 Detection layer: 106 - type = 28
nweights:0 n:1 from_w:416 from_h:416 from_c:32 l.w:208 l.h:208 l.c:64
[Turix-TEST-INFO]shortcut branch 2                                    <---执行分支2的shortcut层
nweights:0 n:1 from_w:208 from_h:208 from_c:64 l.w:104 l.h:104 l.c:128
[Turix-TEST-INFO]shortcut branch 2                                    <---执行分支2的shortcut层
nweights:0 n:1 from_w:104 from_h:104 from_c:128 l.w:104 l.h:104 l.c:128
[Turix-TEST-INFO]shortcut branch 1
nweights:0 n:1 from_w:104 from_h:104 from_c:128 l.w:52 l.h:52 l.c:256
[Turix-TEST-INFO]shortcut branch 2                                    <---执行分支2的shortcut层
nweights:0 n:1 from_w:52 from_h:52 from_c:256 l.w:52 l.h:52 l.c:256
[Turix-TEST-INFO]shortcut branch 1
nweights:0 n:1 from_w:52 from_h:52 from_c:256 l.w:52 l.h:52 l.c:256
[Turix-TEST-INFO]shortcut branch 1
nweights:0 n:1 from_w:52 from_h:52 from_c:256 l.w:52 l.h:52 l.c:256
[Turix-TEST-INFO]shortcut branch 1
nweights:0 n:1 from_w:52 from_h:52 from_c:256 l.w:52 l.h:52 l.c:256
[Turix-TEST-INFO]shortcut branch 1
nweights:0 n:1 from_w:52 from_h:52 from_c:256 l.w:52 l.h:52 l.c:256
[Turix-TEST-INFO]shortcut branch 1
nweights:0 n:1 from_w:52 from_h:52 from_c:256 l.w:52 l.h:52 l.c:256
[Turix-TEST-INFO]shortcut branch 1
nweights:0 n:1 from_w:52 from_h:52 from_c:256 l.w:52 l.h:52 l.c:256
[Turix-TEST-INFO]shortcut branch 1
nweights:0 n:1 from_w:52 from_h:52 from_c:256 l.w:26 l.h:26 l.c:512
[Turix-TEST-INFO]shortcut branch 2                                    <---执行分支2的shortcut层
nweights:0 n:1 from_w:26 from_h:26 from_c:512 l.w:26 l.h:26 l.c:512
[Turix-TEST-INFO]shortcut branch 1
nweights:0 n:1 from_w:26 from_h:26 from_c:512 l.w:26 l.h:26 l.c:512
[Turix-TEST-INFO]shortcut branch 1
nweights:0 n:1 from_w:26 from_h:26 from_c:512 l.w:26 l.h:26 l.c:512
[Turix-TEST-INFO]shortcut branch 1
nweights:0 n:1 from_w:26 from_h:26 from_c:512 l.w:26 l.h:26 l.c:512
[Turix-TEST-INFO]shortcut branch 1
nweights:0 n:1 from_w:26 from_h:26 from_c:512 l.w:26 l.h:26 l.c:512
[Turix-TEST-INFO]shortcut branch 1
nweights:0 n:1 from_w:26 from_h:26 from_c:512 l.w:26 l.h:26 l.c:512
[Turix-TEST-INFO]shortcut branch 1
nweights:0 n:1 from_w:26 from_h:26 from_c:512 l.w:26 l.h:26 l.c:512
[Turix-TEST-INFO]shortcut branch 1
nweights:0 n:1 from_w:26 from_h:26 from_c:512 l.w:13 l.h:13 l.c:1024
[Turix-TEST-INFO]shortcut branch 2                                    <---执行分支2的shortcut层
nweights:0 n:1 from_w:13 from_h:13 from_c:1024 l.w:13 l.h:13 l.c:1024
[Turix-TEST-INFO]shortcut branch 1
nweights:0 n:1 from_w:13 from_h:13 from_c:1024 l.w:13 l.h:13 l.c:1024
[Turix-TEST-INFO]shortcut branch 1
nweights:0 n:1 from_w:13 from_h:13 from_c:1024 l.w:13 l.h:13 l.c:1024
[Turix-TEST-INFO]shortcut branch 1
data/dog.jpg: Predicted in 12724.606000 milli-seconds.
bicycle: 99%
dog: 100%
truck: 93%
Not compiled with OpenCV, saving to predictions.png instead

如此,应当将forward_shortcut_layer函数中标记1处改为:

void forward_shortcut_layer(const layer l, network_state state)
{
    int from_w = state.net.layers[l.index].out_w;
    int from_h = state.net.layers[l.index].out_h;
    int from_c = state.net.layers[l.index].out_c;
    
    ...
}

[说明]

此处修改的正确与否请读者自行辨析。

(2)Upsample网络层

[说明]

在AlexeyAB版本代码实现中,直通层构建可通过以下代码路径查看:

main -> run_detector -> test_detector -> parse_network_cfg_custom -> parse_upsample -> make_upsample_layer -> forward_upsample_layer -> upsample_cpu

upsample层对特征图做了简单上采样。正向传播时,输出特征图的“每块单元”相对原图进行像素级扩增;反向传播时则将“每块单元”中的像素进行加和,过程如下图所示:

Upsample层示例

Upsample层处理过程示例
(3)网络对先验框(anchors)的使用方式

[说明]

在AlexeyAB版本代码实现中,YOLO网络层构建可通过以下代码路径查看:

main -> run_detector -> test_detector -> parse_network_cfg_custom -> parse_yolo -> make_yolo_layer -> forward_yolo_layer

在网络配置文件中,对于3个YOLO层分别定义了不同的mask掩码,它们是这样分配的:

YOLO网络层(以参数标记)mask掩码对应于掩码,分配的anchors尺寸
13×13×2556, 7, 8(116, 90)、(156, 198)、(373, 326)
26×26×2553, 4, 5(30, 61)、(62, 45)、(59, 119)
52×52×2550, 1, 2(10, 13)、(16, 30)、(33, 23)

这里掩码的作用是为不同预测尺度的YOLO层分配聚类框。观察分配情况可见:网络“在小特征图上预测大物体,在大特征图上预测小物体”。

在parse_yolo和make_yolo_layer函数中,关于mask的配置动作为:

/* parse_yolo */
char *a = option_find_str(options, "mask", 0);
int *mask = parse_yolo_mask(a, &num); // mask为动态申请内存的数组
layer l = make_yolo_layer(params.batch, params.w, params.h, num, total, mask, classes, max_boxes);
// ...

/* make_yolo_layer */
layer make_yolo_layer(int batch, int w, int h, int n, int total, int *mask, int classes, int max_boxes)
{
    // ...
    
    if(mask) l.mask = mask;// 不同的YOLO层初始化时,这里的赋值语句将mask配置保存在mask成员
    else{                  // 若未指定mask,则表示全部选中(mask all)
        l.mask = (int*)xcalloc(n, sizeof(int));
        for(i = 0; i < n; ++i){
            l.mask[i] = i;
        }
    }
    
    // ...
}

通过查看forward_yolo_layer函数,mask成员总是在进行bbox相关计算时被访问(即基于anchors更新该层网络biases值时),用于指示当前网络层可见的anchors。

[说明]

YOLO网络层的偏置值(l.biases)其实是定义的anchors,代码路径为:main -> run_detector -> test_detector -> parse_network_cfg_custom -> parse_yolo -> make_yolo_layer

/* make_yolo_layer */
char *a = option_find_str(options, "anchors", 0);
 if (a) {
     int len = strlen(a);
     int n = 1;
     int i;
     for (i = 0; i < len; ++i) {
         if (a[i] == '#') break;
         if (a[i] == ',') ++n;
     }
     for (i = 0; i < n && i < total*2; ++i) {
         float bias = atof(a);
         l.biases[i] = bias;
         a = strchr(a, ',') + 1;
     }
 }

即在当前配置中,l.biases[18] == {10, 13, 16, 30, 33, 23, 30, 61, 62, 45, 59, 119, 116, 90, 156, 198, 373, 326}。

相关代码路径为:

main -> run_detector -> test_detector -> parse_network_cfg_custom -> parse_yolo -> make_yolo_layer -> forward_yolo_layer -> process_batch -> get_yolo_box

main -> run_detector -> test_detector -> parse_network_cfg_custom -> parse_yolo -> make_yolo_layer -> forward_yolo_layer -> process_batch -> delta_yolo_box

(4)YOLO网络层结果输出

[说明]

YOLO网络层结果输出处理可通过以下代码路径查看:

main -> run_detector -> test_detector -> get_network_boxes -> fill_network_boxes -> get_yolo_detections

参照YOLOv3网络结构图可知,YOLO一共有3个YOLO层,分别具有不同的特征图大小,每层网络的输出张量对应论文中描述的:$N × N × [3 × (4 + 1 + 80)] , 即 ,即 N × N × 255$。其中N表示不同的特征图宽高;3表示“每个尺度上预测出的3个框”;4表示边界框bbox预测偏移;1表示目标置信度objectness;80表示目标在每个分类上的条件类概率。

通过阅读get_yolo_detections函数,可得到最终单YOLO网络层的输出数据存储格式如下图所示:

YOLO层输出数据格式

单YOLOv3网络输出层数据存储格式

l.output为YOLO层网络输出张量;feature.w和feature.h为最终特征图宽高;num标号为每特征图像素预测时基于的3个聚类框顺序号。

即对于mask为0, 1, 2的YOLO层,上述输出共有 52 × 52 52×52 52×52个,输出张量为 52 × 52 × [ 3 × ( 4 + 1 + 80 ) ] = 689520 52×52×[3×(4+1+80)]=689520 52×52×[3×(4+1+80)]=689520个。

1.4 **YOLOv4/YOLOv5

YOLO的后续网络融入了新的设计思想,兼顾精度和速度,达到了非常棒的效果,相较于YOLOv3及之前的版本也许更适合工程应用。但考虑到个人时间以及最终的目标,我最终还是放弃了它们转向YOLO-Fastest——它构可能更适合我这样的新手去移植和部署。而对于YOLOv4和YOLOv5…就留给未来的你们好了!

[补充]

以下为一些可能有用的参考资料:

  1. Darknet-YOLOv4_Github
  2. Pytorch-YOLOv4_Github
  3. YOLOv5_Github
  4. YOLOv5-onnx2caffe_Github
  5. 海思开发:yolo v5s :pytorch->onnx->caffe->nnie_CSDN

本文资源共享

百度网盘链接: https://pan.baidu.com/s/1_7TRD9rDUsxgnIGjKYF-UQ

提取码: mhn9

YOLO系列(v1~v3)的学习及YOLO-Fastest在海思平台的部署(上)
YOLO系列(v1~v3)的学习及YOLO-Fastest在海思平台的部署(中)
YOLO系列(v1~v3)的学习及YOLO-Fastest在海思平台的部署(下)

———— END 2021@凌然 ————

  • 6
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值