主要目的是在没有GPU的情况下,上手ppyolo的训练过程,看看paddlepaddle是不是顺手。纯代码实验。PaddleDetection在下文中简称ppdet。
1 基本环境
1.1 软件组成和版本
Windows>= 7
python=3.8
paddle.__version__ '2.3.2'
ppdet.__version__ '2.4.0'
1.2 数据集
HelmetDetection(VOC)
1.3 网络结构
ppyolo_r50vd_dcn
1.4 预训练模型
ResNet50_vd_ssld_pretrained.pdparams
2 环境的搭建
2.1 PaddlePaddle的安装
PaddleDetection依赖apddlepaddle,对于paddlepaddle来说,CPU分为支持avx和不支持avx(比如:Intel Core 2 Q8300),需要手动执行安装。
不支持avx的安装命令
pip install https://paddle-wheel.bj.bcebos.com/2.3.0/windows/windows-cpu-mkl-noavx/paddlepaddle-2.3.0-cp38-cp38-win_amd64.whl
支持avx的安装命令
python -m pip install paddlepaddle -i https://mirror.baidu.com/pypi/simple
2.2 PaddleDetection的安装
PaddleDetection的安装相对简单,没有分支。
我这里是在 D:\lusong\ppdetection\ 目录下操作。
安装
命令行
git clone https://gitee.com/paddlepaddle/PaddleDetection cd PaddleDetection pip install -r requirements.txt python setup.py install
验证
命令行
python ppdet/modeling/tests/test_architectures.py
2.3 数据集
数据集是偶然看到的VOC格式,下载地址为:
http://aistudio.baidu.com/aistudio/projectdetail/1059610
我自己解压缩到 D:\lusong\other\det\HelmetDetection目录。
2.4 预训练模型
模型为RestNet50,下载地址为:
https://paddledet.bj.bcebos.com/models/pretrained/ResNet50_vd_ssld_pretrained.pdparams
到此,下载部分结束,可以断开网络继续操作。
3 训练前处理
3.1 数据集拆分
对数据集完成训练集,验证集,测试集的切分。下面是拆分代码:
import os.path as osp import random import xml.etree.ElementTree as ET # dataset_dir,数据集所在路径,文件夹层级如下: {dataset_dir}/ images/ xxx.jpg annotations/ xxx.xml # val_percent,验证百分比 # test_percent,测试百分比 # save_dir,数据集保存到路径(格式:VOC),注意这里和标准的VOC格式是不一样的 {save_dir}/ train_list.txt val_list.txt test_list.txt labels.txt # 注意,由上可见:本函数默认dataset_dir和save_dir是同一目录 def split_voc_dataset(dataset_dir, val_percent, test_percent, save_dir): # 注意图片目录和标注目录名已全部修改??? # 检查images目录和annotations目录是否存在 if not osp.exists(osp.join(dataset_dir, "images")): logging.error("\'images\' is not found in {}!".format(dataset_dir)) if not osp.exists(osp.join(dataset_dir, "annotations")): logging.error("\'annotations\' is not found in {}!".format( dataset_dir)) # # 获取images目录下所有文件名(images目录及下一层目录) all_image_files = list_files(osp.join(dataset_dir, "images")) # # 元素为 [image_file, anno_name] image_anno_list = list() # 元素为 anno_name文件中的所有object.name文本(去重) label_list = list() for image_file in all_image_files: if not is_pic(image_file): continue # 在annotations目录下,寻找image_file对应的anno_name文件,找到后加入image_anno_list anno_name = replace_ext(image_file, "xml") if osp.exists(osp.join(dataset_dir, "annotations", anno_name)): image_anno_list.append([image_file, anno_name]) # 将anno_name文件中的object子节点的name节点找出来,加入label_list(去重) try: tree = ET.parse( osp.join(dataset_dir, "annotations", anno_name)) except: raise Exception("文件{}不是一个良构的xml文件,请检查标注文件".format( osp.join(dataset_dir, "annotations", anno_name))) objs = tree.findall("object") for i, obj in enumerate(objs): cname = obj.find('name').text if not cname in label_list: label_list.append(cname) else: logging.error("The annotation file {} doesn't exist!".format( anno_name)) # # 打乱image_anno_list后,拆分成train_image_anno_list,val_image_anno_list,test_image_anno_list random.shuffle(image_anno_list) image_num = len(image_anno_list) val_num = int(image_num * val_percent) test_num = int(image_num * test_percent) train_num = image_num - val_num - test_num # train_image_anno_list = image_anno_list[:train_num] val_image_anno_list = image_anno_list[train_num:train_num + val_num] test_image_anno_list = image_anno_list[train_num + val_num:] # # 创建{save_dir}/train_list.txt 文件 with open( osp.join(save_dir, 'train_list.txt'), mode='w', encoding='utf-8') as f: for x in train_image_anno_list: file = osp.join("images", x[0]) label = osp.join("annotations", x[1]) f.write('{} {}\n'.format(file, label)) # # 创建{save_dir}/val_list.txt 文件 with open( osp.join(save_dir, 'val_list.txt'), mode='w', encoding='utf-8') as f: for x in val_image_anno_list: file = osp.join("images", x[0]) label = osp.join("annotations", x[1]) f.write('{} {}\n'.format(file, label)) # # 创建{save_dir}/test_list.txt 文件 if len(test_image_anno_list): with open( osp.join(save_dir, 'test_list.txt'), mode='w', encoding='utf-8') as f: for x in test_image_anno_list: file = osp.join("images", x[0]) label = osp.join("annotations", x[1]) f.write('{} {}\n'.format(file, label)) # # 创建{save_dir}/labels.txt 文件 with open( osp.join(save_dir, 'labels.txt'), mode='w', encoding='utf-8') as f: for l in sorted(label_list): f.write('{}\n'.format(l)) # return train_num, val_num, test_num if __name__ == "__main__": # 切分数据集 split_voc_dataset('PaddleDetection/dataset/MyDataset', 0.2, 0.1, 'PaddleDetection/dataset/MyDataset')
运行上面代码完成后,训练集、验证集、测试集分别记录在文件:train_list.txt,val_list.txt,test_list.txt文件中。
3.2 数据集的配置
数据集的配置主要是写明训练集/验证集/测试集的文件位置(ppdet和数据集路径见上文所述)。
D:\lusong\ppdetection\PaddleDetection\configs\datasets\voc.yml
dataset_dir路径
该路径下包括anno_path所指文件。无需标准的VOC的两级目录:VOCdevkit/VOC2007(或VOC2012)。
代码(yml)
metric: VOC
map_type: 11point
num_classes: 4
TrainDataset:
!VOCDataSet
dataset_dir: D:\lusong\other\det\HelmetDetection
anno_path: train_list.txt
label_list: labels.txt
data_fields: ['image', 'gt_bbox', 'gt_class', 'difficult']
EvalDataset:
!VOCDataSet
dataset_dir: D:\lusong\other\det\HelmetDetection
anno_path: val_list.txt
label_list: labels.txt
data_fields: ['image', 'gt_bbox', 'gt_class', 'difficult']
TestDataset:
!ImageFolder
anno_path: dataset/voc/test_list.txt
4 训练
调用命令行:
python -u tools/train.py -c D:\lusong\ppdetection\PaddleDetection\configs\ppyolo\ppyolo_r50vd_dcn_voc_1.yml -o use_gpu=false pretrain_weights=D:\lusong\other\det\model\ResNet50_vd_ssld_pretrained.pdparams --eval
4.1 继续训练
有时候会意外或主动中断,如需继续训练的话,调用命令行:
python -u tools/train.py \ -c D:\lusong\ppdetection\PaddleDetection\configs\ppyolo\ppyolo_r50vd_dcn_voc_1.yml \ -o use_gpu=false \ -r output/ppyolo_r50vd_dcn_voc_1/best_model \ --eval
5 推理
中断训练后,项看当前训练的怎么样的话,调用命令行:
python -u tools/infer.py -c D:\lusong\ppdetection\PaddleDetection\configs\ppyolo\ppyolo_r50vd_dcn_voc_1.yml -o use_gpu=false weights=D:\lusong\ppdetection\PaddleDetection\output\ppyolo_r50vd_dcn_voc_1\best_model.pdparams --infer_img=D:\lusong\other\det\HelmetDetection\images\hard_hat_workers0.png
至此,可以启动训练,并在成功保存模型的情况下,执行推理。
6 对训练时长的缩减
6.1 修改ppyolo_r50vd_dcn_voc.yml,缩减:训练轮数,batch尺寸,保存时间间隔(轮数)。减少整个训练时长。
snapshot_epoch: 1
TrainReader:
batch_size: 2
epoch: 10
6.2 修改数据集文件,缩减:单轮训练图片数量,单轮验证图片数量。减少单轮训练时长。
缩减单轮训练图片数量:编辑train_list.txt文件,一行一个训练样本,根据训练时长整行删除即可。
缩减单轮验证图片数量:同上编辑val_list.txt文件。
7 小结
1 简单感受了一下VOC格式,划分的训练集,ppdet的配置方式,调用方式。
2 训练代码还是比较简单,很容易上手。
3 纯时间耗费不起(原训练配置ppdet估计时长为140天左右)。
4 ppdet=2.6有bug,会时不时异常退出,浪费时间巨大。
贵阳正值疫情期间,心情有点慌张。