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

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

文章目录

  • 声明
  • 2 工程应用分析
    • 2.1 平台/软件介绍和环境搭建
    • 2.2 网络训练方式选择
    • 2.3 SVP-NNIE前向计算处理过程
      • 2.3.1 例程中对YOLOv3网络模型的初始化操作
      • 2.3.2 图像的输入
      • 2.3.3 NNIE输出数据的内存分布图
      • 2.3.4 网络的后级处理
      • 2.3.5 性能分析和优化思路
  • 3 Caffe框架和网络训练流程
    • 3.1 Caffe平台的搭建
    • 3.2 Caffe计算框架基础(基于mnist示例)
      • 3.2.1 Prototxt文件、网络结构和训练参数
      • 3.2.2 网络的输入层和输出层
        • (1)网络输入层
        • (2)网络输出层
      • 3.2.3 处理操作相关
        • (1)模型的训练 & 结果的保存
        • (2)(在PC上)使用训练好的mnist模型做预测
        • (3)训练日志的可视化
    • 3.3 MobileNet-YOLOv3工程实现分析
      • 3.3.1 工程编译
      • 3.3.2 网络的输入层&检测任务数据集
        • (1)AnnotatedData类型数据输入层
        • (2)VOC数据集转LMDB数据库文件
      • 3.3.3 网络的训练
    • 3.4 标准YOLOv3的适配、预测和训练
      • 3.4.1 (Upsample)网络层的添加
      • 3.4.2 基于Caffe的标准YOLOv3预测
      • 3.4.3 基于Caffe的标准YOLOv3训练
  • 本文资源共享

声明

本文由 凌然 编写。

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

作者联系方式:E-mail: [email protected]

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

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

2 工程应用分析

[说明]

工程拟基于的SDK版本为:用于 Hi3516DV300Hi3516CV500R001C02SPC010 和用于 Hi3519AV100Hi3519AV100R001C02SPC020

[补充]

海思IPC平台中,支持NNIE神经网络推理的型号并不多,截至编写,最高端的芯片型号为Hi3559AV100。由于针对的场景不同,因此需要的算力也不同,开发者需要根据设计目标和网络规模进行选型。

如果最终的工程应用有批量生产的需求,可考虑如A311D、RK3399、S905D3、RV1126、RV1109等带有NPU的SoC作为替代型号(截至2021年)。

2.1 平台/软件介绍和环境搭建

Hi3519AV100是一颗面向监控IP摄像机、运动相机、全景相机、后视镜、航拍无人机等多个产品领域推出的高性能、低功耗的4K Smart Camera SoC。它集成了性能强大的可编程神经网络推理引擎和一个向量DSP,支持多种智能算法应用。——《Hi3519AV100 4K Smart Camera SoC 产品简介(2019-02-21)》

**SVP(Smart Vision Platform)**是海思媒体处理芯片智能视觉异构加速平台。该平台包含了CPU、DSP、NNIE等多个硬件处理单元和运行在这些硬件上SDK开发环境,以及配套的工具链开发环境。

**NNIE(Neural Network Inference Engine)**是海思媒体SoC中专门针对神经网络特别是深度学习卷积神经网络进行加速处理的硬件单元。目前NNIE配套软件及工具链仅支持以Caffe框架,且以Caffe-1.0版本为基础。使用其他框架的网络模型需要转化为Caffe框架下的模型。

nnie_mapper,简称mapper,该工具将用户通过开源深度学习框架训练得到的模型转化成在Hi35xx芯片上或者在仿真库中可以加载的数据指令文件(*.wk)。

RuyiStudio集成windows版的NNIE mapper、Runtime mapper和仿真库,具有生成NNIE wk功能、Runtime wk功能和仿真NNIE功能,同时具有代码编辑、编译、调试、执行功能、网络拓扑显示、目标检测画框、向量相似度对比、调试定位信息获取等功能。——《HiSVP 开发指南(2019-07-10)》(后续简称《指南》)

查看《指南》可知,YOLOv3中的特殊网络层(如Shortcut、Route、Upsample等)都已经被SVP工具支持(或可被类似功能网络层替换),这极大地方便了我们的移植,但如果想获得更高的性能,则必须进行二次开发,详见后文。在Windows平台上仿真网络模型所需的RuyiStudio和Mapper、仿真库之间的关系如下图所示:

YOLO系列(v1~v3)的学习及YOLO-Fastest在海思平台的部署(中)_第1张图片

SVP-NNIE工具关系

截至文章编写,一些开发包中依赖的资源已经不复存在,因此需要手动下载这些资源。这些缺少的资源为:qt-5.6.2-vc14_1.tar.bz2、libtiff-4.0.9-vc14_0.tar.bz2、jpeg-9b-vc14_2.tar.bz2。

