Shortcuts

如何看懂配置文件

MMPose 使用 Python 文件作为配置文件,将模块化设计和继承设计结合到配置系统中,便于进行各种实验。

目录结构

MMPose 的配置文件目录结构如下:

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 模块定义的例子:

# 在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)

只需要通过一行代码对这个类进行注册:

# 在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

# __init__.py of mmpose/models/losses
from .loss_a.py import Loss_A

__all__ = ['Loss_A']

我们就可以通过如下方式来从配置文件定义并进行实例化:

# 在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:钩子类

备注

需要注意的是,所有新增的模块都需要使用注册器(Registry)进行注册,并在对应目录的 __init__.py 中进行 import,以便能够使用配置文件构建其实例。

配置系统

具体而言,一个配置文件主要包含如下五个部分:

  • 通用配置:与训练或测试无关的通用配置,如时间统计,模型存储与加载,可视化等相关 Hook,以及一些分布式相关的环境配置

  • 数据配置:数据增强策略,Dataset和Dataloader相关配置

  • 训练配置:断点恢复、模型权重加载、优化器、学习率调整、训练轮数和测试间隔等

  • 模型配置:模型模块、参数、损失函数等

  • 评测配置:模型性能评测指标

你可以在 $MMPOSE/configs 下找到我们提供的配置文件,配置文件之间通过继承来避免冗余。为了保持配置文件简洁易读,我们将一些必要但不常改动的配置存放到了 $MMPOSE/configs/_base_ 目录下,如果希望查阅完整的配置信息,你可以运行如下指令:

python tools/analysis/print_config.py /PATH/TO/CONFIG

通用配置

通用配置指与训练或测试无关的必要配置,主要包括:

  • 默认Hook:迭代时间统计,训练日志,参数更新,checkpoint 等

  • 环境配置:分布式后端,cudnn,多进程配置等

  • 可视化器:可视化后端和策略设置

  • 日志配置:日志等级,格式,打印和记录间隔等

下面是通用配置的样例说明:

# 通用配置
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'

备注

可视化器后端设置支持 LocalVisBackend 和 TensorboardVisBackend,前者用于本地可视化,后者用于 Tensorboard 可视化,你可以根据需要进行选择。详情见 训练与测试 的 【可视化训练进程】。

通用配置一般单独存放到 $MMPOSE/configs/_base_ 目录下,通过如下方式进行继承:

_base_ = ['../../../_base_/default_runtime.py'] # 以运行时的config文件位置为相对路径起点

数据配置

数据配置指数据处理相关的配置,主要包括:

  • 数据后端:数据供给后端设置,默认为本地硬盘,我们也支持从 LMDB,S3 Bucket 等加载

  • 数据集:图像与标注文件路径

  • 加载:加载策略,批量大小等

  • 流水线:数据增强策略

  • 编码器:根据标注生成特定格式的监督信息

下面是数据配置的样例说明:

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 # 默认情况下不区分验证集和测试集,用户根据需要来自行定义

备注

常用功能可以参考以下教程:

训练配置

训练配置指训练策略相关的配置,主要包括:

  • 从断点恢复训练

  • 模型权重加载

  • 训练轮数和测试间隔

  • 学习率调整策略,如 warmup,scheduler

  • 优化器和学习率

  • 高级训练策略设置,如自动学习率缩放

下面是训练配置的样例说明:

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 模型:

# 定义数据编解码器,如果在数据配置部分已经定义过则无需重复定义
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指标评测器:

val_evaluator = dict(
    type='CocoMetric', # coco 评测指标
    ann_file=data_root + 'annotations/person_keypoints_val2017.json') # 加载评测标注数据
test_evaluator = val_evaluator # 默认情况下不区分验证集和测试集,用户根据需要来自行定义

配置文件命名规则

MMPose 配置文件命名风格如下:

{{算法信息}}_{{模块信息}}_{{训练信息}}_{{数据信息}}.py

文件名总体分为四部分:算法信息,模块信息,训练信息和数据信息。不同部分的单词之间用下划线 '_' 连接,同一部分有多个单词用短横线 '-' 连接。

  • 算法信息:算法名称,如 topdown-heatmaptopdown-rle

  • 模块信息:按照数据流的顺序列举一些中间的模块,其内容依赖于算法任务,如 res101hrnet-w48

  • 训练信息:训练策略的一些设置,包括 batch sizeschedule 等,如 8xb64-210e

  • 数据信息:数据集名称、模态、输入尺寸等,如 ap10k-256x256zebra-160x160

有时为了避免文件名过长,会省略模型信息中一些强相关的模块,只保留关键信息,如RLE-based算法中的GAP,Heatmap-based算法中的 deconv 等。

如果你希望向MMPose添加新的方法,你的配置文件同样需要遵守该命名规则。

常见用法

配置文件的继承

该用法常用于隐藏一些必要但不需要修改的配置,以提高配置文件的可读性。假如有如下两个配置文件:

optimizer_cfg.py:

optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)

resnet50.py:

_base_ = ['optimizer_cfg.py']
model = dict(type='ResNet', depth=50)

虽然我们在 resnet50.py 中没有定义 optimizer 字段,但由于我们写了 _base_ = ['optimizer_cfg.py'],会使这个配置文件获得 optimizer_cfg.py 中的所有字段:

cfg = Config.fromfile('resnet50.py')
cfg.optimizer  # ConfigDict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)

继承字段的修改

对于继承过来的已经定义好的字典,可以直接指定对应字段进行修改,而不需要重新定义完整的字典:

resnet50_lr0.01.py:

_base_ = ['optimizer_cfg.py']
model = dict(type='ResNet', depth=50)
optimizer = dict(lr=0.01) # 直接修改对应字段

这个配置文件只修改了对应字段lr的信息:

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:

_base_ = ['optimizer_cfg.py', 'runtime_cfg.py']
model = dict(type='ResNet', depth=50)
optimizer = dict(_delete_=True, type='SGD', lr=0.01) # 重新定义字典

此时字典中除了 typelr 以外的内容(momentumweight_decay)将被全部删除:

cfg = Config.fromfile('resnet50_lr0.01.py')
cfg.optimizer  # ConfigDict(type='SGD', lr=0.01)

备注

如果你希望更深入地了解配置系统的高级用法,可以查看 MMEngine 教程