mmdetection-yolox
- mmdetection_yolox
- 训练流程
- 一. 注册机制
- 二.训练代码流程【main.py 和 mmdet/apis/train.py】
- 1. `mmdet/models/builder.py/build_dtector(cfg.model)`
- 2.`mmdet/datasets/builder.py/build_dataset(cfg.data.train)`
- 3. `mmdet/datasets/builder.py/build_dataloader(一堆参数)`
- 4.mmcv/parallel/`MMDataParallel`(单gpu版本的model上层封装)
- 5.mmcv/runner-`build_optimizer`
- 6.mmcv/runner/builder/`build_runner`
- 7.runner.register_training_hooks()训练hook
- 8.runner.load_checkpoint(cfg.load_from)
- 9.runner.run()
- 10.validate
- 三.模块
mmdetection_yolox
训练流程
一. 注册机制
注册机制:registry可以看成是一个类映射到一个字符串的映射。
from mmcv.cnnimport MODELSas MMCV_MODELSfrom mmcv.utilsimport Registry#1. 创建注册表(表里填写映射)
MODELS= Registry('models', parent=MMCV_MODELS)#2.将类模块注册到注册表中(字符串和类之间映射)'字符串Converter1' 类<class 'Converter1'>
@MODELS.register_module()classConverter1(object):pass#3.模块注册成功,通过configs使用这个转换器。得到实例化#配置文件中的type:待实例化的类名;后面是初始化参数
converter_cfg=dict(type='Converter1', a=a_value, b=b_value)
converter= MODELS.build(converter_cfg)
二.训练代码流程【main.py 和 mmdet/apis/train.py】
from mmdet.datasetsimport build_datasetfrom mmdet.modelsimport build_detectorfrom mmdet.apisimport train_detectorfrom mmcvimport Config#1.初始化配置文件
cfg=Config.fromfile('configs/123/yolox.py')#2.初始化模型
model=build_detector(cfg.model)#得到的是yolox实例化类#3.初始化数据集
datasets=[build_dataset(cfg.data.train)]#得到的datasets是没有经过pipeline处理的
train_detector(model,datasets,cfg,distributed=False,validate=True)#分布式=F
from mmdet.datasetsimport(build_dataloader, build_dataset)from mmcv.parallelimport MMDataParallelfrom mmcv.runnerimport(DistSamplerSeedHook, EpochBasedRunner,
Fp16OptimizerHook, OptimizerHook, build_optimizer,
build_runner, get_dist_info)deftrain_detector(model,
dataset,
cfg,
distributed=False,
validate=False,
timestamp=None,
meta=None):#4.初始化logger
logger= get_root_logger(log_level=cfg.log_level)#5.初始化数据迭代器
dataset= datasetifisinstance(dataset,(list,tuple))else[dataset]
data_loaders=[
build_dataloader(
ds,
cfg.data.samples_per_gpu,
cfg.data.workers_per_gpu,# `num_gpus` will be ignored if distributed
num_gpus=len(cfg.gpu_ids),#1
dist=distributed,#Fasele
seed=cfg.seed,#0
runner_type=runner_type,
persistent_workers=cfg.data.get('persistent_workers',False))#dataset是一个MultiImageMixDataset对象#每用for拿出一个就是,调用MultiImageMixDataset对象里的_getitem__函数#拿出来的事经过12条pipeline处理的数据for dsin dataset]#6.model放在gpu上(distributed=False)
model= MMDataParallel(
model.cuda(cfg.gpu_ids[0]),#0
device_ids=cfg.gpu_ids)#0#7.初始化optimizer
optimizer= build_optimizer(model, cfg.optimizer)#8.初始化runner
runner_type='EpochBasedRunner'#cfg中的就是这个
runner= build_runner(
cfg.runner,
default_args=dict(
model=model,
optimizer=optimizer,
work_dir=cfg.work_dir,
logger=logger,
meta=meta))#None#日志相关 an ugly workaround to make .log and .log.json filenames the same
runner.timestamp= timestamp#nonne#fp16 setting 混合精度训练 | fp16 用于神经网络训练和预测
fp16_cfg= cfg.get('fp16',None)#有就拿出来,没有就是noneif fp16_cfgisnotNone:#cdg中的是none
optimizer_config= Fp16OptimizerHook(**cfg.optimizer_config,**fp16_cfg, distributed=distributed)elif distributedand'type'notin cfg.optimizer_config:
optimizer_config= OptimizerHook(**cfg.optimizer_config)else:##########################################只执行这里
optimizer_config= cfg.optimizer_config# 9. 注册 hooks
runner.register_training_hooks(
cfg.lr_config,
optimizer_config,
cfg.checkpoint_config,
cfg.log_config,
cfg.get('momentum_config',None),
custom_hooks_config=cfg.get('custom_hooks',None))# 10.register eval hooksif validate:# Support batch_size > 1 in validation
val_samples_per_gpu= cfg.data.val.pop('samples_per_gpu',1)if val_samples_per_gpu>1:# Replace 'ImageToTensor' to 'DefaultFormatBundle'
cfg.data.val.pipeline= replace_ImageToTensor(
cfg.data.val.pipeline)
val_dataset= build_dataset(cfg.data.val,dict(test_mode=True))
val_dataloader= build_dataloader(
val_dataset,
samples_per_gpu=val_samples_per_gpu,
workers_per_gpu=cfg.data.workers_per_gpu,
dist=distributed,
shuffle=False)
eval_cfg= cfg.get('evaluation',{})
eval_cfg['by_epoch']= cfg.runner['type']!='IterBasedRunner'
eval_hook= DistEvalHookif distributedelse EvalHook# In this PR (https://github.com/open-mmlab/mmcv/pull/1193), the# priority of IterTimerHook has been modified from 'NORMAL' to 'LOW'.
runner.register_hook(
eval_hook(val_dataloader,**eval_cfg), priority='LOW')#11.加载权重if cfg.resume_from:
runner.resume(cfg.resume_from)elif cfg.load_from:
runner.load_checkpoint(cfg.load_from)#12.开始训练
runner.run(data_loaders, cfg.workflow)
1.mmdet/models/builder.py/build_dtector(cfg.model)
cfg=Config.fromfile('configs/123/yolox.py')
model=build_detector(cfg.model)#根据配置文件信息得到实例化模型yolox
1).创建注册表 MODELS
from mmcv.cnnimport MODELSas MMCV_MODELSfrom mmcv.utilsimport Registry
MODELS= Registry('models', parent=MMCV_MODELS)
DETECTORS= MODELS
2).通过在模块上方添加@DETECTORS.register_module()
语句,注册模块(在创建模块时将实现的模块注册到注册表中)
@DETECTORS.register_module()classYOLOX(SingleStageDetector)
3).通过build
字句,根据cfg:model=dict(type=YOLOX)
将YOLOX
这个类实例化
model=def build_detector(#cfg=Config.fromfile('configs/123/yolox.py').model
cfg,#这里的cf只是总配置文件中的model字典
train_cfg=None,
test_cfg=None):return DETECTORS.build(
cfg, default_args=dict(train_cfg=train_cfg, test_cfg=test_cfg))
2.mmdet/datasets/builder.py/build_dataset(cfg.data.train)
datasets=[build_dataset(cfg.data.train)]
1).传来的参数:cfg.data.train=train_dataset这个配置字典
传来的配置字典里有:数据集的类型,数据集的地址,数据处理10+2
train_dataset = dict(
type='MultiImageMixDataset',
dataset=dict(
type=dataset_type,
classes = ('human body','ball','circle cage',
'square cage','tyre','metal bucket','cube','cylinder'),
ann_file='data/sna/annotations/train.json',
img_prefix='data/sna/train/',
#2个数据处理
pipeline=[
dict(type='LoadImageFromFile'),
dict(type='LoadAnnotations', with_bbox=True)
],
filter_empty_gt=False,
),
pipeline=train_pipeline)#10个数据处理
2)解读函数 build_dataset(cfg.data.train, default_args=None)
args:传来的配置字典里有:数据集的类型,数据集的地址,数据处理10+2
return:最后得到的dataset
是MultiImageMixDataset(**cp_cfg)
一个对象
from mmcv.utilsimport Registr
DATASETS= Registry('dataset')
PIPELINES= Registry('pipeline')#传过来的cfg是train_dataset这个字典defbuild_dataset(cfg, default_args=None):from.dataset_wrappersimport MultiImageMixDataset# 函数isinstance()可以判断一个变量的类型# 当拿到变量 cfg 时,可以使用 isinstance 判断类型:# isinstance(cfg,(list, tuple))的结果是False# cfg 不是(list, tuple)类型 ,而cfg是个字典!!ifisinstance(cfg,(list,tuple)):
dataset= ConcatDataset([build_dataset(c, default_args)for cin cfg])# cfg字典中的键type;值MultiImageMixDataset(多图像混合数据集)elif cfg['type']=='MultiImageMixDataset':
cp_cfg= copy.deepcopy(cfg)#cp_cfg长得和train_dataset一样# build_dataset(cp_cfg['dataset'])应该是build_from_cfgs)# cp_cfg['dataset']里面是一个COCOdataset对象
cp_cfg['dataset']= build_dataset(cp_cfg['dataset'])
cp_cfg.pop('type')#去掉这个键值对#cp_cfg拿到了datasets,pipeline12个
dataset= MultiImageMixDataset(**cp_cfg)elifisinstance(cfg.get('ann_file'),(list,tuple)):
dataset= _concat_dataset(cfg, default_args)else:#返回一个COCOdataset对象
dataset= build_from_cfg(cfg, DATASETS, default_args)return dataset
cocodataset
类位于mmdet/datasets/coco.py
。继承了customdataset
类
customdataset
类位于mmdet/datasets/custom.py
cp_cfg
字典
这个字典里有两个键值对:dataset,pipeline。
cp_cfg = dict(
dataset=是COCOdataset对象
pipeline=train_pipeline)
train_pipeline
:10个处理
train_pipeline = [
1. dict(type='Mosaic', img_scale=img_scale, pad_val=114.0),
2. dict(
type='RandomAffine',
scaling_ratio_range=(0.1, 2),
border=(-img_scale[0] // 2, -img_scale[1] // 2)),
3. dict(
type='MixUp',
img_scale=img_scale,
ratio_range=(0.8, 1.6),
pad_val=114.0),
4. dict(type='YOLOXHSVRandomAug'),
5. dict(type='RandomFlip', flip_ratio=0.5),
# According to the official implementation, multi-scale
# training is not considered here but in the
# 'mmdet/models/detectors/yolox.py'.
6. dict(type='Resize', img_scale=img_scale, keep_ratio=True),
7. dict(
type='Pad',
pad_to_square=True,
# If the image is three-channel, the pad value needs
# to be set separately for each channel.
pad_val=dict(img=(114.0, 114.0, 114.0))),
8. dict(type='FilterAnnotations', min_gt_bbox_wh=(1, 1), keep_empty=False),
9. dict(type='DefaultFormatBundle'),
10. dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels'])
]
最终得到的结果dataset = MultiImageMixDataset(**cp_cfg)
此类位于mmdet/datasets/dataset_wrappers.py
中
@DATASETS.register_module()classMultiImageMixDataset:"""#多图像数据集包装器wrapper.
适用于多幅图像的训练,如混合数据增强 mosaic and mixup.
Args:
dataset (:obj:`CustomDataset`): 要混合的数据集(因为custom是所有类的父类)
pipeline (Sequence[dict]): 数据预处理操作(一个操作是一个字典)
dynamic_scale (tuple[int], optional): 图片动态放缩的比例. Default to None. .
skip_type_keys (list[str], optional): 要跳过的pipeline. Default to None.
"""def__init__(self,
dataset,#键值对1:dataset 对应COCODataset对象
pipeline,#键值对2:pipeline=train_pipeline(这是一个列表,里面有10个字典)
dynamic_scale=None,
skip_type_keys=None):
self._skip_type_keys= skip_type_keys#none
self.pipeline=[]#10个pipeine的处理模块
self.pipeline_types=[]#10个pipeline的typefor transformin pipeline:#每次拿出一个字典#一共有10个字典ifisinstance(transform,dict):
self.pipeline_types.append(transform['type'])#transform:配置字典。PIPELINES:注册表#实例化类对象transform
transform= build_from_cfg(transform, PIPELINES)#实例化这个处理模块
self.pipeline.append(transform)'''
pipeline_types 里面有12个字符串
self.pipeline_types=['Mosaic','RandomAffine','MixUp','YOLOXHSVRandomAug',
'RandomFlip', 'Resize','Pad','FilterAnnotations',
'DefaultFormatBundle', 'Collect']
transform 里面有12个类对象
self.pipeline=[Mosaic,RandomAffine,MixUp,YOLOXHSVRandomAug,
RandomFlip,Resize,Pad,FilterAnnotations,
DefaultFormatBundle, Collect]
'''
self.dataset= dataset#cocodataset对象
self.CLASSES= dataset.CLASSES#就是调用COCOdataset对象中的len方法:结果=annotations/train.json中的数据条数#目前不知道num_samples 是图片个数还是图片里的bbox的个数#len(dataset)=dataset.__len__(),现在知道了应该是图片的索引
self.num_samples=len(dataset)#这个是python的magic方法。例如对一个对象dataset= MultiImageMixDataset(**cp_cfg)#dataset_item = dataset[idx]#等价于dataset = dataset.__getitem__(idx)def__getitem__(self, idx):# 获取当前某一张的图片信息#self.dataset[idx]是调用cocodataset中的_.__getitem__.#1.custome中的.__getitem__#2.custom中的prepare_train_img(self, idx)#3.cocodataset中的load_annotations(self, ann_file):#4.cocodataset中的 get_ann_info(self, idx):#5.custom pre_pipeline(results)#####self.dataset[idx]得到的事经过load1load2这两个pipeline处理的第idx这张图片#####return self.pipeline(results):最终得到的是一个results字典。#self.dataset[idx]就是 那个根据 img的idx 最终得到的 results字典。#6.这个results传进去,再经过10个pipeline处理,得到最终的results字典
results= copy.deepcopy(self.dataset[idx])#custom里得到的那个result字典# 遍历 transorm,其中可以包括 mosaic 、mixup、flip 等各种 transformfor(transform, transform_type)inzip(self.pipeline, self.pipeline_types):# 考虑到某些训练阶段需要动态关闭掉部分数据增强,故引入 _skip_type_keysif self._skip_type_keysisnotNoneand \
transform_typein self._skip_type_keys:continue##如果transform 中含有 get_indexes 方法,就先走这一步。ifhasattr(transform,'get_indexes'):
indexes= transform.get_indexes(self.dataset)ifnotisinstance(indexes, collections.abc.Sequence):
indexes=[indexes]# 得到混合图片信息
mix_results=[
copy.deepcopy(self.dataset[index])for indexin indexes]
results['mix_results']= mix_results#这个results传进去,再经过10个pipeline处理,得到最终的results字典
results= transform(results)#执行transfor的call函数if'mix_results'in results:
results.pop('mix_results')return results
transform
处理
所有的 pipeline处理都针对的是一张图片,最终得到的datasets对象,里面的__getitem__
方法是获得了一张图片的信息。得到一张图片的信息就是一个results字典。
先进行俩pipeline操作得到原始的ressults
Mosaic,RandomAffine,MixUp,YOLOXHSVRandomAug, RandomFlip,Resize,Pad,FilterAnnotations,
DefaultFormatBundle, Collect
经过这些pipeline处理就是对results
的键值对进行增删
最终得到的results字典里面只有4个键值对:img img_meta gtbox gtlabel
如果调用datasets中的_getitem_方法,最后的得到的是collect的输出
collect_results=dict('img':DC(tensor,stacked=True,cpu_only=False),#DC是datacontainer的缩写'img_metas':DC(dict('flip':bool,'ori_shape':tuple,'img_shape':tuple,'pad_shape':tuple,'scale_factor':floator ndarray(4,),'img_norm_cfg':dict('mean':ndarray,'std':ndarray)),stacked=False,cpu_only=True),'gt_bboxes':DC(tensor,stacked=Fasle,cpu_only=False),'gt_labels':DC(tensor,stacked=False,cpu_only=False))
6)mmcv.utils.build_from_cfg
函数定义位于mmcv/mmcv/utils/registry.py
中
defbuild_from_cfg(cfg, registry, default_args=None):"""从配置字典中构建模块.
Args:
cfg (dict): Config dict. 至少包含 "type".这个键值对(模块参数可不用初始但要指定名称)
registry (:obj:`Registry`): 这个registry 来搜寻 type模块在哪
default_args (dict, optional): 默认的初始化参数
Returns:
object: 返回实例化对象
"""# 1.判断输入的参数形式是否正确ifnotisinstance(cfg,dict):raise TypeError(f'cfg must be a dict, but got {type(cfg)}')if'type'notin cfg:raise KeyError(
f'the cfg dict must contain the key "type", but got {cfg}')ifnotisinstance(registry, Registry):raise TypeError('registry must be an mmcv.Registry object, '
f'but got {type(registry)}')ifnot(isinstance(default_args,dict)or default_argsisNone):raise TypeError('default_args must be a dict or None, '
f'but got {type(default_args)}')# 2.将obj_cls设置为config的type对应的class。
args= cfg.copy()# dict.copy()用来返回一个字典的浅复制。里面东西=cfg这个传入的字典
obj_type= args.pop('type')# 从args中删除type这个键值对,obj_type等于type对应的值。#3.obj_cls就是得到了类名if is_str(obj_type):#registry.get(obj_type)是提取名为obj_type的class赋给obj_cls。
obj_cls= registry.get(obj_type)if obj_clsisNone:raise KeyError(
f'{obj_type} is not in the {registry.name} registry')elif inspect.isclass(obj_type):# 检查是否为类
obj_cls= obj_typeelse:raise TypeError(
f'type must be a str or valid type, but got {type(obj_type)}')if default_argsisnotNone:for name, valuein default_args.items():# dict.setdefault():如果字典中包含有给定键,则返回该键对应的值,否则返回为该键设置的值。
args.setdefault(name, value)return obj_cls(**args)#返回对象(且里面有配置字典所给定的初始值)
3.mmdet/datasets/builder.py/build_dataloader(一堆参数)
1)传来的参数
#最终得到的data_loaders是一个列表,里面是一堆dataloader#[]之进行一次循环,里面只有一个元素,就是一个dataloader对象
data_loaders=[
build_dataloader(
ds,
cfg.data.samples_per_gpu,#8 batchsize
cfg.data.workers_per_gpu,#4 线程# `num_gpus` will be ignored if distributed
num_gpus=len(cfg.gpu_ids),#1
dist=distributed,#False:非分布式训练
seed=cfg.seed,#0
runner_type=runner_type,#EpochBasedRunner#好像不重要,应该是false。关于pytorch版本的东西
persistent_workers=cfg.data