如果你使用的开发包版本为HiSVP_PC_V1.2.1.0ruyi_env_setup-2.0.31RuyiStudio-2.0.31,则直接使用随文附带的资源即可。按照下面的步骤或《指南》中的描述配置RuyiStudio所需运行环境:

  1. 安装wget-1.11.4-1-setup.exe,在命令提示符中输入

    C:\>wget
    wget: missing URL
    Usage: wget [OPTION]... [URL]...
    
    Try `wget --help' for more options.
    

    以验证安装。该工具将用于安装脚本下载网络资源。

  2. 解压MinGW-w64-x86_64-7.3.0-release-posix-seh-rt_v5-rev0.7zC:\mingw64目录。

  3. 解压msys+7za+wget+svn+git+mercurial+cvs-rev13.7zC:\msys目录。此时该目录文件夹组织如下:

    C:\mingw64>dir
     驱动器 C 中的卷是 OS
     卷的序列号是 6009-7164
    
     C:\mingw64 的目录
    
    2021-03-31  17:43              .
    2021-03-31  17:43              ..
    2021-03-31  17:46              bin
    2018-03-19  23:59            49,517 build-info.txt
    2018-03-19  23:39              etc
    2018-03-19  23:59              include
    2018-03-19  23:59              lib
    2018-03-19  23:13              libexec
    2018-03-19  23:59              licenses
    2013-04-24  17:22              msys
    2018-03-19  23:29              opt
    2018-03-19  23:59              share
    2018-03-19  22:08              x86_64-w64-mingw32
    

    将以下目录添加到PATH环境变量(如果目录设置不同,对应地进行修改即可):

    C:\mingw64\bin;C:\mingw64\msys\bin;
    

    C:\mingw64\bin\x86_64-w64-mingw32-gcc.exe在所在目录下建立副本并重命名为mingw32-gcc.exe

  4. HiSVP_PC_V1.2.1.0\tools\nnie\windows\ruyi_env_setup-2.0.31目录拷贝到C:\ruyi_env_setup目录下,该目录将作为最终SVP所需Python3.5+Caffe目录使用。

  5. 拷贝libraries_v140_x64_py35_1.1.0.tar.bz2文件到C:\ruyi_env_setup目录下。

  6. 在当前目录下建立python35目录,拷贝随文资源中Ruyi Python35 Lib Needed目录下所有压缩包到此目录,并执行C:\ruyi_env_setup\setup_python.bat

    [说明]

    如果系统中安装有其它版本的Python,有可能出现调用到错误pip版本的情况,此时需手动指定pip安装压缩包中的whl文件:

    cd C:\ruyi_env_setup\python35
    .\Scripts\pip.exe install Cython-0.28.5-cp35-cp35m-win_amd64.whl
    .\Scripts\pip.exe install opencv_python-3.4.0.12-cp35-cp35m-win_amd64.whl
    .\Scripts\pip.exe install PyYAML-3.13-cp35-cp35m-win_amd64.whl
    
  7. 执行C:\ruyi_env_setup\setup_roi_caffe.bat以安装RuyiSdutio在进行网络模型分析时所需的rpn包。

    [说明]

    即使网络中未使用RPN层也需要进行此步骤,因为工具脚本中有导入rpn包(import rpn)的语句。

这里有几点需要说明:

  1. *如果不需要网络层分析工具,仅使用网络模型转换功能,可不安装Python3.5+Caffe环境。
  2. 根据SDK中提供的脚本进行安装后的Pycaffe仅为CPU版本,因此不适合用于训练。
  3. 如果系统中有多个版本的Python共存,则可能造成冲突。RuyiStudio环境配置脚本中配置了PYTHONPATH环境变量,这可能导致其它版本的Python无法正常找到库位置,此时可尝试使用Anaconda进行隔离。

更具体的操作方法参见《指南》,此处不再赘述。

2.2 网络训练方式选择

最终的网络模型移植将在RuyiStudio工具中完成。因此我们应选取Caffe1.0神经网络计算框架或将最终模型文件转化为Caffe1.0的格式。整体的训练和开发流程如下图所示:

YOLO系列(v1~v3)的学习及YOLO-Fastest在海思平台的部署(中)_第2张图片

网络训练和开发流程

由上图可知,网络训练的重点在于如何得到网络的Caffe模型(*.caffemodel + *.prototxt)。在海思SVP开发SDK中,提供了可用于学习的YOLOv1~YOLOv3预训练模型的caffemodel文件和prototxt文件,它是从YOLO的Darknet版本生成的网络模型转化而来得到的。由此不难想到几种网络训练方式(叙述以YOLO为例):

  1. 使用Darknet或其它框架(如Pytorch或Keras)训练网络,最后使用如Darknet2Caffe等工具对模型进行转化;
  2. 查找已有的可训练版本的Caffe实现,适配编译得到的Caffe进行训练得到最终的模型;
  3. 基于Caffe源码中的examples,编写YOLOv3的训练描述文件,适配编译得到的Caffe进行训练得到最终的模型。

Python的版本(Python2.7 v.s. Python3.5)、CUDA版本和操作系统(Windows v.s. Linux)多种多样,故上述训练方式有众多的可组合路径。由于网络最终需要转换为Caffe模型,因此无论采用上述哪种方式,均需Caffe环境并为其添加Upsample等必要的网络层。更为详细的训练细节请参阅网络训练相关章节。

2.3 SVP-NNIE前向计算处理过程

本节以SVP-NNIE例程中对YOLOv3的处理过程为例,简要记录整个处理流程及其中关键之处,并为后面使用NNIE加速网络预测打好基础。由于文档的形式并不适合解读源码,因此这里仅叙述一些程序理解上的关键之处。你在阅读源码的时候应该至少关注以下问题:

  1. 使用NNIE加速神经网络模型的处理流程是怎样的?
  2. 待检测图像是如何输入到网络模型中的?
  3. 从NNIE中输出的预测结果数据的内存分布图是怎样的,与Darknet框架下的网络输出结果是否一致?
  4. 从网络输出到最终结果需要做怎样的后级处理?
  5. 示例程序的运行速度如何、主要性能瓶颈在哪里,以及实时性优化可以从哪些方面入手?

2.3.1 例程中对YOLOv3网络模型的初始化操作

阅读NNIE例程的时候,需要明白一些重要概念:

  • 网络分段

    网络分段的描述可参见《HiSVP API参考05版(2019-06-25)》P29。YOLOv3网络属于端到端的One-Stage网络模型,且在例程中由应用程序处理网络的最终输出,因此仅有一个网络分段。一个网络分段中可以包含众多的网络层。

  • Blob(源Blob、输出Blob)和YOLOv3输入输出的关系

    YOLOv3模型具有一个输入层和3个输出层(YOLO层),因此具有一个源blob和3个输出blobs。Blob是Caffe框架中的基本类,用于网络层之间的数据传递。因此获取网络段的输出blob即为获取网络的最终输出数据。

[说明]

在MPP例程中,使用YOLOv3做图像预测的过程可通过以下代码路径查看:

sample/svp/nnie/sample_nnie_main.c main -> SAMPLE_SVP_NNIE_Yolov3

和大部分程序一样,在平台上运行网络模型同样遵循“初始化-使用-去初始化”的流程,在例程中,初始化完成的工作主要有:

  • 复制网络模型到MMZ内存并加载;

  • 为NNIE硬件计算申请所需内存,该片内存分为三个部分:

    • 模型计算所需要的辅助内存(u32TmpBufSize),具体大小由加载模型时得到的模型描述结构体提供;
    • 网络模型中各分段任务缓冲区(u32TotalTaskBufSize),具体大小通过调用 HI_MPI_SVP_NNIE_GetTskBufSize API获得;
    • 在各网络段之间传输的源Blob和输出Blob所占空间(astBlobSize),如原始图像和网络段输出等。
  • 为软件计算初始化必要的参数,并为用于处理网络输出数据的辅助空间申请所需内存。辅助空间存放从网络段输出Blobs得到的原始数据,这些数据经过后级计算、排序和NMS等操作,得到最终的输出结果(s32DstRoi、st32DstScore、u32ClassRoiNum)。辅助空间由两部分组成:

    • 用于存放从网络输出Blob获取的和计算过程中的临时数据的空间(u32TmpBufTotalSize),具体大小在例程库中通过 SAMPLE_SVP_NNIE_Yolov3_GetResultTmpBuf 函数计算。该部分空间又由三部分组成:
      • 用于完整获取各输出Blob的内存空间,空间大小为各网络段输出Blob的最大值;
      • 用于存储所有原始输出Blobs(即网络的3个YOLO层)的所有网格(即13×13、26×26、52×52)的所有输出目标(对应3个锚框)的信息(即4bbox+1objectness+80classes)的内存空间;
      • 其它临时变量(例程中为快排操作所需的SAMPLE_SVP_NNIE_STACK_S类型的结构体空间)。
    • 用于存放最终过滤后的目标结果的空间(u32DstRoiSize+u32DstScoreSize+u32ClassRoiNumSize)。

2.3.2 图像的输入

在程序初始化网络模型的硬件参数时,将申请的源blob内存空间地址记录在了 s_stYolov3NnieParam.astSegData[0].astSrc[0] 中,因此按照网络训练时的输入图像格式将待检测图像拷贝到源blob地址空间即可完成图像输入,而后需刷新MMZ缓存以同步数据。

2.3.3 NNIE输出数据的内存分布图

由于例程中使用的YOLOv3网络模型是从原作者Darknet网络预训练权重直接转化而来,因此网络输出数据格式也遵循了原网络输出。所不同的是:海思NNIE为了更好地进行加速,对每行数据进行了对齐操作(参见《HiSVP API参考05版(2019-06-25)》P30)。因此获取网络输出时需要略过这些用于对齐的无效字节。

和输入一样,申请的输出blob内存空间地址记录在了astSegData[0].astDst[0:2]中,对应着YOLOv3网络的3个YOLO层。以13×13的YOLO网络层举例,其输出张量( 13 × 13 × [ 3 × ( 4 + 1 + 80 ) ] 13×13×[3×(4+1+80)] 13×13×[3×(4+1+80)])在内存中的存储布局如下图所示:

YOLO系列(v1~v3)的学习及YOLO-Fastest在海思平台的部署(中)_第3张图片

SVP-NNIE-YOLOv3网络输出内存分布图-1

将数据按照特征图每行排列可以更直观地看到输出内存空间的全貌,如下图所示:

YOLO系列(v1~v3)的学习及YOLO-Fastest在海思平台的部署(中)_第4张图片

SVP-NNIE-YOLOv3网络输出内存分布图-2

2.3.4 网络的后级处理

知道了输出数据在内存中的存储格式,那么例程又是如何对数据进行处理的呢?

YOLO系列(v1~v3)的学习及YOLO-Fastest在海思平台的部署(中)_第5张图片

SVP-NNIE-YOLOv3网络后级处理

如上图所示,处理函数遍历了3个YOLO网络层输出,每一次遍历将blob暂存在 pstSoftwareParam->stGetResultTmpBuf (在处理函数 SVP_NNIE_Yolov3_GetResult 中为 pf32Permute 指针)指向的空间内,而后对该blob数据进行Sigmoid计算和映射关系计算,得到每个网格针对每个锚框预测的bbox信息。遍历结束后,对这些bbox进行快排和NMS等操作即得到最终的输出结果。

通过阅读转换函数可知,存储在 pf32Permute 指向的空间中的数据组织形式为:

YOLO系列(v1~v3)的学习及YOLO-Fastest在海思平台的部署(中)_第6张图片

SVP-NNIE-YOLOv3网络结果转换blob临时空间内存分布图

2.3.5 性能分析和优化思路

某次在Hi3516DV300平台上执行YOLOv3示例程序时主要部分耗时如下:

前向计算过程:

处理动作 时间消耗(us) 在本过程中的占比
待检测图像输入 (未统计) -
缓存同步(任务段内存&输出blob) 530 0.522%
调用API进行前向推理直到完成 100421 98.93%
缓存同步(输出blob) 559 0.55%
Total 101510 -

结果获取和处理过程:

处理动作 时间消耗(us) 在本过程中的占比
变量所有输出blobs,剔除对齐字节并转换输出 933677 99.975%
结果快排 104 0.011%
NMS去重 67 0.0072%
结果过滤和统计 66 0.0071%
Total 933914 -

注1:以上时间数据包括时间打印语句本身带来的处理开销,因此数值会稍微偏大。

注2:在Hi3519AV100平台上,同样例程的计算耗时为:前向67ms,后续461ms。为了让矛盾更清晰,这里仅基于Hi3516DV300的数据做分析和优化。

例程使用的测试图像包含的目标较少,因此快排和NMS没有明显耗时,但从上述结果来看,网络的前项推理和后续对各输出blob的遍历的确花费了大量的时间。阅读代码可知,例程中遍历了所有网格(13×13、26×26、52×52)的所有预测(3个),并对坐标、置信度和类概率执行了Sigmoid计算,对宽高做了exp计算。仅浮点计算次数就以百万计,这其中不乏大量的空预测和无效预测,不加区分地处理每个输出严重浪费了处理器性能。

[补充]

对网络输出使用Sigmoid计算等操作的处理依据来自Darknet-YOLOv3的源码。不同于YOLOv1,YOLOv3在损失函数进行计算前,对YOLO网络层输出中的相关量做了同样的激活计算。

对于例程中采用的结果处理方法,有以下几种改进策略:

  • 先进行过滤处理,再对有效预测结果做激活和映射等浮点运算

    每个网格会为每个尺度的anchorbox预测结果输出80个分类的类概率,而最终处理又仅会选取其中得分最大的作为最终结果类别,因此可先进行数值比较再对单一分类的结果做浮点计算。

  • 重新训练网络,减少网络预测的分类数

    减少网络分类数也就减少了网络的输出张量,进而在加快前向计算的同时降低了后级处理的开销。

网络前向计算速度则快很多,但100ms+的耗时依然没有达到实时性要求,好在对于目标任务,我们并不需要像YOLOv3这样“完美”的网络,因此在网络深度和输出上有不少可裁剪甚至重新设计的空间。例如考虑到监控摄像头所处角度较高,因此拍摄到的目标较小,甚至不会占满半个屏幕,因此可以裁剪特征图较小的输出层;视频监控不需要严格定位目标的边界,而更倾向于对目标的检测和类别的识别,因此可以减少骨干网络后用于特征合并和坐标预测的网络层;另外,诸如“使用深度可分离卷积替换常规卷积核”和“对网络进行剪枝”等方法可能会对减小网络模型体积、提升网络预测速度有帮助。

[补充]

事实上,YOLO系列网络发展至此,从工程应用出发,基于YOLOv3进行改进已经没有太大的必要性了,在后文中我们将一起看一个更轻、更快、更好的YOLO-Fastest模型,它将比YOLOv3更适合追求实时性的工程应用。

3 Caffe框架和网络训练流程

计算框架的选择是和模型本身以及平台支持相关的,使用Caffe不一定合适,其它框架也不一定不适合。这部分内容请参见 2.2 网络训练方式选择 章节。

[说明]

本文保留本章的目的是为未来的读者们提供必要的参考,事实上Caffe框架已经停止更新很久了(BVLC版本),但由于Caffe具有简洁清晰的架构,至今仍被很多的开发者和公司青睐。使用Caffe开发除了需要良好的算法开发能力外还需要C++编程经验,因此对于学生而言,在C++编程能力欠缺的情况下,不建议使用Caffe框架编写具有结构创新的新网络。

一些新的支持NPU或神经网络加速模块的芯片的开发工具包中已经具备了直接对Tensorflow或Keras等模型进行转换的能力,一些新的工程也支持了onnx格式的模型输出支持。Android/IOS端则可以使用诸如ncnn等框架进行部署。因此除非你已经熟悉Caffe或目标任务严重依赖Caffe,建议优先考虑使用其它框架。

本章的内容为后文的Darknet转Caffe提供了必要的基础,例如对Prototxt文件和Caffemodel相关的描述。

3.1 Caffe平台的搭建

在Windows上搭建PyCaffe环境可参考《Windows版PyCaffe-GPU的编译》(MS-Python2版本)和《Windows版PyCaffe-GPU的编译(续)》(BVLC-Python3版本)。

在Linux上搭建OpenCV+Caffe+Python3环境可参考:《Install OpenCV 4.4.0 and Caffe on Ubuntu 20.04 for Python 3》及系列指南,本人未做尝试不予评价。

3.2 Caffe计算框架基础(基于mnist示例)

3.2.1 Prototxt文件、网络结构和训练参数

从深度卷积神经网络的所需出发,一个计算框架应该实现的基本功能包括:对网络结构进行定义的方法、各网络层的前向计算和反向传播的实现、提供不同的权重更新方式、定义在各网络层之间传递的数据的格式、提供数据输入输出接口以及对网络进行保存和恢复的机制等。

Caffe使用三种(或两种)不同的prototxt文件定义训练网络结构、验证网络结构和训练参数,它们根据作用可被命名为 NetName_train.prototxtNetName_test.prototxtNetName_solver.prototxt或类似的名字。本章后续进行描述时可能使用solver文件train文件test文件对上述三类文件进行指代。实际上,由于设计的不同,一些网络可将train文件和test文件实现在一个文件中,这样的网络描述文件一般命名为NetName_train_test.prototxt

接下来以Caffe目录中的lenet_solver.prototxt 文件和对应的 examples/mnist/lenet_train_test.prototxt 文件为例,对Caffe训练所需的Prototxt文件进行简单的说明。这些文件可以被以普通的文本文件形式打开和编辑,当需要以图形化的方式查看网络结构的时候,可使用Netscope(网页版)或者是Netron(网页版或单机版)进行查看。

YOLO系列(v1~v3)的学习及YOLO-Fastest在海思平台的部署(中)_第7张图片

mnist示例网络图形化(Netron生成) 左侧为训练(TRAIN)时网络,右侧为测试(TEST)时网络

由上图可看出,mnist示例使用的Lenet是一个one-stage网络。

solver文件(lenet_solver.prototxt)

solver文件指定了网络训练时的参数,以该文件为例,各参数的作用和介绍如下:

  1. net

    该参数用于指示train_test共用网络模型描述文件的路径。如:

    net: "PATH_TO_FILE/NetName_train_test.prototxt"
    

    当train文件和test文件是各自独立的时候,对其分别指定:

    train_net: "PATH_TO_FILE/NetName_train.prototxt"
    test_net: "PATH_TO_FILE/NetName_test.prototxt"
    
  2. test_iter(test iteration,测试迭代) & max_iter(max iteration,最大迭代)

    迭代表示网络完成一次前向计算-损失函数计算-反向传播的过程。

    batch为单次送入网络进行一次迭代的一批数据,batchsize为该批数据的数量。

    epoch表示将所有训练数据集中的图片在网络中训练一次所需要的迭代次数,即当网络完成一个epoch训练时,训练数据集中的每张图片都被网络”看到“了一次。

    若希望每张图片被训练x次,则需要增加总的迭代次数,即 m a x _ i t e r a t i o n = x × e p o c h max\_iteration=x×epoch max_iteration=x×epoch

YOLO系列(v1~v3)的学习及YOLO-Fastest在海思平台的部署(中)_第8张图片

batch、epoch、max_iteration和训练数据集的关系
```
# 每迭代100次进行一次测试
test_iter: 100
# 设置最大迭代10000次
max_iter: 10000
```
  1. test_interval(测试间隔)

    该参数用于设置测试网络当前效果的间隔,单位为迭代次数。如:

    # 设置每500次迭代进行一次测试
    test_interval: 500
    
  2. base_lr(base learning rate,基础学习率)& momentum(动量)& weight_decay(权重衰减)

    学习率可以理解为每次损失函数的梯度对网络权重的影响力度,一般网络训练初期会选取一个较大的学习率以便网络权重的快速初始化,训练后期则采用相对较小的学习率使网络权重逐步趋近最优值。

    动量算法是对梯度下降算法的一种优化,动量决定了前一次梯度更新在本次更新的权重。动量的引入使梯度更新不仅依赖于本次的损失函数计算结果,还依赖于历史梯度数据,若本次梯度更新方向与历史梯度更新方向相似,则动量算法会加强这次更新,反之减弱。

    权重衰减实际就是L2正则化,其作用为防止网络过拟合。

    # 设置学习率为0.01,动量0.9,权重衰减0.0005
    base_lr: 0.01
    momentum: 0.9
    weight_decay: 0.0005
    
  3. lr_policy(learning rate policy,学习率策略)

    Caffe提供的学习率策略一共有7种,它们分别依赖其它不同的辅助参数,具体如下:

    学习率调整策略 依赖的其它参数 调整公式
    fixed - b a s e _ l r base\_lr base_lr
    step gamma、stepsize $base_lr*gamma^{floor(\frac{iter}{stepsize} )} $
    exp gamma $base_lr*gamma^{iter} $
    inv gamma、power $base_lr*(1+gamma*iter)^{-power} $
    multistep stepvalue 与step类似,根据stepvalue变化
    poly power $base_lr*(1-iter/max_iter)^{power} $
    sigmoid gamma b a s e _ l r ∗ ( 1 / ( 1 + ℓ − g a m m a ∗ ( i t e r − s t e p s i z e ) ) ) base\_lr * \left ( 1 / ( 1 + \ell ^{ -gamma * ( iter - stepsize ) } ) \right ) base_lr(1/(1+gamma(iterstepsize)))

    注:iter为当前迭代次数,floor为向下取整,ceil为向上取整。

  4. display(显示间隔)

    该参数用于设置屏幕打印间隔,单位为迭代次数。如:

    # 设置每100次迭代打印一次结果
    display: 100
    
  5. snapshot(快照)

    该参数用于设置快照保存间隔,单位为迭代次数。如:

    # 设置每5000次迭代保存一次快照
    snapshot: 5000
    

    快照文件包括当前网络权重文件(*.caffemodel)和solver状态文件(*.solverstate)。

  6. snapshot_prefix(快照文件前缀)

    该参数用于指定快照文件存放的位置和文件名前缀,编号和后缀名将由Caffe自动生成。如:

    snapshot_prefix: "PATH_TO_FILE/NetName_PrefixName"
    
  7. solver_mode(解析器模式)

    该参数用于指示训练过程运行在CPU或是GPU上。如:

    solver_mode: GPU
    

然而在众多的网络模型参数配置文件中,我们经常可以看见上述参数之外的其它配置项,这些Caffe提供的配置项共有几十个之多,网上也有很详细的讲解帖子,此处不再赘述,后面如果有需要会单独说明。

train & test文件(lenet_train_test.prototxt)

train文件是网络进行训练时的结构描述文件,test文件是网络进行测试和验证时的结构描述文件。通常来说,train文件中包含网络的损失函数层实现,网络最后的输出层会被输入到损失函数层进行计算,进而通过前向传播更新各层权重,而test文件中网络的后级输出会被连到结果输出层从而获取分类或预测信息。在mnist示例中,train和test文件共用一个。

Caffe的网络描述文件还是非常通俗易懂的,每网络层以这样的形式进行抽象:

name: "NetName" # 该参数在整个网络描述文件中只配置一次
layer{
    # 网络层参数描述
    name: "网络层名称"
    type: "网络层类型"
    bottom: "网络层的输入来源"
    top: "网络层的输出名称"
    param{
        # 所有层共用参数在本网络层的具体配置
        lr_mult: 1 # 学习率相乘系数
    }
    LayerType_param{
        # 该类型网络层特有参数在本网络层的具体配置
    }
}

其中,根据网络层类型的不同,参数项不是必须的,但必须至少具有名称、类型、输入或输出。

本节内容参考了《深度学习实践 基于Caffe的解析》一书。

3.2.2 网络的输入层和输出层

网络层可以有多个输入或多个输出,则对应地增加bottom字段和top字段即可。对于较为常见的卷积层(Convolution)、池化层(Pooling)、BN层(BatchNorm)等,它们的层参数还是比较好理解的。对于某些网络特有层或者是不常见的网络层,我们也可以通过网络层类型、参数命名或者阅读在 src/caffe/proto/caffe.proto 文件中的定义和注释了解对应的含义。然而无论是怎样的网络,都无一例外需要考虑到两种层类型:一是网络的数据输入层;二是网络的输出处理层(包括对应train文件的损失函数层和对应test文件的最终输出处理层)。

(1)网络输入层

mnist示例网络为一个经典的多分类网络,输入数据包括图片和对应的标签,网络输出为一个Softmax分类器。Caffe根据当前网络运行状态(训练或验证)分别使用以下两种网络输入层:

# 训练模式下网络输入层
layer {
  name: "mnist"
  type: "Data"
  top: "data"
  top: "label"
  include {
    phase: TRAIN
  }
  transform_param {
    scale: 0.00390625 # 即1/256
  }
  data_param {
    source: "examples/mnist/mnist_train_lmdb" # 选择训练数据集
    batch_size: 64
    backend: LMDB
  }
}

# 测试/验证模式下网络输入层
layer {
  name: "mnist"
  type: "Data"
  top: "data"
  top: "label"
  include {
    phase: TEST
  }
  transform_param {
    scale: 0.00390625
  }
  data_param {
    source: "examples/mnist/mnist_test_lmdb" # 选择测试数据集
    batch_size: 100
    backend: LMDB
  }
}

根据include项中包含的内容可以猜测出网络层的使用场景,“Data”的网络层类型表示输入图像格式为LevelDB或LMDB,由backend字段具体选择。Data层的transform_param参数包括四个选项:scale、mean_file_size、mirror和crop_size。它们的具体含义在链接给出的博客中很详细地进行了说明。但是Caffe不仅提供了这一种数据输入方式,输入数据还可以来自内存(MemoryData类型)、HDF5文件(HDF5Data类型)、已有的图片(ImageData类型)、带注释数据(AnnotatedData类型)等等,或者使用自定义的网络输入层。这些支持的网络层及其选项都可以在 caffe.proto 文件中找到。

数据输入层的存在为不同的网络结构提供了统一的数据输入接口,通过该层的top输出,其它网络层可从原始的训练集中获取到图像、标签或标注信息。当网络目标任务发生变化时,例如从训练变为部署,可通过替换数据输入层达到兼容不同应用场景的目的。

(2)网络输出层

在mnist示例中,网络末端为两层全连接层。Inner Product意为内积,这是因为全连接层的前向计算的实质为矩阵乘法。

和输入不同,Caffe无法为网络的输出层提供通用的示例或接口,输出一般由设计者定义。以第2章所示转换后的YOLOv3网络结构示例进行说明:它仅获取了YOLOv3网络YOLO层的卷积结果,而后在软件中进行激活等处理,因此网络结构本身并没有包含输出处理层。

对于通常的深度学习,在训练阶段,最后一层网络的输出将连接到损失函数层,通过损失函数校正网络训练方向;在验证或部署阶段则连接到输出数据处理层,以便直接提供网络最终的预测结果。为了提升速度,在训练阶段使用到的网络层一般均需提供CUDA代码实现以便Caffe调用GPU进行加速,而在终端部署的时候则需提供针对目标平台的加速代码。

由于嵌入式平台对一些新型算法、算子、激活函数等支持存在时间差,因此并非总是能完全移植最新的网络结构,这种情况下工程师们一般使用相似的功能对不支持的模块进行替换,并对网络模型进行量化和精简,即所谓工程实践和理论研究的不同之处。

3.2.3 处理操作相关

(1)模型的训练 & 结果的保存

注:本节内容基于BVLC版本Caffe进行,环境搭建参见 3.1 Caffe平台的搭建 章节。

说明:为了简单起见,本节不使用Caffe的Python接口。

说明:以下操作均在 ./mnist_BVLC 目录中进行。

使用随文资源中已经下载好的mnist-LMDB数据集或者按照网上众多博文给出的步骤下载并生成mnist数据集。在 ./mnist_BVLC 目录下准备好训练集和验证集的 mnist_train_lmdb 目录和 mnist_test_lmdb 目录。拷贝Caffe工程中 examples/mnist/lenet_train_test.prototxtexamples/mnist/lenet_solver.prototxt 文件到 ./mnist_BVLC

[补充]

通过在solver文件中添加快照相关配置,可使网络在训练过程中定期生成快照和模型文件。通过在训练时指定 snapshot 参数或者weights 参数可选择基于快照或模型文件进行再训练。

solver文件内容如下:

net: "./lenet_train_test.prototxt"
test_iter: 100
test_interval: 500
base_lr: 0.01
momentum: 0.9
weight_decay: 0.0005
lr_policy: "inv"
gamma: 0.0001
power: 0.75
display: 100
max_iter: 10000
snapshot: 5000
snapshot_prefix: "./lenet_mnist"
solver_mode: GPU

修改train & test网络结构描述文件内容中的数据集位置字段:

name: "LeNet"
layer {
  name: "mnist"
  type: "Data"
  top: "data"
  top: "label"
  include {
    phase: TRAIN
  }
  transform_param {
    scale: 0.00390625
  }
  data_param {
    source: "./mnist_train_lmdb" # 指向训练数据集目录
    batch_size: 64
    backend: LMDB
  }
}
layer {
  name: "mnist"
  type: "Data"
  top: "data"
  top: "label"
  include {
    phase: TEST
  }
  transform_param {
    scale: 0.00390625
  }
  data_param {
    source: "./mnist_test_lmdb" # 指向验证数据集目录
    batch_size: 100
    backend: LMDB
  }
}

...# 后续网络结构

而后使用命令提示符或Windows PowerShell进入 ./mnist_BVLC 并执行:

cd /d PATH_TO_DEST_DIRECTORY/
PATH_TO_BVLC/scripts/build/tools/Release/caffe.exe train --solver=lenet_solver.prototxt 

最终的网络精度达到99.1%:

...
I0504 16:07:09.694408 21636 solver.cpp:447] Snapshotting to binary proto file ./lenet_mnist_iter_10000.caffemodel
I0504 16:07:09.722406 21636 sgd_solver.cpp:273] Snapshotting solver state to binary proto file ./lenet_mnist_iter_10000.solverstate
I0504 16:07:09.737406 21636 solver.cpp:310] Iteration 10000, loss = 0.00320604
I0504 16:07:09.737406 21636 solver.cpp:330] Iteration 10000, Testing net (#0)
I0504 16:07:10.240324 17376 data_layer.cpp:73] Restarting data prefetching from start.
I0504 16:07:10.261320 21636 solver.cpp:397]     Test net output #0: accuracy = 0.9912
I0504 16:07:10.261320 21636 solver.cpp:397]     Test net output #1: loss = 0.0270723 (* 1 = 0.0270723 loss)
I0504 16:07:10.261320 21636 solver.cpp:315] Optimization Done.
I0504 16:07:10.262322 21636 caffe.cpp:260] Optimization Done.

网络的验证过程也可以手动进行:

cd /d PATH_TO_DEST_DIRECTORY/
PATH_TO_BVLC/scripts/build/tools/Release/caffe.exe test --model=lenet_train_test.prototxt --weights=lenet_mnist_iter_10000.caffemodel -iterations 100

使用该命令重复测试100轮。其中model参数和weights参数是必须的。

[补充]

caffe.exe命令的更多参数和可选项可以参考文档或者其它博文。

(2)(在PC上)使用训练好的mnist模型做预测

首先从mnist-LMDB数据集生成均值文件:

cd /d PATH_TO_DEST_DIRECTORY/
PATH_TO_BVLC/scripts/build/tools/Release/compute_image_mean.exe ./mnist_train_lmdb mnist_mean.binaryproto --backend=lmdb 

设法获取28*28的手写数字灰度图像,例如本节使用到的 2.png ,以及标签文件 mnist_labels.txt

0
1
2
3
4
5
6
7
8
9

将网络结构描述文件拷贝一个副本重命名为 lenet_deploy.prototxt ,并将两网络输入层替换为:

layer {
  name: "data"
  type: "Input"
  top: "data"
  input_param {
    shape: {
        dim: 1
        dim: 1
        dim: 28
        dim: 28
    }
  }
}

将网络输出层的 accuracy 层删除,将 SoftmaxWithLoss 层改为:

layer {
  name: "Predict"
  type: "Softmax"
  bottom: "ip2"
  top: "Predict"
}

使用以下语句进行模型预测:

cd /d PATH_TO_DEST_DIRECTORY/
PATH_TO_BVLC/scripts/build/examples/cpp_classification/Release/classification.exe ./lenet_train_test.prototxt ./lenet_mnist_iter_10000.caffemodel ./mnist_mean.binaryproto ./mnist_labels.txt ./2.png
---------- Prediction for ./2.png ----------
1.0000 - "2"
0.0000 - "0"
0.0000 - "3"
0.0000 - "1"
0.0000 - "4"

有可能出现预测结果不正确的情况,这是正常的。

(3)训练日志的可视化

[引用]

本节内容参考:《Caffe - 训练日志 log 可视化分析_CSDN》

Caffe的训练日志即为训练过程中的打印信息,信息输出频次在solver文件中设置,上文已经说明过了。打印的信息包含网络训练过程中的损失、精度等信息,可使用以下语句将训练过程中的输出保存到文件:

cd /d PATH_TO_DEST_DIRECTORY/
PATH_TO_BVLC/scripts/build/tools/Release/caffe.exe train --solver=lenet_solver.prototxt 2>&1 | tee mnist_train.log 

使用Caffe自带的日志分析工具进行数据提取:

cd /d PATH_TO_DEST_DIRECTORY/
python PATH_TO_BVLC/tools/extra/parse_log.py mnist_train.log ./

即可在当前目录中得到 mnist_train.log.trainmnist_train.log.test 两文件。

[注意]

该工具脚本基于Python2语法,可在Anaconda中新建Python2.7的最小环境,而后在该虚拟环境中运行脚本。无需特殊依赖包。

使用以下代码可将损失和精度变化绘制出来。如果需要其它的信息或美化打印图,可自行修改代码。

import pandas as pd
import matplotlib.pyplot as plt

train_log = pd.read_csv("./mnist_train.log.train")
test_log = pd.read_csv("./mnist_train.log.test")

_, ax1 = plt.subplots()
ax1.set_title("mnist log")
ax1.plot(train_log["NumIters"], train_log["loss"], alpha=0.5)
ax1.plot(test_log["NumIters"], test_log["loss"], 'g')
ax1.set_xlabel('iteration')
ax1.set_ylabel('train loss')
plt.legend(loc='upper left')

ax2 = ax1.twinx()
ax2.plot(test_log["NumIters"], test_log["accuracy"], 'r')
ax2.set_ylabel('test accuracy')
plt.legend(loc='upper right')

plt.show()

[说明]

该代码为随文draw_log.py文件,运行环境兼容Python2和Python3。

YOLO系列(v1~v3)的学习及YOLO-Fastest在海思平台的部署(中)_第9张图片

mnist示例日志数据可视化

由打印图可见模型在约1000次迭代之后即达到了饱和。

3.3 MobileNet-YOLOv3工程实现分析

Caffe的文档即使在几大框架中也是最为人诟病的一点,更何况我们是从零开始,如果没有示例确实寸步难行,因此我尝试寻找Caffe版本的YOLOv3代码实现,但并没有找到完全契合的源码,也许是我的搜索技术不到家或者没有人将其开源的缘故吧。在查找过程中发现,被克隆最多的工程是Caffe-YOLOv3-Windows-eric612,该工程将MobileNet和YOLOv3损失函数组合在一起,因此被作者称为“MobileNet-YOLOv3”。

寻找和学习现有的工程实现主要是为了解决以下几方面的问题:

  1. 网上大部分Caffe示例是基于分类网络的,针对目标检测任务的描述较少。Caffe是如何处理针对检测任务的数据输入的?
  2. 接上一问,如何制作可用于训练的LMDB目标检测数据集?
  3. 由于Caffe的网络层编写是基于C++的,而我现在C++上完全是菜鸟,因此如果有可借鉴的代码会减少工作量。
  4. 基于一份示例,尝试网络的训练流程,有助于整体工程的把控和时间节点规划。

3.3.1 工程编译

该工程的编译过程和 3.1 Caffe平台的搭建 章节描述的基本一致。这里仍然使用VS2015进行编译。

[注意]

实践中若开启NCCL选项则会出现 class “boost::common_type” has no member “type” 的错误,因此应予以禁用。

[注意]

若禁用Python接口,编译将使用默认的Python2.7环境,若 libraries_v140_x64_py27_1.1.0.tar.bz2 下载过慢,可手动下载后拷贝到:

C:/Users/USERNAME/.caffe/dependencies/download/

[注意]

编译中可能出现 LINK : fatal error LNK1181: 无法打开输入文件“\openmp.obj” 的错误。

重新解压工程目录,在build_win.cmd脚本运行配置刚刚完成的时候终止脚本,修改 build/CMakeCache.txt 文件中关于OpenMP的配置为:

...
//C++ compiler flags for OpenMP parallization
- OpenMP_CXX_FLAGS:STRING=/openmp
+ OpenMP_CXX_FLAGS:STRING=-openmp

//C compiler flags for OpenMP parallization
- OpenMP_C_FLAGS:STRING=/openmp
+ OpenMP_C_FLAGS:STRING=-openmp
...

而后重新运行脚本,脚本将根据该配置缓存生成VS工程,用VS打开 build/caffe.sln 解决方案并在Release x64下生成全部即可。

3.3.2 网络的输入层&检测任务数据集

(1)AnnotatedData类型数据输入层

[说明]

这里以 models/yolov3/mobilenet_yolov3_train.prototxt 中描述的结构为例。

数据输入层及其注释如下,关于其中一些参数的定义可在 caffe.proto 文件中找到,这一点前文已经说过了。

layer {
  name: "data"
  type: "AnnotatedData"
  top: "data"
  top: "label"
  include {
    phase: TRAIN             # 表示该层仅用于训练过程
  }
  
  transform_param {
    scale: 0.007843          # 即1/127.5
    mirror: true             # 开启镜像
    mean_value: 127.5
    mean_value: 127.5
    mean_value: 127.5        # 用于图像归一化:(x-mean_value)*scale

	resize_param {
      prob: 0.1              # 使用该缩放策略的可能性
      resize_mode: WARP      # OpenCV中通过仿射变换完成图像缩放
      height: 320
      width: 320
      interp_mode: LINEAR    # 双线性插值
      interp_mode: AREA      # 区域插值
      interp_mode: LANCZOS4  # 兰索斯插值
    }

    ...                      # 此处省略了一些缩放参数层

    emit_constraint {
      emit_type: CENTER
    }
    distort_param {          # 图像变换
      brightness_prob: 0.5
      brightness_delta: 32.0 # 亮度相关
      contrast_prob: 0.5
      contrast_lower: 0.5
      contrast_upper: 1.5    # 对比度相关
      hue_prob: 0.5
      hue_delta: 18.0        # 色调相关
      saturation_prob: 0.5
      saturation_lower: 0.5
      saturation_upper: 1.5  # 饱和度相关
      random_order_prob: 0.0
    }
	expand_param {
      prob: 0.66
      max_expand_ratio: 2.0
    }
  }
  data_param {
    source: "examples/VOC0712/VOC0712_trainval_lmdb"  # 训练数据集目录
    batch_size: 6                                     # 训练时的batch大小
    backend: LMDB                                     # 数据集格式
  }
  annotated_data_param {
	yolo_data_type : 1
	yolo_data_jitter : 0.3
    label_map_file:  "data/VOC0712/labelmap_voc.prototxt"  # 数据集分类信息
  }
}

该AnnotatedData类型的网络层来自SSD实现。作者在当前工程中的README中给出了MobileNet-SSD-Windows的工程链接。查看SSD网络的train文件可发现其数据输入层也是使用AnnotatedData类型。该类型网络层允许在图像数据之外增加标注数据,属于用户扩展网络层,因此不在BVLC版本中提供。

(2)VOC数据集转LMDB数据库文件

[引用]

本节内容参考:

  1. caffe-ssd 训练自己的VOC数据集(一):转换VOC xml数据为lmdb格式_CSDN
  2. caffe-ssd训练自己的数据集_博客园

观察已有的配置文件可知,AnnotatedData层输入数据的格式为LMDB(或者LevelDB),因此如果我们想使用这个框架进行训练,首先需要获取到LMDB格式的检测数据集。将VOC(格式的)数据集转为LMDB格式的步骤网上有很多,这里为了防止链接失效再重复记录一遍,应该不算抄袭吧(笑)。

[补充]

VOC数据集下载链接:

  1. VOC2007-TrainVal
  2. VOC2007-Test
  3. VOC2012

下载好的数据集原件已经放在随文资源中。

将数据集解压,即内容存放在 VOCdevkit/VOC2007VOCdevkit/VOC2012 目录中。将以下脚本保存为sh文件(e.g. create_list.sh)存放在 **VOCdevkit/**目录下,并在该目录下通过Windows PowerShell执行:

#!/bin/bash

# --------------------------------------------------
# 1. 注意修改root_dir变量指向的数据集路径!
# 2. 注意修改get_image_size.exe的执行路径!
# 3. 注意将路径中的斜杠换为Linux风格的“/”!
# --------------------------------------------------

root_dir=PATH_TO_DIRECTORY/VOCdevkit/
sub_dir=ImageSets/Main
bash_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
for dataset in trainval test
do
  dst_file=$bash_dir/$dataset.txt
  if [ -f $dst_file ]
  then
    rm -f $dst_file
  fi
  for name in VOC2007 VOC2012
  do
    if [[ $dataset == "test" && $name == "VOC2012" ]]
    then
      continue
    fi
    echo "Create list for $name $dataset..."
    dataset_file=$root_dir/$name/$sub_dir/$dataset.txt

    img_file=$bash_dir/$dataset"_img.txt"
    cp $dataset_file $img_file
    sed -i "s/^/$name\/JPEGImages\//g" $img_file
    sed -i "s/$/.jpg/g" $img_file

    label_file=$bash_dir/$dataset"_label.txt"
    cp $dataset_file $label_file
    sed -i "s/^/$name\/Annotations\//g" $label_file
    sed -i "s/$/.xml/g" $label_file

    paste -d' ' $img_file $label_file >> $dst_file

    rm -f $label_file
    rm -f $img_file
  done

  # Generate image name and size infomation.
  if [ $dataset == "test" ]
  then
    ../../build/tools/Release/get_image_size.exe $root_dir $dst_file $bash_dir/$dataset"_name_size.txt"
  fi

  # Shuffle trainval file.
  if [ $dataset == "trainval" ]
  then
    rand_file=$dst_file.random
    cat $dst_file | perl -MList::Util=shuffle -e 'print shuffle();' > $rand_file
    mv $rand_file $dst_file
  fi
done

即可在目录下生成 test.txttrainval.txt 文件,该过程因处理器或磁盘性能不同可能需要几分钟到十几分钟不等。查看文件可知,其内容涵盖了两大数据集。

[补充]

实际上,该文件内容参考自Caffe_SSD工程的 data/VOC0712目录下的同名文件。

而后我们使用MobileNet-YOLOv3工程提供的 convert_annoset 工具将数据集转为LMDB格式。该工具源码在tools目录下。我们使用的转换命令为:

# 转换测试数据集(test)
PATH_TO_FILE/convert_annoset.exe \
    --anno_type=detection \
    --encode_type=jpg \
    --encoded=true \
    --shuffle=true \
    --label_map_file=PATH_TO_DIRECTORY/VOCdevkit/labelmap_voc.prototxt \
    PATH_TO_DIRECTORY/VOCdevkit/ \
    PATH_TO_DIRECTORY/VOCdevkit/test.txt \
    PATH_TO_DIRECTORY/VOCdevkit/VOCdevkit_test_lmdb 

# 转换训练数据集(trainval)
PATH_TO_FILE/convert_annoset.exe \
    --anno_type=detection \
    --encode_type=jpg \
    --encoded=true \
    --shuffle=true \
    --label_map_file=PATH_TO_DIRECTORY/VOCdevkit/labelmap_voc.prototxt \
    PATH_TO_DIRECTORY/VOCdevkit/ \
    PATH_TO_DIRECTORY/VOCdevkit/trainval.txt \
    PATH_TO_DIRECTORY/VOCdevkit/VOCdevkit_trainval_lmdb 

这些步骤完成后,即可在VOCdevkit目录下得到LMDB数据集文件夹 VOCdevkit_test_lmdbVOCdevkit_trainval_lmdb

3.3.3 网络的训练

修改已有训练文件中相关路径,而后仿照前面的运行示例执行训练命令即可。

[注意]

如果数据集路径不正确,将出现训练进程执行到数据输入层解析时闪退的现象,train文件和test文件中的相关路径都必须被正确设置才能执行训练。

[注意]

该版本工程中,若运行前文中的mnist示例,需要特别指定solver文件中的type字段为“sgd”而非“SGD”,或在相应源码中进行修改亦可。

使用 models/yolov3/mobilenet_yolov3_XXX.prototxt 在作者本机上训练时发现,batch_size设置为1时的显存占用约1.4G。

3.4 标准YOLOv3的适配、预测和训练

Caffe中已经包含了众多常用的网络层,对于YOLOv3使用的一些特殊层,也有可替换的选项,例如shortcut对应elwise、route对应concat。因此如果只是最基本地跑一下YOLOv3,网络层这部分基本没有什么需要额外做的工作。

本节将基于上节的Caffe-YOLOv3-Windows-eric612工程,尝试使用标准YOLOv3网络执行目标检测任务。

3.4.1 (Upsample)网络层的添加

添加网络层的一般步骤简述为:

  1. 实现网络层的C++代码、头文件和CUDA代码,将头文件放在 /include/caffe/layers 目录下,将源文件放在 /src/caffe/layers 目录下;

  2. 修改Caffe原型文件 src/caffe/proto/caffe.proto ,在 message LayerParameter 结构中增加新网络层定义,并分配不同于现有的标号;

    message LayerParameter{
        # 早先的定义
        ...
        optional NewLayerParameter newlayer_param = 1111; # 分配号不可重复,一般按顺序分配,此处仅为示例
    }
    
  3. 接上一步,在文件中(如最末尾处)为新网络层增加 message NewLayerParameter 描述,以告知网络层所需参数;

    message UpsampleParameter{
        optional 变量类型 变量名 = 变量值 [default = 默认值];
        ...
    }
    
  4. 在VS工程中增加对新网络层的编译,或重新生成VS工程;

  5. 重新编译Caffe。

按照上述步骤添加现有的Upsample实现(例如来自Darknet2Caffe工程中的实现)以使Caffe支持Upsample网络层解析。如果操作遇到困难,可参考网上相关经验文章。

3.4.2 基于Caffe的标准YOLOv3预测

[说明]

本节使用的原始YOLOv3的Prototxt文件和Caffemodel文件来自SVP示例中提供的版本,它们都是从Darknet预训练网络转换而来的。

对于网络预测任务,只需要在该Prototxt文件末尾增加Yolov3DetectionOutput网络层以获取输出即可。使用示例可参考MobileNet-YOLO网络的Prototxt文件。下面是修改后的检测输出层参数。将修改后的文件命名为 yolov3_deploy.prototxt 保存到 models/yolov3/ 目录,Caffemodel文件同样复制到该目录下并重命名为 yolov3_deploy.caffemodel

...

layer {
  name: "detection_out"
  type: "Yolov3DetectionOutput"
  bottom: "layer82-conv"
  bottom: "layer94-conv"
  bottom: "layer106-conv"
  top: "detection_out"

  yolov3_detection_output_param {
    confidence_threshold: 0.01
	nms_threshold: 0.3
    num_classes: 80
	
    biases: 10
    biases: 13
    biases: 16
    biases: 30
    biases: 33
    biases: 23
    biases: 30
    biases: 61
    biases: 62
    biases: 45
	biases: 59
    biases: 119
    biases: 116
	biases: 90
    biases: 156
    biases: 198
    biases: 373
    biases: 326
	
    mask:6
    mask:7
    mask:8
    mask:3
    mask:4
    mask:5
    mask:0
    mask:1
    mask:2
    anchors_scale:32
    anchors_scale:16
    anchors_scale:8
    mask_group_num:3
  }
}

按照下面的示意修改现有的检测脚本 examples/demo_yolo_lite.cmd

    ...
    
-   set in_dir=data\
+   set in_dir=data\VOC0712\
    set wait_time=1000
-   build\examples\ssd\Release\ssd_detect models\yolov3\mobilenet_yolov3_lite_deploy.prototxt ^
+   build\examples\ssd\Release\ssd_detect models\yolov3\yolov3_deploy.prototxt ^
-   models\yolov3\mobilenet_yolov3_lite_deploy.caffemodel ^
+   models\yolov3\yolov3_deploy.caffemodel ^
    %in_dir% ^
    
    ...

YOLOv3预训练权重是基于80分类数据集的,因此需要修改 examples/ssd/ssd_detect.cpp 工具源码,删除对custom_class宏定义的注释以启用80分类检测,而后重新编译该工具。

[注意]

若不对ssd_detect.cpp进行修改,由于检测器默认配置为基于VOC-20分类标签,会导致检测结果分类错误。

执行脚本即可看到网络模型的输出演示。

YOLO系列(v1~v3)的学习及YOLO-Fastest在海思平台的部署(中)_第10张图片

使用预训练权重的标准YOLOv3预测结果

3.4.3 基于Caffe的标准YOLOv3训练

MobileNet-YOLO工程使用的YOLOv3网络输出层可以正常处理Darknet转换出的Caffemodel权重佐证了一点:该工程的YOLOv3损失函数层对YOLO网络层的每项输出的定义和Darknet是相同的,因此可以直接复用这些已有的实现进行网络训练,训练出的模型可以直接进行(第2章所示的)移植和验证,而不用大规模修改原例程的处理逻辑。

[补充]

其实在尝试使用现有的标准YOLOv3模型进行预测之前,应当先对网络层实现进行分析,目的是为了查看MobileNet-YOLOv3实现中网络输出张量的内存分布是否和Darknet-YOLOv3的一致。

损失函数通过对不同地址的输出张量数据进行读取并计算损失值,间接规定了每个网络输出的含义。这并不很难理解,结合指针和偏移的概念,如果损失函数假设数据是以 4 + 1 + 80 4+1+80 4+1+80的形式存放在内存中的,并在 *(BaseAddr+4) 的位置读取目标置信度,那么最终的网络输出中,*(BaseAddr+4)位置输出的张量即表示目标的置信度信息——损失函数规定了它的意义。

因此,只需查找代码中对网络输出层数据的使用是否符合 1.3.4-(4) yolo网络层结果输出 章节中示意的内存分布即可做出判断。各网络层源码在 PATH_TO_CAFFE/src/caffe/layers/ 目录下。只需查看 yolov3_layer.cppyolov3_detection_output_layer.cpp 文件中 Forward_cpu 函数对 bottom 参数的使用情况即可。

基于前一节用于预测的网络模型描述文件,参照MobileNet-YOLO对应的描述文件修改YOLOv3训练和验证模型结构描述文件的输入和损失函数层/输出层,修改过程中需要注意以下几点:

  1. 无论是train文件还是test文件,数据输入层所有resize_param参数中的prob字段数值的加和应为1;
  2. 根据使用的数据集的不同,每YOLO网络层的输出张量需要进行修改。在使用3个anchors的情况下,当基于COCO数据集训练时,由于类别数为80,因此YOLO层输出张量为 3 × ( 4 + 1 + 80 ) = 255 3×(4+1+80)=255 3×(4+1+80)=255个;当基于VOC数据集训练时,由于类别数为20,因此YOLO层输出张量为 3 × ( 4 + 1 + 20 ) = 75 3×(4+1+20)=75 3×(4+1+20)=75个;其它或自定义数据集的计算与此类似。涉及修改的YOLO网络层分别为: layer82-convlayer94-convlayer106-conv
  3. 基于上一点,网络的损失函数层和准确度计算层的分类数量同样需要做修改。

修改后的train文件网络输入和损失函数层配置如下:

# 以下内容为网络层输入,使用前文生成的VOC-LMDB数据集
name: "Caffe-YOLOv3"
layer {
  name: "data"
  type: "AnnotatedData"
  top: "data"
  top: "label"
  include {
    phase: TRAIN         # 该字段表示此配置用于训练过程
  }
  
  transform_param {
    scale: 0.007843
    mirror: true
    mean_value: 127.5
    mean_value: 127.5
    mean_value: 127.5
	force_color: true
    resize_param {
      prob: 1.0          # 训练仅适配单一分辨率,因此prob应为1
      resize_mode: WARP
      height: 416
      width: 416
      interp_mode: LINEAR
      interp_mode: AREA
      interp_mode: LANCZOS4
    }
    emit_constraint {
      emit_type: CENTER
    }
    distort_param {
      brightness_prob: 0.5
      brightness_delta: 32.0
      contrast_prob: 0.5
      contrast_lower: 0.5
      contrast_upper: 1.5
      hue_prob: 0.5
      hue_delta: 18.0
      saturation_prob: 0.5
      saturation_lower: 0.5
      saturation_upper: 1.5
      random_order_prob: 0.0
    }
	expand_param {
      prob: 0.66
      max_expand_ratio: 2.0
    }
  }
  
  data_param {
    source: "PATH_TO_FILE/VOCdevkit/VOCdevkit_trainval_lmdb" # 即使在Windows下,目录也要使用'/'分割而非'\'
    batch_size: 1                                            # batch_size的大小根据数据集和显存大小做适配
    backend: LMDB
  }
  annotated_data_param {
	yolo_data_type : 1
	yolo_data_jitter : 0.3
    label_map_file:  "PATH_TO_FILE/VOCdevkit/labelmap_voc.prototxt"
  }
}

# 这里省略掉原Prototxt文件的内容,其中YOLO层输出数量记得要适配数据集
...

# 以下为损失函数层,可以直接复制到文件末尾
layer {
  name: "Yolov3Loss1"
  type: "Yolov3"
  bottom: "layer82-conv" # 这里连接到对应mask的YOLO网络层
  bottom: "label"        # 两个bottom的前后顺序不能调换,这是由该网络层实现决定的:网络层会从bottom[0]中获取图像数据;从bottom[1]中获取标签信息
  top: "det_loss1"
  loss_weight: 1
  yolov3_param {
    side: 13             # 这里设置网络层输出特征图大小
    num_class: 20        # 这里设置网络层输出分类数
    num: 3               # 这里设置每网格预测的bbox数量,亦即该网格分配的anchors数量
    object_scale: 5.0
    noobject_scale: 1.0
    class_scale: 1.0
    coord_scale: 1.0
    thresh: 0.6
	anchors_scale : 32   # 这里设置anchor box缩放倍数,参考网络结构进行计算,该层YOLO输出为输入的32倍下采样
	
    biases: 10
    biases: 13
    biases: 16
    biases: 30
    biases: 33
    biases: 23
    biases: 30
    biases: 61
    biases: 62
    biases: 45
	biases: 59
    biases: 119
    biases: 116
	biases: 90
    biases: 156
    biases: 198
    biases: 373
    biases: 326           # 这里设置总共使用到的anchor boxes
	
	mask:6
	mask:7
	mask:8                # 这里的掩码用于分配anchor boxes
  }
}


layer {
  name: "Yolov3Loss2"
  type: "Yolov3"
  bottom: "layer94-conv"
  bottom: "label"
  top: "det_loss2"
  loss_weight: 1
  yolov3_param {
    side: 26
    num_class: 20
    num: 3  
    object_scale: 5.0
    noobject_scale: 1.0
    class_scale: 1.0
    coord_scale: 1.0
    thresh: 0.6
	anchors_scale : 16
	
    biases: 10
    biases: 13
    biases: 16
    biases: 30
    biases: 33
    biases: 23
    biases: 30
    biases: 61
    biases: 62
    biases: 45
	biases: 59
    biases: 119
    biases: 116
	biases: 90
    biases: 156
    biases: 198
    biases: 373
    biases: 326
	
	mask:3
	mask:4
	mask:5
  }
}


layer {
  name: "Yolov3Loss3"
  type: "Yolov3"
  bottom: "layer106-conv"
  bottom: "label"
  top: "det_loss3"
  loss_weight: 1
  yolov3_param {
    side: 52
    num_class: 20
    num: 3  
    object_scale: 5.0
    noobject_scale: 1.0
    class_scale: 1.0
    coord_scale: 1.0
    thresh: 0.6
	anchors_scale : 8
	
    biases: 10
    biases: 13
    biases: 16
    biases: 30
    biases: 33
    biases: 23
    biases: 30
    biases: 61
    biases: 62
    biases: 45
	biases: 59
    biases: 119
    biases: 116
	biases: 90
    biases: 156
    biases: 198
    biases: 373
    biases: 326
	
	mask:0
	mask:1
	mask:2
  }
}

修改后的test文件网络输入和输出层配置如下:

# 以下内容为网络层输入,使用前文生成的VOC-LMDB数据集
name: "Caffe-YOLOv3"
layer {
  name: "data"
  type: "AnnotatedData"
  top: "data"
  top: "label"
  include {
    phase: TEST          # 该字段表示此配置用于验证过程
  }
  
  transform_param {
    scale: 0.007843
    mean_value: 127.5
    mean_value: 127.5
    mean_value: 127.5
    resize_param {
      prob: 1.0          # 训练仅适配单一分辨率,因此prob应为1
      resize_mode: WARP
      height: 416
      width: 416
      interp_mode: LINEAR
    }
  }
  data_param {
    source: "PATH_TO_FILE/VOCdevkit/VOCdevkit_test_lmdb"
    batch_size: 1        # 一般进行单张验证,因此这里的batch_size为1
    backend: LMDB
  }
  annotated_data_param {
    batch_sampler {
    }
    label_map_file: "PATH_TO_FILE/VOCdevkit/labelmap_voc.prototxt"
  }
}

# 这里省略掉原Prototxt文件的内容,其中YOLO层输出数量记得要适配数据集
...

# 以下为验证层,可以直接复制到文件末尾
layer {
  name: "detection_out"
  type: "Yolov3DetectionOutput"
  bottom: "layer82-conv"
  bottom: "layer94-conv"
  bottom: "layer106-conv" # 连接到全部的YOLO输出层
  top: "detection_out"

  yolov3_detection_output_param {
    confidence_threshold: 0.01
	nms_threshold: 0.45
    num_classes: 20       # 这里根据数据集分类数做修改
	
	
    biases: 10
    biases: 13
    biases: 16
    biases: 30
    biases: 33
    biases: 23
    biases: 30
    biases: 61
    biases: 62
    biases: 45
	biases: 59
    biases: 119
    biases: 116
	biases: 90
    biases: 156
    biases: 198
    biases: 373
    biases: 326           # 这里设置总共使用到的anchor boxes
	
    mask:6
    mask:7
    mask:8
    mask:3
    mask:4
    mask:5
    mask:0
    mask:1
    mask:2                # 这里的掩码用于分配anchor boxes
    anchors_scale:32
    anchors_scale:16
    anchors_scale:8       # 这里设置anchor box缩放倍数,参考网络结构进行计算
    mask_group_num:3      # 这里设置的数量即每个网格分配的anchors数量
  }
}

layer {
  name: "detection_eval"
  type: "DetectionEvaluate"
  bottom: "detection_out"
  bottom: "label"
  top: "detection_eval"

  detection_evaluate_param {
    num_classes: 21       # 这里根据数据集分类数做修改
    background_label_id: 0
    overlap_threshold: 0.5
    evaluate_difficult_gt: false
  }
}

最后,修改训练所需的solver文件。由于本节的尝试仅仅是为了使网络可以正常训练,因此可直接拷贝MobileNet-YOLO使用的solver文件重命名为 yolov3_solver_voc.prototxt ,仅修改其中的配置文件路径部分。

使用以下命令开始网络的训练:

PATH_TO_CAFFE\build\tools\Release\caffe.exe train --solver=models\yolov3\yolov3_solver_voc.prototxt 

网络结构本身需要约1.4GB内存;batch_size设置为1时,训练需要约3.2GB内存;batch_size设置为2时,训练需要约4.2GB内存。

本文资源共享

百度网盘链接: 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@凌然 ————

你可能感兴趣的:(深度学习,YOLO,海思,Caffe,Darknet)