2021年的暑假辅导学生参加首届长三角人工智能算法擂台赛。
初赛为基于AI开发平台ModelArts,使用Python语言编写代码,完成包括党徽、国徽、国旗、军礼、军徽、少先队徽、红领巾、团徽的党史图像检测挑战。
我们自行在网上找了1500张相关图片,并在ModelArts中开始标注,完成后用ModelArts内置的算法开始训练并打榜,有YOLOv3,v5以及Fast-RCNN。由于经费及每日打榜次数的限制,加上初赛时我们经验的不足,最终的成绩是0.52(满分为1,初高中分别排名,非官方参赛队不计入名次),幸好在我们组入围了决赛。
**PS1:**本以为训练集是不提供的,所以自行在网上找图片。后来官方提供了训练集,但此时不忍心丢弃已标注的图片,就在官方的训练集中挑选了300张,加之自行寻找的,共1800张进行训练的。
**PS2:**初赛的训练集中有一些图片是否需要标注给我们带来了很大的困扰:如黑白的、红色的、镂空的,卡通的党徽团徽队徽等等。标了担心对成绩有反作用(测试集不公开的),不标有担心测试集中有。
事后想想若我们对训练集精心挑选+标注一下,也许初赛的成绩会更好一些。
主要考核点有交通信号灯识别、车道线检测、斑马线检测、限速标志识别、施工标志识别、障碍物检测等,其中交通信号灯、斑马线、限速标志检测算法需要基于AI开发平台ModelArts开发。
重点部分:
1、禁用内置YOLOv5
2、官方提供的数据集中有标志样例
3、官方明确表示,训练集和测试集中没有过分夸张的图片
因为决赛禁用内置YOLOv5,我们推测YOLOv5识别率相对会高一些,加之初赛我们组用了Fast-RCNN(费用较高,训练一次在100元左右),结果不甚理想,所以我们组决定尝试自行搭建本地YOLOv5的环境。
Windows中安装深度学习环境pytorch+paddle(Anaconda+Pycharm)
在CSDN上找到了一篇8月底新发的搭建YOLOv5保姆级教程,https://blog.csdn.net/didiaopao/article/details/119787139。
参照教程,在一台系统为Server2012的服务器和一台WIN10的办公用机上同时搭建环境。由于这两台机器都没有显卡,所以跳过了安装显卡驱动的环节。安装好anaconda后,新建了pytorch和paddle的环境(都是基于python3.8的)。
__PS1:__事后找了一台有显卡(P106)的机器,不知为什么在安装pytorch环境时,选择清华源是显示报错,报错信息如下,大概意思是清华源上没有cuda11.1下pytorch的环境,无奈只能选择国外源。(但安装无显卡,仅仅是CPU的pytorch环境是可以选择清华源)
__PS2:__总结不推荐在Server2012上跑YOLOv5,若要用服务器跑,建议使用Server2019以上的版本。
在pycharm中新建工程,分别选择pytorch和paddle环境(CPU),参照教程里的验证代码,输出结果如下(安装成功)。
接下去,按如下教程(https://blog.csdn.net/didiaopao/article/details/119954291)从github上下载YOLOv5源码,解压后并在pycharm中以工程打开(选择pytorch环境)。在执行requestments文件的第一句语句 pip install -r requirements.txt后,发生如下错误。
打开test.py文件后,发现pycharm有如图错误提示,很明显是没有成功安装这两个库。
将opencv_python-4.5.1.18-cp38-cp38-win_amd64.whl复制到Anaconda中的pytorch环境的库安装目录(c:\Users\administrator.conda\envs\pytorch\Lib\site-package),打开Anaconda的命令提示符,激活pytorch环境,进入上述库安装目录,手动输入命令“pip install opencv_python-4.5.1.18-cp38-cp38-win_amd64.whl”,安装完成即可。
执行过pip install -r requirements.txt后,其实已安装了pycocotools库,但YOLOv5依旧说不满足所需,pycharm给出的报错信息是vc++,其实不需要安装相应的vc++。
参照以下方法即可解决:https://blog.csdn.net/Gao_qie/article/details/118391417
其实就是将高版本的库文件下载并解压后,直接复制替换原先安装的低版本的库文件。
这样就解决了opencv_python库和pycootools库不满足需求的问题。
接一下就可以跑训练了,但YOLOv5的数据集标签是TXT格式(矩形中心的坐标xy与wh的归一化值),若在ModelArts中标注后的格式为VOC的XML格式(如图),需要人工将XML的标签转换为TXT的格式,详细转换方法见链接https://blog.csdn.net/didiaopao/article/details/120022845
还需将数据集划分为训练集和验证集来进行YOLOv5模型的训练,一张图片的JPG文件名和标注TXT文件名必须一致。数据最好放在最外一级目录中,数据集的目录格式如下图所示。
做好如上准备后,在正式跑YOLOv5之前还有一些准备工作。
第一、下载预训练权重文件
为了缩短网络的训练时间,并达到更好的精度,一般需加载预训练权重进行训练。而yolov5提供了几个预训练权重,可以对应不同的需求选择不同的版本的预训练权重。通过如下的图可以获得权重的名字和大小信息,可以预料的到,预训练权重越大,训练出来的精度就会相对来说越高,但是其检测的速度就会越慢。预训练权重可以通过这个网址 https://github.com/ultralytics/yolov5/releases 进行下载,如图,本文中以yolov5s.pt为预训练权重。
第二、修改数据配置文件
修改data目录下相应的yaml文件。找到data目录下的voc.yaml文件,将该文件复制一份,将复制的文件重命名,最好和项目相关,这样方便后面操作,这里我修改为csj52.yaml。
打开csj52.yaml文件修改其中的参数,首先将箭头1中的那一行代码注释掉,如果不注释则可能训练时候会报错;箭头2中需要将训练和测试的数据集的路径填上(绝对路径和相对路径都可以,绝对路径不可能出错);箭头3中需要检测的类别数,填写6,表明有6个类别;最后箭头4中填写需要识别的类别的名字(必须是英文,否则会乱码识别不出来)。
第三、修改模型配置文件
由于使用的是yolov5s.pt这个预训练权重,所以要使用models目录下的yolov5s.yaml文件中的相应参数(因为不同的预训练权重对应着不同的网络层数,所以用错预训练权重会报错)。同上修改data目录下的yaml文件一样,我们最好将yolov5s.yaml文件复制一份,然后将其重命名,我将其重命名为yolov5s_csj52.yaml。
打开yolov5s_csj52.yaml文件只需要修改如图中的nc后面的数字就好了,这里是识别六个类别。
其实下面两个参数depth_multiple和width_multiple就是模型的深度和层数的比例值,不同预训练模型的这两个值是不同的。
至此,相应的配置参数就修改好了。
然后找到主函数的入口,这里面有模型的主要参数。模型的主要参数解析如下所示。
if __name__ == '__main__':
"""
opt模型主要参数解析:
--weights:初始化的权重文件的路径地址
--cfg:模型yaml文件的路径地址
--data:数据yaml文件的路径地址
--hyp:超参数文件路径地址
--epochs:训练轮次
--batch-size:喂入批次文件的多少
--img-size:输入图片尺寸
--rect:是否采用矩形训练,默认False
--resume:接着打断训练上次的结果接着训练
--nosave:不保存模型,默认False
--notest:不进行test,默认False
--noautoanchor:不自动调整anchor,默认False
--evolve:是否进行超参数进化,默认False
--bucket:谷歌云盘bucket,一般不会用到
--cache-images:是否提前缓存图片到内存,以加快训练速度,默认False
--image-weights:使用加权图像选择进行训练
--device:训练的设备,cpu;0(表示一个gpu设备cuda:0);0,1,2,3(多个gpu设备)
--multi-scale:是否进行多尺度训练,默认False
--single-cls:数据集是否只有一个类别,默认False
--adam:是否使用adam优化器
--sync-bn:是否使用跨卡同步BN,在DDP模式使用
--local_rank:DDP参数,请勿修改
--workers:最大工作核心数
--project:训练模型的保存位置
--name:模型保存的目录名称
--exist-ok:模型目录是否存在,不存在就创建
"""
parser = argparse.ArgumentParser()
parser.add_argument('--weights', type=str, default='yolov5s.pt', help='initial weights path')
parser.add_argument('--cfg', type=str, default='', help='model.yaml path')
parser.add_argument('--data', type=str, default='data/coco128.yaml', help='data.yaml path')
parser.add_argument('--hyp', type=str, default='data/hyp.scratch.yaml', help='hyperparameters path')
parser.add_argument('--epochs', type=int, default=300)
parser.add_argument('--batch-size', type=int, default=16, help='total batch size for all GPUs')
parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='[train, test] image sizes')
parser.add_argument('--rect', action='store_true', help='rectangular training')
parser.add_argument('--resume', nargs='?', const=True, default=False, help='resume most recent training')
parser.add_argument('--nosave', action='store_true', help='only save final checkpoint')
parser.add_argument('--notest', action='store_true', help='only test final epoch')
parser.add_argument('--noautoanchor', action='store_true', help='disable autoanchor check')
parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters')
parser.add_argument('--bucket', type=str, default='', help='gsutil bucket')
parser.add_argument('--cache-images', action='store_true', help='cache images for faster training')
parser.add_argument('--image-weights', action='store_true', help='use weighted image selection for training')
parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%%')
parser.add_argument('--single-cls', action='store_true', help='train multi-class data as single-class')
parser.add_argument('--adam', action='store_true', help='use torch.optim.Adam() optimizer')
parser.add_argument('--sync-bn', action='store_true', help='use SyncBatchNorm, only available in DDP mode')
parser.add_argument('--local_rank', type=int, default=-1, help='DDP parameter, do not modify')
parser.add_argument('--workers', type=int, default=8, help='maximum number of dataloader workers')
parser.add_argument('--project', default='runs/train', help='save to project/name')
parser.add_argument('--entity', default=None, help='W&B entity')
parser.add_argument('--name', default='exp', help='save to project/name')
parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
parser.add_argument('--quad', action='store_true', help='quad dataloader')
parser.add_argument('--linear-lr', action='store_true', help='linear LR')
parser.add_argument('--label-smoothing', type=float, default=0.0, help='Label smoothing epsilon')
parser.add_argument('--upload_dataset', action='store_true', help='Upload dataset as W&B artifact table')
parser.add_argument('--bbox_interval', type=int, default=-1, help='Set bounding-box image logging interval for W&B')
parser.add_argument('--save_period', type=int, default=-1, help='Log model after every "save_period" epoch')
parser.add_argument('--artifact_alias', type=str, default="latest", help='version of dataset artifact to be used')
opt = parser.parse_args()
训练自己的模型时需要修改如下几个参数。首先将weights权重yolov5s.pt的路径填写到对应的参数里面,然后将修好的models模型的yolov5s_csj52.yaml文件路径填写到相应的参数里面,最后将data数据的csj52.yaml文件路径填写到相对于的参数里面。这几个参数是必须要修改的参数。
parser.add_argument('--weights', type=str, default='weights/yolov5s.pt', help='initial weights path')
parser.add_argument('--cfg', type=str, default='models/yolov5s_csj52.yaml', help='model.yaml path')
parser.add_argument('--data', type=str, default='data/csj52.yaml', help='data.yaml path')
还有几个参数需根据自己的需求来更改:
首先是模型的训练(迭代)轮次,这里默认是训练的300轮,但事实上150轮后,mAP值的变化是不大了,除非x6的预训练模型。
parser.add_argument('--epochs', type=int, default=100)
其次是输入图片的数量(与显存的大小有关)和工作的核心数,每台电脑的硬件配置不一样,所以需要根据电脑硬件性能来。
如出现如下报错信息,则是显存不够,需把batch-size和workers调小一些(默认都是4)。
parser.add_argument('--batch-size', type=int, default=4, help='total batch size for all GPUs')
parser.add_argument('--workers', type=int, default=4, help='maximum number of dataloader workers')
至此,就可以运行train.py函数训练自己的模型了。
等到数据训练好了以后,就会在主目录下产生一个run文件夹,在run/train/exp/weights目录下会产生两个权重文件,一个是最后一轮的权重文件,一个是最好的权重文件,就要利用这个最好的权重文件来做推理测试。除此以外还会产生一些验证文件的图片等一些文件。
打开主目录下的detect.py文件。
然后找到主函数的入口,这里面有模型的主要参数,解析如下所示。
if __name__ == '__main__':
"""
--weights:权重的路径地址
--source:测试数据,可以是图片/视频路径,也可以是'0'(电脑自带摄像头),也可以是rtsp等视频流
--output:网络预测之后的图片/视频的保存路径
--img-size:网络输入图片大小
--conf-thres:置信度阈值
--iou-thres:做nms的iou阈值
--device:是用GPU还是CPU做推理
--view-img:是否展示预测之后的图片/视频,默认False
--save-txt:是否将预测的框坐标以txt文件形式保存,默认False
--classes:设置只保留某一部分类别,形如0或者0 2 3
--agnostic-nms:进行nms是否也去除不同类别之间的框,默认False
--augment:推理的时候进行多尺度,翻转等操作(TTA)推理
--update:如果为True,则对所有模型进行strip_optimizer操作,去除pt文件中的优化器等信息,默认为False
--project:推理的结果保存在runs/detect目录下
--name:结果保存的文件夹名称
"""
parser = argparse.ArgumentParser()
parser.add_argument('--weights', nargs='+', type=str, default='yolov5s.pt', help='model.pt path(s)')
parser.add_argument('--source', type=str, default='data/images', help='source') # file/folder, 0 for webcam
parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)')
parser.add_argument('--conf-thres', type=float, default=0.25, help='object confidence threshold')
parser.add_argument('--iou-thres', type=float, default=0.45, help='IOU threshold for NMS')
parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
parser.add_argument('--view-img', action='store_true', help='display results')
parser.add_argument('--save-txt', action='store_true', help='save results to *.txt')
parser.add_argument('--save-conf', action='store_true', help='save confidences in --save-txt labels')
parser.add_argument('--nosave', action='store_true', help='do not save images/videos')
parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --class 0, or --class 0 2 3')
parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS')
parser.add_argument('--augment', action='store_true', help='augmented inference')
parser.add_argument('--update', action='store_true', help='update all models')
parser.add_argument('--project', default='runs/detect', help='save results to project/name')
parser.add_argument('--name', default='exp', help='save results to project/name')
parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
opt = parser.parse_args()
将刚刚训练好的最好的权重传入到推理函数中去。然后就可以对测试图像进行推理了。
parser.add_argument('--weights', nargs='+', type=str, default='runs/train/exp2/weights/best.pt', help='model.pt path(s)')
对图片进行测试推理,将如下参数修改成图片的路径,然后运行detect.py就可以进行测试了。
parser.add_argument('--source', type=str, default='VOCdevkit/yanzheng/', help='source') # file/folder, 0 for webcam
推理测试结束以后,在run下面会生成一个detect目录,推理结果会保存在exp2目录下。如图所示。
图片的推理结果如下所示。效果还是很不错的。
接下去需要将本地训练的YOLOv5模型上传至ModelArts中进行打榜。
现在需要解决的问题是:需要将哪些文件导入至ModelArts的模型中,导入时的文件目录结构是又怎样的,
我们查阅了华为ModelArts的官方说明文档,了解上传自己模型时需要的注意点。由于水平有限,实现弄得有点吃力。
后来我们想到了如下的方法:
1、先用ModelArts内置的YOLOv5算法训练一次,生成的模型文件在OBS桶中,我们将OBS桶中模型文件(一个目录)导出至本地,尝试寻找其中需要替换成我们本地训练的YOLOv5模型的文件。
2、通过对比后,发现有5个文件需要替换。
3、替换后,将模型文件(一个目录)从OBS桶中导入至ModelArts,再打榜,是不是就解决问题了?
尝试后,的确是这样,打榜成功了,如下图所示。
如上的方法,我们第一次的打榜成绩为:0.5958。事后用了YOLOv5的x+300轮的迭代,取得了我们打榜的最高分0.6771,排在高中组的第一名。
后来我们尝试了其他的一些算法,如内置的Fast-RCNN,结果不甚理想。
高手的确很多,都是深藏不漏,在临近截止时间前,有许多高分出现,绝大多数都是初中组的,真是惭愧啊。
最后我们的成绩依旧在0.6771,高中组第四,全体排名的第13名。
说明:SH-HI 表示高中组;SH-MID表示初中组;图片中的第9名sh-mid-019不是官方的参赛队,不计入排名。