一直都没有写公开心得的习惯,因为一路上都在不断犯错,错误的理解,错误的观点,错误的代码,自己并非科班出身(学了7年的规划学科),常常担心会误导了别人。最近开始找工作,发现保持输出,学会学习表达和交流也是一项重要的能力。我要做一些大胆的事!(这里指发一篇可能会有很多错的心得)!由于学识尚浅,假如各位有缘人能看到这篇文章,对于写的不对的地方请不吝赐教,谢谢!
看到飞桨的课程其实是在一些公众号上(这年头谁没几个贩卖焦虑的公众号)。而被飞桨的课程吸引到则是因为:1、免费课程 2、免费的GPU(Tesla V100) 3、百度的名声 4、百度的结业证书 5、丰厚的奖品。但是核心因素还是因为GPU,自己的老年960实在没办法做正经训练,然而由于AIStudio中只能选择paddlepaddle的框架,所以希望通过课程能够快速上手飞桨。明明很功利地来的,使用之后却被圈粉了。感兴趣的可以直接搜索paddlepaddle的公众号,或者从Aistudio的官网上关注最新的或者往期课程https://aistudio.baidu.com/aistudio/index。
下面进入正题,这门课程主要是对深度学习进行目标检测的pipline进行讲解,相比自己以前上过的其他课程我觉得更加用心也很细致,所有的代码可以直接在AIstudio上运行,也可以下载至本地。同时课程包含notebook以及对应的视频讲解,如果时间比较紧张也可以只看notebook,遇到瓶颈再看视频。但是每个人需求不同,还是要自己体验了才知道好坏。课程的最后提供了一周的时间开展CV练习赛:AI识虫。作为CV领域的经典任务:目标检测,与不久前Kaggle的小麦检测比赛较为相似,并且很好地同课程的内容相契合,课程中覆盖的大部分内容能够在比赛代码中得到体现。所以在此对比赛进行回顾,也是借此对学习内容进行巩固。
比赛使用林业病虫数据集,该数据集提供了2183张图片,其中训练集1693张,验证集245,测试集245张,包含7中昆虫,分别是Boerner、Leconte、Linnaeus、acuminatus、armandi、coleoptera和linnaeus。直接在AIStudio的公开数据集中搜索“昆虫数据集”或访问以下链接:https://aistudio.baidu.com/aistudio/datasetdetail/19638 即可获得数据集。
对于训练集和验证集,每张图片还对应一个格式为xml的标注文件,主要内容包含图片尺寸,Ground Truth Box的位置、大小及其对应的昆虫类别。
课程的课节13已给出YOLO-v3的baseline的notebook,且大部分参数都已经根据昆虫数据集进行设置,建议将代码根据功能整理至.py文件中以方便对各个模块进行查找和修改。下面先对baseline的代码思路流程进行总结,并在下一节讨论提升策略。
对于每个XML格式的文件,使用xml库进行读入得到dict类型的变量作为一个record,每个record对应一张图片,含有图片路径(非xml中的内容)、目标框、目标昆虫类别及其他图片信息。昆虫类别按照字典{ ‘Boerner’: 0, ‘Leconte’: 1, ‘Linnaeus’: 2, ‘acuminatus’: 3, ‘armandi’: 4, ‘coleoptera’: 5, ‘linnaeus’: 6 }形成对应关系。可以发现即使不读取图片,已经能够获取大部分的数据信息。对数据集中各个昆虫的数量进行统计,得到昆虫占比分布图。可以看出在所提供的数据集中,linnaeus这一类昆虫仅仅存在于训练集,而不存在于验证集中,则会影响验证结果的可信度。有必要对训练集与验证集进行重新划分。
根据record中的图像路径读取得到图像数据,注意由OpenCV读入的图像通常需要从BGR进一步转化为RGB。
为了扩大训练数据集,增强模型的泛化能力,baseline中已经提供了较为丰富的数据增广手段:
将像素值归一化(除以255),再标准化(减去均值再除以方差),使得像素值均值为0,对于使用梯度下降进行训练的模型有利于模型进行收敛。
将维度从[H,W,C]调整为[C,H,W],飞桨训练模型数据的标准维度为[N,C,H,W],其中N为Batch size, C为通道数(对于RGB图像即为3),H与W为图像尺寸。
利用yeild构建数据生成器。
训练及验证数据生成器两者一致,但与测试数据生成器有所不同。主要区别为,训练数据生成器为多线程(以避免数据预处理成为训练瓶颈)、含标签、需要进行数据增广,而测试数据生成器为单线程、不含标签、不需要进行数据增广。需要注意的是,测试数据需要进行与训练数据相同的数据变换(这里指归一化,标准化与维度变化,且变换所采用的均值及方差也要一致)。
这一节简要介绍YOLO-v3的基本思想。整体上可以看作:(1)在图片范围内生成大量候选框,根据真实框对候选框是否包含物体进行标注,对于标注为包含物体的候选框,进一步标注预测位置及类别。(2)由卷积神经网络提取得到特征图,利用特征图对候选区域位置及类别进行预测,根据预测值与标签值建立损失函数。下面进一步说明。
首先需要锚框anchor,锚框的生成步骤为:将图像分成长宽为 32 * 32 的小方格,以小方格为中心生成尺寸不同的n个锚框(baseline中n=9)。锚框的大小为超参数。
将全部候选框与真实框进行对比,选取对应的IOU最大的标注为1,表示目标存在于该候选框中,对于其他候选框,如果IOU本身也很大(大于一个给定的阈值),但不是最大的那个,则标注为-1,不参与损失函数计算(这一步在计算得到预测框后进行)。因为这一类候选框很大程度上也与真实框较为相近,只是不是最好的那个,避免将其标注为负样本而影响模型的训练。
最终的候选区域为预测框,标注的预测框由anchor进行中心调整与长宽调整得到,预测框的中心始终存在于与anchor相同的小方格内。
YOLOv3的backbone为Darknet53,出于应用的目的,这里将不多介绍网络的架构,具体代码请查看课程的notebook。
得到特征图后,将由特征图计算预测框的位置和类别(注意这里是预测的结果,而a步骤中是标注的结果),同样预测需要包含三个方面:(1)包含昆虫的概率(2)昆虫区域的位置和形状(3)昆虫属于每一个类别的概率。
以C0层次为例,经过卷积核池化,层次的特征图的步幅为32,特征图大小与一开始生成锚框用的小方块的数目一致,因此只要使得特征图的channels数与我们所需要预测对象的数量一致即可,这一点可以通过对特征图进行多次卷积实现,即将特征图与预测值建立联系。
损失函数同样存在三个方面:(1)是否包含物体的损失函数值(用sigmoid_cross_entropy_with_logits计算)(2)物体位置的损失函数(这里将四个位置变量的损失求和,其中xy由sigmoid_cross_entropy_with_logits计算,hw由abs计算)(3)物体类别的损失函数(用sigmoid_cross_entropy_with_logits计算)
Darknet53可以得到C0、C1、C2三个尺度的特征图,不同尺度的特征图在捕捉不同尺度目标的能力上存在差异,因此采用多尺度检测以适应不同大小的昆虫。每个尺度的特征图分别对应每个小方框区域生成的3个anchor和预测框。总的损失函数等于三个层级的损失函数相加。完成损失函数后,就可以开启端到端的训练了。
在得到预测结果之后,不难想象会有多个预测框对应同一个真实框,那么仅需要保留得分最高的预测框,而将其他冗余框丢弃。判断两个预测框对应同一个物体的规则为:假如两个预测框类别一致,且重合度大于一定阈值,则可认为其预测的是同一个物体。对于预测同一个物体的预测框,保留该类别得分最高的预测框,假如剩下的预测框与其IoU大于阈值,则说明重合度过高,需要丢弃。
竞赛说明中已给出部分调优策略:
1.使用其他模型如faster rcnn等
2.使用其他数据增广方法如旋转和裁剪
3.修改anchor参数的设置
4.调整优化器、学习率策略、正则化系数等
结合飞桨在paddleDetection中的YOLOv3增强模型的提升策略(https://paddledetection.readthedocs.io/featured_model/YOLOv3_ENHANCEMENT.html):
1.将YOLOv3骨架网络更换为ResNet50-vd
2.引入Deformable Convolution v2 (可变形卷积)替换原始卷积操作
3.在FPN部分中增加DropBlock模块,提升模型泛化能力
4.增加IoU Loss分支,提高BBox定位精度,缩小一阶段和两阶段检测网络的差距
5.增加IoU Aware分支,预测输出BBox和真实BBox的IoU,修正用于NMS的评分
6.使用Object365数据集得到预训练模型(提供了相应的预训练模型的下载链接)
考虑到本身竞赛时间不长,且平日里学业压力也比较大。实际操作中我仅仅采用了以下容易实现的模型提升策略。
模型训练最大epoch设置为200(在此设置较大的epoch也是因为没有时间尝试更多的提升策略,希望通过长时间炼丹来产生奇迹),训练过程中保存最优模型参数及优化器参数至文件中。训练过程显示模型精度不断波动,但是整体存在上升趋势,在中后期才提升到90左右的水平 (以后有经验应该写个log记录训练过程)。