学习资料A. MMDet组件整体介绍【博文】《轻松掌握 MMDetection 整体构建流程(一)》系统性地介绍了MMDet中的各种组件例如Backbone, Neck, Head和Enhance等等。1. 安装1.1 新建conda环境openmmlabconda create--nameopenmmlabpython3.10-y1.2 使用国内镜像安装mimpipinstall-Uopenmim-ihttps://pypi.tuna.tsinghua.edu.cn/simple-U即--upgrade表示安装指定包最新可用的版本。Note:“这里为什么要加上“-U”这个安装选项呢”使用-U选项会强制升级已经安装的包如果不使用这个选项pip只会安装新的依赖包而不会升级已经安装的包。在这里使用“-U”选项是为了确保已经安装的openmim包可以被升级到最新版本以便安装MMCV和MMEngine的最新版本。1.3 用mim安装mmcvmim install mmcv-full使用国内镜像进行安装mim install mmcv-full-i https://pypi.tuna.tsinghua.edu.cn/simple如果mim安装出现重复下载多个mmcv-full版本的问题改用pip安装Mmcv官方安装文档《MMCV文档 | 安装 MMCV》安装CPU平台的MMCVpipinstallmmcv-full-fhttps://download.openmmlab.com/mmcv/dist/cpu/torch_version/index.html例如安装基于torch1.12.1版本CPU平台的MMCVpipinstallmmcv-full-fhttps://download.openmmlab.com/mmcv/dist/cpu/torch1.12.1/index.html1.4 下载MMDet项目源代码gitclone https://github.com/open-mmlab/mmdetection.git mm-project1.5 本地编译安装使用清华源pipinstall-v-e.-ihttps://pypi.tuna.tsinghua.edu.cn/simple如果不用本地安装的方式可以采用“export PYTHONPATH 中加入 mmdet 的路径用法也行”也可以试一下看看“sys.append”的方式看看行不行。2 目录结构configs配置文件目录例如configs/centernet/centernet_resnet18_dcnv2_140e_coco.py。3 CookBook3.1 BasicsEpoch从1开始计数3.2 模型训练CPU训练tools/train.py示例python tools/train.py configs/centernet/centernet_resnet18_dcnv2_140e_coco.py单GPU训练tools/train.pypython tools/train.py\CONFIG_FILE\[optional arguments]多GPU训练tools/dist_train.shbash./tools/dist_train.sh\${CONFIG_FILE}\${GPU_NUM}\[optional arguments]CONFIG_FILE模型配置GPU_NUM显卡数量3.2 固定随机种子固定模块参数初始化的随机种子--seed需要在运行train.py时加上--seed参数可以保证每次运行时module的初始参数相同Note在论文复现时可以查看 MMDet-repo 中给出的log文件查看模型在原始训练时使用的seed参数。3.3 日志分析tools/analysis_tools/analyze_logs.py绘制参数曲线 —plot_curvepython tools/analysis_tools/analyze_logs.py plot_curve $LOG_FILE[--keys ${KEYS}][--eval-interval ${EVALUATION_INTERVAL}][--title ${TITLE}][--legend ${LEGEND}][--backend ${BACKEND}][--style ${STYLE}][--out ${OUT_FILE}]LOG_FILE日志的.json文件示例如下./work_dirs/config_name/yyyymmdd_hhmmss.log.json--keys ${KEYS}打印的参数名关键字例如bbox_mAP--out ${OUT_FILE}输出图像的文件名例如mAP.png--title ${TITLE}图像标题Noteplot_curve会根据输出文件名自动生成该格式的图像文件这一点跟OpenCV的imwrite()是类似的。使用plot_curve比较两个模型的运行指标python tools/analysis_tools/analyze_logs.py plot_curve log1.json log2.json--keysbbox_mAP--legendrun1 run2Legend图例4 配置文件model_backbone_epoch_*.py可以使用 Config ViewVSCode插件来查看完整的配置参数4.1workflow工作流程示例说明当配置为workflow [(train, n),(val, 1)]表示先进行n个epoch的训练然后再进行1个epoch的验证然后循环往复,如果写成 [(‘val’, 1),(‘train’, n)] 表示先进行验证然后再开始训练。4.2model模型4.2.1model.backbone主干网络(1) backbone.type主干网络的类名属于mmdetection/mmdet/models/backbones/__init声明的类之一其它参数则是传入到backbone构造函数中的形参(2) frozen_stages在训练时冻结的stages例如ResNet 结构包括 stem 4-stagesfrozen_stages-1表示全部可学习frozen_stages0表示stem权重固定frozen_stages1表示stem和第一个stage权重固定frozen_stages2表示stem和前两个stage权重固定自定义主干网络关于自定义网络可以参考mmdet的官方文档ResNet主干网络class__init__()初始化函数forward()前向运算函数3.2data数据读取data.samples_per_gpu每个GPU上载入的样本数量。data.train训练数据train.dataset数据集设置train.dataset.type属于mmdetection/mmdet/datasets声明的类之一例如CocoDatasettrain.dataset.ann_filetrain标注.json文件train.dataset.img_prefix训练图像文件夹train.dataset.pipeline训练预处理流程train.dataset.filter_empty_gtFalse表示不会过滤无标注图像Note在默认设置下未使用filter_empty_gtFalse的情况在dataset读数据的时候过滤掉无标注图像的图像使用“fliter data”的操作过滤掉这些图像样本不参加训练。3.3lr_config学习率设置3.3.1policy学习率策略step表示StepLrUpdaterHook在使用epoch设置训练周期的情况下step填入的参数以epoch计数3.4optimizer_config优化器设置Centernet_dcnv2使用了默认的优化器设置[SGD]优化器的类名也会与type的名称保持一致可以在对应文件夹中查找3.5checkpoint_config检查点设置例如checkpoint_config dict(interval1)表示每间隔1个epoch保存一次3.6log_config日志设置log_config.interval每间隔n次输出一次状态信息5 常用命令单GPU训练[mmdet-doc]# CONFIG模型配置文件python tools/train.py${CONFIG}6 数据增广data_pipeline关于每个数据增广操作对数据结构具体的更新情况请参考《MMDet-doc | Data loading》6.1Expand搭配Resize使用实现缩放效果关于Expand的效果图请参考《mmdetection中数据增强的可视化 | 八、Expand》6.2RandomFlip随机翻转图像Note:如果想要停用RandomFlip操作不能直接在代码中注释掉而需要将flip_ratio设成0即flip_ratio0.0注意这里用的是0.0因为函数要求flip_ratio参数是float格式。6.3Normalize颜色分量归一化对于meanstd的颜色顺序跟to_rgb参数有关颜色顺序转换meanstd颜色顺序to_rgbTrueRGBdefaultBGR6.4 自定义增广操作[mmdet-CustomizeDataPipelines]关于自定义增广操作的示例可以参考《数据增强神器 SimpleCopyPaste 支持全流程》7 自定义数据集[mmdet]示例标注images:[{file_name:COCO_val2014_000000001268.jpg,height:427,width:640,id:1268},...],annotations:[{segmentation:[[192.81,247.09,...219.03,249.06]],#ifyou have mask labelsarea:1035.749,iscrowd:0,image_id:1268,bbox:[192.81,224.8,74.73,33.43],category_id:16,id:42986},...],categories:[{id:0,name:car},]自定义数据集代码示例# 定义数据集字典coco_output{images:[],categories:[],annotations:[]}categories[{id:1,name:...},]8 MMDet模块化设计原理8.1 Config实现字典与类属性的自动转换MMDet知乎教程《MMCV 核心组件分析(四): Config》8.1.1 通过dict生成configcfgConfig(dict(a1,bdict(b1[0,1])))# 可以通过 .属性方式访问比较方便cfg.b.b1# [0, 1]8.2 Registry实现type字符串到模块类名的自动映射Registry – mmcv用于接收字典参数进行模块构造的工厂类。关于Registry实现原理的介绍请参考MMLab视频教程《社区开放麦#9 | OpenMMLab 模块化设计背后的功臣》Registry机制实现了type字符串到模块类名的自动映射而不需要手动填写字典条目即self._module_dict[cls_obj.__name__]cls_obj8.2 Hook实现训练过程的非侵入式功能扩展MMDet-1.0版本的Hook点位如图所示MMLab-2.0版本中对Hook整体结构的设计进行了一定的更新这里我们咨询了MMDet的叶老师叶老师首先 1.0-val 里的点位在实际训练的过程中是很多是走不到的因为验证会调用Evalhook而在 Evalhook 里再去调用别的hook也不合适。其次2.0中除了图示里提到的点位train和val的点位都会起作用还新增了一些点位例如 before_train, after_load_checkpoint 等具体差异可以参考https://github.com/open-mmlab/mmengine/blob/main/docs/zh_cn/migration/hook.mdHook函数说明IterTimerHook用于计时的Hook类IterTimerHook: [mmcv/runner/hooks/iter_timer.py]IterTimerHook的计时功能最终会在EpochBasedRunner的train()函数中调用来输出每个iteration计算时长deftrain(self,data_loader,**kwargs):...fori,data_batchinenumerate(self.data_loader):self.data_batchdata_batch self._inner_iteri self.call_hook(before_train_iter)self.run_iter(data_batch,train_modeTrue,**kwargs)self.call_hook(after_train_iter)# iter_timer_hook.after_iter()会在after_train_iter中被调用delself.data_batch self._iter1self.call_hook(after_train_epoch)self._epoch18.3 训练和测试整体代码抽象流程8.3.1 训练和验证整体流程MMDet知乎教程《轻松掌握 MMDetection 整体构建流程(二)训练和测试流程深入解析》训练脚本tools/train.py初始化配置cfg Config.fromfile(args.config)初始化 loggerlogger get_root_logger(log_filelog_file, log_levelcfg.log_level)收集运行环境并且打印方便排查硬件和软件相关问题env_info_dict collect_env()初始化 modelmodel build_detector(cfg.model, …)初始化 datasetsdatasets [build_dataset(cfg.data.train)]进入训练器函数流程train_detector(model, datasets, cfg, distributeddistributed, …)train_detector是训练检测器的流程函数在文件起始处的声明为from mmdet.apis import init_random_seed, set_random_seed, train_detector根据import语句引用路径跟踪到 train_detector是位于mmdet/apis/train.py文件中的函数MMDet核心逻辑实现 mmdet/apis/train.py构造 data_loaders内部会初始化 GroupSamplerdata_loaders [build_dataloader(ds, **train_loader_cfg) for ds in dataset]查看是否使用分布式训练初始化对应的 DataParallel初始化 runner一般常用的就是EpochBasedRunnerrunner build_runner(…)mmcv.build_runner(…) ⇒ runner_constructor() ⇒ runner dict(type‘EpochBasedRunner’, …)注册hooksrunner.register_training_hooks(…, custom_hooks_configcfg.get(‘custom_hooks’, None))如果在训练中进行val则还需要注册eval_hookrunner.register_hook( eval_hook(val_dataloader, **eval_cfg), priority‘LOW’)权重恢复和加载 [source]运行开始训练runner.run(data_loaders, cfg.workflow)Runner.run()即是EpochBasedRunner.run()Build runner调用过程示意图Runner训练和验证流程EpochBasedRunner.run()EpochBasedRunner.run()在内部会调用self.train()之后即是Model具体的训练流程如图所示BaseDetector: train_step() | val_step()其核心代码如下所示[mmdet/models/detectors/base.py::BaseDetector]deftrain_step(self,data,optimizer):# 调用本类自身的 forward 方法lossesself(**data)# 解析loss内部包含了求和操作loss,log_varsself._parse_losses(losses)# 返回字典对象outputsdict(lossloss,log_varslog_vars,num_sampleslen(data[img_metas]))returnoutputsdefforward(self,img,img_metas,return_lossTrue,**kwargs):ifreturn_loss:# 训练模式returnself.forward_train(img,img_metas,**kwargs)else:# 测试模式returnself.forward_test(img,img_metas,**kwargs)# forward_train()和forward_test()需要在子类检测器中实现# 两个函数会在对应模式下输出结果# forward_train() ⇒ Loss# forward_test() ⇒ 预测结果SubDetector: forward_train()目前提供了两个检测器子类TwoStageDetector和SingleStageDetector分别用于实现 two-stage 和 single-stage 算法。其中CenterNet是基于SingleStageDetector实现的所以这里我们介绍一下SingleStageDetector的前向训练过程[mmdet/models/detectors/single_stage.py::SingleStageDetector]defforward_train(...):super(SingleStageDetector,self).forward_train(img,img_metas)# 先经过 backbone neck 进行特征提取xself.extract_feat(img)# 调用 bbox_head的 forward_train()方法lossesself.bbox_head.forward_train(x,...)returnlosses8.3.2 测试流程调用MMDataParallel或MMDistributedDataParallel中的forward()方法调用base.py中的forward()方法调用 base.py 中的 self.forward_test 方法如果是单尺度测试则会调用TwoStageDetector或 SingleStageDetector 中的 simple_test方法如果是多尺度测试则调用 aug_test 方法最终调用的是每个具体 Head 模块的simple_test()或者aug_test()方法one-stage和 two-stage的head调用逻辑有些区别Note在MMDet中检测器算法解码操作的核心逻辑是在Head中实现具体则是在simple_test()或aug_test()方法中编写。8.4 Head模型结构损失函数后处理MMDet知乎教程《轻松掌握 MMDetection 中 Head 流程》8.4.1 训练流程上文说到SingleStageDetector会调用self.bbox_head.forward_train()它是Head模块在训练流程中最外层的接口函数于是我们先看看CenterNetHead类的集成关系图可以看到CenterNetHead是直接继承于BaseDenseHeadBaseDenseHeadBaseDenseHead基类比较简单于是anchor-based 和 anchor-free 算法会进一步继承派生得到AnchorHead或者 AnchorFreeHead 类对于BaseDenseHead.forward_train():defforward_train(self,x,img_metas,gt_bboxes,gt_labelsNone,gt_bboxes_ignoreNone,proposal_cfgNone,**kwargs):# 调用子类实现的forward()方法outsself(x)ifgt_labelsisNone:loss_inputsouts(gt_bboxes,img_metas)else:loss_inputsouts(gt_bboxes,gt_labels,img_metas)# 调用子类实现的loss()方法lossesself.loss(*loss_inputs,gt_bboxes_ignoregt_bboxes_ignore)ifproposal_cfgisNone:returnlosseselse:# two-stage算法还需要返回proposalsproposal_listself.get_bboxes(*outs,img_metasimg_metas,cfgproposal_cfg)returnlosses,proposal_list子类Head一般不会重写forward_train()但是它们都会重写forward()和loss()方法其中6.forward()方法用于运行head网络部分输出分类回归分支的特征图7.loss()方法接收forward()输出并且结合label计算loss值。AnchorHead训练Note:TTFHead直接继承于AnchorHead对于AnchorHead其主要是封装了anchor的生成过程。首先我们看看forward()函数的主要逻辑HEADS.register_module()classAnchorHead(BaseDenseHead,BBoxTestMixin):# BBoxTestMixin 是多尺度测试时候调用defforward(self,feats):# 对每张特征图单独计算预测输出returnmulti_apply(self.forward_single,feats)# 单尺度分支的分类回归输出defforward_single(self,x):cls_scoreself.conv_cls(x)bbox_predself.conv_reg(x)returncls_score,bbox_predLoss计算的时序关系图[mmdetv2-view/pumls/head_scope.puml]8.4.2 测试流程前面说过在测试流程中最终会调用 Head 模块的simple_test()或aug_test()方法分别进行单尺度或多尺度测试涉及到具体代码层面one-stage 和 two-stage 的函数调用有区别但最终调用的依然是Head模块的get_bboxes()方法AnchorHead测试One-stage检测器在单尺度模式下会直接调用self.bbox_head.get_bboxes()方法其中基于AnchorHead实现的调用流程是遍历每个特征尺度输出分支利用nms_pre配置参数对该层预测结果按照scores值进行从大到小进行topk截取保留scores最高的前nms_pre的预测结果对保留的预测结果进行bbox解码还原操作还原到最原始图片尺度如果需要进行nms则对所有分支预测保留结果进行统一nms即可否则直接属于多尺度预测结果。多尺度测试除了RPN算法的多尺度测试是在mmdet/models/dense_heads/rpn_test_mixin.py中实现外其余 Head 多尺度测试都是在mmdet/models/dense_heads/dense_test_mixins.py::BBoxTestMixin中实现其思路是对多尺度图片中每张图片单独运行get_bboxes()然后还原到原图尺度最后把多尺度图片预测结果合并统一进行nms。BBox编解码关于MMDet中RetinaNet的编解码操作请参考《轻松掌握 MMDetection 中常用算法(一)RetinaNet 及配置详解》9 函数和类说明9.1mmdet.apismmdet的核心函数apis.train_detector()训练过程主要函数train_detector()内部会调用runner.run()来执行主要的训练过程。参数说明model检测器。dataset数据集对象。变量说明cfg【Config】配置信息对象。Cfg配置信息Cfg.runner运行信息字典9.2mmdet\models模型库BACKBONES主干网络注册器用于主干网络的注册即BACKBONES.register_module()9.3mmdet.datasets数据集读取处理9.3.1CustomDataset自定义数据集prepare_train_img()读取训练图像9.4mmdet.runner运行脚本库Runner.EpochBasedRunner[EpochBasedRunner from mmcv/runner/epoch_based_runner.py]run()进行一次workflow指定的训练过程变量说明epoch_runner【EpochBasedRunner.Method】对应self.train() | self.val()函数。9.3 HeadCenterNetHead继承关系nn.Module ⇒ mmcv.*.BaseModule ⇒ mmdet.*.BaseDenseHead ⇒ CenterNetHead10 自定义Hook官方教程How to implement a custom hook MMDetection documentation11 提交PR请参考博文《【MMDet】提交PR的学习笔记》12 MMDet-docs调试笔记MMDet-docs在调试时其python环境是可以单独配置的关于配置MM-docs编译环境的教程请参考[MMEngine | Build Documentation]不过在调试过程中遇到了编译不成功的问题正在运行 Sphinx v4.0.2创建输出目录… 完成myst v0.18.1: MdParserConfig(commonmark_onlyFalse, gfm_onlyFalse, enable_extensions[‘colon_fence’], disable_syntax[], all_links_externalFalse, url_schemes(‘http’, ‘https’, ‘mailto’, ‘ftp’), ref_domainsNone, highlight_code_blocksTrue, number_code_blocks[], title_to_headerFalse, heading_anchors3, heading_slug_funcNone, footnote_transitionTrue, words_per_minute200, sub_delimiters(‘{’, ‘}’), linkify_fuzzy_linksTrue, dmath_allow_labelsTrue, dmath_allow_spaceTrue, dmath_allow_digitsTrue, dmath_double_inlineFalse, update_mathjaxTrue, mathjax_classes‘tex2jax_process|mathjax_process|math|output_area’)Traceback (most recent call last):File “/home/***/mmdetection/docs/en/./stat.py”, line 31, inassert len(_papertype) 0AssertionError构建 [mo] 0 个 po 文件的目标文件已过期构建 [html] 29 个源文件的目标文件已过期更新环境: [新配置] 已添加 290 已更改0 已移除阅读源… [ 3%] 1_exist_data_modelExtension error (sphinx_markdown_tables):Handler function process_tables at 0x7f3e3a519670 for event ‘source-read’ threw an exception (exception:init() takes 2 positional arguments but 3 were given)make: *** [Makefile:20html] 错误 2我们已经在 MMDet-Github-Discussions上提出了问题“Docs making error: mmdet/docs/en/./stat.py, line 31, in assert len(_papertype) 0 AssertionError #9029”不过目前还没有收到回复13 MMYOLO模型结构图请参考博文《【MMDetection】MMYOLO模型结构图的学习笔记》14 学习笔记14.1 MMDet中有的import语句引入多个关键字时加了“()”例如“from … import (DistSamplerSeedHook, EpochBasedRunner, …)”这是什么特殊的用法吗关于import导入时加入小括号的作用请参考博文《python导入时小括号大作用》简单来说其原因是有时需要导入的函数比较多会超过一行80个字符的PEP建议。于是PEP建议使用标准分组机制也就是用小括号括起来例如from os import (name,getcwd)这种方式就可以分成多行了。