mirror of https://github.com/open-mmlab/mmpose
503 lines
17 KiB
Markdown
503 lines
17 KiB
Markdown
# 如何看懂配置文件
|
||
|
||
MMPose 使用 Python 文件作为配置文件,将模块化设计和继承设计结合到配置系统中,便于进行各种实验。
|
||
|
||
## 目录结构
|
||
|
||
MMPose 的配置文件目录结构如下:
|
||
|
||
```shell
|
||
configs
|
||
|----_base_
|
||
|----datasets
|
||
|----default_runtime.py
|
||
|----animal_2d_keypoint
|
||
|----body_2d_keypoint
|
||
|----body_3d_keypoint
|
||
|----face_2d_keypoint
|
||
|----fashion_2d_keypoint
|
||
|----hand_2d_keypoint
|
||
|----hand_3d_keypoint
|
||
|----wholebody_2d_keypoint
|
||
```
|
||
|
||
## 简介
|
||
|
||
MMPose 拥有一套强大的配置系统,在注册器的配合下,用户可以通过一个配置文件来定义整个项目需要用到的所有内容,以 Python 字典形式组织配置信息,传递给注册器完成对应模块的实例化。
|
||
|
||
下面是一个常见的 Pytorch 模块定义的例子:
|
||
|
||
```Python
|
||
# 在loss_a.py中定义Loss_A类
|
||
Class Loss_A(nn.Module):
|
||
def __init__(self, param1, param2):
|
||
self.param1 = param1
|
||
self.param2 = param2
|
||
def forward(self, x):
|
||
return x
|
||
|
||
# 在需要的地方进行实例化
|
||
loss = Loss_A(param1=1.0, param2=True)
|
||
```
|
||
|
||
只需要通过一行代码对这个类进行注册:
|
||
|
||
```Python
|
||
# 在loss_a.py中定义Loss_A类
|
||
from mmpose.registry import MODELS
|
||
|
||
@MODELS.register_module() # 注册该类到 MODELS 下
|
||
Class Loss_A(nn.Module):
|
||
def __init__(self, param1, param2):
|
||
self.param1 = param1
|
||
self.param2 = param2
|
||
def forward(self, x):
|
||
return x
|
||
```
|
||
|
||
并在对应目录下的 `__init__.py` 中进行 `import`:
|
||
|
||
```Python
|
||
# __init__.py of mmpose/models/losses
|
||
from .loss_a.py import Loss_A
|
||
|
||
__all__ = ['Loss_A']
|
||
```
|
||
|
||
我们就可以通过如下方式来从配置文件定义并进行实例化:
|
||
|
||
```Python
|
||
# 在config_file.py中定义
|
||
loss_cfg = dict(
|
||
type='Loss_A', # 通过type指定类名
|
||
param1=1.0, # 传递__init__所需的参数
|
||
param2=True
|
||
)
|
||
|
||
# 在需要的地方进行实例化
|
||
loss = MODELS.build(loss_cfg) # 等价于 loss = Loss_A(param1=1.0, param2=True)
|
||
```
|
||
|
||
MMPose 预定义的 Registry 在 `$MMPOSE/mmpose/registry.py` 中,目前支持的有:
|
||
|
||
- `DATASETS`:数据集
|
||
|
||
- `TRANSFORMS`:数据变换
|
||
|
||
- `MODELS`:模型模块(Backbone、Neck、Head、Loss等)
|
||
|
||
- `VISUALIZERS`:可视化工具
|
||
|
||
- `VISBACKENDS`:可视化后端
|
||
|
||
- `METRICS`:评测指标
|
||
|
||
- `KEYPOINT_CODECS`:编解码器
|
||
|
||
- `HOOKS`:钩子类
|
||
|
||
```{note}
|
||
需要注意的是,所有新增的模块都需要使用注册器(Registry)进行注册,并在对应目录的 `__init__.py` 中进行 `import`,以便能够使用配置文件构建其实例。
|
||
```
|
||
|
||
## 配置系统
|
||
|
||
具体而言,一个配置文件主要包含如下五个部分:
|
||
|
||
- 通用配置:与训练或测试无关的通用配置,如时间统计,模型存储与加载,可视化等相关 Hook,以及一些分布式相关的环境配置
|
||
|
||
- 数据配置:数据增强策略,Dataset和Dataloader相关配置
|
||
|
||
- 训练配置:断点恢复、模型权重加载、优化器、学习率调整、训练轮数和测试间隔等
|
||
|
||
- 模型配置:模型模块、参数、损失函数等
|
||
|
||
- 评测配置:模型性能评测指标
|
||
|
||
你可以在 `$MMPOSE/configs` 下找到我们提供的配置文件,配置文件之间通过继承来避免冗余。为了保持配置文件简洁易读,我们将一些必要但不常改动的配置存放到了 `$MMPOSE/configs/_base_` 目录下,如果希望查阅完整的配置信息,你可以运行如下指令:
|
||
|
||
```Bash
|
||
python tools/analysis/print_config.py /PATH/TO/CONFIG
|
||
```
|
||
|
||
### 通用配置
|
||
|
||
通用配置指与训练或测试无关的必要配置,主要包括:
|
||
|
||
- **默认Hook**:迭代时间统计,训练日志,参数更新,checkpoint 等
|
||
|
||
- **环境配置**:分布式后端,cudnn,多进程配置等
|
||
|
||
- **可视化器**:可视化后端和策略设置
|
||
|
||
- **日志配置**:日志等级,格式,打印和记录间隔等
|
||
|
||
下面是通用配置的样例说明:
|
||
|
||
```Python
|
||
# 通用配置
|
||
default_scope = 'mmpose'
|
||
default_hooks = dict(
|
||
# 迭代时间统计,包括数据耗时和模型耗时
|
||
timer=dict(type='IterTimerHook'),
|
||
|
||
# 日志打印间隔,默认每 50 iters 打印一次
|
||
logger=dict(type='LoggerHook', interval=50),
|
||
|
||
# 用于调度学习率更新的 Hook
|
||
param_scheduler=dict(type='ParamSchedulerHook'),
|
||
|
||
checkpoint=dict(
|
||
# ckpt 保存间隔,最优 ckpt 参考指标。
|
||
# 例如:
|
||
# save_best='coco/AP' 代表以 coco/AP 作为最优指标,对应 CocoMetric 评测器的 AP 指标
|
||
# save_best='PCK' 代表以 PCK 作为最优指标,对应 PCKAccuracy 评测器的 PCK 指标
|
||
# 更多指标请前往 mmpose/evaluation/metrics/
|
||
type='CheckpointHook', interval=1, save_best='coco/AP',
|
||
|
||
# 最优 ckpt 保留规则,greater 代表越大越好,less 代表越小越好
|
||
rule='greater'),
|
||
|
||
# 分布式随机种子设置 Hook
|
||
sampler_seed=dict(type='DistSamplerSeedHook'))
|
||
env_cfg = dict(
|
||
# cudnn benchmark 开关,用于加速训练,但会增加显存占用
|
||
cudnn_benchmark=False,
|
||
|
||
# opencv 多线程配置,用于加速数据加载,但会增加显存占用
|
||
# 默认为 0,代表使用单线程
|
||
mp_cfg=dict(mp_start_method='fork', opencv_num_threads=0),
|
||
|
||
# 分布式训练后端设置,支持 nccl 和 gloo
|
||
dist_cfg=dict(backend='nccl'))
|
||
|
||
# 可视化器后端设置,默认为本地可视化
|
||
vis_backends = [dict(type='LocalVisBackend')]
|
||
|
||
# 可视化器设置
|
||
visualizer = dict(
|
||
type='PoseLocalVisualizer',
|
||
vis_backends=[dict(type='LocalVisBackend')],
|
||
name='visualizer')
|
||
log_processor = dict( # 训练日志格式、间隔
|
||
type='LogProcessor', window_size=50, by_epoch=True, num_digits=6)
|
||
# 日志记录等级,INFO 代表记录训练日志,WARNING 代表只记录警告信息,ERROR 代表只记录错误信息
|
||
log_level = 'INFO'
|
||
```
|
||
|
||
```{note}
|
||
可视化器后端设置支持 LocalVisBackend 和 TensorboardVisBackend,前者用于本地可视化,后者用于 Tensorboard 可视化,你可以根据需要进行选择。详情见 [训练与测试](./train_and_test.md) 的 【可视化训练进程】。
|
||
```
|
||
|
||
通用配置一般单独存放到 `$MMPOSE/configs/_base_` 目录下,通过如下方式进行继承:
|
||
|
||
```Python
|
||
_base_ = ['../../../_base_/default_runtime.py'] # 以运行时的config文件位置为相对路径起点
|
||
```
|
||
|
||
### 数据配置
|
||
|
||
数据配置指数据处理相关的配置,主要包括:
|
||
|
||
- **数据后端**:数据供给后端设置,默认为本地硬盘,我们也支持从 LMDB,S3 Bucket 等加载
|
||
|
||
- **数据集**:图像与标注文件路径
|
||
|
||
- **加载**:加载策略,批量大小等
|
||
|
||
- **流水线**:数据增强策略
|
||
|
||
- **编码器**:根据标注生成特定格式的监督信息
|
||
|
||
下面是数据配置的样例说明:
|
||
|
||
```Python
|
||
backend_args = dict(backend='local') # 数据加载后端设置,默认从本地硬盘加载
|
||
dataset_type = 'CocoDataset' # 数据集类名
|
||
data_mode = 'topdown' # 算法结构类型,用于指定标注信息加载策略
|
||
data_root = 'data/coco/' # 数据存放路径
|
||
# 定义数据编解码器,用于生成target和对pred进行解码,同时包含了输入图片和输出heatmap尺寸等信息
|
||
codec = dict(
|
||
type='MSRAHeatmap', input_size=(192, 256), heatmap_size=(48, 64), sigma=2)
|
||
train_pipeline = [ # 训练时数据增强
|
||
dict(type='LoadImage', backend_args=backend_args, # 加载图片
|
||
dict(type='GetBBoxCenterScale'), # 根据bbox获取center和scale
|
||
dict(type='RandomBBoxTransform'), # 生成随机位移、缩放、旋转变换矩阵
|
||
dict(type='RandomFlip', direction='horizontal'), # 生成随机翻转变换矩阵
|
||
dict(type='RandomHalfBody'), # 随机半身增强
|
||
dict(type='TopdownAffine', input_size=codec['input_size']), # 根据变换矩阵更新目标数据
|
||
dict(
|
||
type='GenerateTarget', # 根据目标数据生成监督信息
|
||
# 监督信息类型
|
||
encoder=codec, # 传入编解码器,用于数据编码,生成特定格式的监督信息
|
||
dict(type='PackPoseInputs') # 对target进行打包用于训练
|
||
]
|
||
test_pipeline = [ # 测试时数据增强
|
||
dict(type='LoadImage', backend_args=backend_args), # 加载图片
|
||
dict(type='GetBBoxCenterScale'), # 根据bbox获取center和scale
|
||
dict(type='TopdownAffine', input_size=codec['input_size']), # 根据变换矩阵更新目标数据
|
||
dict(type='PackPoseInputs') # 对target进行打包用于训练
|
||
]
|
||
train_dataloader = dict( # 训练数据加载
|
||
batch_size=64, # 批次大小
|
||
num_workers=2, # 数据加载进程数
|
||
persistent_workers=True, # 在不活跃时维持进程不终止,避免反复启动进程的开销
|
||
sampler=dict(type='DefaultSampler', shuffle=True), # 采样策略,打乱数据
|
||
dataset=dict(
|
||
type=dataset_type , # 数据集类名
|
||
data_root=data_root, # 数据集路径
|
||
data_mode=data_mode, # 算法类型
|
||
ann_file='annotations/person_keypoints_train2017.json', # 标注文件路径
|
||
data_prefix=dict(img='train2017/'), # 图像路径
|
||
pipeline=train_pipeline # 数据流水线
|
||
))
|
||
val_dataloader = dict(
|
||
batch_size=32,
|
||
num_workers=2,
|
||
persistent_workers=True, # 在不活跃时维持进程不终止,避免反复启动进程的开销
|
||
drop_last=False,
|
||
sampler=dict(type='DefaultSampler', shuffle=False), # 采样策略,不进行打乱
|
||
dataset=dict(
|
||
type=dataset_type , # 数据集类名
|
||
data_root=data_root, # 数据集路径
|
||
data_mode=data_mode, # 算法类型
|
||
ann_file='annotations/person_keypoints_val2017.json', # 标注文件路径
|
||
bbox_file=
|
||
'data/coco/person_detection_results/COCO_val2017_detections_AP_H_56_person.json', # 检测框标注文件,topdown方法专用
|
||
data_prefix=dict(img='val2017/'), # 图像路径
|
||
test_mode=True, # 测试模式开关
|
||
pipeline=test_pipeline # 数据流水线
|
||
))
|
||
test_dataloader = val_dataloader # 默认情况下不区分验证集和测试集,用户根据需要来自行定义
|
||
```
|
||
|
||
```{note}
|
||
常用功能可以参考以下教程:
|
||
- [恢复训练](https://mmpose.readthedocs.io/zh_CN/dev-1.x/user_guides/train_and_test.html#id7)
|
||
- [自动混合精度训练](https://mmpose.readthedocs.io/zh_CN/dev-1.x/user_guides/train_and_test.html#amp)
|
||
- [设置随机种子](https://mmpose.readthedocs.io/zh_CN/dev-1.x/user_guides/train_and_test.html#id10)
|
||
```
|
||
|
||
### 训练配置
|
||
|
||
训练配置指训练策略相关的配置,主要包括:
|
||
|
||
- 从断点恢复训练
|
||
|
||
- 模型权重加载
|
||
|
||
- 训练轮数和测试间隔
|
||
|
||
- 学习率调整策略,如 warmup,scheduler
|
||
|
||
- 优化器和学习率
|
||
|
||
- 高级训练策略设置,如自动学习率缩放
|
||
|
||
下面是训练配置的样例说明:
|
||
|
||
```Python
|
||
resume = False # 断点恢复
|
||
load_from = None # 模型权重加载
|
||
train_cfg = dict(by_epoch=True, max_epochs=210, val_interval=10) # 训练轮数,测试间隔
|
||
param_scheduler = [
|
||
dict( # warmup策略
|
||
type='LinearLR', begin=0, end=500, start_factor=0.001, by_epoch=False),
|
||
dict( # scheduler
|
||
type='MultiStepLR',
|
||
begin=0,
|
||
end=210,
|
||
milestones=[170, 200],
|
||
gamma=0.1,
|
||
by_epoch=True)
|
||
]
|
||
optim_wrapper = dict(optimizer=dict(type='Adam', lr=0.0005)) # 优化器和学习率
|
||
auto_scale_lr = dict(base_batch_size=512) # 根据batch_size自动缩放学习率
|
||
```
|
||
|
||
### 模型配置
|
||
|
||
模型配置指模型训练和推理相关的配置,主要包括:
|
||
|
||
- 模型结构
|
||
|
||
- 损失函数
|
||
|
||
- 数据解码策略
|
||
|
||
- 测试时增强策略
|
||
|
||
下面是模型配置的样例说明,定义了一个基于 HRNetw32 的 Top-down Heatmap-based 模型:
|
||
|
||
```Python
|
||
# 定义数据编解码器,如果在数据配置部分已经定义过则无需重复定义
|
||
codec = dict(
|
||
type='MSRAHeatmap', input_size=(192, 256), heatmap_size=(48, 64), sigma=2)
|
||
# 模型配置
|
||
model = dict(
|
||
type='TopdownPoseEstimator', # 模型结构决定了算法流程
|
||
data_preprocessor=dict( # 数据归一化和通道顺序调整,作为模型的一部分
|
||
type='PoseDataPreprocessor',
|
||
mean=[123.675, 116.28, 103.53],
|
||
std=[58.395, 57.12, 57.375],
|
||
bgr_to_rgb=True),
|
||
backbone=dict( # 骨干网络定义
|
||
type='HRNet',
|
||
in_channels=3,
|
||
extra=dict(
|
||
stage1=dict(
|
||
num_modules=1,
|
||
num_branches=1,
|
||
block='BOTTLENECK',
|
||
num_blocks=(4, ),
|
||
num_channels=(64, )),
|
||
stage2=dict(
|
||
num_modules=1,
|
||
num_branches=2,
|
||
block='BASIC',
|
||
num_blocks=(4, 4),
|
||
num_channels=(32, 64)),
|
||
stage3=dict(
|
||
num_modules=4,
|
||
num_branches=3,
|
||
block='BASIC',
|
||
num_blocks=(4, 4, 4),
|
||
num_channels=(32, 64, 128)),
|
||
stage4=dict(
|
||
num_modules=3,
|
||
num_branches=4,
|
||
block='BASIC',
|
||
num_blocks=(4, 4, 4, 4),
|
||
num_channels=(32, 64, 128, 256))),
|
||
init_cfg=dict(
|
||
type='Pretrained', # 预训练参数,只加载backbone权重用于迁移学习
|
||
checkpoint='https://download.openmmlab.com/mmpose'
|
||
'/pretrain_models/hrnet_w32-36af842e.pth'),
|
||
),
|
||
head=dict( # 模型头部
|
||
type='HeatmapHead',
|
||
in_channels=32,
|
||
out_channels=17,
|
||
deconv_out_channels=None,
|
||
loss=dict(type='KeypointMSELoss', use_target_weight=True), # 损失函数
|
||
decoder=codec), # 解码器,将heatmap解码成坐标值
|
||
test_cfg=dict(
|
||
flip_test=True, # 开启测试时水平翻转集成
|
||
flip_mode='heatmap', # 对heatmap进行翻转
|
||
shift_heatmap=True, # 对翻转后的结果进行平移提高精度
|
||
))
|
||
```
|
||
|
||
### 评测配置
|
||
|
||
评测配置指公开数据集中关键点检测任务常用的评测指标,主要包括:
|
||
|
||
- AR, AP and mAP
|
||
|
||
- PCK, PCKh, tPCK
|
||
|
||
- AUC
|
||
|
||
- EPE
|
||
|
||
- NME
|
||
|
||
下面是评测配置的样例说明,定义了一个COCO指标评测器:
|
||
|
||
```Python
|
||
val_evaluator = dict(
|
||
type='CocoMetric', # coco 评测指标
|
||
ann_file=data_root + 'annotations/person_keypoints_val2017.json') # 加载评测标注数据
|
||
test_evaluator = val_evaluator # 默认情况下不区分验证集和测试集,用户根据需要来自行定义
|
||
```
|
||
|
||
## 配置文件命名规则
|
||
|
||
MMPose 配置文件命名风格如下:
|
||
|
||
```Python
|
||
{{算法信息}}_{{模块信息}}_{{训练信息}}_{{数据信息}}.py
|
||
```
|
||
|
||
文件名总体分为四部分:算法信息,模块信息,训练信息和数据信息。不同部分的单词之间用下划线 `'_'` 连接,同一部分有多个单词用短横线 `'-'` 连接。
|
||
|
||
- **算法信息**:算法名称,如 `topdown-heatmap`,`topdown-rle` 等
|
||
|
||
- **模块信息**:按照数据流的顺序列举一些中间的模块,其内容依赖于算法任务,如 `res101`,`hrnet-w48`等
|
||
|
||
- **训练信息**:训练策略的一些设置,包括 `batch size`,`schedule` 等,如 `8xb64-210e`
|
||
|
||
- **数据信息**:数据集名称、模态、输入尺寸等,如 `ap10k-256x256`,`zebra-160x160` 等
|
||
|
||
有时为了避免文件名过长,会省略模型信息中一些强相关的模块,只保留关键信息,如RLE-based算法中的`GAP`,Heatmap-based算法中的 `deconv` 等。
|
||
|
||
如果你希望向MMPose添加新的方法,你的配置文件同样需要遵守该命名规则。
|
||
|
||
## 常见用法
|
||
|
||
### 配置文件的继承
|
||
|
||
该用法常用于隐藏一些必要但不需要修改的配置,以提高配置文件的可读性。假如有如下两个配置文件:
|
||
|
||
`optimizer_cfg.py`:
|
||
|
||
```Python
|
||
optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)
|
||
```
|
||
|
||
`resnet50.py`:
|
||
|
||
```Python
|
||
_base_ = ['optimizer_cfg.py']
|
||
model = dict(type='ResNet', depth=50)
|
||
```
|
||
|
||
虽然我们在 `resnet50.py` 中没有定义 optimizer 字段,但由于我们写了 `_base_ = ['optimizer_cfg.py']`,会使这个配置文件获得 `optimizer_cfg.py` 中的所有字段:
|
||
|
||
```Python
|
||
cfg = Config.fromfile('resnet50.py')
|
||
cfg.optimizer # ConfigDict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)
|
||
```
|
||
|
||
### 继承字段的修改
|
||
|
||
对于继承过来的已经定义好的字典,可以直接指定对应字段进行修改,而不需要重新定义完整的字典:
|
||
|
||
`resnet50_lr0.01.py`:
|
||
|
||
```Python
|
||
_base_ = ['optimizer_cfg.py']
|
||
model = dict(type='ResNet', depth=50)
|
||
optimizer = dict(lr=0.01) # 直接修改对应字段
|
||
```
|
||
|
||
这个配置文件只修改了对应字段`lr`的信息:
|
||
|
||
```Python
|
||
cfg = Config.fromfile('resnet50_lr0.01.py')
|
||
cfg.optimizer # ConfigDict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)
|
||
```
|
||
|
||
### 删除字典中的字段
|
||
|
||
如果不仅是需要修改某些字段,还需要删除已定义的一些字段,需要在重新定义这个字典时指定`_delete_=True`,表示将没有在新定义中出现的字段全部删除:
|
||
|
||
`resnet50.py`:
|
||
|
||
```Python
|
||
_base_ = ['optimizer_cfg.py', 'runtime_cfg.py']
|
||
model = dict(type='ResNet', depth=50)
|
||
optimizer = dict(_delete_=True, type='SGD', lr=0.01) # 重新定义字典
|
||
```
|
||
|
||
此时字典中除了 `type` 和 `lr` 以外的内容(`momentum`和`weight_decay`)将被全部删除:
|
||
|
||
```Python
|
||
cfg = Config.fromfile('resnet50_lr0.01.py')
|
||
cfg.optimizer # ConfigDict(type='SGD', lr=0.01)
|
||
```
|
||
|
||
```{note}
|
||
如果你希望更深入地了解配置系统的高级用法,可以查看 [MMEngine 教程](https://mmengine.readthedocs.io/zh_CN/latest/advanced_tutorials/config.html)。
|
||
```
|