diff --git a/README.md b/README.md index 98d2f964..5300c747 100644 --- a/README.md +++ b/README.md @@ -230,6 +230,7 @@ For different parts from MMDetection, we have also prepared user guides and adva - [Learn about configs with YOLOv5](docs/en/tutorials/config.md) - [Data flow](docs/en/tutorials/data_flow.md) - [Custom Installation](docs/en/tutorials/custom_installation.md) +- [Common Warning Notes](docs/zh_cn/tutorials/warning_notes.md) - [FAQ](docs/en/tutorials/faq.md) diff --git a/README_zh-CN.md b/README_zh-CN.md index 4c9c5201..a378415b 100644 --- a/README_zh-CN.md +++ b/README_zh-CN.md @@ -251,6 +251,7 @@ MMYOLO 用法和 MMDetection 几乎一致,所有教程都是通用的,你也 - [学习 YOLOv5 配置文件](docs/zh_cn/tutorials/config.md) - [数据流](docs/zh_cn/tutorials/data_flow.md) - [自定义安装](docs/zh_cn/tutorials/custom_installation.md) +- [常见警告说明](docs/zh_cn/tutorials/warning_notes.md) - [常见问题](docs/zh_cn/tutorials/faq.md) diff --git a/docs/en/index.rst b/docs/en/index.rst index 5516b619..175f166b 100644 --- a/docs/en/index.rst +++ b/docs/en/index.rst @@ -70,6 +70,7 @@ You can switch between Chinese and English documents in the top-right corner of tutorials/config.md tutorials/data_flow.md tutorials/custom_installation.md + tutorials/warning_notes.md tutorials/faq.md diff --git a/docs/en/tutorials/warning_notes.md b/docs/en/tutorials/warning_notes.md new file mode 100644 index 00000000..54ed973d --- /dev/null +++ b/docs/en/tutorials/warning_notes.md @@ -0,0 +1 @@ +# Common Warning Notes diff --git a/docs/zh_cn/index.rst b/docs/zh_cn/index.rst index 1138e9c3..1f27366c 100644 --- a/docs/zh_cn/index.rst +++ b/docs/zh_cn/index.rst @@ -70,6 +70,7 @@ tutorials/config.md tutorials/data_flow.md tutorials/custom_installation.md + tutorials/warning_notes.md tutorials/faq.md diff --git a/docs/zh_cn/recommended_topics/troubleshooting_steps.md b/docs/zh_cn/recommended_topics/troubleshooting_steps.md index 189a9115..7cca926f 100644 --- a/docs/zh_cn/recommended_topics/troubleshooting_steps.md +++ b/docs/zh_cn/recommended_topics/troubleshooting_steps.md @@ -1 +1,109 @@ # 常见错误排除步骤 + +本文档收集用户经常碰到的常见错误情况,并提供详细的排查步骤。如果你发现阅读本文你没有找到正确的解决方案,请联系我们或者提 PR 进行更新。提 PR 请参考 [如何给 MMYOLO 贡献代码](../recommended_topics/contributing.md) + +## xxx is not in the model registry + +这个错误信息是指某个模块没有被注册到 model 中。 这个错误出现的原因非常多,典型的情况有: + +1. 你新增的模块没有在类别前面加上注册器装饰器 @MODELS.register_module() +2. 虽然注册了,但是注册错了位置,例如你实际想注册到 MMYOLO 中,但是你导入的 MODELS 是 MMDet 包里面的 +3. 你注册了且注册正确了,但是没有在对应的 `__init__.py` 中加入导致没有被导入 +4. 以上 3 个步骤都确认没问题,但是你是新增 py 文件来自定义模块的却没有重新安装 MMYOLO 导致没有生效,此时你可以重新安装一遍,即使你是 -e 模式安装也需要重新安装 +5. 如果你是在 mmyolo 包路径下新增了一个 package, 除上述步骤外,你还需要在 [register_all_modules](https://github.com/open-mmlab/mmyolo/blob/main/mmyolo/utils/setup_env.py#L8) 函数中增加其导包代码,否则该 package 不会被自动触发 +6. 你的环境中有多个版本 MMYOLO,你注册的和实际运行的实际上不是同一套代码,导致没有生效。此时你可以在程序运行前输入 `PYTHONPATH="$(dirname $0)/..":$PYTHONPATH` 强行使用当前代码 + +## loss_bbox 始终为 0 + +该原因出现主要有两个原因: + +1. 训练过程中没有 GT 标注数据 +2. 参数设置不合理导致训练中没有正样本 + +第一种情况出现的概率更大。 `loss_bbox` 通常是只考虑正样本的 loss,如果训练中没有正样本则始终为 0。如果是第一种原因照成的 `loss_bbox` 始终为 0,那么通常意味着你配置不对,特别是 dataset 部分的配置不正确。 +一个非常典型的情况是用户的 `dataset` 中 `metainfo` 设置不正确或者设置了但是没有传给 dataset 导致加载后没有找到对应类别的 GT Bbox 标注。 这种情况请仔细阅读我们提供的 [示例配置](https://github.com/open-mmlab/mmyolo/blob/main/projects/misc/custom_dataset/yolov5_s-v61_syncbn_fast_1xb32-100e_cat.py#L27) 。 +验证 dataset 配置是否正确的一个最直接的途径是运行 [browse_dataset 脚本](https://github.com/open-mmlab/mmyolo/blob/main/tools/analysis_tools/browse_dataset.py),如果可视化效果正确则说明是正确的。 + +## MMCV 安装时间非常久 + +这通常意味着你在自己编译 MMCV 而不是直接下载使用我们提供的预编译包。 MMCV 中包括了大量的自定义的 CUDA 算子,如果从源码安装则需要非常久的时间去编译,并且由于其安装成功依赖于严格的底层环境信息,需要多个库的版本一致才可以。如果用户自己编译大概率会失败。 +我们不推荐用户自己去编译 MMCV 而应该优先选择预编译包。如果你当前的环境中我们没有提供对应的预编译包,那么建议你可以快速换一个 Conda 环境,并安装有预编译包的 Torch。 以 torch1.8.0+cu102 为例,如果你想查看目前查看所有的预编译包,可以查看 https://download.openmmlab.com/mmcv/dist/cu102/torch1.8.0/index.html。 + +## 基于官方配置继承新建的配置出现 unexpected keyword argument + +这通常是由于你没有删除 base 配置中的额外参数。 可以在你新建配置中的修改字典中增加 `__delete__=True` 删掉 base 中该类之前的所有参数。 + +## The testing results of the whole dataset is empty + +这通常说明训练效果太差导致网络没有预测出任何符合阈值要求的检测框。 出现这种现象有多个原因,典型的为: + +1. 当前为前几个 epoch,网络当前训练效果还较差,等后续训练久一点后可能就不会出现该警告了 +2. 配置设置不正确,网络虽然正常训练但是实际上无效训练,例如前面的 `loss_bbox` 始终为 0 就会导致上述警告 +3. 超参设置不合理 + +## ValueError: not enough values to unpack(expected 2, got 0) + +这个错误通常是在 epoch 切换时候出现。这是 PyTorch 1.7 的已知问题,在 PyTorch 1.8+ 中已经修复。如果在 PyTorch 1.7 中想修复这个问题,可以简单的设置 dataloader 参数 `persistent_workers` 为 False。 + +## ValueError: need at least one array to concatenate + +这个是一个非常场景的错误,可能出现在训练一开始或者训练正常但是评估时候。不管出现在何阶段,均说明你的配置不对,最常见的错误就是 `num_classes` 参数设置不对。 +在 MMYOLO 或者 MMDet 中大部分配置都是以 COCO 数据为例,因此配置中默认的 `num_classes` 是 80, 如果用户自定义数据集没有正确修改这个字段则会出现上述错误。 +MMYOLO 中有些算法配置会在多个模块中都需要 `num_classes` 参数,用户经常出现的错误就是仅仅修改了某一个地方的 `num_classes` 而没有将所有的 `num_classes` 都修改。想快速解决这个问题,可以使用 [print_config](https://github.com/open-mmlab/mmyolo/blob/main/tools/misc/print_config.py) +脚本打印下全配置,然后全局搜索 `num_classes` 确认是否有没有修改的模块。 + +## 评估时候 IndexError: list index out of range + +具体输出信息是 + +```text + File "site-packages/mmdet/evaluation/metrics/coco_metric.py", line 216, in results2json + data['category_id'] = self.cat_ids[label] +IndexError: list index out of range +``` + +可以看出是评估时候类别索引越界,这个通常的原因是配置中的 `num_classes` 设置不正确,默认的 `num_classes` 是 80,如果你自定义类别小于 80,那么就有可能出现类别越界。注意算法配置的 `num_classes` 一般会用到多个模块,你可能只改了某几个而漏掉了一些。想快速解决这个问题,可以使用 [print_config](https://github.com/open-mmlab/mmyolo/blob/main/tools/misc/print_config.py) +脚本打印下全配置,然后全局搜索 `num_classes` 确认是否有没有修改的模块。 + +## 训练中不打印 loss,但是程序依然正常训练和评估 + +这通常是因为一个训练 epoch 没有超过 50 个迭代,而 MMYOLO 中默认的打印间隔是 50。你可以修改 `default_hooks.logger.interval` 参数。 + +## GPU out of memory + +1. 存在大量 ground truth boxes 或者大量 anchor 的场景,可能在 assigner 会 OOM。 +2. 使用 --amp 来开启混合精度训练。 +3. 你也可以尝试使用 MMDet 中的 AvoidCUDAOOM 来避免该问题。首先它将尝试调用 torch.cuda.empty_cache()。如果失败,将会尝试把输入类型转换到 FP16。如果仍然失败,将会把输入从 GPUs 转换到 CPUs 进行计算。这里提供了两个使用的例子: + +```python +from mmdet.utils import AvoidCUDAOOM + +output = AvoidCUDAOOM.retry_if_cuda_oom(some_function)(input1, input2) +``` + +你也可也使用 AvoidCUDAOOM 作为装饰器让代码遇到 OOM 的时候继续运行: + +```python +from mmdet.utils import AvoidCUDAOOM + +@AvoidCUDAOOM.retry_if_cuda_oom +def function(*args, **kwargs): + ... + return xxx +``` + +## Loss goes Nan + +1. 检查数据的标注是否正常, 长或宽为 0 的框可能会导致回归 loss 变为 nan,一些小尺寸(宽度或高度小于 1)的框在数据增强后也会导致此问题。 因此,可以检查标注并过滤掉那些特别小甚至面积为 0 的框,并关闭一些可能会导致 0 面积框出现数据增强。 +2. 降低学习率:由于某些原因,例如 batch size 大小的变化, 导致当前学习率可能太大。 您可以降低为可以稳定训练模型的值。 +3. 延长 warm up 的时间:一些模型在训练初始时对学习率很敏感。 +4. 添加 gradient clipping: 一些模型需要梯度裁剪来稳定训练过程。 你可以在 config 设置 `optim_wrapper.clip_grad=dict(max_norm=xx)` + +## 训练中其他不符合预期或者错误 + +如果训练或者评估中出现了不属于上述描述的问题,由于原因不明,现提供常用的排除流程: + +1. 首先确认配置是否正确,可以使用 [print_config](https://github.com/open-mmlab/mmyolo/blob/main/tools/misc/print_config.py) 脚本打印全部配置,如果运行成功则说明配置语法没有错误 +2. 确认 COCO 格式的 json 标注是否正确,可以使用 [browse_coco_json.py](https://github.com/open-mmlab/mmyolo/blob/main/tools/misc/browse_coco_json.py) 脚本确认 +3. 确认 dataset 部分配置是否正确,这一步骤几乎是必须要提前运行的,可以提前排查很多问题,可以使用 [browse_dataset.py](https://github.com/open-mmlab/mmyolo/blob/main/tools/misc/browse_dataset.py) 脚本确认 +4. 如果以上 3 步都没有问题,那么出问题可能在 model 部分了。这个部分的排除没有特别的办法,你可以单独写一个脚本来仅运行 model 部分并通过调试来确认,如果对于 model 中多个模块的输入构建存在困惑,可以参考对应模块的单元测试写法 diff --git a/docs/zh_cn/tutorials/faq.md b/docs/zh_cn/tutorials/faq.md index 1cda1854..a088b5bd 100644 --- a/docs/zh_cn/tutorials/faq.md +++ b/docs/zh_cn/tutorials/faq.md @@ -17,3 +17,80 @@ **(3) 多任务支持** 还有一层深远的原因: **MMYOLO 任务不局限于 MMDetection**,后续会支持更多任务例如基于 MMPose 实现关键点相关的应用,基于 MMTracking 实现追踪相关的应用,因此不太适合直接并入 MMDetection 中。 + +## projects 文件夹是用来干什么的? + +projects 文件夹是 OpenMMLab 2.0 中引入的一个全新文件夹。其初衷有如下 3 点: + +1. 便于社区贡献。由于 OpenMMLab 系列代码库对于代码合入有一套规范严谨的流程,这不可避免的会导致算法复现周期很长,不利于社区贡献 +2. 便于快速支持新算法。算法开发周期过长同样会导致用户无法尽快体验最新算法 +3. 便于快速支持新方向和新特性。新发展方向或者一些新的特性可能和现如今代码库中的设计有些不兼容,没法快速合入到代码库中 + +综上所述,projects 文件夹的引入主要是解决算法复现周期过长导致的新算法支持速度较慢,新特性支持较复杂等多个问题。 projects 中每个文件夹属于一个完全独立的工程,社区用户可以通过 +projects 快速支持一些在当前版本中较难支持或者想快速支持的新算法和新特性。等后续设计稳定或者代码符合合入规范,则会考虑合入到主分支中。 + +## YOLOv5 backbone 替换为 Swin 后效果很差 + +在 [轻松更换主干网络](../recommended_topics/replace_backbone.md) 一文中我们提供了大量替换 backbone 的教程,但是该文档只是教用户如何替换 backbone,直接训练不一定能得到比较优异的结果。原因是 +不同 backbone 所需要的训练超参是不一样的,以 Swin 和 YOLOv5 backbone 为例两者差异较大,Swin 属于 transformer 系列算法,而 YOLOv5 backbone 属于卷积系列算法,其训练的优化器、学习率以及其他超参差异较大。 +如果强行将 Swin 作为 YOLOv5 backbone 且想取得不错的效果,需要同时调整诸多参数。 + +## MM 系列开源库中有很多组件,如何在 MMYOLO 中使用? + +在 OpenMMLab 2.0 中对多个 MM 系列开源库之间的模块跨库调用功能进行增强。目前在 MMYOLO 中可以在配置文件中通过 `MM 算法库 A.模块名` 来之间调用 MM 算法库 A 中已经被注册的任意模块。 具体例子可以参考 +[轻松更换主干网络](../recommended_topics/replace_backbone.md) 中使用在 MMClassification 中实现的主干网络章节,其他模块调用也是相同的用法。 + +## MMYOLO 中是否可以加入纯背景图片进行训练? + +将纯背景图片加入训练大部分情况可以抑制误报率,是否将纯背景图片加入训练功能已经大部分数据集上支持了。以 `YOLOv5CocoDataset` 为例,核心控制参数是 `train_dataloader.dataset.filter_cfg.filter_empty_gt`,如果 `filter_empty_gt` 为 True 表示将纯背景图片过滤掉不加入训练,反正将纯 +背景图片加入到训练中。 目前 MMYOLO 中大部分算法都是默认将纯背景图片加入训练中。 + +## MMYOLO 是否有计算模型推理 FPS 脚本? + +MMYOLO 是基于 MMDet 3.x 来开发的,在 MMDet 3.x 中提供了计算模型推理 FPS 的脚本。 具体脚本为 [benchmark](https://github.com/open-mmlab/mmdetection/blob/3.x/tools/analysis_tools/benchmark.py)。我们推荐大家使用 mim 直接跨库启动 MMDet 中的脚本而不是直接复制到 MMYOLO 中。 +关于如果通过 mim 启动 MMDet 中脚本,可以查看 [使用 mim 跨库调用其他 OpenMMLab 仓库的脚本](../common_usage/mim_usage.md)。 + +## MMDeploy 和 EasyDeploy 有啥区别? + +MMDeploy 是由 OpenMMLab 中部署团队开发的针对 OpenMMLab 系列算法库提供部署支持的开源库,支持各种后端和自定义等等强大功能。 EasyDeploy 是由社区小伙伴提供的一个相比 MMDeploy 更加简单易用的部署 projects。 +EasyDeploy 支持的功能目前没有 MMDeploy 多,但是使用上更加简单。 MMYOLO 中同时提供对 MMDeploy 和 EasyDeploy 的支持,用户可以根据自己需求选择。 + +## COCOMetric 中如何查看每个类的 AP + +只需要在配置中设置 `test_evaluator.classwise` 为 True,或者在 test.py 运行时候增加 `--cfg-options test_evaluator.classwise=True` 即可。 + +## MMYOLO 中为何没有支持 MMDet 类似的自动学习率缩放功能? + +原因是实验发现 YOLO 系列算法不是非常满足现象缩放功能。在多个数据集上验证发现会出现不基于 batch size 自动学习率缩放效果好于缩放的情形。因此暂时 MMYOLO 还没有支持自动学习率缩放功能。 + +## 自己训练的模型权重尺寸为啥比官方发布的大? + +原因是用户自己训练的权重通常包括 `optimizer`、`ema_state_dict` 和 `message_hub` 等额外数据,这部分数据我们会在模型发布时候自动删掉,而用户直接基于框架跑的模型权重是全部保留的,所以用户自己训练的模型权重尺寸为啥比官方发布的大。 +你可以使用 [publish_model.py](https://github.com/open-mmlab/mmyolo/blob/main/tools/misc/publish_model.py) 脚本删掉额外字段。 + +## RTMDet 为何训练所占显存比 YOLOv5 多很多? + +训练显存较多的原因主要是 assigner 部分的差异。YOLOv5 采用的是非常简单且高效的 shape 匹配 assigner,而 RTMDet 中采用的是动态的全 batch 计算的 dynamic soft label assigner,其内部的 Cost 矩阵需要消耗比较多的显存,特别是如果当前 batch 中标注框过多时候。 +后续我们会考虑解决这个问题。 + +## 修改一些代码后是否需要重新安装 MMYOLO + +在不新增 py 代码情况下, 如果你遵循最佳实践,即使用 `mim install -v -e .` 安装的 MMYOLO,则对本地代码所作的任何修改都会生效,无需重新安装。但是如果你是新增了 py 文件然后在里面新增的代码,则依然需要重新安装即运行 `mim install -v -e .`。 + +## 如何使用多个 MMYOLO 版本进行开发 + +推荐你拥有多个 MMYOLO 工程文件夹,例如 mmyolo-v1, mmyolo-v2。 在使用不同版本 MMYOLO 时候,你可以在终端运行前设置 + +```shell +PYTHONPATH="$(dirname $0)/..":$PYTHONPATH +``` + +使得当前环境生效。如果要使用环境中安装默认的 MMYOLO 而不是当前正在在使用的,可以删除出现上述命令或者通过如下命令重置 + +```shell +unset PYTHONPATH +``` + +## 训练中保存最好模型 + +只需要在配置中设置 `default_hooks.checkpoint.save_best` 为 auto 字符串或者训练时候通过命令行设置 `--cfg-options default_hooks.checkpoint.save_best=auto` 即可。 diff --git a/docs/zh_cn/tutorials/warning_notes.md b/docs/zh_cn/tutorials/warning_notes.md new file mode 100644 index 00000000..d1051ba1 --- /dev/null +++ b/docs/zh_cn/tutorials/warning_notes.md @@ -0,0 +1,22 @@ +# 常见警告说明 + +本文档收集用户经常疑惑的警告信息说明,方便大家理解。 + +## xxx registry in mmyolo did not set import location + +完整信息为 The xxx registry in mmyolo did not set import location. Fallback to call `mmyolo.utils.register_all_modules` instead.。 +这个警告的含义说某个模块在导入时候发现没有设置导入的 location,导致无法确定其位置,因此会自动调用 `mmyolo.utils.register_all_modules` 触发包的导入。这个警告属于 MMEngine 中非常底层的模块警告, +用户理解起来可能比较困难,不过对大家使用没有任何影响,可以直接忽略。 + +## save_param_schedulers is true but self.param_schedulers is None + +以 YOLOv5 算法为例,这是因为 YOLOv5 中重新写了参数调度器策略 `YOLOv5ParamSchedulerHook`,因此 MMEngine 中设计的 ParamScheduler 是没有使用的,但是 YOLOv5 配置中也没有设置 `save_param_schedulers` 为 False。 +首先这个警告对性能和恢复训练没有任何影响,用户如果觉得这个警告会影响体验,可以设置 `default_hooks.checkpoint.save_param_scheduler` 为 False 或者训练时候通过命令行设置 `--cfg-options default_hooks.checkpoint.save_param_scheduler=False` 即可。 + +## The loss_cls will be 0. This is a normal phenomenon. + +这个和具体算法有关。以 YOLOv5 为例,其分类 loss 是只考虑正样本的,如果类别是 1,那么分类 loss 和 obj loss 就是功能重复的了,因此在设计上当类别是 1 的时候 loss_cls 是不计算的,因此始终是 0,这是正常现象。 + +## The model and loaded state dict do not match exactly + +这个警告是否会影响性能要根据进一步的打印信息来确定。如果是在微调模式下,由于用户自定义类别不一样无法加载 Head 模块的 COCO 预训练,这是一个正常现象,不会影响性能。 diff --git a/tools/misc/publish_model.py b/tools/misc/publish_model.py new file mode 100644 index 00000000..a2ccbf08 --- /dev/null +++ b/tools/misc/publish_model.py @@ -0,0 +1,57 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import argparse +import subprocess + +import torch + + +def parse_args(): + parser = argparse.ArgumentParser( + description='Process a checkpoint to be published') + parser.add_argument('in_file', help='input checkpoint filename') + parser.add_argument('out_file', help='output checkpoint filename') + args = parser.parse_args() + return args + + +def process_checkpoint(in_file, out_file): + checkpoint = torch.load(in_file, map_location='cpu') + + # remove optimizer for smaller file size + if 'optimizer' in checkpoint: + del checkpoint['optimizer'] + if 'message_hub' in checkpoint: + del checkpoint['message_hub'] + if 'ema_state_dict' in checkpoint: + del checkpoint['ema_state_dict'] + + for key in list(checkpoint['state_dict']): + if key.startswith('data_preprocessor'): + checkpoint['state_dict'].pop(key) + elif 'priors_base_sizes' in key: + checkpoint['state_dict'].pop(key) + elif 'grid_offset' in key: + checkpoint['state_dict'].pop(key) + elif 'prior_inds' in key: + checkpoint['state_dict'].pop(key) + + if torch.__version__ >= '1.6': + torch.save(checkpoint, out_file, _use_new_zipfile_serialization=False) + else: + torch.save(checkpoint, out_file) + sha = subprocess.check_output(['sha256sum', out_file]).decode() + if out_file.endswith('.pth'): + out_file_name = out_file[:-4] + else: + out_file_name = out_file + final_file = out_file_name + f'-{sha[:8]}.pth' + subprocess.Popen(['mv', out_file, final_file]) + + +def main(): + args = parse_args() + process_checkpoint(args.in_file, args.out_file) + + +if __name__ == '__main__': + main()