配置文件是 M M D e t e c t i o n {\rm MMDetection} MMDetection中的核心要素,各模块的组建主要通过配置文件完成,而我们在添加或修改网络结构时也主要是针对配置文件的修改。本文将详细介绍 M M D e t e c t i o n {\rm MMDetection} MMDetection中配置文件的使用。
在 M M D e t e c t i o n {\rm MMDetection} MMDetection中为我们提供了查看配置文件的函数,位于tools/print_config.py
中,例如执行以下命令:
python tools/print_config.py configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py
屏幕上会打印出该配置文件的详细信息。
在 M M D e t e c t i o n {\rm MMDetection} MMDetection中存在四个基本的配置文件,它们是其他配置文件的基。位于config/_base_
,分别有数据集dataset
、模型model
、学习策略schedule
和运行时runtime
。
此外,在config
下位于同一文件夹内有且仅有一个基文件,其他配置文件都继承自该文件。如在configs/fcos
文件夹内,fcos_r50_caffe_fpn_gn-head_4x4_1x_coco.py
是其他所有同文件夹内的基文件。所以,如果要在 M M D e t e c t i o n {\rm MMDetection} MMDetection中集成新的算法,需要在configs
文件夹下建立新的文件夹,然后按照格式新建配置文件。
在 M M D e t e c t i o n {\rm MMDetection} MMDetection中,配置文件的命名方式遵循以下规则:
{model}_[model setting]_{backbone}_{neck}_[norm setting]_[misc]_[gpu x batch_per_gpu]_{schedule}_{dataset}
{model}
:表示具体模型,如faster_rcnn
、mask_rcnn
等;[model setting]
:某些模型的具体设置,如htc
的without_semantic
、reppoints
的moment
等;{backbone}
:骨干网络,如r50
、x101
等;{neck}
:网络颈,如fpn
、pafpn
、nasfpn
、c4
等;[norm setting]
:正则化设置,如bn
、gn
、syncbn
、gn-head/gn-neck
、gn-all
等;[misc]
:其他设置,如dconv
、gcb
、attention
、albu
、mstrain
等;[gpu x batch_per_gou]
:使用的总 G P U {\rm GPU} GPU数以及每块 G P U {\rm GPU} GPU的采样数;{schedule}
:设置学习策略,如1x
、2x
、20e
等;{dataset}
:指示具体的数据集,如coco
、cityscapes
、voc_0712
、wider_face
等。为了更加直观地了解 M M D e t e c t i o n {\rm MMDetection} MMDetection中的配置文件的结构,本部分以 M a s k {\rm Mask} Mask- R C N N {\rm RCNN} RCNN为例来说明配置文件的内容,文件位于configs/mask_rcnn/mask_rcnn_r50_fpn_1x_coco.py
:
_base_ = [
'../_base_/models/mask_rcnn_r50_fpn.py',
'../_base_/datasets/coco_instance.py',
'../_base_/schedules/schedule_1x.py',
'../_base_/default_runtime.py'
]
可以看到,该配置文件继承自四个部分。第一部分是有关模型的配置:
# model settings
model = dict( # 模型设置
type='MaskRCNN', # 当前检测器的名称
pretrained='torchvision://resnet50', # ImageNet数据集的预训练骨干网络
backbone=dict( # 骨干网络部分设置
type='ResNet', # 使用ResNet作为骨干网络
depth=50, # 使用ResNet50作为骨干网络
num_stages=4, # 骨干网络的阶段数
out_indices=(0, 1, 2, 3), # 每个阶段输出特征图的索引
frozen_stages=1, # 冻结网络第一阶段的权重
norm_cfg=dict(type='BN', requires_grad=True), # 归一化层的设置
norm_eval=True, # 是否冻结BN层
style='pytorch'), # 深度学习框架
neck=dict( # 网络颈部分设置
type='FPN', # 使用FPN
in_channels=[256, 512, 1024, 2048], # FPN各层的输入通道数,与骨干网络部分对应
out_channels=256, # FPN中的输出通道数
num_outs=5), # 输出的多尺度数
rpn_head=dict( #RPN网络头设置
type='RPNHead', # RPN头
in_channels=256, # RPN部分的输入通道数
feat_channels=256, # RPN部分的输出通道数
anchor_generator=dict( # Anchor产生器设置
type='AnchorGenerator', # Anchor产生器,主要是针对Anchor-Based方法
scales=[8], # Anchor的大小
ratios=[0.5, 1.0, 2.0], # Anchor的宽高比
strides=[4, 8, 16, 32, 64]), # 产生Anchor的特征图的下采样倍数
bbox_coder=dict( # 边界框的编解码设置
type='DeltaXYWHBBoxCoder', # 常用的编解码方法
target_means=[.0, .0, .0, .0], # 均值
target_stds=[1.0, 1.0, 1.0, 1.0]), # 标准差
loss_cls=dict( # 分类分支的损失函数设置
type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0), # 交叉熵损失
loss_bbox=dict(type='L1Loss', loss_weight=1.0)), # 回归分支的损失函数设置
roi_head=dict( # 两阶段检测算法中的RoI处理部分
type='StandardRoIHead', # 标准RoI头
bbox_roi_extractor=dict( # RoI特征提取器
type='SingleRoIExtractor', # 指定RoI特征提取器
roi_layer=dict(type='RoIAlign', out_size=7, sample_num=0), # 使用RoIAlign
out_channels=256, # 得到的特征图的通道数
featmap_strides=[4, 8, 16, 32]), # 特征图的下采样倍数
bbox_head=dict( # 回归分支设置
type='Shared2FCBBoxHead', # 常用的回归分支类型
in_channels=256, # 输入通道数
fc_out_channels=1024, # 全连接层的输出通道数
roi_feat_size=7, # RoI特征图大小
num_classes=80, # 分类分支的类别数
bbox_coder=dict( # 边界框编码器
type='DeltaXYWHBBoxCoder', # 常用的编码方法
target_means=[0., 0., 0., 0.], # 均值
target_stds=[0.1, 0.1, 0.2, 0.2]), # 标准差
reg_class_agnostic=False, # 回归边界框时是否指定具体类别
loss_cls=dict( # 分类分支的损失函数设置
type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0),
loss_bbox=dict(type='L1Loss', loss_weight=1.0)), # 回归分支的损失函数设置
mask_roi_extractor=dict( # 同上
type='SingleRoIExtractor',
roi_layer=dict(type='RoIAlign', out_size=14, sample_num=0),
out_channels=256,
featmap_strides=[4, 8, 16, 32]),
mask_head=dict( # mask分支的设置
type='FCNMaskHead', # Mask分支的检测头
num_convs=4, # 连续使用四次卷积
in_channels=256, # 输入通道数
conv_out_channels=256, # 输出通道数
num_classes=80, # 类别数
loss_mask=dict( # mask分支的损失函数设置
type='CrossEntropyLoss', use_mask=True, loss_weight=1.0))))
# model training and testing settings
train_cfg = dict( # 训练设置
rpn=dict( # RPN部分
assigner=dict( # 候选框采样设置
type='MaxIoUAssigner', # 基于IoU对候选框采样
pos_iou_thr=0.7, # 正样本阈值
neg_iou_thr=0.3, # 负样本阈值
min_pos_iou=0.3, # 最小正样本阈值,防止正样本过少造成样本数的不平衡
match_low_quality=True, # 是否使用低质量的候选框
ignore_iof_thr=-1), # 忽略样本阈值
sampler=dict( # 候选框采样设置
type='RandomSampler', # 常用的随机采样方式
num=256, # 总采样样本数
pos_fraction=0.5, # 正负样本比例
neg_pos_ub=-1, # 最大负样本数
add_gt_as_proposals=False), # 采样后是否添加真实框作为候选框
allowed_border=-1, # 有效候选框的边界填充
pos_weight=-1, # 训练时正样本的权重
debug=False), # 是否设置debug模式
rpn_proposal=dict( # RPN设置
nms_across_levels=False, # 是否跨层使用NMS
nms_pre=2000, # NMS前的候选框数
nms_post=1000, # NMS后的候选框数
max_num=1000, # NMS后的候选框数
nms_thr=0.7, # NMS使用的阈值
min_bbox_size=0), # 允许的最小候选框尺寸
rcnn=dict( # RoI检测头部分设置
assigner=dict( # 同上
type='MaxIoUAssigner',
pos_iou_thr=0.5,
neg_iou_thr=0.5,
min_pos_iou=0.5,
match_low_quality=True,
ignore_iof_thr=-1),
sampler=dict( # 同上
type='RandomSampler',
num=512,
pos_fraction=0.25,
neg_pos_ub=-1,
add_gt_as_proposals=True),
mask_size=28, # mask大小
pos_weight=-1, # 训练时正样本的权重
debug=False)) # 是否设置debug模式
test_cfg = dict( # 测试设置
rpn=dict( # RPN部分设置,同上
nms_across_levels=False,
nms_pre=1000,
nms_post=1000,
max_num=1000,
nms_thr=0.7,
min_bbox_size=0),
rcnn=dict( # RoI检测头部分设置,同上
score_thr=0.05,
nms=dict(type='nms', iou_thr=0.5),
max_per_img=100,
mask_thr_binary=0.5))
第二部分是有关数据集的配置:
_base_ = 'coco_detection.py' # 继承自coco_detection.py
img_norm_cfg = dict(
mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True) # 均值和标准差
train_pipeline = [ # 上文提到的训练管道设置
dict(type='LoadImageFromFile'), # 加载图像
dict(type='LoadAnnotations', with_bbox=True, with_mask=True), # 加载标准
dict(type='Resize', img_scale=(1333, 800), keep_ratio=True), # 固定尺寸
dict(type='RandomFlip', flip_ratio=0.5), # 水平翻转
dict(type='Normalize', **img_norm_cfg), # 归一化
dict(type='Pad', size_divisor=32), # 填充
dict(type='DefaultFormatBundle'), # 数据格式
dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels', 'gt_masks']), # 整合输出
]
data = dict(train=dict(pipeline=train_pipeline)) # 字典
evaluation = dict(metric=['bbox', 'segm']) # 评价指标
其中,基文件coco_detection.py
如下:
dataset_type = 'CocoDataset' # 数据集
data_root = 'data/coco/' # 数据集根目录
img_norm_cfg = dict(
mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True) # 均值和标准差
train_pipeline = [ # 同上
dict(type='LoadImageFromFile'),
dict(type='LoadAnnotations', with_bbox=True),
dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),
dict(type='RandomFlip', flip_ratio=0.5),
dict(type='Normalize', **img_norm_cfg),
dict(type='Pad', size_divisor=32),
dict(type='DefaultFormatBundle'),
dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels']),
]
test_pipeline = [ # 同上
dict(type='LoadImageFromFile'),
dict(
type='MultiScaleFlipAug',
img_scale=(1333, 800),
flip=False,
transforms=[
dict(type='Resize', keep_ratio=True),
dict(type='RandomFlip'),
dict(type='Normalize', **img_norm_cfg),
dict(type='Pad', size_divisor=32),
dict(type='ImageToTensor', keys=['img']),
dict(type='Collect', keys=['img']),
])
]
data = dict(
samples_per_gpu=2, # 每块GPU采样数
workers_per_gpu=2, # 每块GPU线程数
train=dict( # 训练数据集信息
type=dataset_type,
ann_file=data_root + 'annotations/instances_train2017.json',
img_prefix=data_root + 'train2017/',
pipeline=train_pipeline),
val=dict( # 验证数据集信息
type=dataset_type,
ann_file=data_root + 'annotations/instances_val2017.json',
img_prefix=data_root + 'val2017/',
pipeline=test_pipeline),
test=dict( # 测试数据集信息
type=dataset_type,
ann_file=data_root + 'annotations/instances_val2017.json',
img_prefix=data_root + 'val2017/',
pipeline=test_pipeline))
evaluation = dict(interval=1, metric='bbox') # 评价指标
第三部分是有关训练策略的配置:
# 优化器
optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)
optimizer_config = dict(grad_clip=None)
# 学习率
lr_config = dict(
policy='step',
warmup='linear',
warmup_iters=500,
warmup_ratio=0.001,
step=[8, 11])
total_epochs = 12
第四部分是有关运行时的配置:
checkpoint_config = dict(interval=1)
# yapf:disable
log_config = dict(
interval=50,
hooks=[
dict(type='TextLoggerHook'),
# dict(type='TensorboardLoggerHook')
])
# yapf:enable
dist_params = dict(backend='nccl')
log_level = 'INFO'
load_from = None
resume_from = None
workflow = [('train', 1)]
有时候,我们想修改父配置文件中的某一部分,比如修改继承自mask_rcnn_r50_fpn_1x_coco.py
的骨干网络部分。除了自定义父配置文件外,我们也可以在此配置文件上设置关键字_delete=True
,然后根据实际需要添加自己的模块。如:
model = dict(
type='MaskRCNN',
pretrained='torchvision://resnet50',
backbone=dict(
type='ResNet',
depth=50,
num_stages=4,
out_indices=(0, 1, 2, 3),
frozen_stages=1,
norm_cfg=dict(type='BN', requires_grad=True),
norm_eval=True,
style='pytorch'),
neck=dict(...),
rpn_head=dict(...),
roi_head=dict(...))
这里,我们将ResNet
替换成HRNet
:
_base_ = '../mask_rcnn/mask_rcnn_r50_fpn_1x_coco.py'
model = dict(
pretrained='open-mmlab://msra/hrnetv2_w32',
backbone=dict(
_delete_=True,
type='HRNet',
extra=dict(
stage1=dict(
num_modules=1,
num_branches=1,
block='BOTTLENECK',
num_blocks=(4, ),
num_channels=(64, )),
stage2=dict(
num_modules=1,
num_branches=2,
block='BASIC',
num_blocks=(4, 4),
num_channels=(32, 64)),
stage3=dict(
num_modules=4,
num_branches=3,
block='BASIC',
num_blocks=(4, 4, 4),
num_channels=(32, 64, 128)),
stage4=dict(
num_modules=3,
num_branches=4,
block='BASIC',
num_blocks=(4, 4, 4, 4),
num_channels=(32, 64, 128, 256)))),
neck=dict(...))
该配置文件虽然继承自mask_rcnn/mask_rcnn_r50_fpn_1x_coco.py
,但是通过在backbone
中设置关键字_delete=True
可以使定义的骨干网络部分替换父配置文件中相应的部分,其他模块的替换方式一样。
当我们需要修改子配置文件中的相关变量时,必须将修改的变量再次定义。如我们需要在 M a s k {\rm Mask} Mask- R C N N {\rm RCNN} RCNN中加入多尺度策略,我们需要对train_pipeline/test_pipeline
做如下修改:
_base_ = './mask_rcnn_r50_fpn_1x_coco.py'
img_norm_cfg = dict(
mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
train_pipeline = [
dict(type='LoadImageFromFile'),
dict(type='LoadAnnotations', with_bbox=True, with_mask=True),
dict(
type='Resize',
img_scale=[(1333, 640), (1333, 672), (1333, 704), (1333, 736),
(1333, 768), (1333, 800)],
multiscale_mode="value",
keep_ratio=True),
dict(type='RandomFlip', flip_ratio=0.5),
dict(type='Normalize', **img_norm_cfg),
dict(type='Pad', size_divisor=32),
dict(type='DefaultFormatBundle'),
dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels', 'gt_masks']),
]
test_pipeline = [
dict(type='LoadImageFromFile'),
dict(
type='MultiScaleFlipAug',
img_scale=(1333, 800),
flip=False,
transforms=[
dict(type='Resize', keep_ratio=True),
dict(type='RandomFlip'),
dict(type='Normalize', **img_norm_cfg),
dict(type='Pad', size_divisor=32),
dict(type='ImageToTensor', keys=['img']),
dict(type='Collect', keys=['img']),
])
]
data = dict(
train=dict(pipeline=train_pipeline),
val=dict(pipeline=test_pipeline),
test=dict(pipeline=test_pipeline))
本文介绍了 M M D e t e c t i o n {\rm MMDetection} MMDetection中的配置文件系统,在 M M D e t e c t i o n {\rm MMDetection} MMDetection中配置文件是构建整个模型的关键。结合上文介绍的如何在 M M D e t e c t i o n {\rm MMDetection} MMDetection中添加自定义部分,下文将介绍 M M D e t e c t i o n {\rm MMDetection} MMDetection配套的 m m c v {\rm mmcv} mmcv库。