Initial snapshot: hand motion pipeline (Dyn-HaMR + dex-retargeting + SPIDER)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 14:13:49 +08:00
commit 7e4ef6f98b
7002 changed files with 1452249 additions and 0 deletions
@@ -0,0 +1,235 @@
# Tutorial 0: Learn about Configs
We use python files as configs, incorporate modular and inheritance design into our config system, which is convenient to conduct various experiments.
You can find all the provided configs under `$MMPose/configs`. If you wish to inspect the config file,
you may run `python tools/analysis/print_config.py /PATH/TO/CONFIG` to see the complete config.
<!-- TOC -->
- [Modify config through script arguments](#modify-config-through-script-arguments)
- [Config File Naming Convention](#config-file-naming-convention)
- [Config System Example](#config-system-example)
- [FAQ](#faq)
- [Use intermediate variables in configs](#use-intermediate-variables-in-configs)
<!-- TOC -->
## Modify config through script arguments
When submitting jobs using "tools/train.py" or "tools/test.py", you may specify `--cfg-options` to in-place modify the config.
- Update config keys of dict chains.
The config options can be specified following the order of the dict keys in the original config.
For example, `--cfg-options model.backbone.norm_eval=False` changes the all BN modules in model backbones to `train` mode.
- Update keys inside a list of configs.
Some config dicts are composed as a list in your config. For example, the training pipeline `data.train.pipeline` is normally a list
e.g. `[dict(type='LoadImageFromFile'), dict(type='TopDownRandomFlip', flip_prob=0.5), ...]`. If you want to change `'flip_prob=0.5'` to `'flip_prob=0.0'` in the pipeline,
you may specify `--cfg-options data.train.pipeline.1.flip_prob=0.0`.
- Update values of list/tuples.
If the value to be updated is a list or a tuple. For example, the config file normally sets `workflow=[('train', 1)]`. If you want to
change this key, you may specify `--cfg-options workflow="[(train,1),(val,1)]"`. Note that the quotation mark \" is necessary to
support list/tuple data types, and that **NO** white space is allowed inside the quotation marks in the specified value.
## Config File Naming Convention
We follow the style below to name config files. Contributors are advised to follow the same style.
```
configs/{topic}/{task}/{algorithm}/{dataset}/{backbone}_[model_setting]_{dataset}_[input_size]_[technique].py
```
`{xxx}` is required field and `[yyy]` is optional.
- `{topic}`: topic type, e.g. `body`, `face`, `hand`, `animal`, etc.
- `{task}`: task type, `[2d | 3d]_[kpt | mesh]_[sview | mview]_[rgb | rgbd]_[img | vid]`. The task is categorized in 5: (1) 2D or 3D pose estimation, (2) representation type: keypoint (kpt), mesh, or DensePose (dense). (3) Single-view (sview) or multi-view (mview), (4) RGB or RGBD, and (5) Image (img) or Video (vid). e.g. `2d_kpt_sview_rgb_img`, `3d_kpt_sview_rgb_vid`, etc.
- `{algorithm}`: algorithm type, e.g. `associative_embedding`, `deeppose`, etc.
- `{dataset}`: dataset name, e.g. `coco`, etc.
- `{backbone}`: backbone type, e.g. `res50` (ResNet-50), etc.
- `[model setting]`: specific setting for some models.
- `[input_size]`: input size of the model.
- `[technique]`: some specific techniques, including losses, augmentation and tricks, e.g. `wingloss`, `udp`, `fp16`.
### Config System
- An Example of 2D Top-down Heatmap-based Human Pose Estimation
To help the users have a basic idea of a complete config structure and the modules in the config system,
we make brief comments on 'https://github.com/open-mmlab/mmpose/tree/e1ec589884235bee875c89102170439a991f8450/configs/top_down/resnet/coco/res50_coco_256x192.py' as the following.
For more detailed usage and alternative for per parameter in each module, please refer to the API documentation.
```python
# runtime settings
log_level = 'INFO' # The level of logging
load_from = None # load models as a pre-trained model from a given path. This will not resume training
resume_from = None # Resume checkpoints from a given path, the training will be resumed from the epoch when the checkpoint's is saved
dist_params = dict(backend='nccl') # Parameters to setup distributed training, the port can also be set
workflow = [('train', 1)] # Workflow for runner. [('train', 1)] means there is only one workflow and the workflow named 'train' is executed once
checkpoint_config = dict( # Config to set the checkpoint hook, Refer to https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/checkpoint.py for implementation
interval=10) # Interval to save checkpoint
evaluation = dict( # Config of evaluation during training
interval=10, # Interval to perform evaluation
metric='mAP', # Metrics to be performed
save_best='AP') # set `AP` as key indicator to save best checkpoint
# optimizer
optimizer = dict(
# Config used to build optimizer, support (1). All the optimizers in PyTorch
# whose arguments are also the same as those in PyTorch. (2). Custom optimizers
# which are builed on `constructor`, referring to "tutorials/4_new_modules.md"
# for implementation.
type='Adam', # Type of optimizer, refer to https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/optimizer/default_constructor.py#L13 for more details
lr=5e-4, # Learning rate, see detail usages of the parameters in the documentation of PyTorch
)
optimizer_config = dict(grad_clip=None) # Do not use gradient clip
# learning policy
lr_config = dict( # Learning rate scheduler config used to register LrUpdater hook
policy='step', # Policy of scheduler, also support CosineAnnealing, Cyclic, etc. Refer to details of supported LrUpdater from https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/lr_updater.py#L9
warmup='linear', # Type of warmup used. It can be None(use no warmup), 'constant', 'linear' or 'exp'.
warmup_iters=500, # The number of iterations or epochs that warmup
warmup_ratio=0.001, # LR used at the beginning of warmup equals to warmup_ratio * initial_lr
step=[170, 200]) # Steps to decay the learning rate
total_epochs = 210 # Total epochs to train the model
log_config = dict( # Config to register logger hook
interval=50, # Interval to print the log
hooks=[
dict(type='TextLoggerHook'), # The logger used to record the training process
# dict(type='TensorboardLoggerHook') # The Tensorboard logger is also supported
])
channel_cfg = dict(
num_output_channels=17, # The output channels of keypoint head
dataset_joints=17, # Number of joints in the dataset
dataset_channel=[ # Dataset supported channels
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
],
inference_channel=[ # Channels to output
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
])
# model settings
model = dict( # Config of the model
type='TopDown', # Type of the model
pretrained='torchvision://resnet50', # The url/site of the pretrained model
backbone=dict( # Dict for backbone
type='ResNet', # Name of the backbone
depth=50), # Depth of ResNet model
keypoint_head=dict( # Dict for keypoint head
type='TopdownHeatmapSimpleHead', # Name of keypoint head
in_channels=2048, # The input channels of keypoint head
out_channels=channel_cfg['num_output_channels'], # The output channels of keypoint head
loss_keypoint=dict( # Dict for keypoint loss
type='JointsMSELoss', # Name of keypoint loss
use_target_weight=True)), # Whether to consider target_weight during loss calculation
train_cfg=dict(), # Config of training hyper-parameters
test_cfg=dict( # Config of testing hyper-parameters
flip_test=True, # Whether to use flip-test during inference
post_process='default', # Use 'default' post-processing approach.
shift_heatmap=True, # Shift and align the flipped heatmap to achieve higher performance
modulate_kernel=11)) # Gaussian kernel size for modulation. Only used for "post_process='unbiased'"
data_cfg = dict(
image_size=[192, 256], # Size of model input resolution
heatmap_size=[48, 64], # Size of the output heatmap
num_output_channels=channel_cfg['num_output_channels'], # Number of output channels
num_joints=channel_cfg['dataset_joints'], # Number of joints
dataset_channel=channel_cfg['dataset_channel'], # Dataset supported channels
inference_channel=channel_cfg['inference_channel'], # Channels to output
soft_nms=False, # Whether to perform soft-nms during inference
nms_thr=1.0, # Threshold for non maximum suppression.
oks_thr=0.9, # Threshold of oks (object keypoint similarity) score during nms
vis_thr=0.2, # Threshold of keypoint visibility
use_gt_bbox=False, # Whether to use ground-truth bounding box during testing
det_bbox_thr=0.0, # Threshold of detected bounding box score. Used when 'use_gt_bbox=True'
bbox_file='data/coco/person_detection_results/' # Path to the bounding box detection file
'COCO_val2017_detections_AP_H_56_person.json',
)
train_pipeline = [
dict(type='LoadImageFromFile'), # Loading image from file
dict(type='TopDownRandomFlip', # Perform random flip augmentation
flip_prob=0.5), # Probability of implementing flip
dict(
type='TopDownHalfBodyTransform', # Config of TopDownHalfBodyTransform data-augmentation
num_joints_half_body=8, # Threshold of performing half-body transform.
prob_half_body=0.3), # Probability of implementing half-body transform
dict(
type='TopDownGetRandomScaleRotation', # Config of TopDownGetRandomScaleRotation
rot_factor=40, # Rotating to ``[-2*rot_factor, 2*rot_factor]``.
scale_factor=0.5), # Scaling to ``[1-scale_factor, 1+scale_factor]``.
dict(type='TopDownAffine', # Affine transform the image to make input.
use_udp=False), # Do not use unbiased data processing.
dict(type='ToTensor'), # Convert other types to tensor type pipeline
dict(
type='NormalizeTensor', # Normalize input tensors
mean=[0.485, 0.456, 0.406], # Mean values of different channels to normalize
std=[0.229, 0.224, 0.225]), # Std values of different channels to normalize
dict(type='TopDownGenerateTarget', # Generate heatmap target. Different encoding types supported.
sigma=2), # Sigma of heatmap gaussian
dict(
type='Collect', # Collect pipeline that decides which keys in the data should be passed to the detector
keys=['img', 'target', 'target_weight'], # Keys of input
meta_keys=[ # Meta keys of input
'image_file', 'joints_3d', 'joints_3d_visible', 'center', 'scale',
'rotation', 'bbox_score', 'flip_pairs'
]),
]
val_pipeline = [
dict(type='LoadImageFromFile'), # Loading image from file
dict(type='TopDownAffine'), # Affine transform the image to make input.
dict(type='ToTensor'), # Config of ToTensor
dict(
type='NormalizeTensor',
mean=[0.485, 0.456, 0.406], # Mean values of different channels to normalize
std=[0.229, 0.224, 0.225]), # Std values of different channels to normalize
dict(
type='Collect', # Collect pipeline that decides which keys in the data should be passed to the detector
keys=['img'], # Keys of input
meta_keys=[ # Meta keys of input
'image_file', 'center', 'scale', 'rotation', 'bbox_score',
'flip_pairs'
]),
]
test_pipeline = val_pipeline
data_root = 'data/coco' # Root of the dataset
data = dict( # Config of data
samples_per_gpu=64, # Batch size of each single GPU during training
workers_per_gpu=2, # Workers to pre-fetch data for each single GPU
val_dataloader=dict(samples_per_gpu=32), # Batch size of each single GPU during validation
test_dataloader=dict(samples_per_gpu=32), # Batch size of each single GPU during testing
train=dict( # Training dataset config
type='TopDownCocoDataset', # Name of dataset
ann_file=f'{data_root}/annotations/person_keypoints_train2017.json', # Path to annotation file
img_prefix=f'{data_root}/train2017/',
data_cfg=data_cfg,
pipeline=train_pipeline),
val=dict( # Validation dataset config
type='TopDownCocoDataset', # Name of dataset
ann_file=f'{data_root}/annotations/person_keypoints_val2017.json', # Path to annotation file
img_prefix=f'{data_root}/val2017/',
data_cfg=data_cfg,
pipeline=val_pipeline),
test=dict( # Testing dataset config
type='TopDownCocoDataset', # Name of dataset
ann_file=f'{data_root}/annotations/person_keypoints_val2017.json', # Path to annotation file
img_prefix=f'{data_root}/val2017/',
data_cfg=data_cfg,
pipeline=val_pipeline),
)
```
## FAQ
### Use intermediate variables in configs
Some intermediate variables are used in the config files, like `train_pipeline`/`val_pipeline`/`test_pipeline` etc.
For Example, we would like to first define `train_pipeline`/`val_pipeline`/`test_pipeline` and pass them into `data`.
Thus, `train_pipeline`/`val_pipeline`/`test_pipeline` are intermediate variable.
@@ -0,0 +1,153 @@
# Tutorial 1: Finetuning Models
Detectors pre-trained on the COCO dataset can serve as a good pre-trained model for other datasets, e.g., COCO-WholeBody Dataset.
This tutorial provides instruction for users to use the models provided in the [Model Zoo](https://mmpose.readthedocs.io/en/latest/modelzoo.html) for other datasets to obtain better performance.
<!-- TOC -->
- [Outline](#outline)
- [Modify Head](#modify-head)
- [Modify Dataset](#modify-dataset)
- [Modify Training Schedule](#modify-training-schedule)
- [Use Pre-Trained Model](#use-pre-trained-model)
<!-- TOC -->
## Outline
There are two steps to finetune a model on a new dataset.
- Add support for the new dataset following [Tutorial 2: Adding New Dataset](tutorials/../2_new_dataset.md).
- Modify the configs as will be discussed in this tutorial.
To finetune on the custom datasets, the users need to modify four parts in the config.
## Modify Head
Then the new config needs to modify the model according to the keypoint numbers of the new datasets. By only changing `out_channels` in the keypoint_head.
For example, we have 133 keypoints for COCO-WholeBody, and we have 17 keypoints for COCO.
```python
channel_cfg = dict(
num_output_channels=133, # changing from 17 to 133
dataset_joints=133, # changing from 17 to 133
dataset_channel=[
list(range(133)), # changing from 17 to 133
],
inference_channel=list(range(133))) # changing from 17 to 133
# model settings
model = dict(
type='TopDown',
pretrained='https://download.openmmlab.com/mmpose/'
'pretrain_models/hrnet_w48-8ef0771d.pth',
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=(48, 96)),
stage3=dict(
num_modules=4,
num_branches=3,
block='BASIC',
num_blocks=(4, 4, 4),
num_channels=(48, 96, 192)),
stage4=dict(
num_modules=3,
num_branches=4,
block='BASIC',
num_blocks=(4, 4, 4, 4),
num_channels=(48, 96, 192, 384))),
),
keypoint_head=dict(
type='TopdownHeatmapSimpleHead',
in_channels=48,
out_channels=channel_cfg['num_output_channels'], # modify this
num_deconv_layers=0,
extra=dict(final_conv_kernel=1, ),
loss_keypoint=dict(type='JointsMSELoss', use_target_weight=True)),
train_cfg=dict(),
test_cfg=dict(
flip_test=True,
post_process='unbiased',
shift_heatmap=True,
modulate_kernel=17))
```
Note that the `pretrained='https://download.openmmlab.com/mmpose/pretrain_models/hrnet_w48-8ef0771d.pth'` setting is used for initializing backbone.
If you are training a new model from ImageNet-pretrained weights, this is for you.
However, this setting is not related to our task at hand. What we need is load_from, which will be discussed later.
## Modify dataset
The users may also need to prepare the dataset and write the configs about dataset.
MMPose supports multiple (10+) dataset, including COCO, COCO-WholeBody and MPII-TRB.
```python
data_root = 'data/coco'
data = dict(
samples_per_gpu=32,
workers_per_gpu=2,
val_dataloader=dict(samples_per_gpu=32),
test_dataloader=dict(samples_per_gpu=32),
train=dict(
type='TopDownCocoWholeBodyDataset', # modify the name of the dataset
ann_file=f'{data_root}/annotations/coco_wholebody_train_v1.0.json', # modify the path to the annotation file
img_prefix=f'{data_root}/train2017/',
data_cfg=data_cfg,
pipeline=train_pipeline),
val=dict(
type='TopDownCocoWholeBodyDataset', # modify the name of the dataset
ann_file=f'{data_root}/annotations/coco_wholebody_val_v1.0.json', # modify the path to the annotation file
img_prefix=f'{data_root}/val2017/',
data_cfg=data_cfg,
pipeline=val_pipeline),
test=dict(
type='TopDownCocoWholeBodyDataset', # modify the name of the dataset
ann_file=f'{data_root}/annotations/coco_wholebody_val_v1.0.json', # modify the path to the annotation file
img_prefix=f'{data_root}/val2017/',
data_cfg=data_cfg,
pipeline=val_pipeline)
)
```
## Modify training schedule
The finetuning hyperparameters vary from the default schedule. It usually requires smaller learning rate and less training epochs
```python
optimizer = dict(
type='Adam',
lr=5e-4, # reduce it
)
optimizer_config = dict(grad_clip=None)
# learning policy
lr_config = dict(
policy='step',
warmup='linear',
warmup_iters=500,
warmup_ratio=0.001,
step=[170, 200]) # reduce it
total_epochs = 210 # reduce it
```
## Use pre-trained model
Users can load a pre-trained model by setting the `load_from` field of the config to the model's path or link.
The users might need to download the model weights before training to avoid the download time during training.
```python
# use the pre-trained model for the whole HRNet
load_from = 'https://download.openmmlab.com/mmpose/top_down/hrnet/hrnet_w48_coco_384x288_dark-741844ba_20200812.pth' # model path can be found in model zoo
```
@@ -0,0 +1,318 @@
# Tutorial 2: Adding New Dataset
## Customize datasets by reorganizing data to COCO format
The simplest way to use the custom dataset is to convert your annotation format to COCO dataset format.
The annotation json files in COCO format has the following necessary keys:
```python
'images': [
{
'file_name': '000000001268.jpg',
'height': 427,
'width': 640,
'id': 1268
},
...
],
'annotations': [
{
'segmentation': [[426.36,
...
424.34,
223.3]],
'keypoints': [0,0,0,
0,0,0,
0,0,0,
427,220,2,
443,222,2,
414,228,2,
449,232,2,
408,248,1,
454,261,2,
0,0,0,
0,0,0,
411,287,2,
431,287,2,
0,0,0,
458,265,2,
0,0,0,
466,300,1],
'num_keypoints': 10,
'area': 3894.5826,
'iscrowd': 0,
'image_id': 1268,
'bbox': [402.34, 205.02, 65.26, 88.45],
'category_id': 1,
'id': 215218
},
...
],
'categories': [
{'id': 1, 'name': 'person'},
]
```
There are three necessary keys in the json file:
- `images`: contains a list of images with their information like `file_name`, `height`, `width`, and `id`.
- `annotations`: contains the list of instance annotations.
- `categories`: contains the category name ('person') and its ID (1).
## Create a custom dataset_info config file for the dataset
Add a new dataset info config file.
```
configs/_base_/datasets/custom.py
```
An example of the dataset config is as follows.
`keypoint_info` contains the information about each keypoint.
1. `name`: the keypoint name. The keypoint name must be unique.
2. `id`: the keypoint id.
3. `color`: ([B, G, R]) is used for keypoint visualization.
4. `type`: 'upper' or 'lower', will be used in data augmetation.
5. `swap`: indicates the 'swap pair' (also known as 'flip pair'). When applying image horizontal flip, the left part will become the right part. We need to flip the keypoints accordingly.
`skeleton_info` contains the information about the keypoint connectivity, which is used for visualization.
`joint_weights` assigns different loss weights to different keypoints.
`sigmas` is used to calculate the OKS score. Please read [keypoints-eval](https://cocodataset.org/#keypoints-eval) to learn more about it.
```
dataset_info = dict(
dataset_name='coco',
paper_info=dict(
author='Lin, Tsung-Yi and Maire, Michael and '
'Belongie, Serge and Hays, James and '
'Perona, Pietro and Ramanan, Deva and '
r'Doll{\'a}r, Piotr and Zitnick, C Lawrence',
title='Microsoft coco: Common objects in context',
container='European conference on computer vision',
year='2014',
homepage='http://cocodataset.org/',
),
keypoint_info={
0:
dict(name='nose', id=0, color=[51, 153, 255], type='upper', swap=''),
1:
dict(
name='left_eye',
id=1,
color=[51, 153, 255],
type='upper',
swap='right_eye'),
2:
dict(
name='right_eye',
id=2,
color=[51, 153, 255],
type='upper',
swap='left_eye'),
3:
dict(
name='left_ear',
id=3,
color=[51, 153, 255],
type='upper',
swap='right_ear'),
4:
dict(
name='right_ear',
id=4,
color=[51, 153, 255],
type='upper',
swap='left_ear'),
5:
dict(
name='left_shoulder',
id=5,
color=[0, 255, 0],
type='upper',
swap='right_shoulder'),
6:
dict(
name='right_shoulder',
id=6,
color=[255, 128, 0],
type='upper',
swap='left_shoulder'),
7:
dict(
name='left_elbow',
id=7,
color=[0, 255, 0],
type='upper',
swap='right_elbow'),
8:
dict(
name='right_elbow',
id=8,
color=[255, 128, 0],
type='upper',
swap='left_elbow'),
9:
dict(
name='left_wrist',
id=9,
color=[0, 255, 0],
type='upper',
swap='right_wrist'),
10:
dict(
name='right_wrist',
id=10,
color=[255, 128, 0],
type='upper',
swap='left_wrist'),
11:
dict(
name='left_hip',
id=11,
color=[0, 255, 0],
type='lower',
swap='right_hip'),
12:
dict(
name='right_hip',
id=12,
color=[255, 128, 0],
type='lower',
swap='left_hip'),
13:
dict(
name='left_knee',
id=13,
color=[0, 255, 0],
type='lower',
swap='right_knee'),
14:
dict(
name='right_knee',
id=14,
color=[255, 128, 0],
type='lower',
swap='left_knee'),
15:
dict(
name='left_ankle',
id=15,
color=[0, 255, 0],
type='lower',
swap='right_ankle'),
16:
dict(
name='right_ankle',
id=16,
color=[255, 128, 0],
type='lower',
swap='left_ankle')
},
skeleton_info={
0:
dict(link=('left_ankle', 'left_knee'), id=0, color=[0, 255, 0]),
1:
dict(link=('left_knee', 'left_hip'), id=1, color=[0, 255, 0]),
2:
dict(link=('right_ankle', 'right_knee'), id=2, color=[255, 128, 0]),
3:
dict(link=('right_knee', 'right_hip'), id=3, color=[255, 128, 0]),
4:
dict(link=('left_hip', 'right_hip'), id=4, color=[51, 153, 255]),
5:
dict(link=('left_shoulder', 'left_hip'), id=5, color=[51, 153, 255]),
6:
dict(link=('right_shoulder', 'right_hip'), id=6, color=[51, 153, 255]),
7:
dict(
link=('left_shoulder', 'right_shoulder'),
id=7,
color=[51, 153, 255]),
8:
dict(link=('left_shoulder', 'left_elbow'), id=8, color=[0, 255, 0]),
9:
dict(
link=('right_shoulder', 'right_elbow'), id=9, color=[255, 128, 0]),
10:
dict(link=('left_elbow', 'left_wrist'), id=10, color=[0, 255, 0]),
11:
dict(link=('right_elbow', 'right_wrist'), id=11, color=[255, 128, 0]),
12:
dict(link=('left_eye', 'right_eye'), id=12, color=[51, 153, 255]),
13:
dict(link=('nose', 'left_eye'), id=13, color=[51, 153, 255]),
14:
dict(link=('nose', 'right_eye'), id=14, color=[51, 153, 255]),
15:
dict(link=('left_eye', 'left_ear'), id=15, color=[51, 153, 255]),
16:
dict(link=('right_eye', 'right_ear'), id=16, color=[51, 153, 255]),
17:
dict(link=('left_ear', 'left_shoulder'), id=17, color=[51, 153, 255]),
18:
dict(
link=('right_ear', 'right_shoulder'), id=18, color=[51, 153, 255])
},
joint_weights=[
1., 1., 1., 1., 1., 1., 1., 1.2, 1.2, 1.5, 1.5, 1., 1., 1.2, 1.2, 1.5,
1.5
],
sigmas=[
0.026, 0.025, 0.025, 0.035, 0.035, 0.079, 0.079, 0.072, 0.072, 0.062,
0.062, 0.107, 0.107, 0.087, 0.087, 0.089, 0.089
])
```
## Create a custom dataset class
1. First create a package inside the mmpose/datasets/datasets folder.
2. Create a class definition of your dataset in the package folder and register it in the registry with a name. Without a name, it will keep giving the error. `KeyError: 'XXXXX is not in the dataset registry'`
```
@DATASETS.register_module(name='MyCustomDataset')
class MyCustomDataset(SomeOtherBaseClassAsPerYourNeed):
```
3. Make sure you have updated the `__init__.py` of your package folder
4. Make sure you have updated the `__init__.py` of the dataset package folder.
## Create a custom training config file
Create a custom training config file as per your need and the model/architecture you want to use in the configs folder. You may modify an existing config file to use the new custom dataset.
In `configs/my_custom_config.py`:
```python
...
# dataset settings
dataset_type = 'MyCustomDataset'
...
data = dict(
samples_per_gpu=2,
workers_per_gpu=2,
train=dict(
type=dataset_type,
ann_file='path/to/your/train/json',
img_prefix='path/to/your/train/img',
...),
val=dict(
type=dataset_type,
ann_file='path/to/your/val/json',
img_prefix='path/to/your/val/img',
...),
test=dict(
type=dataset_type,
ann_file='path/to/your/test/json',
img_prefix='path/to/your/test/img',
...))
...
```
Make sure you have provided all the paths correctly.
@@ -0,0 +1,153 @@
# Tutorial 3: Custom Data Pipelines
## Design of Data pipelines
Following typical conventions, we use `Dataset` and `DataLoader` for data loading
with multiple workers. `Dataset` returns a dict of data items corresponding
the arguments of models' forward method.
Since the data in pose estimation may not be the same size (image size, gt bbox size, etc.),
we introduce a new `DataContainer` type in MMCV to help collect and distribute
data of different size.
See [here](https://github.com/open-mmlab/mmcv/blob/master/mmcv/parallel/data_container.py) for more details.
The data preparation pipeline and the dataset is decomposed. Usually a dataset
defines how to process the annotations and a data pipeline defines all the steps to prepare a data dict.
A pipeline consists of a sequence of operations. Each operation takes a dict as input and also output a dict for the next transform.
The operations are categorized into data loading, pre-processing, formatting, label generating.
Here is an pipeline example for Simple Baseline (ResNet50).
```python
train_pipeline = [
dict(type='LoadImageFromFile'),
dict(type='TopDownRandomFlip', flip_prob=0.5),
dict(type='TopDownHalfBodyTransform', num_joints_half_body=8, prob_half_body=0.3),
dict(type='TopDownGetRandomScaleRotation', rot_factor=40, scale_factor=0.5),
dict(type='TopDownAffine'),
dict(type='ToTensor'),
dict(
type='NormalizeTensor',
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]),
dict(type='TopDownGenerateTarget', sigma=2),
dict(
type='Collect',
keys=['img', 'target', 'target_weight'],
meta_keys=[
'image_file', 'joints_3d', 'joints_3d_visible', 'center', 'scale',
'rotation', 'bbox_score', 'flip_pairs'
]),
]
val_pipeline = [
dict(type='LoadImageFromFile'),
dict(type='TopDownAffine'),
dict(type='ToTensor'),
dict(
type='NormalizeTensor',
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]),
dict(
type='Collect',
keys=['img'],
meta_keys=[
'image_file', 'center', 'scale', 'rotation', 'bbox_score',
'flip_pairs'
]),
]
```
For each operation, we list the related dict fields that are added/updated/removed.
### Data loading
`LoadImageFromFile`
- add: img, img_file
### Pre-processing
`TopDownRandomFlip`
- update: img, joints_3d, joints_3d_visible, center
`TopDownHalfBodyTransform`
- update: center, scale
`TopDownGetRandomScaleRotation`
- update: scale, rotation
`TopDownAffine`
- update: img, joints_3d, joints_3d_visible
`NormalizeTensor`
- update: img
### Generating labels
`TopDownGenerateTarget`
- add: target, target_weight
### Formatting
`ToTensor`
- update: 'img'
`Collect`
- add: img_meta (the keys of img_meta is specified by `meta_keys`)
- remove: all other keys except for those specified by `keys`
## Extend and use custom pipelines
1. Write a new pipeline in any file, e.g., `my_pipeline.py`. It takes a dict as input and return a dict.
```python
from mmpose.datasets import PIPELINES
@PIPELINES.register_module()
class MyTransform:
def __call__(self, results):
results['dummy'] = True
return results
```
1. Import the new class.
```python
from .my_pipeline import MyTransform
```
1. Use it in config files.
```python
train_pipeline = [
dict(type='LoadImageFromFile'),
dict(type='TopDownRandomFlip', flip_prob=0.5),
dict(type='TopDownHalfBodyTransform', num_joints_half_body=8, prob_half_body=0.3),
dict(type='TopDownGetRandomScaleRotation', rot_factor=40, scale_factor=0.5),
dict(type='TopDownAffine'),
dict(type='MyTransform'),
dict(type='ToTensor'),
dict(
type='NormalizeTensor',
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]),
dict(type='TopDownGenerateTarget', sigma=2),
dict(
type='Collect',
keys=['img', 'target', 'target_weight'],
meta_keys=[
'image_file', 'joints_3d', 'joints_3d_visible', 'center', 'scale',
'rotation', 'bbox_score', 'flip_pairs'
]),
]
```
@@ -0,0 +1,213 @@
# Tutorial 4: Adding New Modules
## Customize optimizer
A customized optimizer could be defined as following.
Assume you want to add a optimizer named as `MyOptimizer`, which has arguments `a`, `b`, and `c`.
You need to first implement the new optimizer in a file, e.g., in `mmpose/core/optimizer/my_optimizer.py`:
```python
from mmcv.runner import OPTIMIZERS
from torch.optim import Optimizer
@OPTIMIZERS.register_module()
class MyOptimizer(Optimizer):
def __init__(self, a, b, c)
```
Then add this module in `mmpose/core/optimizer/__init__.py` thus the registry will
find the new module and add it:
```python
from .my_optimizer import MyOptimizer
```
Then you can use `MyOptimizer` in `optimizer` field of config files.
In the configs, the optimizers are defined by the field `optimizer` like the following:
```python
optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)
```
To use your own optimizer, the field can be changed as
```python
optimizer = dict(type='MyOptimizer', a=a_value, b=b_value, c=c_value)
```
We already support to use all the optimizers implemented by PyTorch, and the only modification is to change the `optimizer` field of config files.
For example, if you want to use `ADAM`, though the performance will drop a lot, the modification could be as the following.
```python
optimizer = dict(type='Adam', lr=0.0003, weight_decay=0.0001)
```
The users can directly set arguments following the [API doc](https://pytorch.org/docs/stable/optim.html?highlight=optim#module-torch.optim) of PyTorch.
## Customize optimizer constructor
Some models may have some parameter-specific settings for optimization, e.g. weight decay for BatchNorm layers.
The users can do those fine-grained parameter tuning through customizing optimizer constructor.
```
from mmcv.utils import build_from_cfg
from mmcv.runner import OPTIMIZER_BUILDERS, OPTIMIZERS
from mmpose.utils import get_root_logger
from .cocktail_optimizer import CocktailOptimizer
@OPTIMIZER_BUILDERS.register_module()
class CocktailOptimizerConstructor:
def __init__(self, optimizer_cfg, paramwise_cfg=None):
def __call__(self, model):
return my_optimizer
```
### Develop new components
We basically categorize model components into 3 types.
- detectors: the whole pose detector model pipeline, usually contains a backbone and keypoint_head.
- backbone: usually an FCN network to extract feature maps, e.g., ResNet, HRNet.
- keypoint_head: the component for pose estimation task, usually contains some deconv layers.
1. Create a new file `mmpose/models/backbones/my_model.py`.
```python
import torch.nn as nn
from ..builder import BACKBONES
@BACKBONES.register_module()
class MyModel(nn.Module):
def __init__(self, arg1, arg2):
pass
def forward(self, x): # should return a tuple
pass
def init_weights(self, pretrained=None):
pass
```
2. Import the module in `mmpose/models/backbones/__init__.py`.
```python
from .my_model import MyModel
```
3. Create a new file `mmpose/models/keypoint_heads/my_head.py`.
You can write a new keypoint head inherit from `nn.Module`,
and overwrite `init_weights(self)` and `forward(self, x)` method.
```python
from ..builder import HEADS
@HEADS.register_module()
class MyHead(nn.Module):
def __init__(self, arg1, arg2):
pass
def forward(self, x):
pass
def init_weights(self):
pass
```
4. Import the module in `mmpose/models/keypoint_heads/__init__.py`
```python
from .my_head import MyHead
```
5. Use it in your config file.
For the top-down 2D pose estimation model, we set the module type as `TopDown`.
```python
model = dict(
type='TopDown',
backbone=dict(
type='MyModel',
arg1=xxx,
arg2=xxx),
keypoint_head=dict(
type='MyHead',
arg1=xxx,
arg2=xxx))
```
### Add new loss
Assume you want to add a new loss as `MyLoss`, for keypoints estimation.
To add a new loss function, the users need implement it in `mmpose/models/losses/my_loss.py`.
The decorator `weighted_loss` enable the loss to be weighted for each element.
```python
import torch
import torch.nn as nn
from mmpose.models import LOSSES
def my_loss(pred, target):
assert pred.size() == target.size() and target.numel() > 0
loss = torch.abs(pred - target)
loss = torch.mean(loss)
return loss
@LOSSES.register_module()
class MyLoss(nn.Module):
def __init__(self, use_target_weight=False):
super(MyLoss, self).__init__()
self.criterion = my_loss()
self.use_target_weight = use_target_weight
def forward(self, output, target, target_weight):
batch_size = output.size(0)
num_joints = output.size(1)
heatmaps_pred = output.reshape(
(batch_size, num_joints, -1)).split(1, 1)
heatmaps_gt = target.reshape((batch_size, num_joints, -1)).split(1, 1)
loss = 0.
for idx in range(num_joints):
heatmap_pred = heatmaps_pred[idx].squeeze(1)
heatmap_gt = heatmaps_gt[idx].squeeze(1)
if self.use_target_weight:
loss += self.criterion(
heatmap_pred * target_weight[:, idx],
heatmap_gt * target_weight[:, idx])
else:
loss += self.criterion(heatmap_pred, heatmap_gt)
return loss / num_joints
```
Then the users need to add it in the `mmpose/models/losses/__init__.py`.
```python
from .my_loss import MyLoss, my_loss
```
To use it, modify the `loss_keypoint` field in the model.
```python
loss_keypoint=dict(type='MyLoss', use_target_weight=False)
```
@@ -0,0 +1,48 @@
# Tutorial 5: Exporting a model to ONNX
Open Neural Network Exchange [(ONNX)](https://onnx.ai/) is an open ecosystem that empowers AI developers to choose the right tools as their project evolves.
<!-- TOC -->
- [Supported Models](#supported-models)
- [Usage](#usage)
- [Prerequisite](#prerequisite)
<!-- TOC -->
## Supported Models
So far, our codebase supports onnx exporting from pytorch models trained with MMPose. The supported models include:
- ResNet
- HRNet
- HigherHRNet
## Usage
For simple exporting, you can use the [script](/tools/pytorch2onnx.py) here. Note that the package `onnx` and `onnxruntime` are required for verification after exporting.
### Prerequisite
First, install onnx.
```shell
pip install onnx onnxruntime
```
We provide a python script to export the pytorch model trained by MMPose to ONNX.
```shell
python tools/deployment/pytorch2onnx.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--shape ${SHAPE}] \
[--verify] [--show] [--output-file ${OUTPUT_FILE}] [--opset-version ${VERSION}]
```
Optional arguments:
- `--shape`: The shape of input tensor to the model. If not specified, it will be set to `1 3 256 192`.
- `--verify`: Determines whether to verify the exported model, runnably and numerically. If not specified, it will be set to `False`.
- `--show`: Determines whether to print the architecture of the exported model. If not specified, it will be set to `False`.
- `--output-file`: The output onnx model name. If not specified, it will be set to `tmp.onnx`.
- `--opset-version`: Determines the operation set version of onnx, we recommend you to use a higher version such as 11 for compatibility. If not specified, it will be set to `11`.
Please fire an issue if you discover any checkpoints that are not perfectly exported or suffer some loss in accuracy.
@@ -0,0 +1,352 @@
# Tutorial 6: Customize Runtime Settings
In this tutorial, we will introduce some methods about how to customize optimization methods, training schedules, workflow and hooks when running your own settings for the project.
<!-- TOC -->
- [Customize Optimization Methods](#customize-optimization-methods)
- [Customize optimizer supported by PyTorch](#customize-optimizer-supported-by-pytorch)
- [Customize self-implemented optimizer](#customize-self-implemented-optimizer)
- [1. Define a new optimizer](#1-define-a-new-optimizer)
- [2. Add the optimizer to registry](#2-add-the-optimizer-to-registry)
- [3. Specify the optimizer in the config file](#3-specify-the-optimizer-in-the-config-file)
- [Customize optimizer constructor](#customize-optimizer-constructor)
- [Additional settings](#additional-settings)
- [Customize Training Schedules](#customize-training-schedules)
- [Customize Workflow](#customize-workflow)
- [Customize Hooks](#customize-hooks)
- [Customize self-implemented hooks](#customize-self-implemented-hooks)
- [1. Implement a new hook](#1-implement-a-new-hook)
- [2. Register the new hook](#2-register-the-new-hook)
- [3. Modify the config](#3-modify-the-config)
- [Use hooks implemented in MMCV](#use-hooks-implemented-in-mmcv)
- [Modify default runtime hooks](#modify-default-runtime-hooks)
- [Checkpoint config](#checkpoint-config)
- [Log config](#log-config)
- [Evaluation config](#evaluation-config)
<!-- TOC -->
## Customize Optimization Methods
### Customize optimizer supported by PyTorch
We already support to use all the optimizers implemented by PyTorch, and the only modification is to change the `optimizer` field of config files.
For example, if you want to use `Adam`, the modification could be as the following.
```python
optimizer = dict(type='Adam', lr=0.0003, weight_decay=0.0001)
```
To modify the learning rate of the model, the users only need to modify the `lr` in the config of optimizer.
The users can directly set arguments following the [API doc](https://pytorch.org/docs/stable/optim.html?highlight=optim#module-torch.optim) of PyTorch.
For example, if you want to use `Adam` with the setting like `torch.optim.Adam(params, lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)` in PyTorch,
the modification could be set as the following.
```python
optimizer = dict(type='Adam', lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)
```
### Customize self-implemented optimizer
#### 1. Define a new optimizer
A customized optimizer could be defined as following.
Assume you want to add an optimizer named `MyOptimizer`, which has arguments `a`, `b`, and `c`.
You need to create a new directory named `mmpose/core/optimizer`.
And then implement the new optimizer in a file, e.g., in `mmpose/core/optimizer/my_optimizer.py`:
```python
from .builder import OPTIMIZERS
from torch.optim import Optimizer
@OPTIMIZERS.register_module()
class MyOptimizer(Optimizer):
def __init__(self, a, b, c):
```
#### 2. Add the optimizer to registry
To find the above module defined above, this module should be imported into the main namespace at first. There are two ways to achieve it.
- Modify `mmpose/core/optimizer/__init__.py` to import it.
The newly defined module should be imported in `mmpose/core/optimizer/__init__.py` so that the registry will
find the new module and add it:
```python
from .my_optimizer import MyOptimizer
```
- Use `custom_imports` in the config to manually import it
```python
custom_imports = dict(imports=['mmpose.core.optimizer.my_optimizer'], allow_failed_imports=False)
```
The module `mmpose.core.optimizer.my_optimizer` will be imported at the beginning of the program and the class `MyOptimizer` is then automatically registered.
Note that only the package containing the class `MyOptimizer` should be imported. `mmpose.core.optimizer.my_optimizer.MyOptimizer` **cannot** be imported directly.
#### 3. Specify the optimizer in the config file
Then you can use `MyOptimizer` in `optimizer` field of config files.
In the configs, the optimizers are defined by the field `optimizer` like the following:
```python
optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)
```
To use your own optimizer, the field can be changed to
```python
optimizer = dict(type='MyOptimizer', a=a_value, b=b_value, c=c_value)
```
### Customize optimizer constructor
Some models may have some parameter-specific settings for optimization, e.g. weight decay for BatchNorm layers.
The users can do those fine-grained parameter tuning through customizing optimizer constructor.
```python
from mmcv.utils import build_from_cfg
from mmcv.runner.optimizer import OPTIMIZER_BUILDERS, OPTIMIZERS
from mmpose.utils import get_root_logger
from .my_optimizer import MyOptimizer
@OPTIMIZER_BUILDERS.register_module()
class MyOptimizerConstructor:
def __init__(self, optimizer_cfg, paramwise_cfg=None):
pass
def __call__(self, model):
return my_optimizer
```
The default optimizer constructor is implemented [here](https://github.com/open-mmlab/mmcv/blob/9ecd6b0d5ff9d2172c49a182eaa669e9f27bb8e7/mmcv/runner/optimizer/default_constructor.py#L11),
which could also serve as a template for new optimizer constructor.
### Additional settings
Tricks not implemented by the optimizer should be implemented through optimizer constructor (e.g., set parameter-wise learning rates) or hooks.
We list some common settings that could stabilize the training or accelerate the training. Feel free to create PR, issue for more settings.
- __Use gradient clip to stabilize training__:
Some models need gradient clip to clip the gradients to stabilize the training process. An example is as below:
```python
optimizer_config = dict(grad_clip=dict(max_norm=35, norm_type=2))
```
- __Use momentum schedule to accelerate model convergence__:
We support momentum scheduler to modify model's momentum according to learning rate, which could make the model converge in a faster way.
Momentum scheduler is usually used with LR scheduler, for example, the following config is used in 3D detection to accelerate convergence.
For more details, please refer to the implementation of [CyclicLrUpdater](https://github.com/open-mmlab/mmcv/blob/f48241a65aebfe07db122e9db320c31b685dc674/mmcv/runner/hooks/lr_updater.py#L327)
and [CyclicMomentumUpdater](https://github.com/open-mmlab/mmcv/blob/f48241a65aebfe07db122e9db320c31b685dc674/mmcv/runner/hooks/momentum_updater.py#L130).
```python
lr_config = dict(
policy='cyclic',
target_ratio=(10, 1e-4),
cyclic_times=1,
step_ratio_up=0.4,
)
momentum_config = dict(
policy='cyclic',
target_ratio=(0.85 / 0.95, 1),
cyclic_times=1,
step_ratio_up=0.4,
)
```
## Customize Training Schedules
we use step learning rate with default value in config files, this calls [`StepLRHook`](https://github.com/open-mmlab/mmcv/blob/f48241a65aebfe07db122e9db320c31b685dc674/mmcv/runner/hooks/lr_updater.py#L153) in MMCV.
We support many other learning rate schedule [here](https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/lr_updater.py), such as `CosineAnnealing` and `Poly` schedule. Here are some examples
- Poly schedule:
```python
lr_config = dict(policy='poly', power=0.9, min_lr=1e-4, by_epoch=False)
```
- ConsineAnnealing schedule:
```python
lr_config = dict(
policy='CosineAnnealing',
warmup='linear',
warmup_iters=1000,
warmup_ratio=1.0 / 10,
min_lr_ratio=1e-5)
```
## Customize Workflow
By default, we recommend users to use `EpochEvalHook` to do evaluation after training epoch, but they can still use `val` workflow as an alternative.
Workflow is a list of (phase, epochs) to specify the running order and epochs. By default it is set to be
```python
workflow = [('train', 1)]
```
which means running 1 epoch for training.
Sometimes user may want to check some metrics (e.g. loss, accuracy) about the model on the validate set.
In such case, we can set the workflow as
```python
[('train', 1), ('val', 1)]
```
so that 1 epoch for training and 1 epoch for validation will be run iteratively.
```{note}
1. The parameters of model will not be updated during val epoch.
1. Keyword `total_epochs` in the config only controls the number of training epochs and will not affect the validation workflow.
1. Workflows `[('train', 1), ('val', 1)]` and `[('train', 1)]` will not change the behavior of `EpochEvalHook` because `EpochEvalHook` is called by `after_train_epoch` and validation workflow only affect hooks that are called through `after_val_epoch`.
Therefore, the only difference between `[('train', 1), ('val', 1)]` and `[('train', 1)]` is that the runner will calculate losses on validation set after each training epoch.
```
## Customize Hooks
### Customize self-implemented hooks
#### 1. Implement a new hook
Here we give an example of creating a new hook in MMPose and using it in training.
```python
from mmcv.runner import HOOKS, Hook
@HOOKS.register_module()
class MyHook(Hook):
def __init__(self, a, b):
pass
def before_run(self, runner):
pass
def after_run(self, runner):
pass
def before_epoch(self, runner):
pass
def after_epoch(self, runner):
pass
def before_iter(self, runner):
pass
def after_iter(self, runner):
pass
```
Depending on the functionality of the hook, the users need to specify what the hook will do at each stage of the training in `before_run`, `after_run`, `before_epoch`, `after_epoch`, `before_iter`, and `after_iter`.
#### 2. Register the new hook
Then we need to make `MyHook` imported. Assuming the file is in `mmpose/core/utils/my_hook.py` there are two ways to do that:
- Modify `mmpose/core/utils/__init__.py` to import it.
The newly defined module should be imported in `mmpose/core/utils/__init__.py` so that the registry will
find the new module and add it:
```python
from .my_hook import MyHook
```
- Use `custom_imports` in the config to manually import it
```python
custom_imports = dict(imports=['mmpose.core.utils.my_hook'], allow_failed_imports=False)
```
#### 3. Modify the config
```python
custom_hooks = [
dict(type='MyHook', a=a_value, b=b_value)
]
```
You can also set the priority of the hook by adding key `priority` to `'NORMAL'` or `'HIGHEST'` as below
```python
custom_hooks = [
dict(type='MyHook', a=a_value, b=b_value, priority='NORMAL')
]
```
By default the hook's priority is set as `NORMAL` during registration.
### Use hooks implemented in MMCV
If the hook is already implemented in MMCV, you can directly modify the config to use the hook as below
```python
mmcv_hooks = [
dict(type='MMCVHook', a=a_value, b=b_value, priority='NORMAL')
]
```
### Modify default runtime hooks
There are some common hooks that are not registered through `custom_hooks` but has been registered by default when importing MMCV, they are
- log_config
- checkpoint_config
- evaluation
- lr_config
- optimizer_config
- momentum_config
In those hooks, only the logger hook has the `VERY_LOW` priority, others' priority are `NORMAL`.
The above-mentioned tutorials already cover how to modify `optimizer_config`, `momentum_config`, and `lr_config`.
Here we reveals how what we can do with `log_config`, `checkpoint_config`, and `evaluation`.
#### Checkpoint config
The MMCV runner will use `checkpoint_config` to initialize [`CheckpointHook`](https://github.com/open-mmlab/mmcv/blob/9ecd6b0d5ff9d2172c49a182eaa669e9f27bb8e7/mmcv/runner/hooks/checkpoint.py#L9).
```python
checkpoint_config = dict(interval=1)
```
The users could set `max_keep_ckpts` to only save only small number of checkpoints or decide whether to store state dict of optimizer by `save_optimizer`.
More details of the arguments are [here](https://mmcv.readthedocs.io/en/latest/api.html#mmcv.runner.CheckpointHook)
#### Log config
The `log_config` wraps multiple logger hooks and enables to set intervals. Now MMCV supports `WandbLoggerHook`, `MlflowLoggerHook`, and `TensorboardLoggerHook`.
The detail usages can be found in the [doc](https://mmcv.readthedocs.io/en/latest/api.html#mmcv.runner.LoggerHook).
```python
log_config = dict(
interval=50,
hooks=[
dict(type='TextLoggerHook'),
dict(type='TensorboardLoggerHook')
])
```
#### Evaluation config
The config of `evaluation` will be used to initialize the [`EvalHook`](https://github.com/open-mmlab/mmpose/blob/master/mmpose/core/evaluation/eval_hooks.py#L11).
Except the key `interval`, other arguments such as `metric` will be passed to the `dataset.evaluate()`
```python
evaluation = dict(interval=1, metric='mAP')
```