本文是我在 “人工智能中的深度学习” 任选课上所提交的期末小论文基础上修改而来,如果有部分地方语言生涩僵硬,敬请理解。如果文章存在缺陷与不足,也希望读者能不吝赐教,评论指正。
下面即为正文。
引言
即使苦于任选课的课时短暂与我自身能力的局限,我并未能充分掌握 TensorFlow 的编程。但我还是非常想尝试使用深度学习来进行一些应用与实践,于是,在目标检测领域极为出名,并且我很早就想尝试使用的 yolo3 就自然而然成为了对象。
一开始我尝试使用过 tiny-yolo3 和 yolo3,但是因为 python3.5.2 的 pip 在我的电脑上可能因为与其他版本 python 冲突的原因而无法正常使用(The test environment is Python-3.5.2 Keras-2.1.5 tensorflow-1.6.0),最终导致我抛弃了使用 tensorflow 来去进行 yolo3 的使用。最终转向了更为小众的 darknet 来避免不必要的麻烦。
此外,虽然 yolo3 相较于此前诸如 R-CNN 一类 Region-based 的目标检测方法最大的优势就是 yolo3 是一种 Region-free 方法,只需要对目标图片进行一次扫描就识别图片中的多个物体并将他们全部标识出来。但是受限于我的硬件条件,我最终还是选择了使用 yolo3 仅对单个物体进行识别分类以保证数据集训练出的权重能够保证高精度的识别。
关于yolo3
yolo3 的核心便是其名为 darknet-53 的全卷积神经网络,令人感到震惊的是,在其神经网络中,没有进行任何的池化层,其完全是由卷积层所构成的。它用卷积层来代替池化层实现了下采样的操作,据说这样可以有效阻止由于池化层导致的低层级特征的损失。
而关于其目标的具体实现,论文洋洋洒洒写了一大截。在我认知尚浅的理解看来,简而言之就是输入图片,运行神经网络,多次卷积+下采样提取特征,得到一些 bounding box 坐标、box 中包含物体的置信度和类别概率 class probabilities,之后进行非极大值抑制,筛选 Boxes,然后标识预测边界框,多次上采样输出最终标识结果(可能理解与实际有较大出入,因为论文的很大一部分所提到的专业名词令我感到有些难以理解,仅仅是依据我现有的知识和部分额外查阅的资料来阐述了我的认知)。
实际操作过程
所需环境与工具
我所使用的软硬件环境如下:
- Device: Microsoft Surface Book 2 15inch
- Operating System: Microsoft Windows 10 20H2
- CPU: Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz
- RAM: DDR4 16GB 1867MHz
- GPU: Intel(R) UHD Graphics 620 & NVIDIA GeForce GTX 1060
借助于 darknet 进行 yolo3 的训练,我使用的工具如下:
- Visual Studio 2017 Community
- CUDA 9.0.176 for Windows10
- cuDNN 9.0 for Windows10 x64 v7.6.0.64
- OpenCV 3.4.0
在准备完所需环境与工具后,下载 darknet 并进行 build 编译即可。
darknet的编译
我在配置编译的环境时主要参考了 Windows10下Yolo3的下载与编译 这篇文章来去进行。
但在编译过程中还需要注意以下两点:
- 截至6月29日,最新版本的darknet在编译时会报错:Compile error : identifier “cudaGraphExec_t” is undefined等等,darknet作者给出的原因是最新版本的darknet需要10.2以上版本的CUDA(作者原话:Now Darknet requires CUDA 10.2 or higher. Preferably CUDA 11.2.),而作者给出的解决方案是下载 旧版本darknet 进行编译,实测确实可以解决编译报错的问题
- 在开始正式编译前需要先对 …\darknet-master\build\darknet 目录下的 darknet.vcxproj 文件的内容进行修改,而在修改 darknet.vcxproj 文件内容时,除了 CUDA 对应版本需要进行修改外,还需要修改 CodeGeneration 标签内的内容,修改依据为显卡型号,在根目录下的 Makefile 内是有注明的:
我使用的显卡为 Nvidia Geforce GTX 1060 Max-Q,因此需要将
<CodeGeneration>compute_35,sm_35;compute_72,sm_72</CodeGeneration>
修改为<CodeGeneration>compute_61,sm_61</CodeGeneration>
在编译完成后,下载 darknet 的 README 里所列出的 yolo3对应的weight文件 并放置到 …\darknet-master\build\darknet\x64 文件夹下,运行对应的 darknet_yolo_v3.cmd 脚本,若能正确对文件夹内自带的图片进行目标识别,输出如下图片结果就说明 darknet 编译成功了。
数据集的准备
darknet/yolo3 的作者提供了官方的数据集标记工具 yolo_mark,标记的坐标数据会被存放在一个与图片同名的 txt 文件内。
而在训练数据集图片的准备与处理上,我以了动漫番剧 我的青春恋爱物语果然有问题。续 中的角色“雪之下雪乃”作为训练识别的对象,并且考虑到该动漫第一二季画风差异较大以及不同发型的雪乃会导致识别精度下降,我没有使用第一季中与非日常发型下的雪乃出现的画面作为训练的原始数据集来去使用。最终使用 Snipaste 截图72张,通过 Batch PNG to JPG 软件将截得的图片转化为 jpg 格式,然后使用 resizer 软件将图片转化为了 480×480 的大小并以数字序号的形式重命名了所有图片的名称,至此就基本完成了数据集图片的准备。
yolo_mark 的使用则其为简单,将准备好数据集的图片文件后将其放入 …\Yolo_mark-master\x64\Release\data\img 目录下后,需要将 …\Yolo_mark-master\x64\Release\data 目录下 obj.data 文件内的 classes 数修改为需要训练识别的物体的类数,这里我只需要训练识别雪乃一个人,所以我在这里设置 classes = 1 即可。同时需要在 obj.name 中添加识别对象的名称,在这里我删除了其原有的 name 条目,只保留了 yukinoshita_yukino(雪之下雪乃)一条 name 。然后运行 yolo_mark.cmd 即可开始对图像进行标记,左键拖拽后松开形成标记框,方向键切换图片。完成标记后,…\Yolo_mark-master\x64\Release\data 目录下会自动生产 train.txt 文件表示数据集包含有哪些图片文件。
训练前的其他准备
在开始数据集的训练前,还需要准备以下两个文件:
- cfg 配置文件:cfg 文件放置在 …\darknet-master\build\darknet\x64 目录下,直接复制 yolov3.cfg 文件并粘贴重命名为了 yolo_obj.cfg 文件作为接下来训练的配置文件,并对其中共三处 classes 和 filters 的数值进行修改。我只训练检测一类物体,所以有 classes=1,filters=(classes+5)×3=(1+5)×3=18。
- 预训练模型:下载 darknet53.conv.74 后放在目录 …\darknet-master\build\darknet\x64 下即可。
此外需要将准备的数据集放置到对应的文件夹内:
- 将 …\Yolo_mark-master\x64\Release\data 目录下的 obj.names 文件、obj.data 文件和 train.txt 文件复制到目录 …\darknet-master\build\darknet\x64\data 下;
- 将 …\Yolo_mark-master\x64\Release\data\img 目录下已经完成标记后的所有图片与产生的对应的 txt 文件复制到 …\darknet-master\build\darknet\x64\data\img 下。
针对自定义数据集进行训练
先对 …\Makefile 文件内的内容进行修改,将 GPU=0 CUDNN=0 CUDNN_HALF=0 修改为了 GPU=1 CUDNN=1 CUDNN_HALF=1,然后在 …\darknet-master\build\darknet\x64 目录下打开 cmd 窗口,输入
darknet.exe detector train data/obj.data yolo_obj.cfg darknet53.conv.74
即可开始训练。
但我在训练时遇到了如下报错
CUDA Error: out of memory
显然,GTX 1060 仅有 6GiB 的显存是无法支撑在 batch=64 subdivisions=16 配置下进行训练。在多次修改尝试后,最终我采用了 batch=4 subdivisions=2 的参数来去进行训练(此处其实还可以选择关闭多尺度训练来解决爆显存的问题,但我没有采用此方法,而是选择了修改 batch/小批量来解决这一问题),虽然这样训练只使用了 3GiB 左右的显存,没能发挥显卡的全部性能,但我觉得问题不大,能跑就行(其实主要还是因为这台笔记本的散热不太好,满载情况下显卡温度极高,害怕出问题所以折中了 batch=4 subdivisions=2,实测稍高一点的 batch 也是可以正常运行的)。
最终经过一晚上的训练,模型迭代了10000次,得到了 yolo_obj_10000.weights 权重文件(在 …\darknet-master\build\darknet\x64\backup 目录中查看)。
训练结果
将 yolo_obj_10000.weights 和需要识别的图片 input.jpg 放到 …\darknet-master\build\darknet\x64 目录下后,在该目录下打开 cmd 窗口输入
darknet.exe detector test data/obj.data yolo_obj.cfg yolo_obj_10000.weights -i 0 -thresh 0.25 input.jpg -ext_output
然后会生成 predications.jpg(生成在 …\darknet-master\build\darknet\x64 目录下)图片结果,同时也会如下图一样展示出来
上面第一张图片是数据集内的一张图片,第二张则是训练集外的一张图片,可见训练的结果是比较精确的,训练出来的权重能够使得 darknet 准确的辨识出我老婆雪乃。
而下面这张图内的雪乃(右一)发型产生了较大变化,可能主要是因为我在训练数据集内没有放置日常发型(如上面这些图片所示的发型)以外的其他发型的图片。
总结
可能仅仅只是这样一次对 yolo3 实验性的学习与使用远比不上自己去写一个神经网络复现某篇论文的算法或是优化某个算法那么厉害,但是我觉得我还是从中学习到了不少的。第一次接触到了本地编译 github 上 clone 下来的项目,第一次接触到了数据集的标识,第一次在自己的电脑上跑训练模型……不过我也不得不承认这样一种浮于表面,脱离代码实际的应用对于我能力的提升是有限的,我也希望在未来我能够深入到代码背后的理论来去学习那些对于深度学习而言更为基础的知识与理论。可能,现在不过才是一个开始。
草,我也是人工智能中的深度学习,玩了一个星期pytorch,弄的一个老婆头像生成器(整一个XP训练器,训练集全是捞来白毛)