华为ModelArts-Lab拓展试验记录(二)
华为ModelArts-Lab拓展试验记录(二)
在2019年7月至2019年12月期间,参加了华为云ModelArt训练营活动,在ModelArt平台上,做了一些AI实验,现在整理一下资料,把我主要做的几个拓展实验内容做了记录,方便以后查阅,且分享给那些对使用华为云ModelArt开发有兴趣的朋友。第二个拓展实验就是训练营第五期内容的拓展,关于物体检测的Faster R-CNN算法的使用mAP评价指标。
1、概述
本次拓展实验内容为对Faster R-CNN算法模型使用mAP评价指标进行衡量,在第五期的notebook案例中,并没有对物体检测模型的精度进行评估,物体检测模型的精度使用mAP评价指标进行衡量。要求在notebook案例中,开发代码,计算出模型的mAP。
和以前一样,从最基本概念入手,由浅入深,介绍本次实验的过程及其内容,并且对实验代码做解析。
1.1、Faster RCNN简介
机器视觉领域的核心问题之一就是目标检测(Object Detection),它的任务是找出图像当中所有感兴趣的目标(物体),确定其位置和大小。Faster RCNN是由Ross Girshick由何凯明等人在2016年将其用于目标检测任务中,能够完成高效的与传统的RCNN相比,利用RPN(Region Proposal Networks)完成候选框的选择。其发展历程是:R-CNN——Fast R-CNN——Faster R-CNN——Mask R-CNN,都是将神经网络应用于目标检测的典型代表,Faster R-CNN具有性能较好,速度较快的优点。
Faster R-CNN是在R-CNN,Fast R-CNN基础之上发展而来,原理图如下:
算法步骤
1.Conv layers:首先使用一组基础conv+relu+pooling层提取image的feture map。接层。
2.Region Proposal Networks:RPN网络用于生成region proposcals.该层通过softmax判断anchors属于foreground或者background,再利用box regression修正anchors获得精确的propocals
3.Roi Pooling:该层收集输入的feature map 和 proposcal,综合这些信息提取proposal feature map,送入后续的全连接层判定目标类别。
4.Classification。利用proposal feature map计算proposcal类别,同时再次bounding box regression获得检验框的最终精确地位置。
具体细节这里就不阐述了,以上几个步骤,在这次实验中都有涉及。
1.2、mAP评价指标
Mean Average Precision(MAP):平均精度均值。P(Precision)精度,正确率。定义为: precision=返回结果中相关文档的数目/返回结果的数目。 Rec(Recall)召回率:定义为:Recall=返回结果中相关文档的数目/所有相关文档的数目。 数学公式理解:
1)True Positive(真正,TP):将正类预测为正类数;2)True Negative(真负,TN):将负类预测为负类数;3)False Positive(假正,FP):将负类预测为正类数误报 (Type I error);4)False Negative(假负,FN):将正类预测为负类数→漏报 (Type II error)。
精确率(precision):P=TP/(TP+FP)(分类后的结果中正类的占比),召回率(recall):recall=TP/(TP+FN)(所有正例被分对的比例。
在图像中,主要计算两个指标:precision和recall。precision,recall都是选多少个样本k的函数,如果我总共有1000个样本,那么我就可以像这样计算1000对P-R,并且把他们画出来,这就是PR曲线:这里有一个趋势,recall越高,precision越低。 平均精度AP(average precision):就是PR曲线下的面积,这里average,等于是对recall取平均。而mean average precision的mean,是对所有类别取平均(每一个类当做一次二分类任务)。现在的图像分类论文基本都是用mAP作为标准。AP是把准确率在recall值为Recall = {0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1}时(总共11个rank水平上),求平均值。mAP是均精度均值:只是把每个类别的AP都算了一遍,再取平均值。
AP是针对单个类别的,mAP是针对所有类别的。
2、mAP检测实验
根据本期实验内容,结合mAP方式,在modelArt平台上,先制定检测流程,在实验代码中添加mAP检测代码,然后对实验数据进行mAP检测。
2.1、定义检测数据集
首先,需要获取测试数据,依然从VOC2007数据里面获取,定义一个imdb(检测数据),作为检测mAP用。也是利用原来的实验部分代码的写法构成。定义了加载函数 def combined_roidb(imdb_names),里面处理比较常规,选用数据集,对图像进行了反转处理。最后返回两个数据:imdb 和 roidb。
这里要用到一个python类:pascal_voc,它实际上是集成了imdb类的一个类,实现了各种功能,有兴趣的话,可以看它的源代码。首先要导入一个它的功能函数:
from datasets.factory import get_imdb
为了计算mAP,需要修改数据集参数:
imdb_name = "voc_2007_test"
在训练之前,我们先要定义好imdb数据,代码如下:
#定义加载数据集函数 def combined_roidb(imdb_names): def get_roidb(imdb_name): # 加载数据集 imdb = get_imdb(imdb_name) #记录了数据集的路径之类的信息,函数通过给数据集的名字返回了该数据集对应的类的对象。 print('Loaded dataset `{:s}` for training'.format(imdb.name)) # 使用ground truth作为数据集策略 #对每张影像名调用_load_pascal_annotation,即可以知道该函数读取的是影像对应存储目标的xml文件。最后返回了“boxes”,“gt_classes”等构成的字典。 imdb.set_proposal_method(cfg.TRAIN.PROPOSAL_METHOD) print('Set proposal method: {:s}'.format(cfg.TRAIN.PROPOSAL_METHOD)) #增加翻转,对每张影像在上面进行了求最大处理,并记录影像路径还有影像高宽等信息在roidb中并返回,返回的roidb是一个长度与影像数一致的列表 roidb = get_training_roidb(imdb) return roidb #对于每个数据集返回了带有该数据集信息的imdb和包含每张影像感兴趣区域信息的roidb(包括每张影像点目标和影像宽高等信息) roidbs = [get_roidb(s) for s in imdb_names.split('+')] roidb = roidbs[0] #roidb包括数据集所有影像的影像路径,所有影像对应的目标,以及宽高等信息。 if len(roidbs) > 1: for r in roidbs[1:]: roidb.extend(r) tmp = get_imdb(imdb_names.split('+')[1]) imdb = datasets.imdb.imdb(imdb_names, tmp.classes) else: imdb = get_imdb(imdb_names) return imdb, roidb
在这里,这个部分主要还是提取VOC2007 的测试数据,作为检测mAP用。也是利用原来的实验部分代码的写法构成。
定义了加载函数 def combined_roidb(imdb_names),里面处理比较常规,选用数据集,对图像进行了反转处理。最后返回两个数据:imdb 和 roidb。
imdb的定义方式:imdb = get_imdb(imdb_name),它是一个基于pascal_voc.py 的类的实例对象,imdb这里的数据集获取的同时,还做了数据的初始化,get_imdb(imdb_name)将会返回的就是pascal_voc(split, year)这样一个对象。它们关系:get_imdb->factory->pascal_voc->(继承)imdb。
最后,不要忘记imdb, roidb = combined_roidb(imdb_name),这行代码是加载测试集数据。
2.2、定义神经网络
在测试计算时,还是需要建立神经网络,用来检测数据。这里还是根据案例情况,这里还是选择Vgg16网络,并且加载我们生成的训练模型,并且配置正确的参数。
from nets.vgg16 import vgg16 net = vgg16()
这里需要定义一下anchor的参数,主要是定义了anchor的尺寸,这里设置anchor_scales = [8, 16, 32]。对base anchor的宽高分别乘以尺寸,从而得到各个不同尺寸的anchor。
'ANCHOR_SCALES': [8, 16, 32],
接着做以下的工作:
1)net.create_architecture,构建faster rcnn进行计算图的操作。这里要注意,modelart环境中的参数和其它场景略有差异,是不需要传sess这个参数的(这个坑了我好久)。这里的参数中,参数ANCHOR_SCALES必须,参数ANCHOR_RATIOS可以省略,其它都按常规传。
net.create_architecture(imdb.num_classes,tag='default',anchor_scales=cfg.ANCHOR_SCALES)
2)加载权重文件,net.load_state_dict方法,主要是加载的文件位于"./models/vgg16-voc0712/vgg16_faster_rcnn_iter_110000.pth",这个就是训练模型文件。
net.load_state_dict(torch.load(saved_model, map_location=lambda storage, loc: storage)) net.eval()
3)选择计算设施:net.to(net._device)。
2.3、图像检测和提取
这个是mAP计算的核心功能,主要是形成可以被计算mAP的图像文件。以下分三个步骤:
步骤一、定义目标图像框容器
根据每一类文件,每一张图片,都定义个容器,就是定义一个数据集存储单元。代码为:
all_boxes = [[[] for _ in range(num_images)] for _ in range(imdb.num_classes)]
这里的只有存储单元,里面都空值。
步骤二、分类图像数据组织和填充,这里首先根据图像分类,按照类别对检测数据进行处理。前期处理:
from model.test import im_detect im = cv2.imread(imdb.image_path_at(i)) scores, boxes = im_detect( net, im)
作用是返回这张图片中的多个目标和分数,存放在boxes和scores中。
主要处理:先获取上述过程中的为每一个图像分配的存储单元的尺寸数据,和图像数据集的数据,检测后,将数据进行填充。这里代码主要是:
inds = np.where(scores[:, j] > thresh)[0] cls_scores = scores[inds, j] cls_boxes = boxes[inds, j*4:(j+1)*4] cls_dets = np.hstack((cls_boxes, cls_scores[:, np.newaxis])) \ .astype(np.float32, copy=False) keep = nms(torch.from_numpy(cls_boxes), torch.from_numpy(cls_scores), NMS_THRESH) cls_dets = cls_dets[keep, :] all_boxes [j][i] = cls_dets
以上步骤完成后,all_boxes 里面已经有了数据。需要注意,这里的计算时间略长,大概要几分钟时间,需要等待。
步骤三、我们要把all_boxes的数据写入文件,这里调用imdb的函数:
imdb._write_voc_results_file(all_boxes)
write_voc_results_file 函数可以在pascal_voc.py的源代码中找到,完成之后,我们打开目录,/data/VOCdevkit2007/result/VOC2007/Main/下面目录,可以看到,每一类文件都做了单独存储,这里要注意,因为是modelart环境,每次产生文件名,里面有个计算机ID代码,每次启动,不一定是一样的。
2.4、计算AP和mAP
这里有两种方式,便捷的方式就是直接调用imdb.evaluate_detections(all_boxes, output_dir),可以忽略一些细节,使用默认设置,直接给出mAP。
第二种方式,我这里的做法是自己写了个文件voc_eval.py,提供voc_eval的函数供调用,这样更加灵活,也能更加符合我进行实验的目的,可以获得更加丰富的数据。voc_eval函数:
def voc_eval(detpath, annopath, imagesetfile, classname, cachedir, ovthresh=0.5, use_07_metric=False, use_diff=False):
关键理如下:
1)读取文件数据,存入数据字典,这里的文件就是上述保存的文件位置,代码如下:
recs = {}#生成一个字典 for i, imagename in enumerate(imagenames): #对于每一张图像进行循环 recs[imagename] = parse_rec(annopath.format(imagename))#在字典里面放入每个图像缓存的标签路径 if i % 100 == 0:#在这里输出标签读取进度。 print('Reading annotation for {:d}/{:d}'.format(i + 1, len(imagenames)))#从这里可以看出来imagenames是什么,是一个测试集合的名字列表,这个Print输出进度。# save print('Saving cached annotations to {:s}'.format(cachefile))#读取的标签保存到一个文件里面 with open(cachefile, 'wb+') as f:#打开缓存文件 pickle.dump(recs, f)#dump是序列化保存,load是反序列化解析
2)从以下代码开始就是具体计算了:
nd = len(image_ids) #统计检测出来的目标数量 tp = np.zeros(nd)#tp = true positive 就是检测结果中检测对的-检测是A类,结果是A类 fp = np.zeros(nd)#fp = false positive 检测结果中检测错的-检测是A类,结果gt是B类。 if BB.shape[0] > 0:#。shape是numpy里面的函数,用于计算矩阵的行数shape[0]和列数shape[1]
最后,每一类数据都会返回三个值:
rec: 召回率
prec:精确度
ap:平均精度
最后得出mAP:0.7020。
- 点赞
- 收藏
- 关注作者
评论(0)