Initial snapshot: hand motion pipeline (Dyn-HaMR + dex-retargeting + SPIDER)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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.
|
||||
+352
@@ -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')
|
||||
```
|
||||
Reference in New Issue
Block a user