MMDetection2

概述
1.
2.

MMDetection 是一个基于 PyTorch 的目标检测开源工具箱。
提供了各种检测算法，数据集构建方法等
MMCV 是一个面向计算机视觉的基础库，为Mmdet提供底
层支持，包括通用IO接口，配置文件解析功能，注册器机制，hook机制，以及统筹全局训练测试流程的runner机制

整体框架Overall framework

配置相关初始化

Mmdetection2训练流程 —— 读取Config
训练的入口文件为tools/train.py 一条简单启动命令如下：
Python tools/train.py configs/mask_rcnn/mask_rcnn_r50_fpn_1x_coco.py
这个python文件里面的内容为：而train.py定义了parse_args()函数读取命令行参数，保存进参数args中。
使用mmdetection的config类，读取_base_列表中的每个配置文件，依次涉及model，dataset，评估方法、优化器、lr以及runner，ckpt等的设置读取Config
其他配置文件之外的参数还包括是否开启cudnn加速，workdir等
如果是分布式训练，要调用mmcv.runner封装的init_dist函数，和pytorch的分布式
训练机制很像。命令行参数中还要加入GPU数量
init_dist(args.launcher, **cfg.dist_params)_, world_size = get_dist_info()
cfg.gpu_ids = range(world_size)
大致逻辑即设节点数为n，gpu数为g，进程总数为word_size = n*g，设置用于同步所有进程的主进程
os.environ['MASTER_ADDR'] = '10.57.23.164'
os.environ['MASTER_PORT'] = '8888'
mp.spawn(train, nprocs=args.gpus, args=(args,)) #用于生成每个进程读取Config在init_dist函数中运行 distributed.init_process_group
这个函数需要知道如何找到进程0（process 0），一边所有的进程都可以同步，也知道了
一共要同步多少进程。每个独立的进程也要知道总共的进程数，以及自己在所有进程中的阶序（rank）,当然也要知道自己要用那张GPU在之后定义dataLoader时，要用到分布式Sampler以实现每块GPU分到数据集的单独一部
分相关类初始化Build model
model = build_detector(cfg.model,train_cfg=cfg.get('train_cfg'),
test_cfg=cfg.get('test_cfg’))调用了mmdet.model.build_detector,实际上是调用了Registry类的build函数以返回一个Detector类的实例
Registry类可以理解为一个字典，key是类名，value是类对象
用法示例：
CATS = mmcv.Registry('cat')# 通过装饰器方式作用在想要加入注册器的具体类中
@CATS.register_module()class BritishShorthair:pass# 类实例化
CATS.get('BritishShorthair')(**args)

所有的backbone，neck，head，roi_extractor，detector，各类型dataset，优化器，取样器等，全部由registry管理

Init model weight
Model.init_weight()
所有model都是BaseModule的子类，而BaseModule则是nn.Module的子类，在其基础上加入了init_weight方法
逻辑为for i in module.children():initialize(i，init_cfg)

Initialize方法中会根据init_cfg的内容，实例化一个initializer，如：module = nn.Linear(2, 3, bias=True)
init_cfg = dict(type='Constant', layer='Linear', val =1 , bias =2)
initialize(module, init_cfg)

Build dataset
datasets = [build_dataset(cfg.data.train)]build_dataset同样是调用Registry的build函数，返回一个数据集类实例，maskrcnn即CocoDataset。
所有的Dataset类的基类为CustomDataset，而它又继承自torch.utils.data.Dataset
根据cfg中第路径将数据加载进来后，很重要的一部分是将数据传入pipelineDataset
CoCo：Build dataset
Datasets.data_infos = load_annotations(ann_file)Build dataset
self._set_group_flag()self.pipeline = Compose(pipeline)

Build dataset

Build dataset

Build dataset

Build dataset

Build dataset
Mmdetection定义了DataContainer类用来包装Tensor变量，原因为：通常情况下，为了组成batch，要把Tensor叠加起来，局限性是：
1.所有张量的大小必须相同。2.类型有限（numpy数组或张量）。
在detection任务中，一个图片具有的实例，bbox数量都不相同，而训练batch中是以图
片为单位的，利用DataContainer包装Tensor可以克服这样的局限性

Build dataset
datasets[0]train_detector(model,datasets,cfg,
distributed=distributed,
validate=(not False),
timestamp=timestamp,meta=meta)
接下来就要进入到mmdet.apis.train_detector方法中，以下的内容就不在
tools/trian.py中Build DataLoader
data_loaders = [build_dataloader(ds,cfg.data.samples_per_gpu, #每个batch每GPU分配多少数据
cfg.data.workers_per_gpu, #加载数据的进程数# cfg.gpus will be ignored if distributedlen(cfg.gpu_ids),
dist=distributed,
seed=cfg.seed) for ds in dataset]
有几个workflow就定义几个data_loader, workflow可以单有train，也可以train，val。
build_dataloader方法在mmdet.dataset.builder.py中定义逻辑即根据GPU的标号RANK，定义DistributedGroupSampler来分组采样数据，最后调用torch.utils.data.DataLoader

Build DataLoader
data_loader = DataLoader(dataset,batch_size=batch_size,
sampler=sampler,
num_workers=num_workers,
collate_fn=partial(collate, samples_per_gpu=samples_per_gpu), #用来将数据组成
Batch的函数，collate为mmcv中针对dataContainer进行组batch的函数
pin_memory=False,
worker_init_fn=init_fn,**kwargs)

DDP

model = MMDistributedDataParallel(model.cuda(),device_ids=[torch.cuda.current_device()],broadcast_buffers=False,
find_unused_parameters=find_unused_parameters)

将模型封装为一个 DistributedDataParallel 模型。这将把模型复制到GPU上进行处理。
MMDistributedDataParallel是pytorch中DDP的子类，可以支持dataContainer并且定义了train_step,Val_step等方法。

Build optimizer
optimizer = build_optimizer(model, cfg.optimizer)

runner初始化

Build Runnerrunner = build_runner(cfg.runner,default_args=dict(model=model,optimizer=optimizer,
work_dir=cfg.work_dir,logger=logger,
meta=meta))
Runner分为EpochBasedRunner和IterBasedRunner，他们都继承自BaseRuner，初始化并没有具体的操作
只是填充一些属性值，其中很关键的有self._hook= []，之后会向其中填充各hook实例Build RunnerRegistry hookrunner.register_training_hooks(cfg.lr_config, cfg.optimizer_config,cfg.checkpoint_config, cfg.log_config,cfg.get('momentum_config', None))

Registry hook

MMdetection中的HOOK可以理解为一种触发器，它规定了在算法训练过程中的种种操作
每个继承自HOOK基类的hook子类，都要实现
def before_run(self, runner)
def after_run (self, runner)def before_epoch (self, runner)
def after_epoch (self, runner)def before_iter(self, runner)
def after_iter(self, runner)
等一系列方法Mmdet内置的hook类通过runner中的register_training_hooks方法，按照事先定好的hook优先级填充进runner._hook列表中自定义的hook类通过register_hook方法注册在训练过程中的特定位置，只需要调用runner.call_hook(“before_run”)，便可以按照优先级，调用
_hook列表中所有hook实例的before_run方法

Registry hook举一个hook运行的具体实例：
After_train_iter的optimizerHook，他会进行反向传播，参数更新@HOOKS.register_module()class OptimizerHook(Hook):
def __init__(self, grad_clip=None):self.grad_clip = grad_clip
def after_train_iter(self, runner):runner.optimizer.zero_grad()runner.outputs['loss'].backward()
if self.grad_clip is not None:
grad_norm = self.clip_grads(runner.model.parameters())
runner.optimizer.step()

Registry hookRunner运行

Run

Run
runner.run(data_loaders, cfg.workflow)

run 方法调用后才是真正开启工作流。设置 workflow = [('train', 3), ('val',1)]，表示先训练 3 个 epoch ，然后切换到
val 工作流，运行 1 个 epoch，然后循环，直到训练 epoch 次数达到指定值对于 EpochBasedRunner，train模式调用runner.train方法，def train(self, data_loader, **kwargs):
self.data_loader = data_loader
self._max_iters = self._max_epochs * len(self.data_loader)
self.call_hook('before_train_epoch')for i, data_batch in enumerate(self.data_loader):self._inner_iter = i
self.call_hook('before_train_iter')
self.run_iter(data_batch, train_mode=True, **kwargs)
self.call_hook('after_train_iter')
self._iter += 1
self.call_hook('after_train_epoch')self._epoch += 1

Runner.run_iterdef run_iter(self, data_batch, train_mode, **kwargs):
if train_mode:# 对于每次迭代，最终是调用如下函数
outputs = self.model.train_step(data_batch,...)else:# 对于每次迭代，最终是调用如下函数
outputs = self.model.val_step(data_batch,...)
if 'log_vars' in outputs:
self.log_buffer.update(outputs['log_vars'],...)
self.outputs = outputs

此处model.train_step，是指的 MMDataParallel 或者 MMDistributedDataParallel包装后的model