本博客讲解如何利用Faster-RCNN去训练Pascol VOC数据集或者训练自定义数据集。
Faster-RCNN原理,参考博文:RCNN、Fast-RCNN、Faster-RCNN理论合集
如何在pytorch中找到Faster-RCNN源码
通过import detection.faster_rcnn , 然后按Ctr + 鼠标左键,就可以参考faster_rcnn所实现的一些源码了
import torchvision.models.detection.faster_rcnn
但这里其实只是代码的一部分,和训练相关的代码这里并没有 ,根据官方的提示。可以在pytorch github中找到 可以看到训练相关的源代码了,如果自己想看faster-cnn源代码可以结合这两部分。
训练准备
环境配置
- Python3.6/3.7/3.8
- Pytorch1.7.1(注意:必须是1.6.0或以上,因为使用官方提供的混合精度训练1.6.0后才支持)
- pycocotools(Linux:pip install pycocotools; Windows:pip install pycocotools-windows(不需要额外安装vs))
- Ubuntu或Centos(不建议Windows)
- 最好使用GPU训练
- 详细环境配置见requirements.txt
这里有使用到coco的评价准则,所以需要安装pycocotools ,系统的话建议使用Linux,训练最好也是使用GPU训练
文件结构
├── backbone: 特征提取网络(分类网络),可以根据自己的要求选择
├── network_files: Faster R-CNN网络(包括Fast R-CNN以及RPN等模块)
├── train_utils: 训练验证相关模块(包括cocotools)
├── my_dataset.py: 自定义dataset用于读取VOC数据集
├── train_mobilenet.py: 以MobileNetV2做为backbone进行训练
├── train_resnet50_fpn.py: 以resnet50+FPN做为backbone进行训练
├── train_multi_GPU.py: 针对使用多GPU的用户使用
├── predict.py: 简易的预测脚本,使用训练好的权重进行预测测试
├── validation.py: 利用训练好的权重验证/测试数据的COCO指标,并生成record_mAP.txt文件
└── pascal_voc_classes.json: pascal_voc标签文件
- 其中
backbone 就是一系列分类网络,可以根据自己的需求进行选择,我这边根据官方的样例使用了两个backbone:mobienetv2 和resnet50+fpn(特征金字塔结构) ; - train_utils就是pytorch github官方给出的训练代码,我这边将他们放在
train_utils 文件中。 my_dataset.py :自定义dataset去读取VOC数据集,官方也提供了直接读取VOC数据集的工具,我这里主要是强调如何自己读取dataset,了解自定义数据集的原理之后就可以按照自己的需求创建自定义数据集,编写相应的脚本去读取就可以了。train_mobilenet.py : 以MobileNetV2做为backbone进行训练,该backbone预测特征层只有一层,和Faster-RCNN论文描述的方法基本上保持一致train_resnet50_fpn.py : 以resnet50+FPN做为backbone进行训练,个人比较推荐用这个脚本,因为该脚本的训练效果是很好的,如果你要在实际应用去使用的话,尽可能去使用该脚本。对于 train_mobilenet.py : 以MobileNetV2做为backbone进行训练,准确率相比要低很多,不建议在实际项目中去使用,主要原因一方面是训练数据不够多,另外该官方没有提供完整的预训练模型权重,但官方给出了完整的resnet50+FPN 的预训练权重。基于resnet50+FPN 预训练权重进行迁移学习,就能很快迭代到属于自己的模型了。train_multi_GPU.py : 针对使用多GPU的用户使用,它的训练方法和单GPU训练方法是不一样的,单GPU可以直接在IDE环境中run 脚本文件进行训练。但train_multi_GPU.py 需要在终端命令行窗口输入指令进行训练。validation.py : 利用训练好的权重验证/测试数据的COCO指标,并生成record_mAP.txt文件pascal_voc_classes.json: pascal_voc标签文件,标签文件如下:
{
"aeroplane": 1,
"bicycle": 2,
"bird": 3,
"boat": 4,
"bottle": 5,
"bus": 6,
"car": 7,
"cat": 8,
"chair": 9,
"cow": 10,
"diningtable": 11,
"dog": 12,
"horse": 13,
"motorbike": 14,
"person": 15,
"pottedplant": 16,
"sheep": 17,
"sofa": 18,
"train": 19,
"tvmonitor": 20
}
标签为啥没有从0开始? 因为在我们目标检测当中,一般0是专门留给我们的背景的。虽然我们说pascal_voc只有20个类别,其实实际训练过程中给出了21个类别,专门为背景设了一个类别。
预训练权重下载
预训练权重下载地址(下载后放入backbone文件夹中)
数据集
本例程使用的是PASCAL VOC2012 数据集
训练方法
- 确保提前准备好数据集
- 确保提前下载好对应预训练模型权重,下载好后放在项目的
backbone 文件夹中,代码中是从backbone文件中寻找预训练权重的。 - 若要训练mobilenetv2+fasterrcnn,直接使用train_mobilenet.py训练脚本,建议在学术研究中使用
- 若要训练resnet50+fpn+fasterrcnn,直接使用train_resnet50_fpn.py训练脚本,建议在工程应用中使用
- 若要使用多GPU训练,使用
python -m torch.distributed.launch --nproc_per_node=8 --use_env train_multi_GPU.py
指令,nproc_per_node参数为使用GPU数量
- 如果想指定使用哪些GPU设备可在指令前加上
CUDA_VISIBLE_DEVICES=0,3 (例如我只要使用设备中的第1块和第4块GPU设备)
CUDA_VISIBLE_DEVICES=0,3 python -m torch.distributed.launch --nproc_per_node=2 --use_env train_multi_GPU.py
其中torch.distributed.launch 是一个多进程的工具,可以开启多个进程执行python脚本,同时调用多个GPU进行加速训练。nproc_per_node 指定GPU的个数,train_multi_GPU.py 就是项目中训练多GPU的代码
注意事项
- 在使用训练脚本时,注意要将
--data-path (VOC_root)设置为自己存放VOCdevkit文件夹所在的根目录 由于带有FPN 结构的Faster RCNN很吃显存 ,如果GPU的显存不够(如果batch_size小于8的话)建议在create_model函数中使用默认的norm_layer , 即不传递norm_layer变量,默认去使用FrozenBatchNorm2d(即不会去更新参数的bn层),使用中发现效果也很好。 - 训练过程中保存的
results.txt 是每个epoch在验证集上的COCO 指标,前12个值是COCO 指标,后面两个值是训练平均损失以及学习率 - 在使用预测脚本时,要将
train_weights 设置为你自己生成的权重路径。 - 使用
validation 文件时,注意确保你的验证集或者测试集中必须包含每个类别的目标,并且使用时只需要修改--num-classes 、--data-path 和--weights-path 即可,其他代码尽量不要改动
训练脚本讲解
这里以train_mobienet.py 代码进行讲解
device = torch.device(parser_data.device if torch.cuda.is_available() else "cpu")
print(device)
data_transform = {
"train": transforms.Compose([transforms.ToTensor(),
transforms.RandomHorizontalFlip(0.5)]),
"val": transforms.Compose([transforms.ToTensor()])
}
ToTensor() :包含了两个功能:1. 图片归一化到(0,1) 2.将nd.array数据转换为tensor
目标检测图像预处理和分类网络的预处理是不一样的,比如transforms.RandomHorizontalFlip(0.5) ,因为在我们目标检测中,我们对图片水平方向进行随机翻转的话,我们所标注的GT Box 坐标也同样需要进行水平翻转。
VOC_root = parser_data.data_path
train_data_set = VOC2012DataSet(VOC_root, data_transform["train"], True)
并通过dataloader载入数据集:
train_data_loader = torch.utils.data.DataLoader(train_data_set,
batch_size=4,
shuffle=True,
num_workers=0,
collate_fn=utils.collate_fn)
其中collate_fn 代码
def collate_fn(batch):
return tuple(zip(*batch))
batch_size 的大小需要根据你电脑的GPU显存去设计,如果提示显存不够就需要减小batch_size 的大小。
同理验证集的数据集
val_data_set = VOC2012DataSet(VOC_root, data_transform["val"], False)
val_data_set_loader = torch.utils.data.DataLoader(val_data_set,
batch_size=2,
shuffle=False,
num_workers=0,
collate_fn=utils.collate_fn)
- num_class类别是加上背景的,所以这里是21
model = create_model(num_classes=21)
print(model)
model.to(device)
- Faster-RCNN还有点麻烦,是分开两部分进行训练的
阶段1: 冻结前置特征提取网络权重(backbone),训练rpn以及fast-rcnn最终预测网络部分
以backbone为mobienet为例,训练第一阶段载入mobienet v2 的backbone 模型初始化backbone这块,然后冻结backbone 去训练RPN以及Fast-RCNN的后半部分。通过固定backbone权重去微调RPN和Fast-RCNN的后半部分。 这里通过5个epoch 对网络进行微调,并保存模型
for param in model.backbone.parameters():
param.requires_grad = False
params = [p for p in model.parameters() if p.requires_grad]
optimizer = torch.optim.SGD(params, lr=0.005,
momentum=0.9, weight_decay=0.0005)
num_epochs = 5
for epoch in range(num_epochs):
utils.train_one_epoch(model, optimizer, train_data_loader,
device, epoch, print_freq=50)
utils.evaluate(model, val_data_set_loader, device=device)
torch.save(model.state_dict(), "./save_weights/pretrain.pth")
阶段2: 解冻前置特征提取网络权重(backbone),接着训练整个网络权重。 这里代码借鉴pytorch 上实现的resnet50+fpn代码来实现的。
for name, parameter in model.backbone.named_parameters():
split_name = name.split(".")[0]
if split_name in ["0", "1", "2", "3"]:
parameter.requires_grad = False
else:
parameter.requires_grad = True
冻结了backbone的部分底层权重,比如前一两层都是相对通用的特征。而且我们的pascal_voc的训练数据不是很大,也就5000多张图片,训练整个网络其实是远远不够的。最终的结果来看,它比训练整个模型的效果要好一点。
对require_grad=True 对需要训练的参数进行训练,学习率策略lr_scheduler ,设置为每隔5步,将学习率降低为原来的0.33倍。
params = [p for p in model.parameters() if p.requires_grad]
optimizer = torch.optim.SGD(params, lr=0.005,
momentum=0.9, weight_decay=0.0005)
lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer,
step_size=5,
gamma=0.33)
这里迭代了20 个epoch ,每迭代一个epoch ,将学习率的调整策略lr_scheduler ,执行一个step 方法,这样就记录了已经执行了一步了,每隔5步,它将会降低一次学习率。
num_epochs = 20
for epoch in range(num_epochs):
utils.train_one_epoch(model, optimizer, train_data_loader,
device, epoch, print_freq=50, warmup=True)
lr_scheduler.step()
保存权重这块,我这里是从第10个epoch 开始保存的,在我训练过程中,我发现基本上在第10个epoch就开始收敛了。
注意这里保存的权重,并不光指模型的权重,之前在训练分类网络的时候,基本上就将model.state_dict() 权重进行保存。这里除了保存模型权重之外,还保存了有关优化器optimizer的状态以及学习策略lr_sheduler以及epoch数,这样我们再后面如果想接着你上次训练的话,可以载入这些参数,接着上次的方法继续训练了。
for epoch in range(num_epochs):
utils.train_one_epoch(model, optimizer, train_data_loader,
device, epoch, print_freq=50, warmup=True)
lr_scheduler.step()
utils.evaluate(model, val_data_set_loader, device=device)
if epoch > 10:
save_files = {
'model': model.state_dict(),
'optimizer': optimizer.state_dict(),
'lr_scheduler': lr_scheduler.state_dict(),
'epoch': epoch}
torch.save(save_files, "./save_weights/mobile-model-{}.pth".format(epoch))
Faster RCNN框架图
|