mmdetection-yolox

2022-10-31 09:59:43

mmdetection-yolox

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:最后得到的datasetMultiImageMixDataset(**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
  • 作者:磨磨dfhu叽叽
  • 原文链接:https://blog.csdn.net/weixin_41179162/article/details/122642506
    更新时间:2022-10-31 09:59:43