forked from mindspore-Ecosystem/mindspore
!7961 new add centerface network.
From: @linqingke Reviewed-by: Signed-off-by:
This commit is contained in:
commit
8193749295
|
@ -0,0 +1,502 @@
|
|||
# Contents
|
||||
|
||||
- [CenterFace Description](#CenterFace-description)
|
||||
- [Model Architecture](#model-architecture)
|
||||
- [Dataset](#dataset)
|
||||
- [Environment Requirements](#environment-requirements)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Script Description](#script-description)
|
||||
- [Script and Sample Code](#script-and-sample-code)
|
||||
- [Script Parameters](#script-parameters)
|
||||
- [Training Process](#training-process)
|
||||
- [Training](#training)
|
||||
- [Testing Process](#testing-process)
|
||||
- [Evaluation](#testing)
|
||||
- [Evaluation Process](#evaluation-process)
|
||||
- [Evaluation](#evaluation)
|
||||
- [Convert Process](#convert-process)
|
||||
- [Convert](#convert)
|
||||
- [Model Description](#model-description)
|
||||
- [Performance](#performance)
|
||||
- [Evaluation Performance](#evaluation-performance)
|
||||
- [Inference Performance](#inference-performance)
|
||||
- [ModelZoo Homepage](#modelzoo-homepage)
|
||||
|
||||
|
||||
# [CenterFace Description](#contents)
|
||||
|
||||
CenterFace is a practical anchor-free face detection and alignment method for edge devices, we support training and evaluation on Ascend910.
|
||||
|
||||
Face detection and alignment in unconstrained environment is always deployed on edge devices which have limited memory storage and low computing power.
|
||||
CenterFace proposes a one-stage method to simultaneously predict facial box and landmark location with real-time speed and high accuracy.
|
||||
|
||||
[Paper](https://arxiv.org/ftp/arxiv/papers/1911/1911.03599.pdf): CenterFace: Joint Face Detection and Alignment Using Face as Point.
|
||||
Xu, Yuanyuan(Huaqiao University) and Yan, Wan(StarClouds) and Sun, Haixin(Xiamen University)
|
||||
and Yang, Genke(Shanghai Jiaotong University) and Luo, Jiliang(Huaqiao University)
|
||||
|
||||
# [Model Architecture](#contents)
|
||||
|
||||
CenterFace uses mobilenet_v2 as pretrained backbone, add 4 layer fpn, with four head.
|
||||
Four loss is presented, total loss is their weighted mean.
|
||||
|
||||
# [Dataset](#contents)
|
||||
|
||||
Note that you can run the scripts based on the dataset mentioned in original paper or widely used in relevant domain/network architecture. In the following sections, we will introduce how to run the scripts using the related dataset below.
|
||||
|
||||
Dataset support: [WiderFace] or datasetd with the same format as WiderFace
|
||||
Annotation support: [WiderFace] or annotation as the same format as WiderFace
|
||||
|
||||
- The directory structure is as follows, the name of directory and file is user define:
|
||||
```
|
||||
├── dataset
|
||||
├── centerface
|
||||
├── annotations
|
||||
│ ├─ train.json
|
||||
│ └─ val.json
|
||||
├─ images
|
||||
│ ├─ train
|
||||
│ │ └─images
|
||||
│ │ ├─class1_image_folder
|
||||
│ │ ├─ ...
|
||||
│ │ └─classn_image_folder
|
||||
│ └─ val
|
||||
│ └─images
|
||||
│ ├─class1_image_folder
|
||||
│ ├─ ...
|
||||
│ └─classn_image_folder
|
||||
└─ ground_truth
|
||||
├─val.mat
|
||||
├─ ...
|
||||
└─xxx.mat
|
||||
```
|
||||
we suggest user to use WiderFace dataset to experience our model,
|
||||
other datasets need to use the same format as WiderFace.
|
||||
|
||||
# [Environment Requirements](#contents)
|
||||
|
||||
- Hardware(Ascend)
|
||||
- Prepare hardware environment with Ascend processor. If you want to try Ascend, please send the [application form](https://obs-9be7.obs.cn-east-2.myhuaweicloud.com/file/other/Ascend%20Model%20Zoo%E4%BD%93%E9%AA%8C%E8%B5%84%E6%BA%90%E7%94%B3%E8%AF%B7%E8%A1%A8.docx) to ascend@huawei.com. Once approved, you can get the resources.
|
||||
- Framework
|
||||
- [MindSpore](https://cmc-szv.clouddragon.huawei.com/cmcversion/index/search?searchKey=Do-MindSpore%20V100R001C00B622)
|
||||
- For more information, please check the resources below:
|
||||
- [MindSpore tutorials](https://www.mindspore.cn/tutorial/zh-CN/master/index.html)
|
||||
- [MindSpore API](https://www.mindspore.cn/api/zh-CN/master/index.html)
|
||||
|
||||
# [Quick Start](#contents)
|
||||
|
||||
After installing MindSpore via the official website, you can start training and evaluation as follows:
|
||||
|
||||
step1: prepare pretrained model: train a mobilenet_v2 model by mindspore or use the script below:
|
||||
```python
|
||||
#CenterFace need a pretrained mobilenet_v2 model:
|
||||
# mobilenet_v2_key.ckpt is a model with all value zero, we need the key/cell/module name for this model.
|
||||
# you must first use this script to convert your mobilenet_v2 pytorch model to mindspore model as a pretrain model.
|
||||
# The key/cell/module name must as follow, otherwise you need to modify "name_map" function:
|
||||
# --mindspore: as the same as mobilenet_v2_key.ckpt
|
||||
# --pytorch: same as official pytorch model(e.g., official mobilenet_v2-b0353104.pth)
|
||||
python torch_to_ms_mobilenetv2.py --ckpt_fn=./mobilenet_v2_key.ckpt --pt_fn=./mobilenet_v2-b0353104.pth --out_ckpt_fn=./mobilenet_v2.ckpt
|
||||
```
|
||||
step2: prepare user rank_table
|
||||
```python
|
||||
# user can use your own rank table file
|
||||
# or use the [hccl_tools](https://gitee.com/mindspore/mindspore/tree/master/model_zoo/utils/hccl_tools) to generate rank table file
|
||||
# e.g., python hccl_tools.py --device_num "[0,8)"
|
||||
python hccl_tools.py --device_num "[0,8)"
|
||||
```
|
||||
step3: train
|
||||
```python
|
||||
cd scripts;
|
||||
# prepare data_path, use symbolic link
|
||||
ln -sf [USE_DATA_DIR] dataset
|
||||
# check you dir to make sure your datas are in the right path
|
||||
ls ./dataset/centerface # data path
|
||||
ls ./dataset/centerface/annotations/train.json # annot_path
|
||||
ls ./dataset/centerface/images/train/images # img_dir
|
||||
```
|
||||
```python
|
||||
# enter script dir, train CenterFace
|
||||
sh train_distribute.sh
|
||||
# after training
|
||||
mkdir ./model
|
||||
cp device0/outputs/*/*.ckpt ./model # cp model to [MODEL_PATH]
|
||||
```
|
||||
step4: test
|
||||
```python
|
||||
# test CenterFace preparing
|
||||
cd ../dependency/centernet/src/lib/external;
|
||||
python setup.py install;
|
||||
make;
|
||||
cd -; #cd ../../../../../scripts;
|
||||
cd ../dependency/evaluate;
|
||||
python setup.py install; # used for eval
|
||||
cd -; #cd ../../scripts;
|
||||
mkdir ./output
|
||||
mkdir ./output/centerface
|
||||
# check you dir to make sure your datas are in the right path
|
||||
ls ./dataset/images/val/images/ # data path
|
||||
ls ./dataset/centerface/ground_truth/val.mat # annot_path
|
||||
```
|
||||
```python
|
||||
# test CenterFace
|
||||
sh test_distribute.sh
|
||||
```
|
||||
step5: eval
|
||||
```python
|
||||
# after test, eval CenterFace, get MAP
|
||||
# cd ../dependency/evaluate;
|
||||
# python setup.py install;
|
||||
# cd -; #cd ../../scripts;
|
||||
sh eval_all.sh
|
||||
```
|
||||
|
||||
# [Script Description](#contents)
|
||||
|
||||
## [Script and Sample Code](#contents)
|
||||
|
||||
```
|
||||
├── cv
|
||||
├── centerface
|
||||
├── train.py // training scripts
|
||||
├── test.py // testing training outputs
|
||||
├── export.py // convert mindspore model to air model
|
||||
├── README.md // descriptions about CenterFace
|
||||
├── scripts
|
||||
│ ├──eval.sh // evaluate a single testing result
|
||||
│ ├──eval_all.sh // choose a range of testing results to evaluate
|
||||
│ ├──test.sh // testing a single model
|
||||
│ ├──test_distribute.sh // testing a range of models
|
||||
│ ├──test_and_eval.sh // test then evaluate a single model
|
||||
│ ├──train_standalone.sh // train in ascend with single npu
|
||||
│ ├──train_distribute.sh // train in ascend with multi npu
|
||||
├── src
|
||||
│ ├──__init__.py
|
||||
│ ├──centerface.py // centerface networks, training entry
|
||||
│ ├──dataset.py // generate dataloader and data processing entry
|
||||
│ ├──config.py // centerface unique configs
|
||||
│ ├──losses.py // losses for centerface
|
||||
│ ├──lr_scheduler.py // learning rate scheduler
|
||||
│ ├──mobile_v2.py // modified mobilenet_v2 backbone
|
||||
│ ├──utils.py // auxiliary functions for train, to log and preload
|
||||
│ ├──var_init.py // weight initilization
|
||||
│ ├──convert_weight_mobilenetv2.py // convert pretrained backbone to mindspore
|
||||
│ ├──convert_weight.py // CenterFace model convert to mindspore
|
||||
└── dependency // third party codes: MIT License
|
||||
├──extd // training dependency: data augmentation
|
||||
│ ├──utils
|
||||
│ │ └──augmentations.py // data anchor sample of PyramidBox to generate small images
|
||||
├──evaluate // evaluate dependency
|
||||
│ ├──box_overlaps.pyx // box overlaps
|
||||
│ ├──setup.py // setupfile for box_overlaps.pyx
|
||||
│ ├──eval.py // evaluate testing results
|
||||
└──centernet // modified from 'centernet'
|
||||
└──src
|
||||
└──lib
|
||||
├──datasets
|
||||
│ ├──dataset // train dataset core
|
||||
│ │ ├──coco_hp.py // read and formatting data
|
||||
│ ├──sample
|
||||
│ │ └──multi_pose.py // core for data processing
|
||||
├──detectors // test core, including running, pre-processing and post-processing
|
||||
│ ├──base_detector.py // user can add your own test core; for example, use pytorch or tf for pre/post processing
|
||||
├──external // test dependency
|
||||
│ ├──__init__.py
|
||||
│ ├──Makefile // makefile for nms
|
||||
│ ├──nms.pyx // use soft_nms
|
||||
│ ├──setup.py // setupfile for nms.pyx
|
||||
└──utils
|
||||
└──image.py // image processing functions
|
||||
```
|
||||
|
||||
## [Script Parameters](#contents)
|
||||
1. train scripts parameters
|
||||
the command is: python train.py [train parameters]
|
||||
Major parameters train.py as follows:
|
||||
```python
|
||||
--lr: learning rate
|
||||
--per_batch_size: batch size on each device
|
||||
--is_distributed: multi-device or not
|
||||
--t_max: for cosine lr_scheduler
|
||||
--max_epoch: training epochs
|
||||
--warmup_epochs: warmup_epochs, not needed for adam, needed for sgd
|
||||
--lr scheduler: learning rate scheduler, default is multistep
|
||||
--lr_epochs: decrease lr steps
|
||||
--lr_gamma: decrease lr by a factor
|
||||
--weight_decay: weight decay
|
||||
--loss_scale: mix precision training
|
||||
--pretrained_backbone: pretrained mobilenet_v2 model path
|
||||
--data_dir: data dir
|
||||
--annot_path: annotations path
|
||||
--img_dir: img dir in data_dir
|
||||
```
|
||||
2. centerface unique configs: in config.py; not recommend user to change
|
||||
|
||||
3. test scripts parameters:
|
||||
the command is: python test.py [test parameters]
|
||||
Major parameters test.py as follows:
|
||||
```python
|
||||
test_script_path: test.py path;
|
||||
--is_distributed: multi-device or not
|
||||
--data_dir: img dir
|
||||
--test_model: test model dir
|
||||
--ground_truth_mat: ground_truth file, mat type
|
||||
--save_dir: save_path for evaluate
|
||||
--rank: use device id
|
||||
--ckpt_name: test model name
|
||||
# blow are used for calculate ckpt/model name
|
||||
# model/ckpt name is "0-" + str(ckpt_num) + "_" + str(steps_per_epoch*ckpt_num) + ".ckpt";
|
||||
# ckpt_num is epoch number, can be calculated by device_num
|
||||
# detail can be found in "test.py"
|
||||
# if ckpt is specified not need below 4 parameter
|
||||
--device_num: training device number
|
||||
--steps_per_epoch: steps for each epoch
|
||||
--start: start loop number, used to calculate first epoch number
|
||||
--end: end loop number, used to calculate last epoch number
|
||||
```
|
||||
|
||||
4. eval scripts parameters:
|
||||
the command is: python eval.py [pred] [gt]
|
||||
Major parameters eval.py as follows:
|
||||
```python
|
||||
--pred: pred path, test output test.py->[--save_dir]
|
||||
--gt: ground truth path
|
||||
```
|
||||
|
||||
## [Training Process](#contents)
|
||||
|
||||
### Training
|
||||
|
||||
'task_set' is important for multi-npu train to get higher speed
|
||||
--task_set: 0, not task_set; 1 task_set;
|
||||
--task_set_core: task_set core number, most time = cpu number/nproc_per_node
|
||||
|
||||
step1: user need train a mobilenet_v2 model by mindspore or use the script below:
|
||||
```python
|
||||
python torch_to_ms_mobilenetv2.py --ckpt_fn=./mobilenet_v2_key.ckpt --pt_fn=./mobilenet_v2-b0353104.pth --out_ckpt_fn=./mobilenet_v2.ckpt
|
||||
```
|
||||
step2: prepare user rank_table
|
||||
```python
|
||||
# user can use your own rank table file
|
||||
# or use the [hccl_tools](https://gitee.com/mindspore/mindspore/tree/master/model_zoo/utils/hccl_tools) to generate rank table file
|
||||
# e.g., python hccl_tools.py --device_num "[0,8)"
|
||||
python hccl_tools.py --device_num "[0,8)"
|
||||
```
|
||||
step3: train
|
||||
- Single device
|
||||
```python
|
||||
# enter script dir, train CenterFace
|
||||
cd scripts
|
||||
# you need to change the parameter in train_standalone.sh
|
||||
# or use symbolic link as quick start
|
||||
# or use the command as follow:
|
||||
# USE_DEVICE_ID: your device
|
||||
# PRETRAINED_BACKBONE: your pretrained model path
|
||||
# DATASET: dataset path
|
||||
# ANNOTATIONS: annotation path
|
||||
# images: img_dir in dataset path
|
||||
sh train_standalone.sh [USE_DEVICE_ID] [PRETRAINED_BACKBONE] [DATASET] [ANNOTATIONS] [IMAGES]
|
||||
# after training
|
||||
cp device0/outputs/*/*.ckpt [MODEL_PATH]
|
||||
```
|
||||
- multi-device (recommended)
|
||||
```python
|
||||
# enter script dir, train CenterFace
|
||||
cd scripts;
|
||||
# you need to change the parameter in train_distribute.sh
|
||||
# or use symbolic link as quick start
|
||||
# or use the command as follow, most are the same as train_standalone.sh, the different is RANK_TABLE
|
||||
# RANK_TABLE: for multi-device only, from generate_rank_table.py or user writing
|
||||
sh train_distribute.sh [RANK_TABLE] [PRETRAINED_BACKBONE] [DATASET] [ANNOTATIONS] [IMAGES]
|
||||
# after training
|
||||
cp device0/outputs/*/*.ckpt [MODEL_PATH]
|
||||
```
|
||||
After training with 8 device, the loss value will be achieved as follows:
|
||||
```python
|
||||
# grep "loss is " device0/xxx.log
|
||||
# epoch: 1 step: 1, loss is greater than 500 and less than 5000
|
||||
2020-09-24 19:00:53,550:INFO:epoch:1, iter:0, average_loss:loss:1148.415649, loss:1148.4156494140625, overflow:False, loss_scale:1024.0
|
||||
[WARNING] DEBUG(51499,python):2020-09-24-19:00:53.590.008 [mindspore/ccsrc/debug/dump_proto.cc:218] SetValueToProto] Unsupported type UInt
|
||||
2020-09-24 19:00:53,784:INFO:epoch:1, iter:1, average_loss:loss:798.286713, loss:448.15777587890625, overflow:False, loss_scale:1024.0
|
||||
...
|
||||
2020-09-24 19:01:58,095:INFO:epoch:2, iter:197, average_loss:loss:1.942609, loss:1.5492267608642578, overflow:False, loss_scale:1024.0
|
||||
2020-09-24 19:01:58,501:INFO:epoch[2], loss:1.942609, 477.97 imgs/sec, lr:0.004000000189989805
|
||||
2020-09-24 19:01:58,502:INFO:==========end epoch===============
|
||||
2020-09-24 19:02:00,780:INFO:epoch:3, iter:0, average_loss:loss:2.107658, loss:2.1076583862304688, overflow:False, loss_scale:1024.0
|
||||
...
|
||||
# epoch: 140 average loss is greater than 0.3 and less than 1.5:
|
||||
2020-09-24 20:19:16,255:INFO:epoch:140, iter:196, average_loss:loss:0.906300, loss:1.1071504354476929, overflow:False, loss_scale:1024.0
|
||||
2020-09-24 20:19:16,347:INFO:epoch:140, iter:197, average_loss:loss:0.904684, loss:0.586264967918396, overflow:False, loss_scale:1024.0
|
||||
2020-09-24 20:19:16,747:INFO:epoch[140], loss:0.904684, 480.10 imgs/sec, lr:3.9999998989515007e-05
|
||||
2020-09-24 20:19:16,748:INFO:==========end epoch===============
|
||||
2020-09-24 20:19:16,748:INFO:==========end training===============
|
||||
```
|
||||
The model checkpoint will be saved in the scripts/device0/output/xxx/xxx.ckpt
|
||||
|
||||
## [Testing Process](#contents)
|
||||
|
||||
### Testing
|
||||
|
||||
```python
|
||||
# after train, prepare for test CenterFace
|
||||
cd scripts;
|
||||
cd ../dependency/centernet/src/lib/external;
|
||||
python setup.py install;
|
||||
make;
|
||||
cd ../../../scripts;
|
||||
mkdir [SAVE_PATH]
|
||||
```
|
||||
1. test a single ckpt file
|
||||
```python
|
||||
# you need to change the parameter in test.sh
|
||||
# or use symbolic link as quick start
|
||||
# or use the command as follow:
|
||||
# MODEL_PATH: ckpt path saved during training
|
||||
# DATASET: img dir
|
||||
# GROUND_TRUTH_MAT: ground_truth file, mat type
|
||||
# SAVE_PATH: save_path for evaluate
|
||||
# DEVICE_ID: use device id
|
||||
# CKPT: test model name
|
||||
sh test.sh [MODEL_PATH] [DATASET] [GROUND_TRUTH_MAT] [SAVE_PATH] [DEVICE_ID] [CKPT]
|
||||
```
|
||||
2. test many out ckpt for user to choose the best one
|
||||
```python
|
||||
# you need to change the parameter in test.sh
|
||||
# or use symbolic link as quick start
|
||||
# or use the command as follow, most are the same as test.sh, the different are:
|
||||
# DEVICE_NUM: training device number
|
||||
# STEPS_PER_EPOCH: steps for each epoch
|
||||
# START: start loop number, used to calculate first epoch number
|
||||
# END: end loop number, used to calculate last epoch number
|
||||
sh test_distribute.sh [MODEL_PATH] [DATASET] [GROUND_TRUTH_MAT] [SAVE_PATH] [DEVICE_NUM] [STEPS_PER_EPOCH] [START] [END]
|
||||
```
|
||||
After testing, you can find many txt file save the box information and scores,
|
||||
open it you can see:
|
||||
```python
|
||||
646.3 189.1 42.1 51.8 0.747 # left top hight weight score
|
||||
157.4 408.6 43.1 54.1 0.667
|
||||
120.3 212.4 38.7 42.8 0.650
|
||||
...
|
||||
```
|
||||
## [Evaluation Process](#contents)
|
||||
|
||||
### Evaluation
|
||||
|
||||
```python
|
||||
# after test, prepare for eval CenterFace, get MAP
|
||||
cd ../dependency/evaluate;
|
||||
python setup.py install;
|
||||
cd ../../../scripts;
|
||||
```
|
||||
1. eval a single testing output
|
||||
```python
|
||||
# you need to change the parameter in eval.sh
|
||||
# default eval the ckpt saved in ./scripts/output/centerface/999
|
||||
sh eval.sh
|
||||
```
|
||||
2. eval many testing output for user to choose the best one
|
||||
```python
|
||||
# you need to change the parameter in eval_all.sh
|
||||
# default eval the ckpt saved in ./scripts/output/centerface/[89-140]
|
||||
sh eval_all.sh
|
||||
```
|
||||
3. test+eval
|
||||
```python
|
||||
# you need to change the parameter in test_and_eval.sh
|
||||
# or use symbolic link as quick start, default eval the ckpt saved in ./scripts/output/centerface/999
|
||||
# or use the command as follow, most are the same as test.sh, the different are:
|
||||
# GROUND_TRUTH_PATH: ground truth path
|
||||
sh test_and_eval.sh [MODEL_PATH] [DATASET] [GROUND_TRUTH_MAT] [SAVE_PATH] [CKPT] [GROUND_TRUTH_PATH]
|
||||
```
|
||||
you can see the MAP below by eval.sh
|
||||
```
|
||||
(ci3.7) [root@bms-aiserver scripts]# ./eval.sh
|
||||
start eval
|
||||
==================== Results = ==================== ./scripts/output/centerface/999
|
||||
Easy Val AP: 0.923914407045363
|
||||
Medium Val AP: 0.9166100571371586
|
||||
Hard Val AP: 0.7810750535799462
|
||||
=================================================
|
||||
end eval
|
||||
```
|
||||
|
||||
you can see the MAP below by eval_all.sh
|
||||
```
|
||||
(ci3.7) [root@bms-aiserver scripts]# ./eval_all.sh
|
||||
==================== Results = ==================== ./scripts/output/centerface/89
|
||||
Easy Val AP: 0.8884892849068273
|
||||
Medium Val AP: 0.8928813452811216
|
||||
Hard Val AP: 0.7721131614294564
|
||||
=================================================
|
||||
==================== Results = ==================== ./scripts/output/centerface/90
|
||||
Easy Val AP: 0.8836073914165545
|
||||
Medium Val AP: 0.8875938506473486
|
||||
Hard Val AP: 0.775956751740446
|
||||
...
|
||||
==================== Results = ==================== ./scripts/output/centerface/125
|
||||
Easy Val AP: 0.923914407045363
|
||||
Medium Val AP: 0.9166100571371586
|
||||
Hard Val AP: 0.7810750535799462
|
||||
=================================================
|
||||
==================== Results = ==================== ./scripts/output/centerface/126
|
||||
Easy Val AP: 0.9218741197149122
|
||||
Medium Val AP: 0.9151860193570651
|
||||
Hard Val AP: 0.7825645670331809
|
||||
...
|
||||
==================== Results = ==================== ./scripts/output/centerface/140
|
||||
Easy Val AP: 0.9250715236965638
|
||||
Medium Val AP: 0.9170429723233877
|
||||
Hard Val AP: 0.7822182013830674
|
||||
=================================================
|
||||
```
|
||||
## [Convert Process](#contents)
|
||||
|
||||
### Convert
|
||||
If you want to infer the network on Ascend 310, you should convert the model to AIR:
|
||||
|
||||
```python
|
||||
python export.py [BATCH_SIZE] [PRETRAINED_BACKBONE]
|
||||
```
|
||||
|
||||
# [Model Description](#contents)
|
||||
|
||||
## [Performance](#contents)
|
||||
|
||||
### Evaluation Performance
|
||||
CenterFace on 13K images(The annotation and data format must be the same as widerFace)
|
||||
|
||||
| Parameters | CenterFace |
|
||||
| -------------------------- | ----------------------------------------------------------- |
|
||||
| Resource | Ascend 910; CPU 2.60GHz, 192cores; Memory, 755G |
|
||||
| uploaded Date | 10/29/2020 (month/day/year) |
|
||||
| MindSpore Version | 1.0.0 |
|
||||
| Dataset | 13K images |
|
||||
| Training Parameters | epoch=140, steps=198 * epoch, batch_size = 8, lr=0.004 |
|
||||
| Optimizer | Adam |
|
||||
| Loss Function | Focal Loss, L1 Loss, Smooth L1 Loss |
|
||||
| outputs | heatmaps |
|
||||
| Loss | 0.3-1.5, average loss for last epoch is in 0.8-1.0 |
|
||||
| Speed | 1p 65 img/s, 8p 475 img/s |
|
||||
| Total time | train(8p) 1.1h, test 50min, eval 5-10min |
|
||||
| Checkpoint for Fine tuning | 22M (.ckpt file) |
|
||||
| Scripts | https://gitee.com/mindspore/mindspore/tree/master/model_zoo/official/cv/centerface |
|
||||
|
||||
### Inference Performance
|
||||
CenterFace on 3.2K images(The annotation and data format must be the same as widerFace)
|
||||
|
||||
| Parameters | CenterFace |
|
||||
| -------------------------- | ----------------------------------------------------------- |
|
||||
| Resource | Ascend 910; CPU 2.60GHz, 192cores; Memory, 755G |
|
||||
| uploaded Date | 10/29/2020 (month/day/year) |
|
||||
| MindSpore Version | 1.0.0 |
|
||||
| Dataset | 3.2K images |
|
||||
| batch_size | 1 |
|
||||
| outputs | box position and sorces, and probability |
|
||||
| Accuracy | Easy 92.2% Medium 91.5% Hard 78.2% (+-0.5%) |
|
||||
| Model for inference | 22M (.ckpt file) |
|
||||
|
||||
# [Description of Random Situation](#contents)
|
||||
|
||||
In dataset.py, we set the seed inside ```create_dataset``` function.
|
||||
In var_init.py, we set seed for weight initilization
|
||||
|
||||
# [ModelZoo Homepage](#contents)
|
||||
Please check the official [homepage](https://gitee.com/mindspore/mindspore/tree/master/model_zoo).
|
|
@ -0,0 +1,89 @@
|
|||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Xingyi Zhou
|
||||
All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import numpy as np
|
||||
import pycocotools.coco as coco
|
||||
import cv2
|
||||
|
||||
class CenterfaceDataset():
|
||||
"""
|
||||
Centerface dataset definition.
|
||||
"""
|
||||
def __init__(self, config, split='train'):
|
||||
self.split = split
|
||||
self.config = config
|
||||
self.max_objs = config.max_objs
|
||||
self.img_dir = self.config.img_dir
|
||||
self.annot_path = self.config.annot_path
|
||||
|
||||
print('==> getting centerface key point {} data.'.format(split))
|
||||
self.coco = coco.COCO(self.annot_path)
|
||||
image_ids = self.coco.getImgIds()
|
||||
|
||||
if split == 'train':
|
||||
self.images = []
|
||||
for img_id in image_ids:
|
||||
idxs = self.coco.getAnnIds(imgIds=[img_id])
|
||||
if idxs:
|
||||
self.images.append(img_id)
|
||||
else:
|
||||
self.images = image_ids
|
||||
self.num_samples = len(self.images)
|
||||
print('Loaded {} {} samples'.format(split, self.num_samples)) # Loaded train 12671 samples
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""
|
||||
Args:
|
||||
index (int): Index
|
||||
|
||||
Returns:
|
||||
(image, target) (tuple): target is index of the target class.
|
||||
"""
|
||||
img_id = self.images[index]
|
||||
file_name = self.coco.loadImgs(ids=[img_id])[0]['file_name']
|
||||
img_path = os.path.join(self.img_dir, file_name)
|
||||
ann_ids = self.coco.getAnnIds(imgIds=[img_id])
|
||||
anns = self.coco.loadAnns(ids=ann_ids)
|
||||
num_objs = len(anns)
|
||||
if num_objs > self.max_objs:
|
||||
num_objs = self.max_objs
|
||||
anns = np.random.choice(anns, num_objs)
|
||||
# dataType ERROR —— to_list
|
||||
target = []
|
||||
for ann in anns:
|
||||
tmp = []
|
||||
tmp.extend(ann['bbox'])
|
||||
tmp.extend(ann['keypoints'])
|
||||
target.append(tmp)
|
||||
|
||||
img = cv2.imread(img_path)
|
||||
return img, target
|
||||
|
||||
def __len__(self):
|
||||
return self.num_samples
|
|
@ -0,0 +1,217 @@
|
|||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Xingyi Zhou
|
||||
All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import math
|
||||
import numpy as np
|
||||
import cv2
|
||||
|
||||
from dependency.centernet.src.lib.utils.image import color_aug
|
||||
from dependency.centernet.src.lib.utils.image import get_affine_transform, affine_transform
|
||||
from dependency.centernet.src.lib.utils.image import gaussian_radius, draw_umich_gaussian
|
||||
from dependency.extd.utils.augmentations import anchor_crop_image_sampling
|
||||
|
||||
def get_border(border, size):
|
||||
"""
|
||||
Get border
|
||||
"""
|
||||
i = 1
|
||||
while size - border // i <= border // i: # size > 2 * (border // i)
|
||||
i *= 2
|
||||
return border // i
|
||||
|
||||
def coco_box_to_bbox(box):
|
||||
"""
|
||||
(x1, y1, w, h) -> (x1, y1, x2, y2)
|
||||
"""
|
||||
bbox = np.array([box[0], box[1], box[0] + box[2], box[1] + box[3]], dtype=np.float32)
|
||||
return bbox
|
||||
|
||||
def preprocess_train(image, target, config):
|
||||
"""
|
||||
Preprocess training data
|
||||
"""
|
||||
data_rng = np.random.RandomState(123)
|
||||
eig_val = np.array([0.2141788, 0.01817699, 0.00341571], dtype=np.float32)
|
||||
eig_vec = np.array([
|
||||
[-0.58752847, -0.69563484, 0.41340352],
|
||||
[-0.5832747, 0.00994535, -0.81221408],
|
||||
[-0.56089297, 0.71832671, 0.41158938]
|
||||
], dtype=np.float32)
|
||||
mean = np.array([0.40789654, 0.44719302, 0.47026115], dtype=np.float32).reshape((1, 1, 3))
|
||||
std = np.array([0.28863828, 0.27408164, 0.27809835], dtype=np.float32).reshape((1, 1, 3))
|
||||
num_objs = len(target)
|
||||
|
||||
anns = []
|
||||
for each in target:
|
||||
ann = {}
|
||||
ann['bbox'] = each[0:4]
|
||||
ann['keypoints'] = each[4:]
|
||||
anns.append(ann)
|
||||
|
||||
cv2.setNumThreads(0)
|
||||
img, anns = anchor_crop_image_sampling(image, anns)
|
||||
|
||||
_, width = img.shape[0], img.shape[1]
|
||||
c = np.array([img.shape[1] / 2., img.shape[0] / 2.], dtype=np.float32)
|
||||
s = max(img.shape[0], img.shape[1]) * 1.0
|
||||
rot = 0
|
||||
flipped = False
|
||||
if config.rand_crop:
|
||||
#s = s * np.random.choice(np.arange(0.8, 1.3, 0.05)) # for 768*768 or 800* 800
|
||||
s = s * np.random.choice(np.arange(0.6, 1.0, 0.05)) # for 512 * 512
|
||||
border = s * np.random.choice([0.1, 0.2, 0.25])
|
||||
w_border = get_border(border, img.shape[1]) # w > 2 * w_border
|
||||
h_border = get_border(border, img.shape[0]) # h > 2 * h_border
|
||||
c[0] = np.random.randint(low=w_border, high=img.shape[1] - w_border)
|
||||
c[1] = np.random.randint(low=h_border, high=img.shape[0] - h_border)
|
||||
else:
|
||||
sf = config.scale
|
||||
cf = config.shift
|
||||
c[0] += s * np.clip(np.random.randn() * cf, -2 * cf, 2 * cf)
|
||||
c[1] += s * np.clip(np.random.randn() * cf, -2 * cf, 2 * cf)
|
||||
s = s * np.clip(np.random.randn() * sf + 1, 1 - sf, 1 + sf)
|
||||
if np.random.random() < config.rotate:
|
||||
rf = config.rotate
|
||||
rot = np.clip(np.random.randn() * rf, -rf * 2, rf * 2)
|
||||
|
||||
if np.random.random() < config.flip: # opt.flip = 0.5
|
||||
flipped = True
|
||||
img = img[:, ::-1, :]
|
||||
c[0] = width - c[0] - 1
|
||||
|
||||
trans_input = get_affine_transform(c, s, rot, [config.input_res, config.input_res])
|
||||
inp = cv2.warpAffine(img, trans_input, (config.input_res, config.input_res), flags=cv2.INTER_LINEAR)
|
||||
|
||||
inp = (inp.astype(np.float32) / 255.)
|
||||
if config.color_aug:
|
||||
color_aug(data_rng, inp, eig_val, eig_vec)
|
||||
|
||||
inp = (inp - mean) / std
|
||||
inp = inp.transpose(2, 0, 1)
|
||||
|
||||
output_res = config.output_res
|
||||
num_joints = config.num_joints
|
||||
max_objs = config.max_objs
|
||||
trans_output_rot = get_affine_transform(c, s, rot, [output_res, output_res])
|
||||
trans_output = get_affine_transform(c, s, 0, [output_res, output_res])
|
||||
|
||||
# map
|
||||
hm = np.zeros((config.num_classes, output_res, output_res), dtype=np.float32)
|
||||
hm_hp = np.zeros((num_joints, output_res, output_res), dtype=np.float32)
|
||||
|
||||
wh = np.zeros((output_res, output_res, 2), dtype=np.float32)
|
||||
reg = np.zeros((output_res, output_res, 2), dtype=np.float32)
|
||||
ind = np.zeros((output_res, output_res), dtype=np.float32) # as float32, need no data_type change later
|
||||
|
||||
reg_mask = np.zeros((max_objs), dtype=np.uint8)
|
||||
wight_mask = np.zeros((output_res, output_res, 2), dtype=np.float32)
|
||||
|
||||
kps = np.zeros((output_res, output_res, num_joints * 2), dtype=np.float32)
|
||||
kps_mask = np.zeros((output_res, output_res, num_joints * 2), dtype=np.float32)
|
||||
#
|
||||
hp_offset = np.zeros((max_objs * num_joints, 2), dtype=np.float32)
|
||||
hp_ind = np.zeros((max_objs * num_joints), dtype=np.int64)
|
||||
hp_mask = np.zeros((max_objs * num_joints), dtype=np.int64)
|
||||
|
||||
draw_gaussian = draw_umich_gaussian
|
||||
|
||||
gt_det = []
|
||||
for k in range(num_objs):
|
||||
ann = anns[k]
|
||||
bbox = coco_box_to_bbox(ann['bbox']) # [x,y,w,h]--[x1,y1,x2,y2]
|
||||
cls_id = 0 #int(ann['category_id']) - 1
|
||||
pts = np.array(ann['keypoints'], np.float32).reshape(num_joints, 3) # (x,y,0/1)
|
||||
if flipped:
|
||||
bbox[[0, 2]] = width - bbox[[2, 0]] - 1
|
||||
pts[:, 0] = width - pts[:, 0] - 1
|
||||
for e in config.flip_idx: # flip_idx = [[0, 1], [3, 4]]
|
||||
pts[e[0]], pts[e[1]] = pts[e[1]].copy(), pts[e[0]].copy()
|
||||
|
||||
bbox[:2] = affine_transform(bbox[:2], trans_output) # [0, 1] -- (x1, y1)
|
||||
bbox[2:] = affine_transform(bbox[2:], trans_output) # [2, 3] -- (x2, y2)
|
||||
bbox = np.clip(bbox, 0, output_res - 1)
|
||||
h, w = bbox[3] - bbox[1], bbox[2] - bbox[0]
|
||||
if (h > 0 and w > 0) or (rot != 0):
|
||||
radius = gaussian_radius((math.ceil(h), math.ceil(w)))
|
||||
radius = max(0, int(radius))
|
||||
ct = np.array([(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2], dtype=np.float32)
|
||||
ct_int = ct.astype(np.int32)
|
||||
|
||||
ind[ct_int[1], ct_int[0]] = 1.0
|
||||
wh[ct_int[1], ct_int[0], :] = np.log(1. * w / 4), np.log(1. * h / 4)
|
||||
reg[ct_int[1], ct_int[0], :] = ct[0] - ct_int[0], ct[1] - ct_int[1]
|
||||
|
||||
reg_mask[k] = 1.0
|
||||
wight_mask[ct_int[1], ct_int[0], 0] = 1
|
||||
wight_mask[ct_int[1], ct_int[0], 1] = 1
|
||||
|
||||
# if w*h <= 20: # can get what we want sometime, but unstable
|
||||
# wight_mask[k] = 15
|
||||
if w*h <= 40:
|
||||
wight_mask[ct_int[1], ct_int[0], 0] = 5
|
||||
wight_mask[ct_int[1], ct_int[0], 1] = 5
|
||||
if w*h <= 20:
|
||||
wight_mask[ct_int[1], ct_int[0], 0] = 10
|
||||
wight_mask[ct_int[1], ct_int[0], 1] = 10
|
||||
if w*h <= 10:
|
||||
wight_mask[ct_int[1], ct_int[0], 0] = 15
|
||||
wight_mask[ct_int[1], ct_int[0], 1] = 15
|
||||
if w*h <= 4:
|
||||
wight_mask[ct_int[1], ct_int[0], 0] = 0.1
|
||||
wight_mask[ct_int[1], ct_int[0], 1] = 0.1
|
||||
|
||||
num_kpts = pts[:, 2].sum()
|
||||
if num_kpts == 0:
|
||||
hm[cls_id, ct_int[1], ct_int[0]] = 0.9999
|
||||
|
||||
hp_radius = gaussian_radius((math.ceil(h), math.ceil(w)))
|
||||
hp_radius = max(0, int(hp_radius))
|
||||
for j in range(num_joints):
|
||||
if pts[j, 2] > 0:
|
||||
pts[j, :2] = affine_transform(pts[j, :2], trans_output_rot)
|
||||
if pts[j, 0] >= 0 and pts[j, 0] < output_res and pts[j, 1] >= 0 and pts[j, 1] < output_res:
|
||||
kps[ct_int[1], ct_int[0], j * 2 : j * 2 + 2] = pts[j, :2] - ct_int
|
||||
kps[ct_int[1], ct_int[0], j * 2 : j * 2 + 1] = kps[ct_int[1], ct_int[0], j * 2 : j * 2 + 1] / w
|
||||
kps[ct_int[1], ct_int[0], j * 2 + 1: j * 2 + 2] = kps[ct_int[1], ct_int[0],
|
||||
j * 2 + 1 : j * 2 + 2] / h
|
||||
kps_mask[ct_int[1], ct_int[0], j * 2 : j * 2 + 2] = 1.0
|
||||
|
||||
pt_int = pts[j, :2].astype(np.int32)
|
||||
hp_offset[k * num_joints + j] = pts[j, :2] - pt_int
|
||||
hp_ind[k * num_joints + j] = pt_int[1] * output_res + pt_int[0]
|
||||
hp_mask[k * num_joints + j] = 1
|
||||
|
||||
draw_gaussian(hm_hp[j], pt_int, hp_radius)
|
||||
kps_mask[ct_int[1], ct_int[0], j * 2 : j * 2 + 2] = \
|
||||
0.0 if ann['bbox'][2] * ann['bbox'][3] <= 8.0 else 1.0
|
||||
draw_gaussian(hm[cls_id], ct_int, radius)
|
||||
gt_det.append([ct[0] - w / 2, ct[1] - h / 2,
|
||||
ct[0] + w / 2, ct[1] + h / 2, 1] +
|
||||
pts[:, :2].reshape(num_joints * 2).tolist() + [cls_id])
|
||||
|
||||
return inp, hm, reg_mask, ind, wh, wight_mask, reg, kps_mask, kps
|
|
@ -0,0 +1,216 @@
|
|||
###modified based on centernet###
|
||||
#MIT License
|
||||
#Copyright (c) 2019 Xingyi Zhou
|
||||
#All rights reserved.
|
||||
"""Basic definition of detector"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import numpy as np
|
||||
import cv2
|
||||
|
||||
from mindspore import Tensor
|
||||
|
||||
from dependency.centernet.src.lib.external.nms import soft_nms
|
||||
from dependency.centernet.src.lib.utils.image import get_affine_transform, affine_transform
|
||||
|
||||
def transform_preds(coords, center, scale, output_size):
|
||||
"""
|
||||
Transform target coords
|
||||
"""
|
||||
target_coords = np.zeros(coords.shape)
|
||||
trans = get_affine_transform(center, scale, 0, output_size, inv=1)
|
||||
for p in range(coords.shape[0]):
|
||||
target_coords[p, 0:2] = affine_transform(coords[p, 0:2], trans)
|
||||
return target_coords
|
||||
|
||||
def multi_pose_post_process(dets, c, s, h, w):
|
||||
"""
|
||||
Multi pose post process
|
||||
dets_result: 4 + score:1 + kpoints:10 + class:1 = 16
|
||||
dets: batch x max_dets x 40
|
||||
return list of 39 in image coord
|
||||
"""
|
||||
ret = []
|
||||
for i in range(dets.shape[0]):
|
||||
bbox = transform_preds(dets[i, :, :4].reshape(-1, 2), c[i], s[i], (w, h))
|
||||
pts = transform_preds(dets[i, :, 5:15].reshape(-1, 2), c[i], s[i], (w, h))
|
||||
top_preds = np.concatenate([bbox.reshape(-1, 4), dets[i, :, 4:5], pts.reshape(-1, 10)],
|
||||
axis=1).astype(np.float32).tolist()
|
||||
ret.append({np.ones(1, dtype=np.int32)[0]: top_preds})
|
||||
return ret
|
||||
|
||||
class CenterFaceDetector():
|
||||
"""
|
||||
Centerface detector
|
||||
"""
|
||||
def __init__(self, opt, model):
|
||||
self.flip_idx = opt.flip_idx
|
||||
|
||||
print('Creating model...')
|
||||
self.model = model
|
||||
|
||||
self.mean = np.array(opt.mean, dtype=np.float32).reshape((1, 1, 3))
|
||||
self.std = np.array(opt.std, dtype=np.float32).reshape((1, 1, 3))
|
||||
self.max_per_image = 100
|
||||
self.num_classes = opt.num_classes
|
||||
self.scales = opt.test_scales
|
||||
self.opt = opt
|
||||
self.pause = False
|
||||
|
||||
def pre_process(self, image, scale, meta=None):
|
||||
"""
|
||||
Preprocess method
|
||||
"""
|
||||
height, width = image.shape[0:2]
|
||||
new_height = int(height * scale)
|
||||
new_width = int(width * scale)
|
||||
if self.opt.fix_res: # True
|
||||
inp_height, inp_width = self.opt.input_h, self.opt.input_w
|
||||
c = np.array([new_width / 2., new_height / 2.], dtype=np.float32)
|
||||
s = max(height, width) * 1.0
|
||||
else:
|
||||
inp_height = int(np.ceil(new_height / 32) * 32)
|
||||
inp_width = int(np.ceil(new_width / 32) * 32)
|
||||
c = np.array([new_width // 2, new_height // 2], dtype=np.float32)
|
||||
s = np.array([inp_width, inp_height], dtype=np.float32)
|
||||
|
||||
trans_input = get_affine_transform(c, s, 0, [inp_width, inp_height])
|
||||
resized_image = cv2.resize(image, (new_width, new_height))
|
||||
inp_image = cv2.warpAffine(
|
||||
resized_image, trans_input, (inp_width, inp_height),
|
||||
flags=cv2.INTER_LINEAR)
|
||||
inp_image = ((inp_image / 255. - self.mean) / self.std).astype(np.float32)
|
||||
|
||||
images = inp_image.transpose(2, 0, 1).reshape(1, 3, inp_height, inp_width)
|
||||
if self.opt.flip_test:
|
||||
images = np.concatenate((images, images[:, :, :, ::-1]), axis=0)
|
||||
|
||||
meta = {'c': c, 's': s, 'out_height': inp_height // self.opt.down_ratio,
|
||||
'out_width': inp_width // self.opt.down_ratio}
|
||||
return images, meta
|
||||
|
||||
def process(self, images):
|
||||
"""
|
||||
Process method
|
||||
"""
|
||||
images = Tensor(images)
|
||||
# test with mindspore model
|
||||
output_hm, output_wh, output_off, output_kps, topk_inds = self.model(images)
|
||||
# Tensor to numpy
|
||||
output_hm = output_hm.asnumpy().astype(np.float32)
|
||||
output_wh = output_wh.asnumpy().astype(np.float32)
|
||||
output_off = output_off.asnumpy().astype(np.float32)
|
||||
output_kps = output_kps.asnumpy().astype(np.float32)
|
||||
topk_inds = topk_inds.asnumpy().astype(np.long)
|
||||
|
||||
reg = output_off if self.opt.reg_offset else None
|
||||
|
||||
dets = self.centerface_decode(output_hm, output_wh, output_kps, reg=reg, opt_k=self.opt.K, topk_inds=topk_inds)
|
||||
|
||||
return dets
|
||||
|
||||
def post_process(self, dets, meta, scale=1):
|
||||
"""
|
||||
Post process process
|
||||
"""
|
||||
dets = dets.reshape(1, -1, dets.shape[2])
|
||||
dets = multi_pose_post_process(
|
||||
dets.copy(), [meta['c']], [meta['s']],
|
||||
meta['out_height'], meta['out_width'])
|
||||
for j in range(1, self.num_classes + 1):
|
||||
dets[0][j] = np.array(dets[0][j], dtype=np.float32).reshape(-1, 15)
|
||||
# import pdb; pdb.set_trace()
|
||||
dets[0][j][:, :4] /= scale
|
||||
dets[0][j][:, 5:] /= scale
|
||||
return dets[0]
|
||||
|
||||
def merge_outputs(self, detections):
|
||||
"""
|
||||
Merge detection outputs
|
||||
"""
|
||||
results = {}
|
||||
results[1] = np.concatenate([detection[1] for detection in detections], axis=0).astype(np.float32)
|
||||
if self.opt.nms or len(self.opt.test_scales) > 1:
|
||||
soft_nms(results[1], Nt=0.5, method=2)
|
||||
results[1] = results[1].tolist()
|
||||
return results
|
||||
|
||||
def run(self, image_or_path_or_tensor, meta=None):
|
||||
"""
|
||||
Run method
|
||||
"""
|
||||
pre_processed = False
|
||||
if isinstance(image_or_path_or_tensor, np.ndarray):
|
||||
image = image_or_path_or_tensor
|
||||
elif isinstance(image_or_path_or_tensor, str):
|
||||
image = cv2.imread(image_or_path_or_tensor)
|
||||
else:
|
||||
image = image_or_path_or_tensor['image'][0].numpy()
|
||||
pre_processed_images = image_or_path_or_tensor
|
||||
pre_processed = True
|
||||
|
||||
detections = []
|
||||
for scale in self.scales: # [1]
|
||||
if not pre_processed:
|
||||
images, meta = self.pre_process(image, scale, meta) # --1: pre_process
|
||||
else:
|
||||
# import pdb; pdb.set_trace()
|
||||
images = pre_processed_images['images'][scale][0]
|
||||
meta = pre_processed_images['meta'][scale]
|
||||
meta = {k: v.numpy()[0] for k, v in meta.items()}
|
||||
|
||||
dets = self.process(images) # --2: process
|
||||
|
||||
dets = self.post_process(dets, meta, scale) # box:4+score:1+kpoints:10+class:1=16 ## --3: post_process
|
||||
|
||||
detections.append(dets)
|
||||
|
||||
results = self.merge_outputs(detections) # --4: merge_outputs
|
||||
return {'results': results}
|
||||
|
||||
def centerface_decode(self, heat, wh, kps, reg=None, opt_k=100, topk_inds=None):
|
||||
"""
|
||||
Decode detection bbox
|
||||
"""
|
||||
batch, _, _, width = wh.shape
|
||||
|
||||
num_joints = kps.shape[1] // 2
|
||||
|
||||
scores = heat
|
||||
inds = topk_inds
|
||||
ys_int = (topk_inds / width).astype(np.int32).astype(np.float32)
|
||||
xs_int = (topk_inds % width).astype(np.int32).astype(np.float32)
|
||||
|
||||
reg = reg.reshape(batch, 2, -1)
|
||||
reg_tmp = np.zeros((batch, 2, opt_k), dtype=np.float32)
|
||||
for i in range(batch):
|
||||
reg_tmp[i, 0, :] = reg[i, 0, inds[i]]
|
||||
reg_tmp[i, 1, :] = reg[i, 1, inds[i]]
|
||||
reg = reg_tmp.transpose(0, 2, 1)
|
||||
|
||||
if reg is not None:
|
||||
xs = xs_int.reshape(batch, opt_k, 1) + reg[:, :, 0:1]
|
||||
ys = ys_int.reshape(batch, opt_k, 1) + reg[:, :, 1:2]
|
||||
else:
|
||||
xs = xs_int.reshape(batch, opt_k, 1) + 0.5
|
||||
ys = ys_int.reshape(batch, opt_k, 1) + 0.5
|
||||
|
||||
wh = wh.reshape(batch, 2, -1)
|
||||
wh_tmp = np.zeros((batch, 2, opt_k), dtype=np.float32)
|
||||
for i in range(batch):
|
||||
wh_tmp[i, 0, :] = wh[i, 0, inds[i]]
|
||||
wh_tmp[i, 1, :] = wh[i, 1, inds[i]]
|
||||
|
||||
wh = wh_tmp.transpose(0, 2, 1)
|
||||
wh = np.exp(wh) * 4.
|
||||
scores = scores.reshape(batch, opt_k, 1)
|
||||
bboxes = np.concatenate([xs - wh[..., 0:1] / 2, ys - wh[..., 1:2] / 2, xs + wh[..., 0:1] / 2,
|
||||
ys + wh[..., 1:2] / 2], axis=2)
|
||||
|
||||
clses = np.zeros((batch, opt_k, 1), dtype=np.float32)
|
||||
kps = np.zeros((batch, opt_k, num_joints * 2), dtype=np.float32)
|
||||
detections = np.concatenate([bboxes, scores, kps, clses], axis=2) # box:4+score:1+kpoints:10+class:1=16
|
||||
return detections
|
0
model_zoo/official/cv/centerface/denpendency/centernet/src/lib/external/__init__.py
vendored
Normal file
0
model_zoo/official/cv/centerface/denpendency/centernet/src/lib/external/__init__.py
vendored
Normal file
391
model_zoo/official/cv/centerface/denpendency/centernet/src/lib/external/nms.pyx
vendored
Normal file
391
model_zoo/official/cv/centerface/denpendency/centernet/src/lib/external/nms.pyx
vendored
Normal file
|
@ -0,0 +1,391 @@
|
|||
# --------------------------------------------------------
|
||||
# Fast R-CNN
|
||||
# Copyright (c) 2015 Microsoft
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# Written by Ross Girshick
|
||||
# --------------------------------------------------------
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Soft-NMS: Improving Object Detection With One Line of Code
|
||||
# Copyright (c) University of Maryland, College Park
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# Written by Navaneeth Bodla and Bharat Singh
|
||||
# ----------------------------------------------------------
|
||||
|
||||
import numpy as np
|
||||
cimport numpy as np
|
||||
|
||||
cdef inline np.float32_t max(np.float32_t a, np.float32_t b):
|
||||
return a if a >= b else b
|
||||
|
||||
cdef inline np.float32_t min(np.float32_t a, np.float32_t b):
|
||||
return a if a <= b else b
|
||||
|
||||
def nms(np.ndarray[np.float32_t, ndim=2] dets, np.float thresh):
|
||||
cdef np.ndarray[np.float32_t, ndim=1] x1 = dets[:, 0]
|
||||
cdef np.ndarray[np.float32_t, ndim=1] y1 = dets[:, 1]
|
||||
cdef np.ndarray[np.float32_t, ndim=1] x2 = dets[:, 2]
|
||||
cdef np.ndarray[np.float32_t, ndim=1] y2 = dets[:, 3]
|
||||
cdef np.ndarray[np.float32_t, ndim=1] scores = dets[:, 4]
|
||||
|
||||
cdef np.ndarray[np.float32_t, ndim=1] areas = (x2 - x1 + 1) * (y2 - y1 + 1)
|
||||
cdef np.ndarray[np.int_t, ndim=1] order = scores.argsort()[::-1]
|
||||
|
||||
cdef int ndets = dets.shape[0]
|
||||
cdef np.ndarray[np.int_t, ndim=1] suppressed = \
|
||||
np.zeros((ndets), dtype=np.int)
|
||||
|
||||
# nominal indices
|
||||
cdef int _i, _j
|
||||
# sorted indices
|
||||
cdef int i, j
|
||||
# temp variables for box i's (the box currently under consideration)
|
||||
cdef np.float32_t ix1, iy1, ix2, iy2, iarea
|
||||
# variables for computing overlap with box j (lower scoring box)
|
||||
cdef np.float32_t xx1, yy1, xx2, yy2
|
||||
cdef np.float32_t w, h
|
||||
cdef np.float32_t inter, ovr
|
||||
|
||||
keep = []
|
||||
for _i in range(ndets):
|
||||
i = order[_i]
|
||||
if suppressed[i] == 1:
|
||||
continue
|
||||
keep.append(i)
|
||||
ix1 = x1[i]
|
||||
iy1 = y1[i]
|
||||
ix2 = x2[i]
|
||||
iy2 = y2[i]
|
||||
iarea = areas[i]
|
||||
for _j in range(_i + 1, ndets):
|
||||
j = order[_j]
|
||||
if suppressed[j] == 1:
|
||||
continue
|
||||
xx1 = max(ix1, x1[j])
|
||||
yy1 = max(iy1, y1[j])
|
||||
xx2 = min(ix2, x2[j])
|
||||
yy2 = min(iy2, y2[j])
|
||||
w = max(0.0, xx2 - xx1 + 1)
|
||||
h = max(0.0, yy2 - yy1 + 1)
|
||||
inter = w * h
|
||||
ovr = inter / (iarea + areas[j] - inter)
|
||||
if ovr >= thresh:
|
||||
suppressed[j] = 1
|
||||
|
||||
return keep
|
||||
|
||||
def soft_nms(np.ndarray[float, ndim=2] boxes, float sigma=0.5, float Nt=0.3, float threshold=0.001, unsigned int method=0):
|
||||
cdef unsigned int N = boxes.shape[0]
|
||||
cdef float iw, ih, box_area
|
||||
cdef float ua
|
||||
cdef int pos = 0
|
||||
cdef float maxscore = 0
|
||||
cdef int maxpos = 0
|
||||
cdef float x1,x2,y1,y2,tx1,tx2,ty1,ty2,ts,area,weight,ov
|
||||
|
||||
for i in range(N):
|
||||
maxscore = boxes[i, 4]
|
||||
maxpos = i
|
||||
|
||||
tx1 = boxes[i,0]
|
||||
ty1 = boxes[i,1]
|
||||
tx2 = boxes[i,2]
|
||||
ty2 = boxes[i,3]
|
||||
ts = boxes[i,4]
|
||||
|
||||
pos = i + 1
|
||||
# get max box
|
||||
while pos < N:
|
||||
if maxscore < boxes[pos, 4]:
|
||||
maxscore = boxes[pos, 4]
|
||||
maxpos = pos
|
||||
pos = pos + 1
|
||||
|
||||
# add max box as a detection
|
||||
boxes[i,0] = boxes[maxpos,0]
|
||||
boxes[i,1] = boxes[maxpos,1]
|
||||
boxes[i,2] = boxes[maxpos,2]
|
||||
boxes[i,3] = boxes[maxpos,3]
|
||||
boxes[i,4] = boxes[maxpos,4]
|
||||
|
||||
# swap ith box with position of max box
|
||||
boxes[maxpos,0] = tx1
|
||||
boxes[maxpos,1] = ty1
|
||||
boxes[maxpos,2] = tx2
|
||||
boxes[maxpos,3] = ty2
|
||||
boxes[maxpos,4] = ts
|
||||
|
||||
tx1 = boxes[i,0]
|
||||
ty1 = boxes[i,1]
|
||||
tx2 = boxes[i,2]
|
||||
ty2 = boxes[i,3]
|
||||
ts = boxes[i,4]
|
||||
|
||||
pos = i + 1
|
||||
# NMS iterations, note that N changes if detection boxes fall below threshold
|
||||
while pos < N:
|
||||
x1 = boxes[pos, 0]
|
||||
y1 = boxes[pos, 1]
|
||||
x2 = boxes[pos, 2]
|
||||
y2 = boxes[pos, 3]
|
||||
s = boxes[pos, 4]
|
||||
|
||||
area = (x2 - x1 + 1) * (y2 - y1 + 1)
|
||||
iw = (min(tx2, x2) - max(tx1, x1) + 1)
|
||||
if iw > 0:
|
||||
ih = (min(ty2, y2) - max(ty1, y1) + 1)
|
||||
if ih > 0:
|
||||
ua = float((tx2 - tx1 + 1) * (ty2 - ty1 + 1) + area - iw * ih)
|
||||
ov = iw * ih / ua #iou between max box and detection box
|
||||
|
||||
if method == 1: # linear
|
||||
if ov > Nt:
|
||||
weight = 1 - ov
|
||||
else:
|
||||
weight = 1
|
||||
elif method == 2: # gaussian
|
||||
weight = np.exp(-(ov * ov)/sigma)
|
||||
else: # original NMS
|
||||
if ov > Nt:
|
||||
weight = 0
|
||||
else:
|
||||
weight = 1
|
||||
|
||||
boxes[pos, 4] = weight*boxes[pos, 4]
|
||||
|
||||
# if box score falls below threshold, discard the box by swapping with last box
|
||||
# update N
|
||||
if boxes[pos, 4] < threshold:
|
||||
boxes[pos,0] = boxes[N-1, 0]
|
||||
boxes[pos,1] = boxes[N-1, 1]
|
||||
boxes[pos,2] = boxes[N-1, 2]
|
||||
boxes[pos,3] = boxes[N-1, 3]
|
||||
boxes[pos,4] = boxes[N-1, 4]
|
||||
N = N - 1
|
||||
pos = pos - 1
|
||||
|
||||
pos = pos + 1
|
||||
|
||||
keep = [i for i in range(N)]
|
||||
return keep
|
||||
|
||||
def soft_nms_39(np.ndarray[float, ndim=2] boxes, float sigma=0.5, float Nt=0.3, float threshold=0.001, unsigned int method=0):
|
||||
cdef unsigned int N = boxes.shape[0]
|
||||
cdef float iw, ih, box_area
|
||||
cdef float ua
|
||||
cdef int pos = 0
|
||||
cdef float maxscore = 0
|
||||
cdef int maxpos = 0
|
||||
cdef float x1,x2,y1,y2,tx1,tx2,ty1,ty2,ts,area,weight,ov
|
||||
cdef float tmp
|
||||
|
||||
for i in range(N):
|
||||
maxscore = boxes[i, 4]
|
||||
maxpos = i
|
||||
|
||||
tx1 = boxes[i,0]
|
||||
ty1 = boxes[i,1]
|
||||
tx2 = boxes[i,2]
|
||||
ty2 = boxes[i,3]
|
||||
ts = boxes[i,4]
|
||||
|
||||
pos = i + 1
|
||||
# get max box
|
||||
while pos < N:
|
||||
if maxscore < boxes[pos, 4]:
|
||||
maxscore = boxes[pos, 4]
|
||||
maxpos = pos
|
||||
pos = pos + 1
|
||||
|
||||
# add max box as a detection
|
||||
boxes[i,0] = boxes[maxpos,0]
|
||||
boxes[i,1] = boxes[maxpos,1]
|
||||
boxes[i,2] = boxes[maxpos,2]
|
||||
boxes[i,3] = boxes[maxpos,3]
|
||||
boxes[i,4] = boxes[maxpos,4]
|
||||
|
||||
# swap ith box with position of max box
|
||||
boxes[maxpos,0] = tx1
|
||||
boxes[maxpos,1] = ty1
|
||||
boxes[maxpos,2] = tx2
|
||||
boxes[maxpos,3] = ty2
|
||||
boxes[maxpos,4] = ts
|
||||
|
||||
for j in range(5, 39):
|
||||
tmp = boxes[i, j]
|
||||
boxes[i, j] = boxes[maxpos, j]
|
||||
boxes[maxpos, j] = tmp
|
||||
|
||||
tx1 = boxes[i,0]
|
||||
ty1 = boxes[i,1]
|
||||
tx2 = boxes[i,2]
|
||||
ty2 = boxes[i,3]
|
||||
ts = boxes[i,4]
|
||||
|
||||
pos = i + 1
|
||||
# NMS iterations, note that N changes if detection boxes fall below threshold
|
||||
while pos < N:
|
||||
x1 = boxes[pos, 0]
|
||||
y1 = boxes[pos, 1]
|
||||
x2 = boxes[pos, 2]
|
||||
y2 = boxes[pos, 3]
|
||||
s = boxes[pos, 4]
|
||||
|
||||
area = (x2 - x1 + 1) * (y2 - y1 + 1)
|
||||
iw = (min(tx2, x2) - max(tx1, x1) + 1)
|
||||
if iw > 0:
|
||||
ih = (min(ty2, y2) - max(ty1, y1) + 1)
|
||||
if ih > 0:
|
||||
ua = float((tx2 - tx1 + 1) * (ty2 - ty1 + 1) + area - iw * ih)
|
||||
ov = iw * ih / ua #iou between max box and detection box
|
||||
|
||||
if method == 1: # linear
|
||||
if ov > Nt:
|
||||
weight = 1 - ov
|
||||
else:
|
||||
weight = 1
|
||||
elif method == 2: # gaussian
|
||||
weight = np.exp(-(ov * ov)/sigma)
|
||||
else: # original NMS
|
||||
if ov > Nt:
|
||||
weight = 0
|
||||
else:
|
||||
weight = 1
|
||||
|
||||
boxes[pos, 4] = weight*boxes[pos, 4]
|
||||
|
||||
# if box score falls below threshold, discard the box by swapping with last box
|
||||
# update N
|
||||
if boxes[pos, 4] < threshold:
|
||||
boxes[pos,0] = boxes[N-1, 0]
|
||||
boxes[pos,1] = boxes[N-1, 1]
|
||||
boxes[pos,2] = boxes[N-1, 2]
|
||||
boxes[pos,3] = boxes[N-1, 3]
|
||||
boxes[pos,4] = boxes[N-1, 4]
|
||||
for j in range(5, 39):
|
||||
tmp = boxes[pos, j]
|
||||
boxes[pos, j] = boxes[N - 1, j]
|
||||
boxes[N - 1, j] = tmp
|
||||
N = N - 1
|
||||
pos = pos - 1
|
||||
|
||||
pos = pos + 1
|
||||
|
||||
keep = [i for i in range(N)]
|
||||
return keep
|
||||
|
||||
def soft_nms_merge(np.ndarray[float, ndim=2] boxes, float sigma=0.5, float Nt=0.3, float threshold=0.001, unsigned int method=0, float weight_exp=6):
|
||||
cdef unsigned int N = boxes.shape[0]
|
||||
cdef float iw, ih, box_area
|
||||
cdef float ua
|
||||
cdef int pos = 0
|
||||
cdef float maxscore = 0
|
||||
cdef int maxpos = 0
|
||||
cdef float x1,x2,y1,y2,tx1,tx2,ty1,ty2,ts,area,weight,ov
|
||||
cdef float mx1,mx2,my1,my2,mts,mbs,mw
|
||||
|
||||
for i in range(N):
|
||||
maxscore = boxes[i, 4]
|
||||
maxpos = i
|
||||
|
||||
tx1 = boxes[i,0]
|
||||
ty1 = boxes[i,1]
|
||||
tx2 = boxes[i,2]
|
||||
ty2 = boxes[i,3]
|
||||
ts = boxes[i,4]
|
||||
|
||||
pos = i + 1
|
||||
# get max box
|
||||
while pos < N:
|
||||
if maxscore < boxes[pos, 4]:
|
||||
maxscore = boxes[pos, 4]
|
||||
maxpos = pos
|
||||
pos = pos + 1
|
||||
|
||||
# add max box as a detection
|
||||
boxes[i,0] = boxes[maxpos,0]
|
||||
boxes[i,1] = boxes[maxpos,1]
|
||||
boxes[i,2] = boxes[maxpos,2]
|
||||
boxes[i,3] = boxes[maxpos,3]
|
||||
boxes[i,4] = boxes[maxpos,4]
|
||||
|
||||
mx1 = boxes[i, 0] * boxes[i, 5]
|
||||
my1 = boxes[i, 1] * boxes[i, 5]
|
||||
mx2 = boxes[i, 2] * boxes[i, 6]
|
||||
my2 = boxes[i, 3] * boxes[i, 6]
|
||||
mts = boxes[i, 5]
|
||||
mbs = boxes[i, 6]
|
||||
|
||||
# swap ith box with position of max box
|
||||
boxes[maxpos,0] = tx1
|
||||
boxes[maxpos,1] = ty1
|
||||
boxes[maxpos,2] = tx2
|
||||
boxes[maxpos,3] = ty2
|
||||
boxes[maxpos,4] = ts
|
||||
|
||||
tx1 = boxes[i,0]
|
||||
ty1 = boxes[i,1]
|
||||
tx2 = boxes[i,2]
|
||||
ty2 = boxes[i,3]
|
||||
ts = boxes[i,4]
|
||||
|
||||
pos = i + 1
|
||||
# NMS iterations, note that N changes if detection boxes fall below threshold
|
||||
while pos < N:
|
||||
x1 = boxes[pos, 0]
|
||||
y1 = boxes[pos, 1]
|
||||
x2 = boxes[pos, 2]
|
||||
y2 = boxes[pos, 3]
|
||||
s = boxes[pos, 4]
|
||||
|
||||
area = (x2 - x1 + 1) * (y2 - y1 + 1)
|
||||
iw = (min(tx2, x2) - max(tx1, x1) + 1)
|
||||
if iw > 0:
|
||||
ih = (min(ty2, y2) - max(ty1, y1) + 1)
|
||||
if ih > 0:
|
||||
ua = float((tx2 - tx1 + 1) * (ty2 - ty1 + 1) + area - iw * ih)
|
||||
ov = iw * ih / ua #iou between max box and detection box
|
||||
|
||||
if method == 1: # linear
|
||||
if ov > Nt:
|
||||
weight = 1 - ov
|
||||
else:
|
||||
weight = 1
|
||||
elif method == 2: # gaussian
|
||||
weight = np.exp(-(ov * ov)/sigma)
|
||||
else: # original NMS
|
||||
if ov > Nt:
|
||||
weight = 0
|
||||
else:
|
||||
weight = 1
|
||||
|
||||
mw = (1 - weight) ** weight_exp
|
||||
mx1 = mx1 + boxes[pos, 0] * boxes[pos, 5] * mw
|
||||
my1 = my1 + boxes[pos, 1] * boxes[pos, 5] * mw
|
||||
mx2 = mx2 + boxes[pos, 2] * boxes[pos, 6] * mw
|
||||
my2 = my2 + boxes[pos, 3] * boxes[pos, 6] * mw
|
||||
mts = mts + boxes[pos, 5] * mw
|
||||
mbs = mbs + boxes[pos, 6] * mw
|
||||
|
||||
boxes[pos, 4] = weight*boxes[pos, 4]
|
||||
|
||||
# if box score falls below threshold, discard the box by swapping with last box
|
||||
# update N
|
||||
if boxes[pos, 4] < threshold:
|
||||
boxes[pos,0] = boxes[N-1, 0]
|
||||
boxes[pos,1] = boxes[N-1, 1]
|
||||
boxes[pos,2] = boxes[N-1, 2]
|
||||
boxes[pos,3] = boxes[N-1, 3]
|
||||
boxes[pos,4] = boxes[N-1, 4]
|
||||
N = N - 1
|
||||
pos = pos - 1
|
||||
|
||||
pos = pos + 1
|
||||
|
||||
boxes[i, 0] = mx1 / mts
|
||||
boxes[i, 1] = my1 / mts
|
||||
boxes[i, 2] = mx2 / mbs
|
||||
boxes[i, 3] = my2 / mbs
|
||||
|
||||
keep = [i for i in range(N)]
|
||||
return keep
|
42
model_zoo/official/cv/centerface/denpendency/centernet/src/lib/external/setup.py
vendored
Normal file
42
model_zoo/official/cv/centerface/denpendency/centernet/src/lib/external/setup.py
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Xingyi Zhou
|
||||
All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
from distutils.core import setup
|
||||
from distutils.extension import Extension
|
||||
import numpy
|
||||
from Cython.Build import cythonize
|
||||
|
||||
extensions = [
|
||||
Extension(
|
||||
"nms",
|
||||
["nms.pyx"],
|
||||
extra_compile_args=["-Wno-cpp", "-Wno-unused-function"]
|
||||
)
|
||||
]
|
||||
|
||||
setup(
|
||||
name="coco",
|
||||
ext_modules=cythonize(extensions),
|
||||
include_dirs=[numpy.get_include()]
|
||||
)
|
|
@ -0,0 +1,170 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
# Copyright (c) Microsoft
|
||||
# Licensed under the MIT License.
|
||||
# Written by Bin Xiao (Bin.Xiao@microsoft.com)
|
||||
# Modified by Xingyi Zhou
|
||||
# ------------------------------------------------------------------------------
|
||||
"""Image process"""
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import random
|
||||
import numpy as np
|
||||
import cv2
|
||||
|
||||
def get_3rd_point(a, b):
|
||||
"""
|
||||
Get 3rd point
|
||||
"""
|
||||
direct = a - b
|
||||
return b + np.array([-direct[1], direct[0]], dtype=np.float32)
|
||||
|
||||
def get_dir(src_point, rot_rad):
|
||||
"""
|
||||
Get dir
|
||||
"""
|
||||
sn, cs = np.sin(rot_rad), np.cos(rot_rad) # (0, 1)
|
||||
|
||||
src_result = [0, 0]
|
||||
src_result[0] = src_point[0] * cs - src_point[1] * sn
|
||||
src_result[1] = src_point[0] * sn + src_point[1] * cs
|
||||
|
||||
return src_result
|
||||
|
||||
def get_affine_transform(center,
|
||||
scale,
|
||||
rot,
|
||||
output_size,
|
||||
shift=np.array([0, 0], dtype=np.float32),
|
||||
inv=0):
|
||||
"""
|
||||
Get affine transform
|
||||
"""
|
||||
if not isinstance(scale, np.ndarray) and not isinstance(scale, list):
|
||||
scale = np.array([scale, scale], dtype=np.float32)
|
||||
|
||||
scale_tmp = scale
|
||||
src_w = scale_tmp[0]
|
||||
dst_w = output_size[0]
|
||||
dst_h = output_size[1]
|
||||
|
||||
rot_rad = np.pi * rot / 180
|
||||
src_dir = get_dir([0, src_w * -0.5], rot_rad)
|
||||
dst_dir = np.array([0, dst_w * -0.5], np.float32)
|
||||
|
||||
src = np.zeros((3, 2), dtype=np.float32)
|
||||
dst = np.zeros((3, 2), dtype=np.float32)
|
||||
src[0, :] = center + scale_tmp * shift
|
||||
src[1, :] = center + src_dir + scale_tmp * shift
|
||||
dst[0, :] = [dst_w * 0.5, dst_h * 0.5]
|
||||
dst[1, :] = np.array([dst_w * 0.5, dst_h * 0.5], np.float32) + dst_dir
|
||||
|
||||
src[2:, :] = get_3rd_point(src[0, :], src[1, :])
|
||||
dst[2:, :] = get_3rd_point(dst[0, :], dst[1, :])
|
||||
|
||||
if inv:
|
||||
trans = cv2.getAffineTransform(np.float32(dst), np.float32(src))
|
||||
else:
|
||||
trans = cv2.getAffineTransform(np.float32(src), np.float32(dst))
|
||||
|
||||
return trans
|
||||
|
||||
def affine_transform(pt, t):
|
||||
"""
|
||||
Affine transform
|
||||
"""
|
||||
new_pt = np.array([pt[0], pt[1], 1.], dtype=np.float32).T
|
||||
new_pt = np.dot(t, new_pt)
|
||||
return new_pt[:2]
|
||||
|
||||
def grayscale(image):
|
||||
return cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
def lighting_(data_rng, image, alphastd, eigval, eigvec):
|
||||
alpha = data_rng.normal(scale=alphastd, size=(3,))
|
||||
image += np.dot(eigvec, eigval * alpha)
|
||||
|
||||
def blend_(alpha, image1, image2):
|
||||
image1 *= alpha
|
||||
image2 *= (1 - alpha)
|
||||
image1 += image2
|
||||
|
||||
def saturation_(data_rng, image, gs, gs_mean, var):
|
||||
gs_mean = gs_mean
|
||||
alpha = 1. + data_rng.uniform(low=-var, high=var)
|
||||
blend_(alpha, image, gs[:, :, None])
|
||||
|
||||
def brightness_(data_rng, image, gs, gs_mean, var):
|
||||
gs = gs
|
||||
gs_mean = gs_mean
|
||||
alpha = 1. + data_rng.uniform(low=-var, high=var)
|
||||
image *= alpha
|
||||
|
||||
def contrast_(data_rng, image, gs, gs_mean, var):
|
||||
gs = gs
|
||||
alpha = 1. + data_rng.uniform(low=-var, high=var)
|
||||
blend_(alpha, image, gs_mean)
|
||||
|
||||
def color_aug(data_rng, image, eig_val, eig_vec):
|
||||
functions = [brightness_, contrast_, saturation_]
|
||||
random.shuffle(functions)
|
||||
|
||||
gs = grayscale(image)
|
||||
gs_mean = gs.mean()
|
||||
for f in functions:
|
||||
f(data_rng, image, gs, gs_mean, 0.4)
|
||||
lighting_(data_rng, image, 0.1, eig_val, eig_vec)
|
||||
|
||||
def gaussian_radius(det_size, min_overlap=0.7):
|
||||
"""
|
||||
Gaussian radius
|
||||
"""
|
||||
height, width = det_size
|
||||
|
||||
a1 = 1
|
||||
b1 = (height + width)
|
||||
c1 = width * height * (1 - min_overlap) / (1 + min_overlap)
|
||||
sq1 = np.sqrt(b1 ** 2 - 4 * a1 * c1)
|
||||
r1 = (b1 + sq1) / 2
|
||||
|
||||
a2 = 4
|
||||
b2 = 2 * (height + width)
|
||||
c2 = (1 - min_overlap) * width * height
|
||||
sq2 = np.sqrt(b2 ** 2 - 4 * a2 * c2)
|
||||
r2 = (b2 + sq2) / 2
|
||||
|
||||
a3 = 4 * min_overlap
|
||||
b3 = -2 * min_overlap * (height + width)
|
||||
c3 = (min_overlap - 1) * width * height
|
||||
sq3 = np.sqrt(b3 ** 2 - 4 * a3 * c3)
|
||||
r3 = (b3 + sq3) / 2
|
||||
return min(r1, r2, r3)
|
||||
|
||||
def gaussian2d(shape, sigma=1):
|
||||
m, n = [(ss - 1.) / 2. for ss in shape]
|
||||
y, x = np.ogrid[-m:m+1, -n:n+1]
|
||||
|
||||
h = np.exp(-(x * x + y * y) / (2 * sigma * sigma))
|
||||
h[h < np.finfo(h.dtype).eps * h.max()] = 0
|
||||
return h
|
||||
|
||||
def draw_umich_gaussian(heatmap, center, radius, k=1):
|
||||
"""
|
||||
Draw umich gaussian
|
||||
"""
|
||||
diameter = 2 * radius + 1
|
||||
gaussian = gaussian2d((diameter, diameter), sigma=diameter / 6)
|
||||
|
||||
x, y = int(center[0]), int(center[1])
|
||||
|
||||
height, width = heatmap.shape[0:2]
|
||||
|
||||
left, right = min(x, radius), min(width - x, radius + 1)
|
||||
top, bottom = min(y, radius), min(height - y, radius + 1)
|
||||
|
||||
masked_heatmap = heatmap[y - top:y + bottom, x - left:x + right]
|
||||
masked_gaussian = gaussian[radius - top:radius + bottom, radius - left:radius + right]
|
||||
if min(masked_gaussian.shape) > 0 and min(masked_heatmap.shape) > 0:
|
||||
np.maximum(masked_heatmap, masked_gaussian * k, out=masked_heatmap)
|
||||
return heatmap
|
|
@ -0,0 +1,55 @@
|
|||
# --------------------------------------------------------
|
||||
# Fast R-CNN
|
||||
# Copyright (c) 2015 Microsoft
|
||||
# Licensed under The MIT License [see LICENSE for details]
|
||||
# Written by Sergey Karayev
|
||||
# --------------------------------------------------------
|
||||
|
||||
cimport cython
|
||||
import numpy as np
|
||||
cimport numpy as np
|
||||
|
||||
DTYPE = np.float
|
||||
ctypedef np.float_t DTYPE_t
|
||||
|
||||
def bbox_overlaps(
|
||||
np.ndarray[DTYPE_t, ndim=2] boxes,
|
||||
np.ndarray[DTYPE_t, ndim=2] query_boxes):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
boxes: (N, 4) ndarray of float
|
||||
query_boxes: (K, 4) ndarray of float
|
||||
Returns
|
||||
-------
|
||||
overlaps: (N, K) ndarray of overlap between boxes and query_boxes
|
||||
"""
|
||||
cdef unsigned int N = boxes.shape[0]
|
||||
cdef unsigned int K = query_boxes.shape[0]
|
||||
cdef np.ndarray[DTYPE_t, ndim=2] overlaps = np.zeros((N, K), dtype=DTYPE)
|
||||
cdef DTYPE_t iw, ih, box_area
|
||||
cdef DTYPE_t ua
|
||||
cdef unsigned int k, n
|
||||
for k in range(K):
|
||||
box_area = (
|
||||
(query_boxes[k, 2] - query_boxes[k, 0] + 1) *
|
||||
(query_boxes[k, 3] - query_boxes[k, 1] + 1)
|
||||
)
|
||||
for n in range(N):
|
||||
iw = (
|
||||
min(boxes[n, 2], query_boxes[k, 2]) -
|
||||
max(boxes[n, 0], query_boxes[k, 0]) + 1
|
||||
)
|
||||
if iw > 0:
|
||||
ih = (
|
||||
min(boxes[n, 3], query_boxes[k, 3]) -
|
||||
max(boxes[n, 1], query_boxes[k, 1]) + 1
|
||||
)
|
||||
if ih > 0:
|
||||
ua = float(
|
||||
(boxes[n, 2] - boxes[n, 0] + 1) *
|
||||
(boxes[n, 3] - boxes[n, 1] + 1) +
|
||||
box_area - iw * ih
|
||||
)
|
||||
overlaps[n, k] = iw * ih / ua
|
||||
return overlaps
|
|
@ -0,0 +1,316 @@
|
|||
"""
|
||||
WiderFace evaluation code
|
||||
author: wondervictor
|
||||
mail: tianhengcheng@gmail.com
|
||||
copyright@wondervictor
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Vic Chan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
from __future__ import division
|
||||
|
||||
import os
|
||||
import pickle
|
||||
import argparse
|
||||
import numpy as np
|
||||
from scipy.io import loadmat
|
||||
from bbox import bbox_overlaps
|
||||
|
||||
def get_gt_boxes(gt_dir):
|
||||
""" gt dir: (wider_face_val.mat, wider_easy_val.mat, wider_medium_val.mat, wider_hard_val.mat)"""
|
||||
|
||||
gt_mat = loadmat(os.path.join(gt_dir, 'wider_face_val.mat')) # you own ground_truth name
|
||||
hard_mat = loadmat(os.path.join(gt_dir, 'wider_hard_val.mat'))
|
||||
medium_mat = loadmat(os.path.join(gt_dir, 'wider_medium_val.mat'))
|
||||
easy_mat = loadmat(os.path.join(gt_dir, 'wider_easy_val.mat'))
|
||||
|
||||
facebox_list = gt_mat['face_bbx_list']
|
||||
event_list = gt_mat['event_list']
|
||||
file_list = gt_mat['file_list']
|
||||
|
||||
hard_gt_list = hard_mat['gt_list']
|
||||
medium_gt_list = medium_mat['gt_list']
|
||||
easy_gt_list = easy_mat['gt_list']
|
||||
|
||||
return facebox_list, event_list, file_list, hard_gt_list, medium_gt_list, easy_gt_list
|
||||
|
||||
|
||||
def get_gt_boxes_from_txt(gt_path, cache_dir):
|
||||
"""
|
||||
Get gt boxes from binary txt file.
|
||||
"""
|
||||
cache_file = os.path.join(cache_dir, 'gt_cache.pkl')
|
||||
if os.path.exists(cache_file):
|
||||
f = open(cache_file, 'rb')
|
||||
boxes = pickle.load(f)
|
||||
f.close()
|
||||
return boxes
|
||||
|
||||
f = open(gt_path, 'r')
|
||||
state = 0
|
||||
lines = f.readlines()
|
||||
lines = list(map(lambda x: x.rstrip('\r\n'), lines))
|
||||
boxes = {}
|
||||
f.close()
|
||||
current_boxes = []
|
||||
current_name = None
|
||||
for line in lines:
|
||||
if state == 0 and '--' in line:
|
||||
state = 1
|
||||
current_name = line
|
||||
continue
|
||||
if state == 1:
|
||||
state = 2
|
||||
continue
|
||||
|
||||
if state == 2 and '--' in line:
|
||||
state = 1
|
||||
boxes[current_name] = np.array(current_boxes).astype('float32')
|
||||
current_name = line
|
||||
current_boxes = []
|
||||
continue
|
||||
|
||||
if state == 2:
|
||||
box = [float(x) for x in line.split(' ')[:4]]
|
||||
current_boxes.append(box)
|
||||
continue
|
||||
|
||||
f = open(cache_file, 'wb')
|
||||
pickle.dump(boxes, f)
|
||||
f.close()
|
||||
return boxes
|
||||
|
||||
|
||||
def read_pred_file(filepath):
|
||||
|
||||
with open(filepath, 'r') as f:
|
||||
lines = f.readlines()
|
||||
img_file = lines[0].rstrip('\n\r')
|
||||
lines = lines[2:]
|
||||
|
||||
boxes = np.array(list(map(lambda x: [float(a) for a in x.rstrip('\r\n').split(' ')], lines))).astype('float')
|
||||
return img_file.split('/')[-1], boxes
|
||||
|
||||
|
||||
def get_preds(pred_dir):
|
||||
"""Get preds"""
|
||||
events = os.listdir(pred_dir)
|
||||
boxes = dict()
|
||||
#pbar = tqdm.tqdm(events)
|
||||
pbar = events
|
||||
for event in pbar:
|
||||
#pbar.set_description('Reading Predictions ')
|
||||
event_dir = os.path.join(pred_dir, event)
|
||||
event_images = os.listdir(event_dir)
|
||||
current_event = dict()
|
||||
for imgtxt in event_images:
|
||||
imgname, box = read_pred_file(os.path.join(event_dir, imgtxt))
|
||||
current_event[imgname.rstrip('.jpg')] = box
|
||||
boxes[event] = current_event
|
||||
return boxes
|
||||
|
||||
|
||||
def norm_score(pred_norm):
|
||||
""" norm score
|
||||
pred_norm {key: [[x1,y1,x2,y2,s]]}
|
||||
"""
|
||||
max_score = 0
|
||||
min_score = 1
|
||||
|
||||
for _, k in pred_norm.items():
|
||||
for _, v in k.items():
|
||||
if v.size == 0:
|
||||
continue
|
||||
min_v = np.min(v[:, -1])
|
||||
max_v = np.max(v[:, -1])
|
||||
max_score = max(max_v, max_score)
|
||||
min_score = min(min_v, min_score)
|
||||
|
||||
diff = max_score - min_score
|
||||
for _, k in pred_norm.items():
|
||||
for _, v in k.items():
|
||||
if v.size == 0:
|
||||
continue
|
||||
v[:, -1] = (v[:, -1] - min_score)/diff
|
||||
|
||||
|
||||
def image_eval(pred_eval, gt, ignore, iou_thresh):
|
||||
""" single image evaluation
|
||||
pred_eval: Nx5
|
||||
gt: Nx4
|
||||
ignore:
|
||||
"""
|
||||
pred_t = pred_eval.copy()
|
||||
gt_t = gt.copy()
|
||||
pred_recall = np.zeros(pred_t.shape[0])
|
||||
recall_list = np.zeros(gt_t.shape[0])
|
||||
proposal_list = np.ones(pred_t.shape[0])
|
||||
|
||||
pred_t[:, 2] = pred_t[:, 2] + pred_t[:, 0]
|
||||
pred_t[:, 3] = pred_t[:, 3] + pred_t[:, 1]
|
||||
gt_t[:, 2] = gt_t[:, 2] + gt_t[:, 0]
|
||||
gt_t[:, 3] = gt_t[:, 3] + gt_t[:, 1]
|
||||
|
||||
overlaps = bbox_overlaps(pred_t[:, :4], gt_t)
|
||||
|
||||
for h in range(pred_t.shape[0]):
|
||||
|
||||
gt_overlap = overlaps[h]
|
||||
max_overlap, max_idx = gt_overlap.max(), gt_overlap.argmax()
|
||||
if max_overlap >= iou_thresh:
|
||||
if ignore[max_idx] == 0:
|
||||
recall_list[max_idx] = -1
|
||||
proposal_list[h] = -1
|
||||
elif recall_list[max_idx] == 0:
|
||||
recall_list[max_idx] = 1
|
||||
|
||||
r_keep_index = np.where(recall_list == 1)[0]
|
||||
pred_recall[h] = len(r_keep_index)
|
||||
return pred_recall, proposal_list
|
||||
|
||||
|
||||
def img_pr_info(thresh_num, pred_info, proposal_list, pred_recall):
|
||||
"""
|
||||
Image pr info
|
||||
"""
|
||||
pr_info = np.zeros((thresh_num, 2)).astype('float')
|
||||
for t in range(thresh_num):
|
||||
|
||||
thresh = 1 - (t+1)/thresh_num
|
||||
r_index = np.where(pred_info[:, 4] >= thresh)[0]
|
||||
if r_index.size == 0:
|
||||
pr_info[t, 0] = 0
|
||||
pr_info[t, 1] = 0
|
||||
else:
|
||||
r_index = r_index[-1]
|
||||
p_index = np.where(proposal_list[:r_index+1] == 1)[0]
|
||||
pr_info[t, 0] = len(p_index)
|
||||
pr_info[t, 1] = pred_recall[r_index]
|
||||
return pr_info
|
||||
|
||||
|
||||
def dataset_pr_info(thresh_num, pr_curve, count_face):
|
||||
pr_curve_t = np.zeros((thresh_num, 2))
|
||||
for i in range(thresh_num):
|
||||
pr_curve_t[i, 0] = pr_curve[i, 1] / pr_curve[i, 0]
|
||||
pr_curve_t[i, 1] = pr_curve[i, 1] / count_face
|
||||
return pr_curve_t
|
||||
|
||||
|
||||
def voc_ap(rec, prec):
|
||||
"""
|
||||
Voc ap calculation
|
||||
"""
|
||||
# correct AP calculation
|
||||
# first append sentinel values at the end
|
||||
mrec = np.concatenate(([0.], rec, [1.]))
|
||||
mpre = np.concatenate(([0.], prec, [0.]))
|
||||
|
||||
# compute the precision envelope
|
||||
for i in range(mpre.size - 1, 0, -1):
|
||||
mpre[i - 1] = np.maximum(mpre[i - 1], mpre[i])
|
||||
|
||||
# to calculate area under PR curve, look for points
|
||||
# where X axis (recall) changes value
|
||||
i = np.where(mrec[1:] != mrec[:-1])[0]
|
||||
|
||||
# and sum (\Delta recall) * prec
|
||||
ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1])
|
||||
return ap
|
||||
|
||||
|
||||
def evaluation(pred_evaluation, gt_path, iou_thresh=0.4):
|
||||
"""
|
||||
evaluation method.
|
||||
"""
|
||||
print_pred = pred_evaluation
|
||||
pred_evaluation = get_preds(pred_evaluation)
|
||||
norm_score(pred_evaluation)
|
||||
facebox_list, event_list, file_list, hard_gt_list, medium_gt_list, easy_gt_list = get_gt_boxes(gt_path)
|
||||
event_num = len(event_list)
|
||||
thresh_num = 1000
|
||||
setting_gts = [easy_gt_list, medium_gt_list, hard_gt_list]
|
||||
|
||||
aps = []
|
||||
for setting_id in range(3):
|
||||
# different setting
|
||||
gt_list = setting_gts[setting_id]
|
||||
count_face = 0
|
||||
pr_curve = np.zeros((thresh_num, 2)).astype('float')
|
||||
# [hard, medium, easy]
|
||||
# pbar = tqdm.tqdm(range(event_num)) # 61
|
||||
pbar = range(event_num)
|
||||
error_count = 0
|
||||
for i in pbar:
|
||||
event_name = str(event_list[i][0][0])
|
||||
img_list = file_list[i][0]
|
||||
pred_list = pred_evaluation[event_name]
|
||||
sub_gt_list = gt_list[i][0]
|
||||
gt_bbx_list = facebox_list[i][0]
|
||||
|
||||
for j, _ in enumerate(img_list):
|
||||
try:
|
||||
pred_info = pred_list[str(img_list[j][0][0])]
|
||||
except KeyError:
|
||||
error_count += 1
|
||||
continue
|
||||
|
||||
gt_boxes = gt_bbx_list[j][0].astype('float')
|
||||
keep_index = sub_gt_list[j][0]
|
||||
count_face += len(keep_index)
|
||||
if gt_boxes.size == 0 or pred_info.size == 0:
|
||||
continue
|
||||
ignore = np.zeros(gt_boxes.shape[0])
|
||||
if keep_index.size != 0:
|
||||
ignore[keep_index-1] = 1
|
||||
pred_recall, proposal_list = image_eval(pred_info, gt_boxes, ignore, iou_thresh)
|
||||
|
||||
pr_curve += img_pr_info(thresh_num, pred_info, proposal_list, pred_recall)
|
||||
|
||||
pr_curve = dataset_pr_info(thresh_num, pr_curve, count_face)
|
||||
|
||||
propose = pr_curve[:, 0]
|
||||
recall = pr_curve[:, 1]
|
||||
|
||||
ap = voc_ap(recall, propose)
|
||||
aps.append(ap)
|
||||
|
||||
print("==================== Results = ====================", print_pred)
|
||||
print("Easy Val AP: {}".format(aps[0]))
|
||||
print("Medium Val AP: {}".format(aps[1]))
|
||||
print("Hard Val AP: {}".format(aps[2]))
|
||||
print("=================================================")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-p', '--pred', default='',
|
||||
help='test output, txt contain box positions and scores')
|
||||
parser.add_argument('-g', '--gt', default='', help='ground truth path, mat format')
|
||||
args = parser.parse_args()
|
||||
|
||||
pred = args.pred
|
||||
if os.path.isdir(pred):
|
||||
evaluation(pred, args.gt)
|
||||
else:
|
||||
pass
|
|
@ -0,0 +1,13 @@
|
|||
"""
|
||||
WiderFace evaluation code
|
||||
author: wondervictor
|
||||
mail: tianhengcheng@gmail.com
|
||||
copyright@wondervictor
|
||||
"""
|
||||
|
||||
from distutils.core import setup, Extension
|
||||
import numpy
|
||||
from Cython.Build import cythonize
|
||||
|
||||
package = Extension('bbox', ['box_overlaps.pyx'], include_dirs=[numpy.get_include()])
|
||||
setup(ext_modules=cythonize([package]))
|
|
@ -0,0 +1,78 @@
|
|||
#EXTD: Extremely Tiny Face Detector via Iterative Filter Reuse
|
||||
# MIT license
|
||||
|
||||
# Copyright (c) 2019-present NAVER Corp.
|
||||
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE
|
||||
"""Augmentations"""
|
||||
|
||||
import random
|
||||
import numpy as np
|
||||
import cv2
|
||||
|
||||
def anchor_crop_image_sampling(image, anns):
|
||||
"""
|
||||
Crop anchors.
|
||||
"""
|
||||
max_size = 12000
|
||||
inf_distance = 9999999
|
||||
|
||||
boxes = []
|
||||
for ann in anns:
|
||||
boxes.append([ann['bbox'][0], ann['bbox'][1], ann['bbox'][0] + ann['bbox'][2], ann['bbox'][1] + ann['bbox'][3]])
|
||||
boxes = np.asarray(boxes, dtype=np.float32)
|
||||
|
||||
height, width, _ = image.shape
|
||||
|
||||
box_area = (boxes[:, 2] - boxes[:, 0] + 1) * (boxes[:, 3] - boxes[:, 1] + 1)
|
||||
rand_idx = random.randint(0, len(box_area) - 1)
|
||||
rand_side = box_area[rand_idx] ** 0.5
|
||||
|
||||
anchors = [16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 128, 256, 512]
|
||||
distance = inf_distance
|
||||
anchor_idx = 5
|
||||
for i, anchor in enumerate(anchors):
|
||||
if abs(anchor - rand_side) < distance:
|
||||
distance = abs(anchor - rand_side)
|
||||
anchor_idx = i
|
||||
|
||||
target_anchor = random.choice(anchors[0:min(anchor_idx + 1, 11)])
|
||||
ratio = float(target_anchor) / rand_side
|
||||
ratio = ratio * (2 ** random.uniform(-1, 1))
|
||||
|
||||
if int(height * ratio * width * ratio) > max_size * max_size:
|
||||
ratio = (max_size * max_size / (height * width)) ** 0.5
|
||||
|
||||
interp_methods = [cv2.INTER_LINEAR, cv2.INTER_CUBIC, cv2.INTER_AREA, cv2.INTER_NEAREST, cv2.INTER_LANCZOS4]
|
||||
interp_method = random.choice(interp_methods)
|
||||
image = cv2.resize(image, None, None, fx=ratio, fy=ratio, interpolation=interp_method)
|
||||
|
||||
boxes[:, 0] *= ratio
|
||||
boxes[:, 1] *= ratio
|
||||
boxes[:, 2] *= ratio
|
||||
boxes[:, 3] *= ratio
|
||||
|
||||
boxes = boxes.tolist()
|
||||
for i, _ in enumerate(anns):
|
||||
anns[i]['bbox'] = [boxes[i][0], boxes[i][1], boxes[i][2] - boxes[i][0], boxes[i][3] - boxes[i][1]]
|
||||
for j in range(5):
|
||||
anns[i]['keypoints'][j * 3] *= ratio
|
||||
anns[i]['keypoints'][j * 3 + 1] *= ratio
|
||||
|
||||
return image, anns
|
|
@ -0,0 +1,62 @@
|
|||
# Copyright 2020 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
"""Convert ckpt to air."""
|
||||
import os
|
||||
import argparse
|
||||
import numpy as np
|
||||
|
||||
from mindspore import context
|
||||
from mindspore import Tensor
|
||||
from mindspore.train.serialization import export, load_checkpoint, load_param_into_net
|
||||
|
||||
from src.centerface import CenterfaceMobilev2
|
||||
|
||||
context.set_context(mode=context.GRAPH_MODE, device_target="Ascend", save_graphs=False)
|
||||
|
||||
def save_air():
|
||||
"""Save air file"""
|
||||
print('============= centerface start save air ==================')
|
||||
|
||||
parser = argparse.ArgumentParser(description='Convert ckpt to air')
|
||||
parser.add_argument('--pretrained', type=str, default='', help='pretrained model to load')
|
||||
parser.add_argument('--batch_size', type=int, default=8, help='batch size')
|
||||
|
||||
args = parser.parse_args()
|
||||
network = CenterfaceMobilev2()
|
||||
|
||||
if os.path.isfile(args.pretrained):
|
||||
param_dict = load_checkpoint(args.pretrained)
|
||||
param_dict_new = {}
|
||||
for key, values in param_dict.items():
|
||||
if key.startswith('moments.') or key.startswith('moment1.') or key.startswith('moment2.'):
|
||||
continue
|
||||
elif key.startswith('centerface_network.'):
|
||||
param_dict_new[key[19:]] = values
|
||||
else:
|
||||
param_dict_new[key] = values
|
||||
load_param_into_net(network, param_dict_new)
|
||||
print('load model {} success'.format(args.pretrained))
|
||||
|
||||
input_data = np.random.uniform(low=0, high=1.0, size=(args.batch_size, 3, 832, 832)).astype(np.float32)
|
||||
|
||||
tensor_input_data = Tensor(input_data)
|
||||
export(network, tensor_input_data,
|
||||
file_name=args.pretrained.replace('.ckpt', '_' + str(args.batch_size) + 'b.air'), file_format='AIR')
|
||||
|
||||
print("export model success.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
save_air()
|
|
@ -0,0 +1,22 @@
|
|||
#!/bin/bash
|
||||
# Copyright 2020 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
|
||||
root=$PWD
|
||||
save_path=$root/output/centerface/
|
||||
ground_truth_path=$root/dataset/centerface/ground_truth
|
||||
echo "start eval"
|
||||
python ../dependency/evaluate/eval.py --pred=$save_path --gt=$ground_truth_path
|
||||
echo "end eval"
|
|
@ -0,0 +1,26 @@
|
|||
#!/bin/sh
|
||||
# Copyright 2020 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
|
||||
root=$PWD
|
||||
save_path=$root/output/centerface/
|
||||
ground_truth_path=$root/dataset/centerface/ground_truth
|
||||
#for i in $(seq start_epoch end_epoch+1)
|
||||
for i in $(seq 89 200)
|
||||
do
|
||||
python ../dependency/evaluate/eval.py --pred=$save_path$i --gt=$ground_truth_path &
|
||||
sleep 10
|
||||
done
|
||||
wait
|
|
@ -0,0 +1,131 @@
|
|||
#!/bin/bash
|
||||
# Copyright 2020 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
|
||||
if [ $# -gt 6 ]
|
||||
then
|
||||
echo "Usage: sh test.sh [MODEL_PATH] [DATASET] [GROUND_TRUTH_MAT] [SAVE_PATH] [DEVICE_ID] [CKPT]"
|
||||
echo " or: sh test.sh [MODEL_PATH] [DATASET] [GROUND_TRUTH_MAT] [SAVE_PATH] [DEVICE_ID]"
|
||||
echo " or: sh test.sh [MODEL_PATH] [DATASET] [GROUND_TRUTH_MAT] [SAVE_PATH]"
|
||||
echo " or: sh test.sh [MODEL_PATH] [DATASET] [GROUND_TRUTH_MAT]"
|
||||
echo " or: sh test.sh [MODEL_PATH] [DATASET]"
|
||||
echo " or: sh test.sh [MODEL_PATH]"
|
||||
echo " or: sh test.sh "
|
||||
exit 1
|
||||
fi
|
||||
|
||||
get_real_path(){
|
||||
if [ "${1:0:1}" == "/" ]; then
|
||||
echo "$1"
|
||||
else
|
||||
echo "$(realpath -m $PWD/$1)"
|
||||
fi
|
||||
}
|
||||
|
||||
current_exec_path=$(pwd)
|
||||
echo ${current_exec_path}
|
||||
|
||||
dirname_path=$(dirname "$(pwd)")
|
||||
echo ${dirname_path}
|
||||
|
||||
SCRIPT_NAME='test.py'
|
||||
|
||||
ulimit -c unlimited
|
||||
|
||||
root=${current_exec_path} # your script path
|
||||
model_path=$root/model/
|
||||
dataset_root=$root/dataset
|
||||
dataset_path=$dataset_root/centerface/images/val/images/
|
||||
ground_truth_mat=$dataset_root/centerface/ground_truth/val.mat
|
||||
save_path=$root/output/centerface/
|
||||
device_id=0
|
||||
ckpt="0.ckpt" # the model saved for epoch=125
|
||||
|
||||
if [ $# == 1 ]
|
||||
then
|
||||
model_path=$(get_real_path $1)
|
||||
if [ ! -f $model_path ]
|
||||
then
|
||||
echo "error: model_path=$model_path is not a file"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $# == 2 ]
|
||||
then
|
||||
dataset_path=$(get_real_path $2)
|
||||
if [ ! -f $dataset_path ]
|
||||
then
|
||||
echo "error: dataset_path=$dataset_path is not a file"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $# == 3 ]
|
||||
then
|
||||
ground_truth_mat=$(get_real_path $3)
|
||||
if [ ! -f $ground_truth_mat ]
|
||||
then
|
||||
echo "error: ground_truth_mat=$ground_truth_mat is not a file"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $# == 4 ]
|
||||
then
|
||||
save_path=$(get_real_path $4)
|
||||
if [ ! -f $save_path ]
|
||||
then
|
||||
echo "error: save_path=$save_path is not a file"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $# == 5 ]
|
||||
then
|
||||
device_id=$5
|
||||
fi
|
||||
|
||||
if [ $# == 6 ]
|
||||
then
|
||||
ckpt=$6
|
||||
fi
|
||||
|
||||
echo $model_path
|
||||
echo $dataset_path
|
||||
echo $ground_truth_mat
|
||||
echo $save_path
|
||||
|
||||
export PYTHONPATH=${dirname_path}:$PYTHONPATH
|
||||
export RANK_SIZE=1
|
||||
|
||||
echo 'start testing'
|
||||
rm -rf ${current_exec_path}/device_test$device_id
|
||||
echo 'start rank '$device_id
|
||||
mkdir ${current_exec_path}/device_test$device_id
|
||||
cd ${current_exec_path}/device_test$device_id || exit
|
||||
export RANK_ID=0
|
||||
dev=`expr $device_id + 0`
|
||||
export DEVICE_ID=$dev
|
||||
python ${dirname_path}/${SCRIPT_NAME} \
|
||||
--is_distributed=0 \
|
||||
--data_dir=$dataset_path \
|
||||
--test_model=$model_path \
|
||||
--ground_truth_mat=$ground_truth_mat \
|
||||
--save_dir=$save_path \
|
||||
--rank=$device_id \
|
||||
--ckpt_name=$ckpt > test.log 2>&1 &
|
||||
|
||||
echo 'running'
|
|
@ -0,0 +1,146 @@
|
|||
#!/bin/bash
|
||||
# Copyright 2020 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
|
||||
if [ $# -gt 6 ]
|
||||
then
|
||||
echo "Usage: sh test_and_eval.sh [MODEL_PATH] [DATASET] [GROUND_TRUTH_MAT] [SAVE_PATH] [DEVICE_ID] [CKPT] [GROUND_TRUTH_PATH]"
|
||||
echo " or: sh test_and_eval.sh [MODEL_PATH] [DATASET] [GROUND_TRUTH_MAT] [SAVE_PATH] [DEVICE_ID] [CKPT]"
|
||||
echo " or: sh test_and_eval.sh [MODEL_PATH] [DATASET] [GROUND_TRUTH_MAT] [SAVE_PATH] [DEVICE_ID]"
|
||||
echo " or: sh test_and_eval.sh [MODEL_PATH] [DATASET] [GROUND_TRUTH_MAT] [SAVE_PATH]"
|
||||
echo " or: sh test_and_eval.sh [MODEL_PATH] [DATASET] [GROUND_TRUTH_MAT]"
|
||||
echo " or: sh test_and_eval.sh [MODEL_PATH] [DATASET]"
|
||||
echo " or: sh test_and_eval.sh [MODEL_PATH]"
|
||||
echo " or: sh test_and_eval.sh "
|
||||
exit 1
|
||||
fi
|
||||
|
||||
get_real_path(){
|
||||
if [ "${1:0:1}" == "/" ]; then
|
||||
echo "$1"
|
||||
else
|
||||
echo "$(realpath -m $PWD/$1)"
|
||||
fi
|
||||
}
|
||||
|
||||
current_exec_path=$(pwd)
|
||||
echo ${current_exec_path}
|
||||
|
||||
dirname_path=$(dirname "$(pwd)")
|
||||
echo ${dirname_path}
|
||||
|
||||
SCRIPT_NAME='test.py'
|
||||
|
||||
ulimit -c unlimited
|
||||
|
||||
root=${current_exec_path} # your script path
|
||||
model_path=$root/model/
|
||||
dataset_root=$root/dataset
|
||||
dataset_path=$dataset_root/centerface/images/val/images/
|
||||
ground_truth_mat=$dataset_root/centerface/ground_truth/val.mat
|
||||
save_path=$root/output/centerface/999
|
||||
device_id=0
|
||||
ckpt="0-125_24750.ckpt" # the model saved for epoch=125
|
||||
ground_truth_path=$root/dataset/centerface/ground_truth
|
||||
|
||||
if [ $# == 1 ]
|
||||
then
|
||||
model_path=$(get_real_path $1)
|
||||
if [ ! -f $model_path ]
|
||||
then
|
||||
echo "error: model_path=$model_path is not a file"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $# == 2 ]
|
||||
then
|
||||
dataset_path=$(get_real_path $2)
|
||||
if [ ! -f $dataset_path ]
|
||||
then
|
||||
echo "error: dataset_path=$dataset_path is not a file"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $# == 3 ]
|
||||
then
|
||||
ground_truth_mat=$(get_real_path $3)
|
||||
if [ ! -f $ground_truth_mat ]
|
||||
then
|
||||
echo "error: ground_truth_mat=$ground_truth_mat is not a file"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $# == 4 ]
|
||||
then
|
||||
save_path=$(get_real_path $4)
|
||||
if [ ! -f $save_path ]
|
||||
then
|
||||
echo "error: save_path=$save_path is not a file"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $# == 5 ]
|
||||
then
|
||||
device_id=$5
|
||||
fi
|
||||
|
||||
if [ $# == 6 ]
|
||||
then
|
||||
ckpt=$6
|
||||
fi
|
||||
|
||||
if [ $# == 7 ]
|
||||
then
|
||||
ground_truth_path=$(get_real_path $7)
|
||||
if [ ! -f $ground_truth_path ]
|
||||
then
|
||||
echo "error: ground_truth_path=$ground_truth_path is not a file"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo $model_path
|
||||
echo $dataset_path
|
||||
echo $ground_truth_mat
|
||||
echo $save_path
|
||||
echo $ground_truth_path
|
||||
|
||||
export PYTHONPATH=${dirname_path}:$PYTHONPATH
|
||||
export RANK_SIZE=1
|
||||
|
||||
echo 'start testing'
|
||||
rm -rf ${current_exec_path}/device_test$device_id
|
||||
echo 'start rank '$device_id
|
||||
mkdir ${current_exec_path}/device_test$device_id
|
||||
cd ${current_exec_path}/device_test$device_id || exit
|
||||
export RANK_ID=0
|
||||
dev=`expr $device_id + 0`
|
||||
export DEVICE_ID=$dev
|
||||
python ${dirname_path}/${SCRIPT_NAME} \
|
||||
--is_distributed=0 \
|
||||
--data_dir=$dataset_path \
|
||||
--test_model=$model_path \
|
||||
--ground_truth_mat=$ground_truth_mat \
|
||||
--save_dir=$save_path \
|
||||
--rank=$device_id \
|
||||
--ckpt_name=$ckpt \
|
||||
--eval=1 \
|
||||
--ground_truth_path=$ground_truth_path > test.log 2>&1 &
|
||||
|
||||
echo 'running'
|
|
@ -0,0 +1,157 @@
|
|||
#!/bin/bash
|
||||
# Copyright 2020 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
|
||||
if [ $# -gt 8 ]
|
||||
then
|
||||
echo "Usage: sh test_distribute.sh [MODEL_PATH] [DATASET] [GROUND_TRUTH_MAT] [SAVE_PATH] [DEVICE_NUM] [STEPS_PER_EPOCH] [START] [END]"
|
||||
echo " or: sh test_distribute.sh [MODEL_PATH] [DATASET] [GROUND_TRUTH_MAT] [SAVE_PATH] [DEVICE_NUM] [STEPS_PER_EPOCH] [START]"
|
||||
echo " or: sh test_distribute.sh [MODEL_PATH] [DATASET] [GROUND_TRUTH_MAT] [SAVE_PATH] [DEVICE_NUM] [STEPS_PER_EPOCH]"
|
||||
echo " or: sh test_distribute.sh [MODEL_PATH] [DATASET] [GROUND_TRUTH_MAT] [SAVE_PATH] [DEVICE_NUM]"
|
||||
echo " or: sh test_distribute.sh [MODEL_PATH] [DATASET] [GROUND_TRUTH_MAT] [SAVE_PATH] [DEVICE_NUM]"
|
||||
echo " or: sh test_distribute.sh [MODEL_PATH] [DATASET] [GROUND_TRUTH_MAT] [SAVE_PATH]"
|
||||
echo " or: sh test_distribute.sh [MODEL_PATH] [DATASET] [GROUND_TRUTH_MAT]"
|
||||
echo " or: sh test_distribute.sh [MODEL_PATH] [DATASET]"
|
||||
echo " or: sh test_distribute.sh [MODEL_PATH] [DATASET]"
|
||||
echo " or: sh test_distribute.sh [MODEL_PATH]"
|
||||
echo " or: sh test_distribute.sh "
|
||||
exit 1
|
||||
fi
|
||||
|
||||
get_real_path(){
|
||||
if [ "${1:0:1}" == "/" ]; then
|
||||
echo "$1"
|
||||
else
|
||||
echo "$(realpath -m $PWD/$1)"
|
||||
fi
|
||||
}
|
||||
|
||||
current_exec_path=$(pwd)
|
||||
echo ${current_exec_path}
|
||||
|
||||
dirname_path=$(dirname "$(pwd)")
|
||||
echo ${dirname_path}
|
||||
|
||||
SCRIPT_NAME='test.py'
|
||||
|
||||
ulimit -c unlimited
|
||||
|
||||
root=${current_exec_path} # your script path
|
||||
model_path=$root/model/
|
||||
dataset_root=$root/dataset
|
||||
dataset_path=$dataset_root/centerface/images/val/images/
|
||||
ground_truth_mat=$dataset_root/centerface/ground_truth/val.mat
|
||||
save_path=$root/output/centerface/
|
||||
# blow are used for calculate model name
|
||||
# model/ckpt name is "0-" + str(ckpt_num) + "_" + str(198*ckpt_num) + ".ckpt";
|
||||
# ckpt_num is epoch number, can be calculated by device_num
|
||||
# detail can be found in "test.py"
|
||||
device_num=8
|
||||
steps_per_epoch=198 #198 for 8P; 1583 for 1p
|
||||
start=11 # start epoch number = start * device_num + min(device_phy_id) + 1
|
||||
end=18 # end epoch number = end * device_num + max(device_phy_id) + 1
|
||||
|
||||
if [ $# == 1 ]
|
||||
then
|
||||
model_path=$(get_real_path $1)
|
||||
if [ ! -f $model_path ]
|
||||
then
|
||||
echo "error: model_path=$model_path is not a file"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $# == 2 ]
|
||||
then
|
||||
dataset_path=$(get_real_path $2)
|
||||
if [ ! -f $dataset_path ]
|
||||
then
|
||||
echo "error: dataset_path=$dataset_path is not a file"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $# == 3 ]
|
||||
then
|
||||
ground_truth_mat=$(get_real_path $3)
|
||||
if [ ! -f $ground_truth_mat ]
|
||||
then
|
||||
echo "error: ground_truth_mat=$ground_truth_mat is not a file"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $# == 4 ]
|
||||
then
|
||||
save_path=$(get_real_path $4)
|
||||
if [ ! -f $save_path ]
|
||||
then
|
||||
echo "error: save_path=$save_path is not a file"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $# == 5 ]
|
||||
then
|
||||
device_num=$5
|
||||
fi
|
||||
|
||||
if [ $# == 6 ]
|
||||
then
|
||||
steps_per_epoch=$6
|
||||
fi
|
||||
|
||||
if [ $# == 7 ]
|
||||
then
|
||||
start=$7
|
||||
fi
|
||||
|
||||
if [ $# == 8 ]
|
||||
then
|
||||
end=$8
|
||||
fi
|
||||
|
||||
echo $model_path
|
||||
echo $dataset_path
|
||||
echo $ground_truth_mat
|
||||
echo $save_path
|
||||
|
||||
export PYTHONPATH=${dirname_path}:$PYTHONPATH
|
||||
export RANK_SIZE=1
|
||||
|
||||
echo 'start testing'
|
||||
rm -rf ${current_exec_path}/device_test*
|
||||
for((i=0;i<=$device_num-1;i++));
|
||||
do
|
||||
echo 'start rank '$i
|
||||
mkdir ${current_exec_path}/device_test$i
|
||||
cd ${current_exec_path}/device_test$i || exit
|
||||
export RANK_ID=0
|
||||
dev=`expr $i + 0`
|
||||
export DEVICE_ID=$dev
|
||||
python ${dirname_path}/${SCRIPT_NAME} \
|
||||
--is_distributed=0 \
|
||||
--data_dir=$dataset_path \
|
||||
--test_model=$model_path \
|
||||
--ground_truth_mat=$ground_truth_mat \
|
||||
--save_dir=$save_path \
|
||||
--rank=$i \
|
||||
--device_num=$device_num \
|
||||
--steps_per_epoch=$steps_per_epoch \
|
||||
--start=$start \
|
||||
--end=$end > test.log 2>&1 &
|
||||
done
|
||||
|
||||
echo 'running'
|
|
@ -0,0 +1,141 @@
|
|||
#!/bin/bash
|
||||
# Copyright 2020 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
|
||||
if [ $# != 0 ] && [ $# != 1 ] && [ $# != 2 ] && [ $# != 3 ] && [ $# != 4 ] && [ $# != 5 ]
|
||||
then
|
||||
echo "Usage: sh train_distribute.sh [RANK_TABLE] [PRETRAINED_BACKBONE] [DATASET] [ANNOTATIONS] [IMAGES]"
|
||||
echo " or: sh train_distribute.sh [RANK_TABLE] [PRETRAINED_BACKBONE] [DATASET] [ANNOTATIONS]"
|
||||
echo " or: sh train_distribute.sh [RANK_TABLE] [PRETRAINED_BACKBONE] [DATASET]"
|
||||
echo " or: sh train_distribute.sh [RANK_TABLE] [PRETRAINED_BACKBONE]"
|
||||
echo " or: sh train_distribute.sh [RANK_TABLE]"
|
||||
echo " or: sh train_distribute.sh "
|
||||
exit 1
|
||||
fi
|
||||
|
||||
get_real_path(){
|
||||
if [ "${1:0:1}" == "/" ]; then
|
||||
echo "$1"
|
||||
else
|
||||
echo "$(realpath -m $PWD/$1)"
|
||||
fi
|
||||
}
|
||||
|
||||
current_exec_path=$(pwd)
|
||||
echo ${current_exec_path}
|
||||
|
||||
dirname_path=$(dirname "$(pwd)")
|
||||
echo ${dirname_path}
|
||||
|
||||
rm -rf ${current_exec_path}/device*
|
||||
SCRIPT_NAME='train.py'
|
||||
|
||||
ulimit -c unlimited
|
||||
|
||||
root=${current_exec_path} # your script path
|
||||
pretrained_backbone=${dirname_path}/mobilenet_v2.ckpt # or mobilenet_v2-b0353104.ckpt
|
||||
dataset_path=$root/dataset/centerface
|
||||
annot_path=$dataset_path/annotations/train.json
|
||||
img_dir=$dataset_path/images/train/images
|
||||
rank_table=$root/rank_table_8p.json
|
||||
|
||||
if [ $# == 1 ]
|
||||
then
|
||||
rank_table=$(get_real_path $1)
|
||||
if [ ! -f $rank_table ]
|
||||
then
|
||||
echo "error: rank_table=$rank_table is not a file"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $# == 2 ]
|
||||
then
|
||||
pretrained_backbone=$(get_real_path $2)
|
||||
if [ ! -f $pretrained_backbone ]
|
||||
then
|
||||
echo "error: pretrained_backbone=$pretrained_backbone is not a file"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $# == 3 ]
|
||||
then
|
||||
dataset_path=$(get_real_path $3)
|
||||
if [ ! -f $dataset_path ]
|
||||
then
|
||||
echo "error: dataset_path=$dataset_path is not a file"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $# == 4 ]
|
||||
then
|
||||
annot_path=$(get_real_path $4)
|
||||
if [ ! -f $annot_path ]
|
||||
then
|
||||
echo "error: annot_path=$annot_path is not a file"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $# == 5 ]
|
||||
then
|
||||
img_dir=$(get_real_path $5)
|
||||
if [ ! -f $img_dir ]
|
||||
then
|
||||
echo "error: img_dir=$img_dir is not a file"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo $rank_table
|
||||
echo $pretrained_backbone
|
||||
echo $dataset_path
|
||||
echo $annot_path
|
||||
echo $img_dir
|
||||
|
||||
export PYTHONPATH=${dirname_path}:$PYTHONPATH
|
||||
export RANK_TABLE_FILE=$rank_table
|
||||
export RANK_SIZE=8
|
||||
|
||||
task_set_core=24 # for taskset, task_set_core=total cpu number/RANK_SIZE
|
||||
echo 'start training'
|
||||
for((i=0;i<=$RANK_SIZE-1;i++));
|
||||
do
|
||||
echo 'start rank '$i
|
||||
mkdir ${current_exec_path}/device$i
|
||||
cd ${current_exec_path}/device$i || exit
|
||||
export RANK_ID=$i
|
||||
dev=`expr $i + 0`
|
||||
export DEVICE_ID=$dev
|
||||
taskset -c $((i*task_set_core))-$(((i+1)*task_set_core-1)) python ${dirname_path}/${SCRIPT_NAME} \
|
||||
--lr=4e-3 \
|
||||
--per_batch_size=8 \
|
||||
--is_distributed=1 \
|
||||
--t_max=140 \
|
||||
--max_epoch=140 \
|
||||
--warmup_epochs=0 \
|
||||
--lr_scheduler=multistep \
|
||||
--lr_epochs=90,120 \
|
||||
--weight_decay=0.0000 \
|
||||
--loss_scale=1024 \
|
||||
--pretrained_backbone=$pretrained_backbone \
|
||||
--data_dir=$dataset_path \
|
||||
--annot_path=$annot_path \
|
||||
--img_dir=$img_dir > train.log 2>&1 &
|
||||
done
|
||||
|
||||
echo 'running'
|
|
@ -0,0 +1,131 @@
|
|||
#!/bin/bash
|
||||
# Copyright 2020 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
|
||||
if [ $# != 0 ] && [ $# != 1 ] && [ $# != 2 ] && [ $# != 3 ] && [ $# != 4 ] && [ $# != 5 ]
|
||||
then
|
||||
echo "Usage: sh train_standalone.sh [USE_DEVICE_ID] [PRETRAINED_BACKBONE] [DATASET] [ANNOTATIONS] [IMAGES]"
|
||||
echo " or: sh train_standalone.sh [USE_DEVICE_ID] [PRETRAINED_BACKBONE] [DATASET] [ANNOTATIONS]"
|
||||
echo " or: sh train_standalone.sh [USE_DEVICE_ID] [PRETRAINED_BACKBONE] [DATASET]"
|
||||
echo " or: sh train_standalone.sh [USE_DEVICE_ID] [PRETRAINED_BACKBONE]"
|
||||
echo " or: sh train_standalone.sh [USE_DEVICE_ID]"
|
||||
echo " or: sh train_standalone.sh "
|
||||
exit 1
|
||||
fi
|
||||
|
||||
get_real_path(){
|
||||
if [ "${1:0:1}" == "/" ]; then
|
||||
echo "$1"
|
||||
else
|
||||
echo "$(realpath -m $PWD/$1)"
|
||||
fi
|
||||
}
|
||||
|
||||
current_exec_path=$(pwd)
|
||||
echo ${current_exec_path}
|
||||
|
||||
dirname_path=$(dirname "$(pwd)")
|
||||
echo ${dirname_path}
|
||||
|
||||
SCRIPT_NAME='train.py'
|
||||
|
||||
ulimit -c unlimited
|
||||
|
||||
root=${current_exec_path} # your script path
|
||||
pretrained_backbone=${dirname_path}/mobilenet_v2.ckpt # or mobilenet_v2-b0353104.ckpt
|
||||
dataset_path=$root/dataset/centerface
|
||||
annot_path=$dataset_path/annotations/train.json
|
||||
img_dir=$dataset_path/images/train/images
|
||||
use_device_id=0
|
||||
|
||||
if [ $# == 1 ]
|
||||
then
|
||||
use_device_id=$1
|
||||
fi
|
||||
|
||||
if [ $# == 2 ]
|
||||
then
|
||||
pretrained_backbone=$(get_real_path $2)
|
||||
if [ ! -f $pretrained_backbone ]
|
||||
then
|
||||
echo "error: pretrained_backbone=$pretrained_backbone is not a file"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $# == 3 ]
|
||||
then
|
||||
dataset_path=$(get_real_path $3)
|
||||
if [ ! -f $dataset_path ]
|
||||
then
|
||||
echo "error: dataset_path=$dataset_path is not a file"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $# == 4 ]
|
||||
then
|
||||
annot_path=$(get_real_path $4)
|
||||
if [ ! -f $annot_path ]
|
||||
then
|
||||
echo "error: annot_path=$annot_path is not a file"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $# == 5 ]
|
||||
then
|
||||
img_dir=$(get_real_path $5)
|
||||
if [ ! -f $img_dir ]
|
||||
then
|
||||
echo "error: img_dir=$img_dir is not a file"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo $use_device_id
|
||||
echo $pretrained_backbone
|
||||
echo $dataset_path
|
||||
echo $annot_path
|
||||
echo $img_dir
|
||||
|
||||
export PYTHONPATH=${dirname_path}:$PYTHONPATH
|
||||
export RANK_SIZE=1
|
||||
|
||||
echo 'start training'
|
||||
echo 'start rank '$use_device_id
|
||||
rm -rf ${current_exec_path}/device$use_device_id
|
||||
mkdir ${current_exec_path}/device$use_device_id
|
||||
cd ${current_exec_path}/device$use_device_id || exit
|
||||
export RANK_ID=0
|
||||
dev=`expr $use_device_id + 0`
|
||||
export DEVICE_ID=$dev
|
||||
python ${dirname_path}/${SCRIPT_NAME} \
|
||||
--lr=5e-4 \
|
||||
--per_batch_size=8 \
|
||||
--is_distributed=0 \
|
||||
--t_max=140 \
|
||||
--max_epoch=140 \
|
||||
--warmup_epochs=0 \
|
||||
--lr_scheduler=multistep \
|
||||
--lr_epochs=90,120 \
|
||||
--weight_decay=0.0000 \
|
||||
--loss_scale=1024 \
|
||||
--pretrained_backbone=$pretrained_backbone \
|
||||
--data_dir=$dataset_path \
|
||||
--annot_path=$annot_path \
|
||||
--img_dir=$img_dir > train.log 2>&1 &
|
||||
|
||||
echo 'running'
|
|
@ -0,0 +1,324 @@
|
|||
# Copyright 2020 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
"""centerface networks"""
|
||||
|
||||
from src.config import ConfigCenterface
|
||||
from src.mobile_v2 import mobilenet_v2
|
||||
from src.losses import FocalLoss, SmoothL1LossNew, SmoothL1LossNewCMask
|
||||
|
||||
import mindspore as ms
|
||||
import mindspore.nn as nn
|
||||
from mindspore.common.tensor import Tensor
|
||||
from mindspore import context
|
||||
from mindspore.parallel._auto_parallel_context import auto_parallel_context
|
||||
from mindspore.communication.management import get_group_size
|
||||
from mindspore.ops import operations as P
|
||||
from mindspore.ops import functional as F
|
||||
from mindspore.ops import composite as C
|
||||
from mindspore.common import dtype as mstype
|
||||
from mindspore.ops.operations import NPUGetFloatStatus, NPUAllocFloatStatus, NPUClearFloatStatus, ReduceSum, LessEqual
|
||||
from mindspore.context import ParallelMode
|
||||
|
||||
_grad_scale = C.MultitypeFuncGraph("grad_scale")
|
||||
reciprocal = P.Reciprocal()
|
||||
|
||||
@_grad_scale.register("Tensor", "Tensor")
|
||||
def tensor_grad_scale(scale, grad):
|
||||
return grad * reciprocal(scale)
|
||||
|
||||
def conv1x1(in_channels, out_channels, stride=1, padding=0, has_bias=False):
|
||||
return nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, has_bias=has_bias,
|
||||
padding=padding, pad_mode="pad")
|
||||
|
||||
|
||||
def conv3x3(in_channels, out_channels, stride=1, padding=1, has_bias=False):
|
||||
return nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, has_bias=has_bias,
|
||||
padding=padding, pad_mode="pad")
|
||||
|
||||
|
||||
def convTranspose2x2(in_channels, out_channels, has_bias=False): # Davinci devices only support 'groups=1'
|
||||
return nn.Conv2dTranspose(in_channels, out_channels, kernel_size=2, stride=2, has_bias=has_bias,
|
||||
weight_init='normal', bias_init='zeros')
|
||||
|
||||
|
||||
class IDAUp(nn.Cell):
|
||||
"""
|
||||
IDA Module.
|
||||
"""
|
||||
def __init__(self, out_dim, channel):
|
||||
super(IDAUp, self).__init__()
|
||||
self.out_dim = out_dim
|
||||
self.up = nn.SequentialCell([
|
||||
convTranspose2x2(out_dim, out_dim, has_bias=False),
|
||||
nn.BatchNorm2d(out_dim, eps=0.001, momentum=0.9).add_flags_recursive(fp32=True),
|
||||
nn.ReLU()])
|
||||
self.conv = nn.SequentialCell([
|
||||
conv1x1(channel, out_dim),
|
||||
nn.BatchNorm2d(out_dim, eps=0.001, momentum=0.9).add_flags_recursive(fp32=True),
|
||||
nn.ReLU()])
|
||||
|
||||
def construct(self, x0, x1):
|
||||
x = self.up(x0)
|
||||
y = self.conv(x1)
|
||||
out = x + y
|
||||
return out
|
||||
|
||||
|
||||
class MobileNetUp(nn.Cell):
|
||||
"""
|
||||
Mobilenet module.
|
||||
"""
|
||||
def __init__(self, channels, out_dim=24):
|
||||
super(MobileNetUp, self).__init__()
|
||||
channels = channels[::-1]
|
||||
self.conv = nn.SequentialCell([
|
||||
conv1x1(channels[0], out_dim),
|
||||
nn.BatchNorm2d(out_dim, eps=0.001).add_flags_recursive(fp32=True),
|
||||
nn.ReLU()])
|
||||
self.conv_last = nn.SequentialCell([
|
||||
conv3x3(out_dim, out_dim),
|
||||
nn.BatchNorm2d(out_dim, eps=1e-5, momentum=0.99).add_flags_recursive(fp32=True),
|
||||
nn.ReLU()])
|
||||
|
||||
self.up1 = IDAUp(out_dim, channels[1])
|
||||
self.up2 = IDAUp(out_dim, channels[2])
|
||||
self.up3 = IDAUp(out_dim, channels[3])
|
||||
|
||||
def construct(self, x1, x2, x3, x4): # tuple/list can be type of input of a subnet
|
||||
x = self.conv(x4) # top_layer, change outdim
|
||||
|
||||
x = self.up1(x, x3)
|
||||
x = self.up2(x, x2)
|
||||
x = self.up3(x, x1)
|
||||
x = self.conv_last(x)
|
||||
return x
|
||||
|
||||
class Cast(nn.Cell):
|
||||
def __init__(self):
|
||||
super(Cast, self).__init__()
|
||||
self.cast = P.Cast()
|
||||
|
||||
def construct(self, x):
|
||||
return self.cast(x, ms.float32)
|
||||
|
||||
class CenterfaceMobilev2(nn.Cell):
|
||||
"""
|
||||
Mobilev2 based CenterFace network.
|
||||
|
||||
Args:
|
||||
num_classes: Integer. Class number.
|
||||
feature_shape: List. Input image shape, [N,C,H,W].
|
||||
|
||||
Returns:
|
||||
Cell, cell instance of Darknet based YOLOV3 neural network.
|
||||
CenterFace use the same structure.
|
||||
|
||||
Examples:
|
||||
yolov3_darknet53(80, [1,3,416,416])
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(CenterfaceMobilev2, self).__init__()
|
||||
self.config = ConfigCenterface()
|
||||
|
||||
self.base = mobilenet_v2()
|
||||
channels = self.base.feat_channel
|
||||
self.dla_up = MobileNetUp(channels, out_dim=self.config.head_conv)
|
||||
|
||||
self.hm_head = nn.SequentialCell([conv1x1(self.config.head_conv, 1, has_bias=True),
|
||||
nn.Sigmoid().add_flags_recursive(fp32=True)])
|
||||
self.wh_head = conv1x1(self.config.head_conv, 2, has_bias=True)
|
||||
self.off_head = conv1x1(self.config.head_conv, 2, has_bias=True)
|
||||
self.kps_head = conv1x1(self.config.head_conv, 10, has_bias=True)
|
||||
|
||||
def construct(self, x):
|
||||
x1, x2, x3, x4 = self.base(x)
|
||||
x = self.dla_up(x1, x2, x3, x4)
|
||||
|
||||
output_hm = self.hm_head(x)
|
||||
output_wh = self.wh_head(x)
|
||||
output_off = self.off_head(x)
|
||||
output_kps = self.kps_head(x)
|
||||
return output_hm, output_wh, output_off, output_kps
|
||||
|
||||
class CenterFaceLoss(nn.Cell):
|
||||
"""
|
||||
Loss method defination.
|
||||
"""
|
||||
def __init__(self, wh_weight, reg_offset, off_weight, hm_weight, lm_weight):
|
||||
super(CenterFaceLoss, self).__init__()
|
||||
# --- config parameter
|
||||
self.wh_weight = wh_weight
|
||||
self.reg_offset = reg_offset
|
||||
self.off_weight = off_weight
|
||||
self.hm_weight = hm_weight
|
||||
self.lm_weight = lm_weight
|
||||
# ---
|
||||
self.cls_loss = FocalLoss()
|
||||
self.reg_loss = SmoothL1LossNew()
|
||||
self.reg_loss_cmask = SmoothL1LossNewCMask()
|
||||
self.print = P.Print()
|
||||
# self.reduce_sum = P.ReduceSum()
|
||||
|
||||
def construct(self, output_hm, output_wh, output_off, output_kps, hm, reg_mask, ind, wh, wight_mask, hm_offset,
|
||||
hps_mask, landmarks):
|
||||
"""
|
||||
Construct method.
|
||||
"""
|
||||
hm_loss = self.cls_loss(output_hm, hm) # 1. focal loss, center points
|
||||
wh_loss = self.reg_loss(output_wh, ind, wh, wight_mask) # 2. weight and height
|
||||
off_loss = self.reg_loss(output_off, ind, hm_offset, wight_mask) # 3. offset
|
||||
lm_loss = self.reg_loss_cmask(output_kps, hps_mask, ind, landmarks) # 4. landmark loss
|
||||
|
||||
loss = self.hm_weight * hm_loss + self.wh_weight * wh_loss + \
|
||||
self.off_weight * off_loss + self.lm_weight * lm_loss
|
||||
|
||||
# depend is needed when wight_mask and reg_mask is not been used
|
||||
F.depend(loss, F.sqrt(F.cast(wight_mask, mstype.float32)))
|
||||
F.depend(loss, F.sqrt(F.cast(reg_mask, mstype.float32)))
|
||||
# add print when you want to see loss detail and do debug
|
||||
#self.print('hm_loss=', hm_loss, 'wh_loss=', wh_loss, 'off_loss=', off_loss, 'lm_loss=', lm_loss, 'loss=', loss)
|
||||
return loss
|
||||
|
||||
|
||||
class CenterFaceWithLossCell(nn.Cell):
|
||||
"""
|
||||
Centerface with loss cell.
|
||||
"""
|
||||
def __init__(self, network):
|
||||
super(CenterFaceWithLossCell, self).__init__()
|
||||
self.centerface_network = network
|
||||
self.config = ConfigCenterface()
|
||||
self.loss = CenterFaceLoss(self.config.wh_weight, self.config.reg_offset, self.config.off_weight,
|
||||
self.config.hm_weight, self.config.lm_weight)
|
||||
self.reduce_sum = P.ReduceSum()
|
||||
self.print = P.Print()
|
||||
|
||||
def construct(self, x, hm, reg_mask, ind, wh, wight_mask, hm_offset, hps_mask, landmarks):
|
||||
output_hm, output_wh, output_off, output_kps = self.centerface_network(x)
|
||||
loss = self.loss(output_hm, output_wh, output_off, output_kps, hm, reg_mask, ind, wh, wight_mask, hm_offset,
|
||||
hps_mask, landmarks)
|
||||
return loss
|
||||
|
||||
class TrainingWrapper(nn.Cell):
|
||||
"""
|
||||
Training wrapper
|
||||
"""
|
||||
def __init__(self, network, optimizer, sens=1.0):
|
||||
super(TrainingWrapper, self).__init__(auto_prefix=False)
|
||||
self.network = network
|
||||
self.network.set_grad() #False
|
||||
self.network.add_flags(defer_inline=True)
|
||||
self.weights = optimizer.parameters
|
||||
self.optimizer = optimizer
|
||||
self.grad = C.GradOperation(get_by_list=True, sens_param=True)
|
||||
self.sens = sens
|
||||
self.reducer_flag = False
|
||||
self.grad_reducer = None
|
||||
|
||||
self.parallel_mode = context.get_auto_parallel_context("parallel_mode")
|
||||
if self.parallel_mode in [ParallelMode.DATA_PARALLEL, ParallelMode.HYBRID_PARALLEL]:
|
||||
self.reducer_flag = True
|
||||
if self.reducer_flag:
|
||||
mean = context.get_auto_parallel_context("gradients_mean")
|
||||
if auto_parallel_context().get_device_num_is_set():
|
||||
degree = context.get_auto_parallel_context("device_num")
|
||||
else:
|
||||
degree = get_group_size()
|
||||
self.grad_reducer = nn.DistributedGradReducer(optimizer.parameters, mean, degree)
|
||||
|
||||
self.hyper_map = C.HyperMap()
|
||||
self.alloc_status = NPUAllocFloatStatus()
|
||||
self.get_status = NPUGetFloatStatus()
|
||||
self.clear_status = NPUClearFloatStatus()
|
||||
self.reduce_sum = ReduceSum(keep_dims=False)
|
||||
self.base = Tensor(1, mstype.float32)
|
||||
self.less_equal = LessEqual()
|
||||
self.allreduce = P.AllReduce()
|
||||
self.is_distributed = self.parallel_mode != ParallelMode.STAND_ALONE
|
||||
|
||||
# x, hm, reg_mask, ind, wh, wight_mask, hm_offset, hps_mask, landmarks
|
||||
def construct(self, x, hm, reg_mask, ind, wh, wight_mask, hm_offset, hps_mask, landmarks):
|
||||
"""
|
||||
Construct method.
|
||||
"""
|
||||
weights = self.weights
|
||||
loss = self.network(x, hm, reg_mask, ind, wh, wight_mask, hm_offset, hps_mask, landmarks)
|
||||
|
||||
# init overflow buffer
|
||||
init = self.alloc_status()
|
||||
# clear overflow buffer
|
||||
self.clear_status(init)
|
||||
|
||||
#sens = sens_input #P.Fill()(P.DType()(loss), P.Shape()(loss), sens_input) # user can contral loss scale by add a sens_input
|
||||
sens = P.Fill()(P.DType()(loss), P.Shape()(loss), self.sens)
|
||||
grads = self.grad(self.network, weights)(x, hm, reg_mask, ind, wh, wight_mask, hm_offset, hps_mask, landmarks,
|
||||
sens)
|
||||
#grads = self.hyper_map(F.partial(_grad_scale, sens), grads) # if add this, the loss_scale optimizer is needed to set to 1
|
||||
if self.reducer_flag:
|
||||
grads = self.grad_reducer(grads)
|
||||
|
||||
# get the overflow buffer
|
||||
self.get_status(init)
|
||||
# sum overflow buffer elements, 0:not overflow , >0:overflow
|
||||
flag_sum = self.reduce_sum(init, (0,))
|
||||
if self.is_distributed:
|
||||
# sum overflow flag over devices
|
||||
flag_reduce = self.allreduce(flag_sum)
|
||||
cond = self.less_equal(self.base, flag_reduce)
|
||||
else:
|
||||
cond = self.less_equal(self.base, flag_sum)
|
||||
|
||||
ret = (loss, cond, sens)
|
||||
return F.depend(ret, self.optimizer(grads))
|
||||
|
||||
|
||||
class CenterFaceWithNms(nn.Cell):
|
||||
"""
|
||||
CenterFace with nms.
|
||||
"""
|
||||
def __init__(self, network):
|
||||
super(CenterFaceWithNms, self).__init__()
|
||||
self.centerface_network = network
|
||||
self.config = ConfigCenterface()
|
||||
# two type of maxpool self.maxpool2d = nn.MaxPool2d(kernel_size=3, stride=1, pad_mode='same')
|
||||
self.maxpool2d = P.MaxPoolWithArgmax(ksize=3, strides=1, padding='same')
|
||||
self.topk = P.TopK(sorted=True)
|
||||
self.reshape = P.Reshape()
|
||||
self.print = P.Print()
|
||||
self.test_batch = self.config.test_batch_size
|
||||
self.k = self.config.K
|
||||
|
||||
def construct(self, x):
|
||||
"""
|
||||
Construct method.
|
||||
"""
|
||||
output_hm, output_wh, output_off, output_kps = self.centerface_network(x)
|
||||
output_hm_nms, _ = self.maxpool2d(output_hm)
|
||||
abs_error = P.Abs()(output_hm - output_hm_nms)
|
||||
abs_out = P.Abs()(output_hm)
|
||||
error = abs_error / (abs_out + 1e-12)
|
||||
|
||||
# cannot use P.Equal()(output_hm, output_hm_nms), since maxpooling output has 0.1% error
|
||||
keep = P.Select()(P.LessEqual()(error, 1e-3), \
|
||||
P.Fill()(ms.float32, P.Shape()(error), 1.0), \
|
||||
P.Fill()(ms.float32, P.Shape()(error), 0.0))
|
||||
output_hm = output_hm * keep
|
||||
|
||||
# get topK and index
|
||||
scores = self.reshape(output_hm, (self.test_batch, -1))
|
||||
topk_scores, topk_inds = self.topk(scores, self.k)
|
||||
return topk_scores, output_wh, output_off, output_kps, topk_inds
|
|
@ -0,0 +1,64 @@
|
|||
# Copyright 2020 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
"""centerface unique configs"""
|
||||
|
||||
class ConfigCenterface():
|
||||
"""
|
||||
Config setup
|
||||
"""
|
||||
flip_idx = [[0, 1], [3, 4]]
|
||||
default_resolution = [512, 512]
|
||||
heads = {'hm': 1, 'wh': 2, 'hm_offset': 2, 'landmarks': 5 * 2}
|
||||
head_conv = 64
|
||||
max_objs = 64
|
||||
|
||||
rand_crop = True
|
||||
scale = 0.4
|
||||
shift = 0.1
|
||||
aug_rot = 0
|
||||
color_aug = True
|
||||
flip = 0.5
|
||||
input_res = 512 #768 #800
|
||||
output_res = 128 #192 #200
|
||||
num_classes = 1
|
||||
num_joints = 5
|
||||
reg_offset = True
|
||||
hm_hp = True
|
||||
reg_hp_offset = True
|
||||
dense_hp = False
|
||||
hm_weight = 1.0
|
||||
wh_weight = 0.1
|
||||
off_weight = 1.0
|
||||
lm_weight = 0.1
|
||||
rotate = 0
|
||||
|
||||
# for test
|
||||
mean = [0.408, 0.447, 0.470]
|
||||
std = [0.289, 0.274, 0.278]
|
||||
test_scales = [0.999,]
|
||||
nms = 1
|
||||
flip_test = 0
|
||||
fix_res = True
|
||||
input_h = 832 #800
|
||||
input_w = 832 #800
|
||||
K = 200
|
||||
down_ratio = 4
|
||||
test_batch_size = 1
|
||||
|
||||
seed = 317
|
||||
master_batch_size = 8
|
||||
num_workers = 8
|
||||
not_rand_crop = False
|
||||
no_color_aug = False
|
|
@ -0,0 +1,181 @@
|
|||
# Copyright 2020 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
"""
|
||||
Centerface model transform
|
||||
"""
|
||||
import os
|
||||
import argparse
|
||||
import torch
|
||||
from mindspore.train.serialization import load_checkpoint, save_checkpoint
|
||||
from mindspore import Tensor
|
||||
|
||||
parser = argparse.ArgumentParser(description='')
|
||||
parser.add_argument('--ckpt_fn', type=str, default='/model_path/centerface.ckpt',
|
||||
help='ckpt for user to get cell/module name')
|
||||
parser.add_argument('--pt_fn', type=str, default='/model_path/centerface.pth', help='checkpoint filename to convert')
|
||||
parser.add_argument('--out_fn', type=str, default='/model_path/centerface_out.ckpt',
|
||||
help='convert output ckpt/pth path')
|
||||
parser.add_argument('--pt2ckpt', type=int, default=1, help='1 : pt2ckpt; 0 : ckpt2pt')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
def load_model(model_path):
|
||||
"""
|
||||
Load model
|
||||
"""
|
||||
checkpoint = torch.load(model_path, map_location=lambda storage, loc: storage)
|
||||
print('loaded {}, epoch {}'.format(model_path, checkpoint['epoch']))
|
||||
state_dict_ = checkpoint['state_dict']
|
||||
state_dict = {}
|
||||
|
||||
# convert data_parallal to model
|
||||
for k in state_dict_:
|
||||
if k.find("num_batches_tracked") != -1:
|
||||
continue
|
||||
elif k.startswith('module') and not k.startswith('module_list'):
|
||||
state_dict[k[7:]] = state_dict_[k]
|
||||
else:
|
||||
state_dict[k] = state_dict_[k]
|
||||
|
||||
return state_dict
|
||||
|
||||
def save_model(path, epoch=0, model=None, optimizer=None, state_dict=None):
|
||||
"""
|
||||
Sace model file
|
||||
"""
|
||||
if state_dict is None:
|
||||
if isinstance(model, torch.nn.DataParallel):
|
||||
state_dict = model.module.state_dict()
|
||||
else:
|
||||
state_dict = model.state_dict()
|
||||
data = {'epoch': epoch,
|
||||
'state_dict': state_dict}
|
||||
if not optimizer is None:
|
||||
data['optimizer'] = optimizer.state_dict()
|
||||
torch.save(data, path)
|
||||
|
||||
def load_model_ms(model_path):
|
||||
"""
|
||||
Load mindspore model
|
||||
"""
|
||||
state_dict_useless = ['global_step', 'learning_rate',
|
||||
'beta1_power', 'beta2_power']
|
||||
if os.path.isfile(model_path):
|
||||
param_dict = load_checkpoint(model_path)
|
||||
param_dict_new = {}
|
||||
for key, values in param_dict.items():
|
||||
if key in state_dict_useless or key.startswith('moments.') \
|
||||
or key.startswith('moment1.') or key.startswith('moment2.'):
|
||||
continue
|
||||
elif key.startswith('centerface_network.'):
|
||||
param_dict_new[key[19:]] = values
|
||||
else:
|
||||
param_dict_new[key] = values
|
||||
else:
|
||||
assert FileNotFoundError('{} not exists or not a pre-trained file'.format(model_path))
|
||||
exit(1)
|
||||
return param_dict_new
|
||||
|
||||
def name_map(ckpt):
|
||||
"""
|
||||
Name map
|
||||
"""
|
||||
out = {}
|
||||
for name in ckpt:
|
||||
# conv + bn
|
||||
pt_name = name
|
||||
# backbone
|
||||
pt_name = pt_name.replace('need_fp1', 'feature_1')
|
||||
pt_name = pt_name.replace('need_fp2', 'feature_2')
|
||||
pt_name = pt_name.replace('need_fp3', 'feature_4')
|
||||
pt_name = pt_name.replace('need_fp4', 'feature_6')
|
||||
pt_name = pt_name.replace('.features', '')
|
||||
pt_name = pt_name.replace('.moving_mean', '.running_mean')
|
||||
pt_name = pt_name.replace('.moving_variance', '.running_var')
|
||||
pt_name = pt_name.replace('.gamma', '.weight')
|
||||
pt_name = pt_name.replace('.beta', '.bias')
|
||||
# fpn
|
||||
pt_name = pt_name.replace('.up1', '.up_0')
|
||||
pt_name = pt_name.replace('.up2', '.up_1')
|
||||
pt_name = pt_name.replace('.up3', '.up_2')
|
||||
# heads
|
||||
pt_name = pt_name.replace('hm_head.0.', 'hm.')
|
||||
pt_name = pt_name.replace('wh_head.', 'wh.')
|
||||
pt_name = pt_name.replace('off_head.', 'hm_offset.')
|
||||
pt_name = pt_name.replace('kps_head.', 'landmarks.')
|
||||
|
||||
out[pt_name] = name
|
||||
return out
|
||||
|
||||
def pt_to_ckpt(pt, ckpt, out_path):
|
||||
"""
|
||||
Pt convert to ckpt file
|
||||
"""
|
||||
state_dict_torch = load_model(pt)
|
||||
state_dict_ms = load_model_ms(ckpt)
|
||||
name_relate = name_map(state_dict_ms)
|
||||
|
||||
new_params_list = []
|
||||
for key in state_dict_torch:
|
||||
param_dict = {}
|
||||
parameter = state_dict_torch[key]
|
||||
parameter = parameter.numpy()
|
||||
|
||||
# depwise conv pytorch[cout, 1, k , k] -> ms[1, cin, k , k], cin = cout
|
||||
if state_dict_ms[name_relate[key]].data.shape != parameter.shape:
|
||||
parameter = parameter.transpose(1, 0, 2, 3)
|
||||
print('ms=', state_dict_ms[name_relate[key]].data.shape, 'pytorch=', parameter.shape, 'name=', key)
|
||||
|
||||
param_dict['name'] = name_relate[key]
|
||||
param_dict['data'] = Tensor(parameter)
|
||||
new_params_list.append(param_dict)
|
||||
|
||||
save_checkpoint(new_params_list, out_path)
|
||||
return state_dict_ms
|
||||
|
||||
def ckpt_to_pt(pt, ckpt, out_path):
|
||||
"""
|
||||
Ckpt convert to pt file
|
||||
"""
|
||||
state_dict_torch = load_model(pt)
|
||||
state_dict_ms = load_model_ms(ckpt)
|
||||
name_relate = name_map(state_dict_ms)
|
||||
|
||||
state_dict = {}
|
||||
for key in state_dict_torch:
|
||||
name = name_relate[key]
|
||||
parameter = state_dict_ms[name].data
|
||||
parameter = parameter.asnumpy()
|
||||
if state_dict_ms[name_relate[key]].data.shape != state_dict_torch[key].numpy().shape:
|
||||
print('before ms=', state_dict_ms[name_relate[key]].data.shape, 'pytorch=',
|
||||
state_dict_torch[key].numpy().shape, 'name=', key)
|
||||
parameter = parameter.transpose(1, 0, 2, 3)
|
||||
print('after ms=', state_dict_ms[name_relate[key]].data.shape, 'pytorch=',
|
||||
state_dict_torch[key].numpy().shape, 'name=', key)
|
||||
|
||||
state_dict[key] = torch.from_numpy(parameter)
|
||||
|
||||
save_model(out_path, epoch=0, model=None, optimizer=None, state_dict=state_dict)
|
||||
|
||||
return state_dict
|
||||
|
||||
if __name__ == "__main__":
|
||||
if args.pt2ckpt == 1:
|
||||
pt_to_ckpt(args.pt_fn, args.ckpt_fn, args.out_fn)
|
||||
elif args.pt2ckpt == 0:
|
||||
ckpt_to_pt(args.pt_fn, args.ckpt_fn, args.out_fn)
|
||||
else:
|
||||
# user defined functions
|
||||
pass
|
|
@ -0,0 +1,138 @@
|
|||
# Copyright 2020 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
"""
|
||||
Mobilenet model transform: torch => mindspore
|
||||
"""
|
||||
import os
|
||||
import argparse
|
||||
import torch
|
||||
from mindspore.train.serialization import load_checkpoint, save_checkpoint
|
||||
from mindspore import Tensor
|
||||
|
||||
parser = argparse.ArgumentParser(description='')
|
||||
parser.add_argument('--ckpt_fn', type=str, default='/model_path/mobilenet_v2_key.ckpt',
|
||||
help='ckpt for user to get cell/module name')
|
||||
parser.add_argument('--pt_fn', type=str, default='/model_path/mobilenet_v2-b0353104.pth',
|
||||
help='checkpoint filename to convert')
|
||||
parser.add_argument('--out_ckpt_fn', type=str, default='/model_path/mobilenet_v2-b0353104.ckpt',
|
||||
help='convert output ckpt path')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
def load_model(model_path):
|
||||
"""
|
||||
Load model
|
||||
"""
|
||||
state_dict_ = torch.load(model_path, map_location=torch.device('cpu'))
|
||||
state_dict = {}
|
||||
|
||||
# convert data_parallal to model
|
||||
for k in state_dict_:
|
||||
if k.find("num_batches_tracked") != -1:
|
||||
continue
|
||||
elif k.startswith('module') and not k.startswith('module_list'):
|
||||
state_dict[k[7:]] = state_dict_[k]
|
||||
else:
|
||||
state_dict[k] = state_dict_[k]
|
||||
return state_dict
|
||||
|
||||
def load_model_ms(model_path):
|
||||
"""
|
||||
Load mindspore model
|
||||
"""
|
||||
state_dict_useless = ['global_step', 'learning_rate',
|
||||
'beta1_power', 'beta2_power']
|
||||
if os.path.isfile(model_path):
|
||||
param_dict = load_checkpoint(model_path)
|
||||
param_dict_new = {}
|
||||
for key, values in param_dict.items():
|
||||
if key in state_dict_useless or key.startswith('moments.') \
|
||||
or key.startswith('moment1.') or key.startswith('moment2.'):
|
||||
continue
|
||||
elif key.startswith('centerface_network.'): #useless, since the start name is "network.backbone."
|
||||
param_dict_new[key[19:]] = values
|
||||
else:
|
||||
param_dict_new[key] = values
|
||||
else:
|
||||
assert FileNotFoundError('{} not exists or not a pre-trained file'.format(model_path))
|
||||
exit(1)
|
||||
return param_dict_new
|
||||
|
||||
def name_map(ckpt):
|
||||
"""
|
||||
Name map
|
||||
"""
|
||||
out = {}
|
||||
for name in ckpt:
|
||||
# conv + bn
|
||||
pt_name = name
|
||||
|
||||
pt_name = pt_name.replace('network.backbone.', '')
|
||||
# backbone
|
||||
pt_name = pt_name.replace('need_fp1', 'feature_1')
|
||||
pt_name = pt_name.replace('need_fp2', 'feature_2')
|
||||
pt_name = pt_name.replace('need_fp3', 'feature_4')
|
||||
pt_name = pt_name.replace('need_fp4', 'feature_6')
|
||||
pt_name = pt_name.replace('.features', '')
|
||||
pt_name = pt_name.replace('.moving_mean', '.running_mean')
|
||||
pt_name = pt_name.replace('.moving_variance', '.running_var')
|
||||
pt_name = pt_name.replace('.gamma', '.weight')
|
||||
pt_name = pt_name.replace('.beta', '.bias')
|
||||
# fpn
|
||||
pt_name = pt_name.replace('.up1', '.up_0')
|
||||
pt_name = pt_name.replace('.up2', '.up_1')
|
||||
pt_name = pt_name.replace('.up3', '.up_2')
|
||||
|
||||
# heads
|
||||
pt_name = pt_name.replace('hm_head.0.', 'hm.')
|
||||
pt_name = pt_name.replace('wh_head.', 'wh.')
|
||||
pt_name = pt_name.replace('off_head.', 'hm_offset.')
|
||||
pt_name = pt_name.replace('kps_head.', 'landmarks.')
|
||||
|
||||
pt_name = pt_name.replace('network.head.fc.', 'classifier.1.')
|
||||
|
||||
out[pt_name] = name
|
||||
return out
|
||||
|
||||
def pt_to_ckpt(pt, ckpt, out_ckpt):
|
||||
"""
|
||||
Pt convert to ckpt file
|
||||
"""
|
||||
state_dict_torch = load_model(pt)
|
||||
state_dict_ms = load_model_ms(ckpt)
|
||||
name_relate = name_map(state_dict_ms)
|
||||
new_params_list = []
|
||||
|
||||
for key in state_dict_torch:
|
||||
param_dict = {}
|
||||
parameter = state_dict_torch[key]
|
||||
parameter = parameter.numpy()
|
||||
|
||||
# depwise conv pytorch[cout, 1, k , k] -> ms[1, cin, k , k], cin = cout
|
||||
if state_dict_ms[name_relate[key]].data.shape != parameter.shape:
|
||||
parameter = parameter.transpose(1, 0, 2, 3)
|
||||
print('ms=', state_dict_ms[name_relate[key]].data.shape, 'pytorch=', parameter.shape, 'name=', key)
|
||||
|
||||
|
||||
param_dict['name'] = name_relate[key]
|
||||
param_dict['data'] = Tensor(parameter)
|
||||
new_params_list.append(param_dict)
|
||||
|
||||
save_checkpoint(new_params_list, out_ckpt)
|
||||
return state_dict_ms
|
||||
|
||||
if __name__ == "__main__":
|
||||
# beta <=> bias, gamma <=> weight
|
||||
pt_to_ckpt(args.pt_fn, args.ckpt_fn, args.out_ckpt_fn)
|
|
@ -0,0 +1,56 @@
|
|||
# Copyright 2020 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
"""generate dataloader and data processing entry"""
|
||||
|
||||
import mindspore.dataset.engine as de
|
||||
|
||||
from src.utils import DistributedSampler
|
||||
|
||||
from dependency.centernet.src.lib.datasets.dataset.coco_hp import CenterfaceDataset
|
||||
from dependency.centernet.src.lib.datasets.sample.multi_pose import preprocess_train
|
||||
|
||||
def GetDataLoader(per_batch_size,
|
||||
max_epoch,
|
||||
rank,
|
||||
group_size,
|
||||
config,
|
||||
split='train'):
|
||||
"""
|
||||
Centerface get data loader
|
||||
"""
|
||||
centerface_gen = CenterfaceDataset(config=config, split=split)
|
||||
sampler = DistributedSampler(centerface_gen, rank, group_size, shuffle=(split == 'train')) # user defined sampling strategy
|
||||
de_dataset = de.GeneratorDataset(centerface_gen, ["image", "anns"], sampler=sampler, num_parallel_workers=16)
|
||||
|
||||
if group_size > 1:
|
||||
num_parallel_workers = 24
|
||||
else:
|
||||
num_parallel_workers = 64
|
||||
if split == 'train':
|
||||
compose_map_func = (lambda image, anns: preprocess_train(image, anns, config=config))
|
||||
columns = ['image', "hm", 'reg_mask', 'ind', 'wh', 'wight_mask', 'hm_offset', 'hps_mask', 'landmarks']
|
||||
de_dataset = de_dataset.map(input_columns=["image", "anns"],
|
||||
output_columns=columns,
|
||||
column_order=columns,
|
||||
operations=compose_map_func,
|
||||
num_parallel_workers=num_parallel_workers,
|
||||
python_multiprocessing=True)
|
||||
|
||||
de_dataset = de_dataset.batch(per_batch_size, drop_remainder=True, num_parallel_workers=8)
|
||||
if split == 'train':
|
||||
#de_dataset = de_dataset.repeat(1) # if use this, need an additional "for" cycle epoch times
|
||||
de_dataset = de_dataset.repeat(max_epoch)
|
||||
|
||||
return de_dataset, de_dataset.get_dataset_size()
|
|
@ -0,0 +1,124 @@
|
|||
# Copyright 2020 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
"""losses for centerface"""
|
||||
|
||||
import mindspore.nn as nn
|
||||
from mindspore.ops import operations as P
|
||||
from mindspore.common import dtype as mstype
|
||||
|
||||
# focal loss: afa=2, beta=4
|
||||
class FocalLoss(nn.Cell):
|
||||
'''nn.Cell warpper for focal loss'''
|
||||
def __init__(self):
|
||||
super(FocalLoss, self).__init__()
|
||||
self.log = P.Log()
|
||||
self.pow = P.Pow()
|
||||
self.sum = P.ReduceSum()
|
||||
self.print = P.Print()
|
||||
|
||||
def construct(self, pred, gt):
|
||||
"""Construct method"""
|
||||
pos_inds = P.Select()(P.Equal()(gt, 1.0), P.Fill()(P.DType()(gt), P.Shape()(gt), 1.0), P.Fill()(P.DType()(gt),
|
||||
P.Shape()(gt),
|
||||
0.0))
|
||||
neg_inds = P.Select()(P.Less()(gt, 1.0), P.Fill()(P.DType()(gt), P.Shape()(gt), 1.0), P.Fill()(P.DType()(gt),
|
||||
P.Shape()(gt),
|
||||
0.0))
|
||||
|
||||
neg_weights = self.pow(1 - gt, 4) # beta=4
|
||||
# afa=2
|
||||
pos_loss = self.log(pred) * self.pow(1 - pred, 2) * pos_inds
|
||||
neg_loss = self.log(1 - pred) * self.pow(pred, 2) * neg_weights * neg_inds
|
||||
|
||||
num_pos = self.sum(pos_inds, ())
|
||||
num_pos = P.Select()(P.Equal()(num_pos, 0.0), P.Fill()(P.DType()(num_pos), P.Shape()(num_pos), 1.0), num_pos)
|
||||
|
||||
pos_loss = self.sum(pos_loss, ())
|
||||
neg_loss = self.sum(neg_loss, ())
|
||||
loss = - (pos_loss + neg_loss) / num_pos
|
||||
return loss
|
||||
|
||||
class SmoothL1LossNew(nn.Cell):
|
||||
"""Smoothl1loss"""
|
||||
def __init__(self):
|
||||
super(SmoothL1LossNew, self).__init__()
|
||||
self.transpose = P.Transpose()
|
||||
self.smooth_l1_loss = nn.SmoothL1Loss()
|
||||
self.shape = P.Shape()
|
||||
self.expand_dims = P.ExpandDims()
|
||||
self.sum = P.ReduceSum()
|
||||
self.cast = P.Cast()
|
||||
|
||||
def construct(self, output, ind, target, wight_mask=None):
|
||||
'''
|
||||
:param output: [b, c, h, w] to [b, h, w, c]
|
||||
:param ind:
|
||||
:param target:
|
||||
:return:
|
||||
'''
|
||||
output = self.transpose(output, (0, 2, 3, 1))
|
||||
# dim = self.shape(output)[3]
|
||||
mask = P.Select()(P.Equal()(ind, 1), P.Fill()(mstype.float32, P.Shape()(ind), 1.0), P.Fill()(mstype.float32,
|
||||
P.Shape()(ind),
|
||||
0.0))
|
||||
# ind = self.cast(ind, mstype.float32)
|
||||
target = self.cast(target, mstype.float32)
|
||||
output = self.cast(output, mstype.float32)
|
||||
num = self.cast(self.sum(mask, ()), mstype.float32)
|
||||
mask = self.expand_dims(mask, -1) # [batch,h,w]--[batch,h,w,c]
|
||||
output = output * mask
|
||||
target = target * mask
|
||||
loss = self.smooth_l1_loss(output, target)
|
||||
if wight_mask is not None:
|
||||
loss = loss * wight_mask
|
||||
loss = self.sum(loss, ())
|
||||
else:
|
||||
#some version need: F.depend(loss, F.sqrt(F.cast(wight_mask, mstype.float32)))
|
||||
loss = self.sum(loss, ())
|
||||
loss = loss / (num + 1e-4)
|
||||
return loss
|
||||
|
||||
class SmoothL1LossNewCMask(nn.Cell):
|
||||
"""Smoothl1loss with mask"""
|
||||
def __init__(self):
|
||||
super(SmoothL1LossNewCMask, self).__init__()
|
||||
self.transpose = P.Transpose()
|
||||
self.smooth_l1_loss = nn.L1Loss(reduction='sum') # or use nn.SmoothL1Loss()
|
||||
self.shape = P.Shape()
|
||||
self.expand_dims = P.ExpandDims()
|
||||
self.sum = P.ReduceSum()
|
||||
self.cast = P.Cast()
|
||||
|
||||
def construct(self, output, cmask, ind, target):
|
||||
'''
|
||||
:param output: [b, c, h, w] to [b, h, w, c]
|
||||
:param ind:
|
||||
:param target:
|
||||
:return:
|
||||
'''
|
||||
num = self.sum(cmask, ())
|
||||
output = self.transpose(output, (0, 2, 3, 1))
|
||||
|
||||
ind = self.cast(ind, mstype.float32)
|
||||
target = self.cast(target, mstype.float32)
|
||||
cmask = self.cast(cmask, mstype.float32)
|
||||
output = self.cast(output, mstype.float32)
|
||||
ind = self.expand_dims(ind, -1)
|
||||
output = output * ind
|
||||
target = target * ind
|
||||
loss = self.smooth_l1_loss(output*cmask, target*cmask)
|
||||
#loss = self.sum(loss, ()) # if use SmoothL1Loss, this is needed
|
||||
loss = loss / (num + 1e-4)
|
||||
return loss
|
|
@ -0,0 +1,851 @@
|
|||
# Copyright 2020 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
"""learning rate scheduler"""
|
||||
|
||||
import math
|
||||
from collections import Counter
|
||||
import numpy as np
|
||||
|
||||
__all__ = ["LambdaLR", "MultiplicativeLR", "StepLR", "MultiStepLR", "ExponentialLR", "CosineAnnealingLR", "CyclicLR",
|
||||
"CosineAnnealingWarmRestarts", "OneCycleLR", "POLYLR"]
|
||||
|
||||
class _WarmUp():
|
||||
"""
|
||||
Basic class for warm up
|
||||
"""
|
||||
def __init__(self, warmup_init_lr):
|
||||
self.warmup_init_lr = warmup_init_lr
|
||||
|
||||
def get_lr(self, current_step=0):
|
||||
# Get learning rate during warmup
|
||||
current_step = 0
|
||||
raise NotImplementedError
|
||||
|
||||
class _LinearWarmUp(_WarmUp):
|
||||
"""
|
||||
Class for linear warm up
|
||||
"""
|
||||
def __init__(self, lr, warmup_epochs, steps_per_epoch, warmup_init_lr=0):
|
||||
self.base_lr = lr
|
||||
self.warmup_init_lr = warmup_init_lr
|
||||
self.warmup_steps = int(warmup_epochs * steps_per_epoch)
|
||||
|
||||
super(_LinearWarmUp, self).__init__(warmup_init_lr)
|
||||
|
||||
def get_warmup_steps(self):
|
||||
return self.warmup_steps
|
||||
|
||||
def get_lr(self, current_step=0):
|
||||
lr_inc = (float(self.base_lr) - float(self.warmup_init_lr)) / float(self.warmup_steps)
|
||||
lr = float(self.warmup_init_lr) + lr_inc * current_step
|
||||
return lr
|
||||
|
||||
class _ConstWarmUp(_WarmUp):
|
||||
"""
|
||||
Class for const warm up
|
||||
"""
|
||||
def __init__(self, warmup_init_lr):
|
||||
super(_ConstWarmUp, self).__init__(warmup_init_lr)
|
||||
self.warmup_init_lr = warmup_init_lr
|
||||
|
||||
def get_lr(self, current_step=0):
|
||||
current_step = 0
|
||||
return self.warmup_init_lr
|
||||
|
||||
class _LRScheduler():
|
||||
"""
|
||||
Basic class for learning rate scheduler
|
||||
"""
|
||||
def __init__(self, lr, max_epoch, steps_per_epoch):
|
||||
self.base_lr = lr
|
||||
self.steps_per_epoch = steps_per_epoch
|
||||
self.total_steps = int(max_epoch * steps_per_epoch)
|
||||
|
||||
def get_lr(self):
|
||||
# Compute learning rate using chainable form of the scheduler
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class LambdaLR(_LRScheduler):
|
||||
r"""
|
||||
Lambda learning rate scheduler
|
||||
|
||||
Sets the learning rate to the initial lr times a given function.
|
||||
|
||||
Args:
|
||||
lr (float): Initial learning rate which is the lower boundary in the cycle.
|
||||
steps_per_epoch (int): The number of steps per epoch to train for.
|
||||
max_epoch (int): The number of epochs to train for.
|
||||
lr_lambda (func. or list): A function which computes a multiplicative factor given an integer parameter epoch.
|
||||
warmup_epochs (int, optional): The number of epochs to Warmup. Default: 0
|
||||
|
||||
Outputs:
|
||||
numpy.ndarray, shape=(1, steps_per_epoch*max_epoch)
|
||||
|
||||
Example:
|
||||
>>> lambda1 = lambda epoch: epoch // 30
|
||||
>>> scheduler = LambdaLR(lr=0.1, lr_lambda=lambda1, steps_per_epoch=5000, max_epoch=90)
|
||||
>>> lr = scheduler.get_lr()
|
||||
"""
|
||||
|
||||
def __init__(self, lr, lr_lambda, steps_per_epoch, max_epoch, warmup_epochs=0):
|
||||
self.lr_lambda = lr_lambda
|
||||
self.warmup = _LinearWarmUp(lr, warmup_epochs, steps_per_epoch)
|
||||
super(LambdaLR, self).__init__(lr, max_epoch, steps_per_epoch)
|
||||
|
||||
def get_lr(self):
|
||||
warmup_steps = self.warmup.get_warmup_steps()
|
||||
|
||||
lr_each_step = []
|
||||
for i in range(self.total_steps):
|
||||
if i < warmup_steps:
|
||||
lr = self.warmup.get_lr(i+1)
|
||||
else:
|
||||
cur_ep = i // self.steps_per_epoch
|
||||
lr = self.base_lr * self.lr_lambda(cur_ep)
|
||||
lr_each_step.append(lr)
|
||||
|
||||
return np.array(lr_each_step).astype(np.float32)
|
||||
|
||||
|
||||
class MultiplicativeLR(_LRScheduler):
|
||||
"""
|
||||
Multiplicative learning rate scheduler
|
||||
|
||||
Multiply the learning rate by the factor given in the specified function.
|
||||
|
||||
Args:
|
||||
lr (float): Initial learning rate which is the lower boundary in the cycle.
|
||||
steps_per_epoch (int): The number of steps per epoch to train for.
|
||||
max_epoch (int): The number of epochs to train for.
|
||||
lr_lambda (func. or list): A function which computes a multiplicative factor given an integer parameter epoch.
|
||||
warmup_epochs (int, optional): The number of epochs to Warmup. Default: 0
|
||||
|
||||
Outputs:
|
||||
numpy.ndarray, shape=(1, steps_per_epoch*max_epoch)
|
||||
|
||||
Example:
|
||||
>>> lmbda = lambda epoch: 0.95
|
||||
>>> scheduler = MultiplicativeLR(lr=0.1, lr_lambda=lambda1, steps_per_epoch=5000, max_epoch=90)
|
||||
>>> lr = scheduler.get_lr()
|
||||
"""
|
||||
def __init__(self, lr, lr_lambda, steps_per_epoch, max_epoch, warmup_epochs=0):
|
||||
self.lr_lambda = lr_lambda
|
||||
self.warmup = _LinearWarmUp(lr, warmup_epochs, steps_per_epoch)
|
||||
super(MultiplicativeLR, self).__init__(lr, max_epoch, steps_per_epoch)
|
||||
|
||||
def get_lr(self):
|
||||
warmup_steps = self.warmup.get_warmup_steps()
|
||||
|
||||
lr_each_step = []
|
||||
current_lr = self.base_lr
|
||||
for i in range(self.total_steps):
|
||||
if i < warmup_steps:
|
||||
lr = self.warmup.get_lr(i+1)
|
||||
else:
|
||||
cur_ep = i // self.steps_per_epoch
|
||||
if i % self.steps_per_epoch == 0 and cur_ep > 0:
|
||||
current_lr = current_lr * self.lr_lambda(cur_ep)
|
||||
|
||||
lr = current_lr
|
||||
|
||||
lr_each_step.append(lr)
|
||||
|
||||
return np.array(lr_each_step).astype(np.float32)
|
||||
|
||||
|
||||
class StepLR(_LRScheduler):
|
||||
"""
|
||||
Step learning rate scheduler
|
||||
|
||||
Decays the learning rate by gamma every epoch_size epochs.
|
||||
|
||||
Args:
|
||||
lr (float): Initial learning rate which is the lower boundary in the cycle.
|
||||
epoch_size (int): Period of learning rate decay.
|
||||
gamma (float): Multiplicative factor of learning rate decay.
|
||||
steps_per_epoch (int): The number of steps per epoch to train for.
|
||||
max_epoch (int): The number of epochs to train for.
|
||||
warmup_epochs (int, optional): The number of epochs to Warmup. Default: 0
|
||||
|
||||
Outputs:
|
||||
numpy.ndarray, shape=(1, steps_per_epoch*max_epoch)
|
||||
|
||||
Example:
|
||||
>>> # Assuming optimizer uses lr = 0.05 for all groups
|
||||
>>> # lr = 0.05 if epoch < 30
|
||||
>>> # lr = 0.005 if 30 <= epoch < 60
|
||||
>>> # lr = 0.0005 if 60 <= epoch < 90
|
||||
>>> # ...
|
||||
>>> scheduler = StepLR(lr=0.1, epoch_size=30, gamma=0.1, steps_per_epoch=5000, max_epoch=90)
|
||||
>>> lr = scheduler.get_lr()
|
||||
"""
|
||||
|
||||
def __init__(self, lr, epoch_size, gamma, steps_per_epoch, max_epoch, warmup_epochs=0):
|
||||
self.epoch_size = epoch_size
|
||||
self.gamma = gamma
|
||||
self.warmup = _LinearWarmUp(lr, warmup_epochs, steps_per_epoch)
|
||||
super(StepLR, self).__init__(lr, max_epoch, steps_per_epoch)
|
||||
|
||||
def get_lr(self):
|
||||
warmup_steps = self.warmup.get_warmup_steps()
|
||||
|
||||
lr_each_step = []
|
||||
for i in range(self.total_steps):
|
||||
if i < warmup_steps:
|
||||
lr = self.warmup.get_lr(i+1)
|
||||
else:
|
||||
cur_ep = i // self.steps_per_epoch
|
||||
lr = self.base_lr * self.gamma**(cur_ep // self.epoch_size)
|
||||
|
||||
lr_each_step.append(lr)
|
||||
|
||||
return np.array(lr_each_step).astype(np.float32)
|
||||
|
||||
|
||||
class POLYLR(_LRScheduler):
|
||||
"""
|
||||
Poly learning rate scheduler
|
||||
"""
|
||||
def __init__(self, lr, steps_per_epoch, max_epoch, end_lr, power):
|
||||
self.end_lr = end_lr
|
||||
self.power = power
|
||||
self.max_epoch = max_epoch
|
||||
self.lr = lr
|
||||
self.end_lr = end_lr
|
||||
super(POLYLR, self).__init__(lr, max_epoch, steps_per_epoch)
|
||||
|
||||
def get_lr(self):
|
||||
lr_each_step = []
|
||||
total_steps = self.steps_per_epoch * self.max_epoch
|
||||
for i in range(total_steps):
|
||||
step_ = min(i, total_steps)
|
||||
lr_each_step.append((self.lr - self.end_lr) * ((1.0 - step_ / total_steps) ** self.power) + self.end_lr)
|
||||
print("lr_each_step:", lr_each_step[-1])
|
||||
return np.array(lr_each_step).astype(np.float32)
|
||||
|
||||
class MultiStepLR(_LRScheduler):
|
||||
"""
|
||||
Multi-step learning rate scheduler
|
||||
|
||||
Decays the learning rate by gamma once the number of epoch reaches one of the milestones.
|
||||
|
||||
Args:
|
||||
lr (float): Initial learning rate which is the lower boundary in the cycle.
|
||||
milestones (list): List of epoch indices. Must be increasing.
|
||||
gamma (float): Multiplicative factor of learning rate decay.
|
||||
steps_per_epoch (int): The number of steps per epoch to train for.
|
||||
max_epoch (int): The number of epochs to train for.
|
||||
warmup_epochs (int, optional): The number of epochs to Warmup. Default: 0
|
||||
|
||||
Outputs:
|
||||
numpy.ndarray, shape=(1, steps_per_epoch*max_epoch)
|
||||
|
||||
Example:
|
||||
>>> # Assuming optimizer uses lr = 0.05 for all groups
|
||||
>>> # lr = 0.05 if epoch < 30
|
||||
>>> # lr = 0.005 if 30 <= epoch < 80
|
||||
>>> # lr = 0.0005 if epoch >= 80
|
||||
>>> scheduler = MultiStepLR(lr=0.1, milestones=[30,80], gamma=0.1, steps_per_epoch=5000, max_epoch=90)
|
||||
>>> lr = scheduler.get_lr()
|
||||
"""
|
||||
|
||||
def __init__(self, lr, milestones, gamma, steps_per_epoch, max_epoch, warmup_epochs=0):
|
||||
self.milestones = Counter(milestones)
|
||||
self.gamma = gamma
|
||||
self.warmup = _LinearWarmUp(lr, warmup_epochs, steps_per_epoch)
|
||||
super(MultiStepLR, self).__init__(lr, max_epoch, steps_per_epoch)
|
||||
|
||||
def get_lr(self):
|
||||
warmup_steps = self.warmup.get_warmup_steps()
|
||||
|
||||
lr_each_step = []
|
||||
current_lr = self.base_lr
|
||||
for i in range(self.total_steps):
|
||||
if i < warmup_steps:
|
||||
lr = self.warmup.get_lr(i+1)
|
||||
else:
|
||||
cur_ep = i // self.steps_per_epoch
|
||||
if i % self.steps_per_epoch == 0 and cur_ep in self.milestones:
|
||||
current_lr = current_lr * self.gamma
|
||||
lr = current_lr
|
||||
|
||||
lr_each_step.append(lr)
|
||||
|
||||
return np.array(lr_each_step).astype(np.float32)
|
||||
|
||||
|
||||
class ExponentialLR(_LRScheduler):
|
||||
"""
|
||||
Exponential learning rate scheduler
|
||||
|
||||
Decays the learning rate of each parameter group by gamma every epoch.
|
||||
|
||||
Args:
|
||||
lr (float): Initial learning rate which is the lower boundary in the cycle.
|
||||
gamma (float): Multiplicative factor of learning rate decay.
|
||||
steps_per_epoch (int): The number of steps per epoch to train for.
|
||||
max_epoch (int): The number of epochs to train for.
|
||||
warmup_epochs (int, optional): The number of epochs to Warmup. Default: 0
|
||||
|
||||
Outputs:
|
||||
numpy.ndarray, shape=(1, steps_per_epoch*max_epoch)
|
||||
|
||||
Example:
|
||||
>>> scheduler = ExponentialLR(lr=0.1, gamma=0.1, steps_per_epoch=5000, max_epoch=90)
|
||||
>>> lr = scheduler.get_lr()
|
||||
"""
|
||||
|
||||
def __init__(self, lr, gamma, steps_per_epoch, max_epoch, warmup_epochs=0):
|
||||
self.gamma = gamma
|
||||
self.warmup = _LinearWarmUp(lr, warmup_epochs, steps_per_epoch)
|
||||
super(ExponentialLR, self).__init__(lr, max_epoch, steps_per_epoch)
|
||||
|
||||
def get_lr(self):
|
||||
warmup_steps = self.warmup.get_warmup_steps()
|
||||
|
||||
lr_each_step = []
|
||||
current_lr = self.base_lr
|
||||
for i in range(self.total_steps):
|
||||
if i < warmup_steps:
|
||||
lr = self.warmup.get_lr(i+1)
|
||||
else:
|
||||
if i % self.steps_per_epoch == 0 and i > 0:
|
||||
current_lr = current_lr * self.gamma
|
||||
lr = current_lr
|
||||
|
||||
lr_each_step.append(lr)
|
||||
|
||||
return np.array(lr_each_step).astype(np.float32)
|
||||
|
||||
|
||||
class CosineAnnealingLR(_LRScheduler):
|
||||
r"""
|
||||
Cosine annealing scheduler
|
||||
|
||||
Set the learning rate using a cosine annealing schedule, where :math:`\eta_{max}`
|
||||
is set to the initial lr and :math:`t_{cur}` is the number of epochs since the
|
||||
last restart in SGDR:
|
||||
|
||||
.. math::
|
||||
\begin{aligned}
|
||||
\eta_t & = \eta_{min} + \frac{1}{2}(\eta_{max} - \eta_{min})\left(1
|
||||
+ \cos\left(\frac{t_{cur}}{t_{max}}\pi\right)\right),
|
||||
& t_{cur} \neq (2k+1)t_{max}; \\
|
||||
\eta_{t+1} & = \eta_{t} + \frac{1}{2}(\eta_{max} - \eta_{min})
|
||||
\left(1 - \cos\left(\frac{1}{t_{max}}\pi\right)\right),
|
||||
& t_{cur} = (2k+1)t_{max}.
|
||||
\end{aligned}
|
||||
|
||||
It has been proposed in `SGDR: Stochastic Gradient Descent with Warm Restarts`_.
|
||||
.. _SGDR\: Stochastic Gradient Descent with Warm Restarts:
|
||||
https://arxiv.org/abs/1608.03983
|
||||
|
||||
Note:
|
||||
This only implements the cosine annealing part of SGDR, and not the restarts.
|
||||
|
||||
Args:
|
||||
lr (float): Initial learning rate which is the lower boundary in the cycle.
|
||||
t_max (int): Maximum number of iterations.
|
||||
steps_per_epoch (int): The number of steps per epoch to train for.
|
||||
max_epoch (int): The number of epochs to train for.
|
||||
warmup_epochs (int, optional): The number of epochs to Warmup. Default: 0
|
||||
eta_min (float, optional): Minimum learning rate. Default: 0.
|
||||
|
||||
Outputs:
|
||||
numpy.ndarray, shape=(1, steps_per_epoch*max_epoch)
|
||||
|
||||
Example:
|
||||
>>> scheduler = CosineAnnealingLR(lr=0.1, t_max=120, steps_per_epoch=5000, max_epoch=90)
|
||||
>>> lr = scheduler.get_lr()
|
||||
"""
|
||||
|
||||
def __init__(self, lr, t_max, steps_per_epoch, max_epoch, warmup_epochs=0, eta_min=0):
|
||||
self.t_max = t_max
|
||||
self.eta_min = eta_min
|
||||
self.warmup = _LinearWarmUp(lr, warmup_epochs, steps_per_epoch)
|
||||
super(CosineAnnealingLR, self).__init__(lr, max_epoch, steps_per_epoch)
|
||||
|
||||
def get_lr(self):
|
||||
warmup_steps = self.warmup.get_warmup_steps()
|
||||
|
||||
lr_each_step = []
|
||||
current_lr = self.base_lr
|
||||
for i in range(self.total_steps):
|
||||
if i < warmup_steps:
|
||||
lr = self.warmup.get_lr(i+1)
|
||||
else:
|
||||
cur_ep = i // self.steps_per_epoch
|
||||
if i % self.steps_per_epoch == 0 and i > 0:
|
||||
current_lr = self.eta_min + (self.base_lr - self.eta_min) * \
|
||||
(1. + math.cos(math.pi*cur_ep / self.t_max)) / 2
|
||||
|
||||
lr = current_lr
|
||||
|
||||
lr_each_step.append(lr)
|
||||
|
||||
return np.array(lr_each_step).astype(np.float32)
|
||||
|
||||
|
||||
class CyclicLR(_LRScheduler):
|
||||
r"""
|
||||
Cyclical learning rate scheduler
|
||||
|
||||
Sets the learning rate according to cyclical learning rate policy (CLR).
|
||||
The policy cycles the learning rate between two boundaries with a constant
|
||||
frequency, as detailed in the paper `Cyclical Learning Rates for Training
|
||||
Neural Networks`_. The distance between the two boundaries can be scaled on
|
||||
a per-iteration or per-cycle basis.
|
||||
|
||||
Cyclical learning rate policy changes the learning rate after every batch.
|
||||
|
||||
This class has three built-in policies, as put forth in the paper:
|
||||
|
||||
* "triangular": A basic triangular cycle without amplitude scaling.
|
||||
* "triangular2": A basic triangular cycle that scales initial amplitude by half each cycle.
|
||||
* "exp_range": A cycle that scales initial amplitude by :math:`\text{gamma}^{\text{cycle iterations}}`
|
||||
at each cycle iteration.
|
||||
|
||||
This implementation was adapted from the github repo: `bckenstler/CLR`_
|
||||
.. _Cyclical Learning Rates for Training Neural Networks: https://arxiv.org/abs/1506.01186
|
||||
.. _bckenstler/CLR: https://github.com/bckenstler/CLR
|
||||
|
||||
Args:
|
||||
lr (float): Initial learning rate which is the lower boundary in the cycle.
|
||||
max_lr (float): Upper learning rate boundaries in the cycle.
|
||||
steps_per_epoch (int): The number of steps per epoch to train for.
|
||||
max_epoch (int): The number of epochs to train for.
|
||||
step_size_up (int): Number of training iterations in the
|
||||
increasing half of a cycle.
|
||||
Default: 2000
|
||||
step_size_down (int): Number of training iterations in the
|
||||
decreasing half of a cycle. If step_size_down is None,
|
||||
it is set to step_size_up.
|
||||
Default: None
|
||||
mode (str): One of {triangular, triangular2, exp_range}.
|
||||
Values correspond to policies detailed above.
|
||||
If scale_fn is not None, this argument is ignored.
|
||||
Default: 'triangular'
|
||||
gamma (float): Constant in 'exp_range' scaling function: gamma**(cycle iterations)
|
||||
Default: 1.0
|
||||
scale_fn (function): Custom scaling policy defined by a single argument lambda function, where
|
||||
0 <= scale_fn(x) <= 1 for all x >= 0. If specified, then 'mode' is ignored.
|
||||
Default: None
|
||||
scale_mode (str): {'cycle', 'iterations'}. Defines whether scale_fn is evaluated on
|
||||
cycle number or cycle iterations (training iterations since start of cycle).
|
||||
Default: 'cycle'
|
||||
warmup_epochs (int, optional): The number of epochs to Warmup. Default: 0
|
||||
|
||||
Outputs:
|
||||
numpy.ndarray, shape=(1, steps_per_epoch*max_epoch)
|
||||
|
||||
Example:
|
||||
>>> scheduler = CyclicLR(lr=0.1, max_lr=1.0, steps_per_epoch=5000, max_epoch=90)
|
||||
>>> lr = scheduler.get_lr()
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
lr,
|
||||
max_lr,
|
||||
steps_per_epoch,
|
||||
max_epoch,
|
||||
step_size_up=2000,
|
||||
step_size_down=None,
|
||||
mode='triangular',
|
||||
gamma=1.,
|
||||
scale_fn=None,
|
||||
scale_mode='cycle',
|
||||
warmup_epochs=0):
|
||||
|
||||
self.max_lr = max_lr
|
||||
|
||||
step_size_up = float(step_size_up)
|
||||
step_size_down = float(step_size_down) if step_size_down is not None else step_size_up
|
||||
self.total_size = step_size_up + step_size_down
|
||||
self.step_ratio = step_size_up / self.total_size
|
||||
|
||||
if mode not in ['triangular', 'triangular2', 'exp_range'] \
|
||||
and scale_fn is None:
|
||||
raise ValueError('mode is invalid and scale_fn is None')
|
||||
|
||||
self.mode = mode
|
||||
self.gamma = gamma
|
||||
|
||||
if scale_fn is None:
|
||||
if self.mode == 'triangular':
|
||||
self.scale_fn = self._triangular_scale_fn
|
||||
self.scale_mode = 'cycle'
|
||||
elif self.mode == 'triangular2':
|
||||
self.scale_fn = self._triangular2_scale_fn
|
||||
self.scale_mode = 'cycle'
|
||||
elif self.mode == 'exp_range':
|
||||
self.scale_fn = self._exp_range_scale_fn
|
||||
self.scale_mode = 'iterations'
|
||||
else:
|
||||
self.scale_fn = scale_fn
|
||||
self.scale_mode = scale_mode
|
||||
|
||||
self.warmup = _LinearWarmUp(lr, warmup_epochs, steps_per_epoch)
|
||||
super(CyclicLR, self).__init__(lr, max_epoch, steps_per_epoch)
|
||||
|
||||
def _triangular_scale_fn(self):
|
||||
return 1.
|
||||
|
||||
def _triangular2_scale_fn(self, x):
|
||||
return 1 / (2. ** (x - 1))
|
||||
|
||||
def _exp_range_scale_fn(self, x):
|
||||
return self.gamma**(x)
|
||||
|
||||
def get_lr(self):
|
||||
warmup_steps = self.warmup.get_warmup_steps()
|
||||
|
||||
lr_each_step = []
|
||||
for i in range(self.total_steps):
|
||||
if i < warmup_steps:
|
||||
lr = self.warmup.get_lr(i+1)
|
||||
else:
|
||||
# Calculates the learning rate at batch index.
|
||||
cycle = math.floor(1 + i / self.total_size)
|
||||
x = 1. + i / self.total_size - cycle
|
||||
if x <= self.step_ratio:
|
||||
scale_factor = x / self.step_ratio
|
||||
else:
|
||||
scale_factor = (x - 1) / (self.step_ratio - 1)
|
||||
|
||||
base_height = (self.max_lr - self.base_lr) * scale_factor
|
||||
if self.scale_mode == 'cycle':
|
||||
lr = self.base_lr + base_height * self.scale_fn(cycle)
|
||||
elif self.mode == 'triangular':
|
||||
lr = self.base_lr + base_height * self.scale_fn()
|
||||
else:
|
||||
lr = self.base_lr + base_height * self.scale_fn(i)
|
||||
|
||||
lr_each_step.append(lr)
|
||||
|
||||
return np.array(lr_each_step).astype(np.float32)
|
||||
|
||||
|
||||
class CosineAnnealingWarmRestarts(_LRScheduler):
|
||||
r"""
|
||||
Cosine annealing scheduler with warm restarts
|
||||
|
||||
Set the learning rate using a cosine annealing schedule, where
|
||||
:math:`\eta_{max}` is set to the initial lr, :math:`t_{cur}` is the
|
||||
number of epochs since the last restart and :math:`t_{i}` is the number
|
||||
of epochs between two warm restarts in SGDR:
|
||||
|
||||
.. math::
|
||||
\eta_t = \eta_{min} + \frac{1}{2}(\eta_{max} - \eta_{min})\left(1 +
|
||||
\cos\left(\frac{t_{cur}}{t_{i}}\pi\right)\right)
|
||||
|
||||
When :math:`t_{cur}=t_{i}`, set :math:`\eta_t = \eta_{min}`.
|
||||
When :math:`t_{cur}=0` after restart, set :math:`\eta_t=\eta_{max}`.
|
||||
|
||||
It has been proposed in `SGDR: Stochastic Gradient Descent with Warm Restarts`_.
|
||||
.. _SGDR\: Stochastic Gradient Descent with Warm Restarts:
|
||||
https://arxiv.org/abs/1608.03983
|
||||
|
||||
Args:
|
||||
|
||||
warmup_epochs (int): The number of epochs to Warmup.
|
||||
Default: 0
|
||||
|
||||
Args:
|
||||
lr (float): Initial learning rate which is the lower boundary in the cycle.
|
||||
steps_per_epoch (int): The number of steps per epoch to train for.
|
||||
max_epoch (int): The number of epochs to train for.
|
||||
t_0 (int): Number of iterations for the first restart.
|
||||
t_mult (int, optional): A factor increases :math:`t_{i}` after a restart. Default: 1.
|
||||
eta_min (float, optional): Minimum learning rate. Default: 0.
|
||||
warmup_epochs (int, optional): The number of epochs to Warmup. Default: 0
|
||||
|
||||
Outputs:
|
||||
numpy.ndarray, shape=(1, steps_per_epoch*max_epoch)
|
||||
|
||||
Example:
|
||||
>>> scheduler = CosineAnnealingWarmRestarts(lr=0.1, steps_per_epoch=5000, max_epoch=90, t_0=2)
|
||||
>>> lr = scheduler.get_lr()
|
||||
"""
|
||||
|
||||
def __init__(self, lr, steps_per_epoch, max_epoch, t_0, t_mult=1, eta_min=0, warmup_epochs=0):
|
||||
if t_0 <= 0 or not isinstance(t_0, int):
|
||||
raise ValueError("Expected positive integer t_0, but got {}".format(t_0))
|
||||
if t_mult < 1 or not isinstance(t_mult, int):
|
||||
raise ValueError("Expected integer t_mult >= 1, but got {}".format(t_mult))
|
||||
self.t_0 = t_0
|
||||
self.t_i = t_0
|
||||
self.t_mult = t_mult
|
||||
self.eta_min = eta_min
|
||||
self.t_cur = 0
|
||||
|
||||
self.warmup = _LinearWarmUp(lr, warmup_epochs, steps_per_epoch)
|
||||
super(CosineAnnealingWarmRestarts, self).__init__(lr, max_epoch, steps_per_epoch)
|
||||
|
||||
def get_lr(self):
|
||||
warmup_steps = self.warmup.get_warmup_steps()
|
||||
|
||||
lr_each_step = []
|
||||
for i in range(self.total_steps):
|
||||
if i < warmup_steps:
|
||||
lr = self.warmup.get_lr(i+1)
|
||||
else:
|
||||
if i % self.steps_per_epoch == 0 and i > 0:
|
||||
self.t_cur += 1
|
||||
if self.t_cur >= self.t_i:
|
||||
self.t_cur = self.t_cur - self.t_i
|
||||
self.t_i = self.t_i * self.t_mult
|
||||
|
||||
lr = self.eta_min + (self.base_lr - self.eta_min) * \
|
||||
(1 + math.cos(math.pi * self.t_cur / self.t_i)) / 2
|
||||
|
||||
lr_each_step.append(lr)
|
||||
|
||||
return np.array(lr_each_step).astype(np.float32)
|
||||
|
||||
|
||||
class OneCycleLR(_LRScheduler):
|
||||
r"""
|
||||
One cycle learning rate scheduler
|
||||
|
||||
Sets the learning rate of each parameter group according to the
|
||||
1cycle learning rate policy. The 1cycle policy anneals the learning
|
||||
rate from an initial learning rate to some maximum learning rate and then
|
||||
from that maximum learning rate to some minimum learning rate much lower
|
||||
than the initial learning rate.
|
||||
This policy was initially described in the paper `Super-Convergence:
|
||||
Very Fast Training of Neural Networks Using Large Learning Rates`_.
|
||||
|
||||
The 1cycle learning rate policy changes the learning rate after every batch.
|
||||
This scheduler is not chainable.
|
||||
|
||||
.. _Super-Convergence\: Very Fast Training of Neural Networks Using Large Learning Rates:
|
||||
https://arxiv.org/abs/1708.07120
|
||||
|
||||
Args:
|
||||
lr (float): Initial learning rate which is the lower boundary in the cycle.
|
||||
steps_per_epoch (int): The number of steps per epoch to train for.
|
||||
max_epoch (int): The number of epochs to train for.
|
||||
pct_start (float): The percentage of the cycle (in number of steps) spent
|
||||
increasing the learning rate.
|
||||
Default: 0.3
|
||||
anneal_strategy (str): {'cos', 'linear'}
|
||||
Specifies the annealing strategy: "cos" for cosine annealing, "linear" for
|
||||
linear annealing.
|
||||
Default: 'cos'
|
||||
div_factor (float): Determines the max learning rate via
|
||||
:math:`max_lr = lr * div_factor`
|
||||
Default: 25
|
||||
final_div_factor (float): Determines the minimum learning rate via
|
||||
:math:`min_lr = lr / final_div_factor`
|
||||
Default: 1e4
|
||||
warmup_epochs (int, optional): The number of epochs to Warmup. Default: 0
|
||||
|
||||
Outputs:
|
||||
numpy.ndarray, shape=(1, steps_per_epoch*max_epoch)
|
||||
|
||||
Example:
|
||||
>>> scheduler = OneCycleLR(lr=0.1, steps_per_epoch=5000, max_epoch=90)
|
||||
>>> lr = scheduler.get_lr()
|
||||
"""
|
||||
def __init__(self,
|
||||
lr,
|
||||
steps_per_epoch,
|
||||
max_epoch,
|
||||
pct_start=0.3,
|
||||
anneal_strategy='cos',
|
||||
div_factor=25.,
|
||||
final_div_factor=1e4,
|
||||
warmup_epochs=0):
|
||||
|
||||
self.warmup = _LinearWarmUp(lr, warmup_epochs, steps_per_epoch)
|
||||
super(OneCycleLR, self).__init__(lr, max_epoch, steps_per_epoch)
|
||||
|
||||
self.step_size_up = float(pct_start * self.total_steps) - 1
|
||||
self.step_size_down = float(self.total_steps - self.step_size_up) - 1
|
||||
|
||||
# Validate pct_start
|
||||
if pct_start < 0 or pct_start > 1 or not isinstance(pct_start, float):
|
||||
raise ValueError("Expected float between 0 and 1 pct_start, but got {}".format(pct_start))
|
||||
|
||||
# Validate anneal_strategy
|
||||
if anneal_strategy not in ['cos', 'linear']:
|
||||
raise ValueError("anneal_strategy must by one of 'cos' or 'linear', instead got {}".format(anneal_strategy))
|
||||
if anneal_strategy == 'cos':
|
||||
self.anneal_func = self._annealing_cos
|
||||
elif anneal_strategy == 'linear':
|
||||
self.anneal_func = self._annealing_linear
|
||||
|
||||
# Initialize learning rate variables
|
||||
self.max_lr = lr * div_factor
|
||||
self.min_lr = lr / final_div_factor
|
||||
|
||||
def _annealing_cos(self, start, end, pct):
|
||||
"Cosine anneal from `start` to `end` as pct goes from 0.0 to 1.0."
|
||||
cos_out = math.cos(math.pi * pct) + 1
|
||||
return end + (start - end) / 2.0 * cos_out
|
||||
|
||||
def _annealing_linear(self, start, end, pct):
|
||||
"Linearly anneal from `start` to `end` as pct goes from 0.0 to 1.0."
|
||||
return (end - start) * pct + start
|
||||
|
||||
def get_lr(self):
|
||||
warmup_steps = self.warmup.get_warmup_steps()
|
||||
|
||||
lr_each_step = []
|
||||
for i in range(self.total_steps):
|
||||
if i < warmup_steps:
|
||||
lr = self.warmup.get_lr(i+1)
|
||||
else:
|
||||
if i <= self.step_size_up:
|
||||
lr = self.anneal_func(self.base_lr, self.max_lr, i / self.step_size_up)
|
||||
|
||||
else:
|
||||
down_step_num = i - self.step_size_up
|
||||
lr = self.anneal_func(self.max_lr, self.min_lr, down_step_num / self.step_size_down)
|
||||
|
||||
lr_each_step.append(lr)
|
||||
|
||||
return np.array(lr_each_step).astype(np.float32)
|
||||
|
||||
def linear_warmup_lr(current_step, warmup_steps, base_lr, init_lr):
|
||||
"""
|
||||
Linear warmup learning rate scheduler
|
||||
"""
|
||||
lr_inc = (float(base_lr) - float(init_lr)) / float(warmup_steps)
|
||||
lr = float(init_lr) + lr_inc * current_step
|
||||
return lr
|
||||
|
||||
def warmup_step_lr(lr, lr_epochs, steps_per_epoch, warmup_epochs, max_epoch, gamma=0.1):
|
||||
"""
|
||||
Warmup step learning rate scheduler
|
||||
"""
|
||||
base_lr = lr
|
||||
warmup_init_lr = 0
|
||||
total_steps = int(max_epoch * steps_per_epoch)
|
||||
warmup_steps = int(warmup_epochs * steps_per_epoch)
|
||||
milestones = lr_epochs
|
||||
milestones_steps = []
|
||||
for milestone in milestones:
|
||||
milestones_step = milestone * steps_per_epoch
|
||||
milestones_steps.append(milestones_step)
|
||||
|
||||
lr_each_step = []
|
||||
lr = base_lr
|
||||
milestones_steps_counter = Counter(milestones_steps)
|
||||
for i in range(total_steps):
|
||||
if i < warmup_steps:
|
||||
lr = linear_warmup_lr(i + 1, warmup_steps, base_lr, warmup_init_lr)
|
||||
else:
|
||||
lr = lr * gamma**milestones_steps_counter[i]
|
||||
lr_each_step.append(lr)
|
||||
|
||||
return np.array(lr_each_step).astype(np.float32)
|
||||
|
||||
def multi_step_lr(lr, milestones, steps_per_epoch, max_epoch, gamma=0.1):
|
||||
return warmup_step_lr(lr, milestones, steps_per_epoch, 0, max_epoch, gamma=gamma)
|
||||
|
||||
def step_lr(lr, epoch_size, steps_per_epoch, max_epoch, gamma=0.1):
|
||||
lr_epochs = []
|
||||
for i in range(1, max_epoch):
|
||||
if i % epoch_size == 0:
|
||||
lr_epochs.append(i)
|
||||
return multi_step_lr(lr, lr_epochs, steps_per_epoch, max_epoch, gamma=gamma)
|
||||
|
||||
def warmup_cosine_annealing_lr(lr, steps_per_epoch, warmup_epochs, max_epoch, t_max, eta_min=0):
|
||||
"""
|
||||
Warmup cosine learning rate scheduler
|
||||
"""
|
||||
base_lr = lr
|
||||
warmup_init_lr = 0
|
||||
total_steps = int(max_epoch * steps_per_epoch)
|
||||
warmup_steps = int(warmup_epochs * steps_per_epoch)
|
||||
|
||||
lr_each_step = []
|
||||
for i in range(total_steps):
|
||||
last_epoch = i // steps_per_epoch
|
||||
if i < warmup_steps:
|
||||
lr = linear_warmup_lr(i + 1, warmup_steps, base_lr, warmup_init_lr)
|
||||
else:
|
||||
lr = eta_min + (base_lr - eta_min) * (1. + math.cos(math.pi*last_epoch / t_max)) / 2
|
||||
lr_each_step.append(lr)
|
||||
|
||||
return np.array(lr_each_step).astype(np.float32)
|
||||
|
||||
|
||||
def warmup_cosine_annealing_lr_v2(lr, steps_per_epoch, warmup_epochs, max_epoch, t_max, eta_min=0):
|
||||
"""
|
||||
Warmup cosine v2 learning rate scheduler
|
||||
"""
|
||||
base_lr = lr
|
||||
warmup_init_lr = 0
|
||||
total_steps = int(max_epoch * steps_per_epoch)
|
||||
warmup_steps = int(warmup_epochs * steps_per_epoch)
|
||||
|
||||
last_lr = 0
|
||||
last_epoch_v1 = 0
|
||||
|
||||
t_max_v2 = int(max_epoch*1/3)
|
||||
|
||||
lr_each_step = []
|
||||
for i in range(total_steps):
|
||||
last_epoch = i // steps_per_epoch
|
||||
if i < warmup_steps:
|
||||
lr = linear_warmup_lr(i + 1, warmup_steps, base_lr, warmup_init_lr)
|
||||
else:
|
||||
if i < total_steps*2/3:
|
||||
lr = eta_min + (base_lr - eta_min) * (1. + math.cos(math.pi*last_epoch / t_max)) / 2
|
||||
last_lr = lr
|
||||
last_epoch_v1 = last_epoch
|
||||
else:
|
||||
base_lr = last_lr
|
||||
last_epoch = last_epoch-last_epoch_v1
|
||||
lr = eta_min + (base_lr - eta_min) * (1. + math.cos(math.pi * last_epoch / t_max_v2)) / 2
|
||||
|
||||
lr_each_step.append(lr)
|
||||
return np.array(lr_each_step).astype(np.float32)
|
||||
|
||||
|
||||
def warmup_cosine_annealing_lr_sample(lr, steps_per_epoch, warmup_epochs, max_epoch, t_max, eta_min=0):
|
||||
"""
|
||||
Warmup cosine learning rate scheduler sampler
|
||||
"""
|
||||
start_sample_epoch = 60
|
||||
step_sample = 2
|
||||
tobe_sampled_epoch = 60
|
||||
end_sampled_epoch = start_sample_epoch + step_sample*tobe_sampled_epoch
|
||||
max_sampled_epoch = max_epoch+tobe_sampled_epoch
|
||||
t_max = max_sampled_epoch
|
||||
|
||||
base_lr = lr
|
||||
warmup_init_lr = 0
|
||||
total_steps = int(max_epoch * steps_per_epoch)
|
||||
total_sampled_steps = int(max_sampled_epoch * steps_per_epoch)
|
||||
warmup_steps = int(warmup_epochs * steps_per_epoch)
|
||||
|
||||
lr_each_step = []
|
||||
|
||||
for i in range(total_sampled_steps):
|
||||
last_epoch = i // steps_per_epoch
|
||||
if last_epoch in range(start_sample_epoch, end_sampled_epoch, step_sample):
|
||||
continue
|
||||
if i < warmup_steps:
|
||||
lr = linear_warmup_lr(i + 1, warmup_steps, base_lr, warmup_init_lr)
|
||||
else:
|
||||
lr = eta_min + (base_lr - eta_min) * (1. + math.cos(math.pi*last_epoch / t_max)) / 2
|
||||
lr_each_step.append(lr)
|
||||
|
||||
assert total_steps == len(lr_each_step)
|
||||
return np.array(lr_each_step).astype(np.float32)
|
|
@ -0,0 +1,203 @@
|
|||
# Copyright 2020 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
"""modified mobilenet_v2 backbone"""
|
||||
|
||||
import mindspore.nn as nn
|
||||
from mindspore.ops import operations as P
|
||||
from mindspore.ops.operations import TensorAdd
|
||||
from mindspore import Parameter
|
||||
from mindspore.common.initializer import initializer
|
||||
|
||||
from src.var_init import KaimingNormal
|
||||
|
||||
__all__ = ['MobileNetV2', 'mobilenet_v2', 'DepthWiseConv']
|
||||
|
||||
def _make_divisible(v, divisor, min_value=None):
|
||||
"""
|
||||
This function is taken from the original tf repo.
|
||||
It ensures that all layers have a channel number that is divisible by 8
|
||||
It can be seen here:
|
||||
https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py
|
||||
:param v:
|
||||
:param divisor:
|
||||
:param min_value:
|
||||
:return:
|
||||
"""
|
||||
if min_value is None:
|
||||
min_value = divisor
|
||||
new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)
|
||||
# Make sure that round down does not go down by more than 10%.
|
||||
if new_v < 0.9 * v:
|
||||
new_v += divisor
|
||||
return new_v
|
||||
|
||||
class DepthWiseConv(nn.Cell):
|
||||
"""
|
||||
Depthwise convolution
|
||||
"""
|
||||
def __init__(self, in_planes, kernel_size, stride, pad_mode, pad, channel_multiplier=1, has_bias=False):
|
||||
super(DepthWiseConv, self).__init__()
|
||||
self.has_bias = has_bias
|
||||
self.depthwise_conv = P.DepthwiseConv2dNative(channel_multiplier=channel_multiplier, kernel_size=kernel_size,
|
||||
stride=stride, pad_mode=pad_mode, pad=pad)
|
||||
self.bias_add = P.BiasAdd()
|
||||
|
||||
weight_shape = [channel_multiplier, in_planes, kernel_size, kernel_size]
|
||||
self.weight = Parameter(initializer(KaimingNormal(mode='fan_out'), weight_shape), name='weight')
|
||||
|
||||
if has_bias:
|
||||
bias_shape = [channel_multiplier * in_planes]
|
||||
self.bias = Parameter(initializer('zeros', bias_shape), name='bias')
|
||||
else:
|
||||
self.bias = None
|
||||
|
||||
def construct(self, x):
|
||||
output = self.depthwise_conv(x, self.weight)
|
||||
if self.has_bias:
|
||||
output = self.bias_add(output, self.bias)
|
||||
return output
|
||||
|
||||
|
||||
class ConvBNReLU(nn.Cell):
|
||||
"""
|
||||
Convolution and batchnorm and relu
|
||||
"""
|
||||
def __init__(self, in_planes, out_planes, kernel_size=3, stride=1, groups=1):
|
||||
padding = (kernel_size - 1) // 2
|
||||
super(ConvBNReLU, self).__init__()
|
||||
if groups == 1:
|
||||
conv = nn.Conv2d(in_planes, out_planes, kernel_size, stride, pad_mode="pad", padding=padding,
|
||||
has_bias=False)
|
||||
else:
|
||||
conv = DepthWiseConv(in_planes, kernel_size, stride, pad_mode="pad", pad=padding)
|
||||
|
||||
layers = [conv, nn.BatchNorm2d(out_planes).add_flags_recursive(fp32=True), nn.ReLU6()] #, momentum=0.9
|
||||
self.features = nn.SequentialCell(layers)
|
||||
self.in_planes = in_planes
|
||||
self.print = P.Print()
|
||||
|
||||
def construct(self, x):
|
||||
x = self.features(x)
|
||||
return x
|
||||
|
||||
|
||||
class InvertedResidual(nn.Cell):
|
||||
"""
|
||||
Inverted residual module
|
||||
"""
|
||||
def __init__(self, inp, oup, stride, expand_ratio):
|
||||
super(InvertedResidual, self).__init__()
|
||||
self.stride = stride
|
||||
assert stride in [1, 2]
|
||||
|
||||
hidden_dim = int(round(inp * expand_ratio))
|
||||
self.use_res_connect = self.stride == 1 and inp == oup
|
||||
|
||||
layers = []
|
||||
if expand_ratio != 1:
|
||||
# pw
|
||||
layers.append(ConvBNReLU(inp, hidden_dim, kernel_size=1))
|
||||
layers.extend([
|
||||
# dw
|
||||
ConvBNReLU(hidden_dim, hidden_dim, stride=stride, groups=hidden_dim),
|
||||
# pw-linear
|
||||
nn.Conv2d(hidden_dim, oup, kernel_size=1, stride=1, has_bias=False),
|
||||
nn.BatchNorm2d(oup).add_flags_recursive(fp32=True)
|
||||
])
|
||||
|
||||
self.conv = nn.SequentialCell(layers)
|
||||
self.add = TensorAdd()
|
||||
self.cast = P.Cast()
|
||||
|
||||
def construct(self, x):
|
||||
identity = x
|
||||
x = self.conv(x)
|
||||
if self.use_res_connect:
|
||||
return self.add(identity, x)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class MobileNetV2(nn.Cell):
|
||||
"""
|
||||
MobileNet V2 main class, backbone
|
||||
|
||||
Args:
|
||||
width_mult (float): Width multiplier - adjusts number of channels in each layer by this amount
|
||||
inverted_residual_setting: Network structure
|
||||
round_nearest (int): Round the number of channels in each layer to be a multiple of this number
|
||||
Set to 1 to turn off rounding
|
||||
"""
|
||||
def __init__(self, width_mult=1.0, inverted_residual_setting=None, round_nearest=8):
|
||||
super(MobileNetV2, self).__init__()
|
||||
block = InvertedResidual
|
||||
input_channel = 32
|
||||
if inverted_residual_setting is None:
|
||||
inverted_residual_setting = [
|
||||
# t, c, n, s
|
||||
[1, 16, 1, 1],
|
||||
[6, 24, 2, 2],
|
||||
[6, 32, 3, 2],
|
||||
[6, 64, 4, 2],
|
||||
[6, 96, 3, 1],
|
||||
[6, 160, 3, 2],
|
||||
[6, 320, 1, 1],
|
||||
]
|
||||
self.feat_id = [1, 2, 4, 6]
|
||||
self.feat_channel = []
|
||||
|
||||
# only check the first element, assuming user knows t,c,n,s are required
|
||||
if inverted_residual_setting is None or len(inverted_residual_setting[0]) != 4:
|
||||
raise ValueError("inverted_residual_setting should be non-empty "
|
||||
"or a 4-element list, got {}".format(inverted_residual_setting))
|
||||
|
||||
# building first layer
|
||||
input_channel = _make_divisible(input_channel * width_mult, round_nearest)
|
||||
features = [ConvBNReLU(3, input_channel, stride=2)]
|
||||
|
||||
for index, (t, c, n, s) in enumerate(inverted_residual_setting):
|
||||
output_channel = _make_divisible(c * width_mult, round_nearest)
|
||||
for i in range(n):
|
||||
stride = s if i == 0 else 1
|
||||
features.append(block(input_channel, output_channel, stride, expand_ratio=t))
|
||||
input_channel = output_channel
|
||||
|
||||
if index == 1:
|
||||
self.need_fp1 = nn.SequentialCell(features)
|
||||
self.feat_channel.append(output_channel)
|
||||
features = []
|
||||
elif index == 2:
|
||||
self.need_fp2 = nn.SequentialCell(features)
|
||||
self.feat_channel.append(output_channel)
|
||||
features = []
|
||||
elif index == 4:
|
||||
self.need_fp3 = nn.SequentialCell(features)
|
||||
self.feat_channel.append(output_channel)
|
||||
features = []
|
||||
elif index == 6:
|
||||
self.need_fp4 = nn.SequentialCell(features)
|
||||
self.feat_channel.append(output_channel)
|
||||
features = []
|
||||
|
||||
|
||||
def construct(self, x):
|
||||
x1 = self.need_fp1(x)
|
||||
x2 = self.need_fp2(x1)
|
||||
x3 = self.need_fp3(x2)
|
||||
x4 = self.need_fp4(x3)
|
||||
return x1, x2, x3, x4
|
||||
|
||||
def mobilenet_v2(**kwargs):
|
||||
return MobileNetV2(**kwargs)
|
|
@ -0,0 +1,247 @@
|
|||
# Copyright 2020 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
"""auxiliary functions for train, to print and preload"""
|
||||
|
||||
import math
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
import numpy as np
|
||||
|
||||
from mindspore.train.serialization import load_checkpoint
|
||||
import mindspore.nn as nn
|
||||
|
||||
from src.mobile_v2 import DepthWiseConv
|
||||
|
||||
def load_backbone(net, ckpt_path, args):
|
||||
"""
|
||||
Load backbone
|
||||
"""
|
||||
param_dict = load_checkpoint(ckpt_path)
|
||||
centerface_backbone_prefix = 'base'
|
||||
mobilev2_backbone_prefix = 'network.backbone'
|
||||
find_param = []
|
||||
not_found_param = []
|
||||
|
||||
def replace_names(name, replace_name, replace_idx):
|
||||
names = name.split('.')
|
||||
if len(names) < 4:
|
||||
raise "centerface_backbone_prefix name too short"
|
||||
tmp = names[2] + '.' + names[3]
|
||||
if replace_name != tmp:
|
||||
replace_name = tmp
|
||||
replace_idx += 1
|
||||
name = name.replace(replace_name, 'features' + '.' + str(replace_idx))
|
||||
return name, replace_name, replace_idx
|
||||
|
||||
replace_name = 'need_fp1.0'
|
||||
replace_idx = 0
|
||||
for name, cell in net.cells_and_names():
|
||||
if name.startswith(centerface_backbone_prefix):
|
||||
name = name.replace(centerface_backbone_prefix, mobilev2_backbone_prefix)
|
||||
if isinstance(cell, (nn.Conv2d, nn.Dense, DepthWiseConv)):
|
||||
name, replace_name, replace_idx = replace_names(name, replace_name, replace_idx)
|
||||
mobilev2_weight = '{}.weight'.format(name)
|
||||
mobilev2_bias = '{}.bias'.format(name)
|
||||
if mobilev2_weight in param_dict:
|
||||
cell.weight.set_data(param_dict[mobilev2_weight].data)
|
||||
find_param.append(mobilev2_weight)
|
||||
else:
|
||||
not_found_param.append(mobilev2_weight)
|
||||
if mobilev2_bias in param_dict:
|
||||
cell.bias.set_data(param_dict[mobilev2_bias].data)
|
||||
find_param.append(mobilev2_bias)
|
||||
else:
|
||||
not_found_param.append(mobilev2_bias)
|
||||
elif isinstance(cell, (nn.BatchNorm2d, nn.BatchNorm1d)):
|
||||
name, replace_name, replace_idx = replace_names(name, replace_name, replace_idx)
|
||||
mobilev2_moving_mean = '{}.moving_mean'.format(name)
|
||||
mobilev2_moving_variance = '{}.moving_variance'.format(name)
|
||||
mobilev2_gamma = '{}.gamma'.format(name)
|
||||
mobilev2_beta = '{}.beta'.format(name)
|
||||
if mobilev2_moving_mean in param_dict:
|
||||
cell.moving_mean.set_data(param_dict[mobilev2_moving_mean].data)
|
||||
find_param.append(mobilev2_moving_mean)
|
||||
else:
|
||||
not_found_param.append(mobilev2_moving_mean)
|
||||
if mobilev2_moving_variance in param_dict:
|
||||
cell.moving_variance.set_data(param_dict[mobilev2_moving_variance].data)
|
||||
find_param.append(mobilev2_moving_variance)
|
||||
else:
|
||||
not_found_param.append(mobilev2_moving_variance)
|
||||
if mobilev2_gamma in param_dict:
|
||||
cell.gamma.set_data(param_dict[mobilev2_gamma].data)
|
||||
find_param.append(mobilev2_gamma)
|
||||
else:
|
||||
not_found_param.append(mobilev2_gamma)
|
||||
if mobilev2_beta in param_dict:
|
||||
cell.beta.set_data(param_dict[mobilev2_beta].data)
|
||||
find_param.append(mobilev2_beta)
|
||||
else:
|
||||
not_found_param.append(mobilev2_beta)
|
||||
|
||||
args.logger.info('================found_param {}========='.format(len(find_param)))
|
||||
args.logger.info(find_param)
|
||||
args.logger.info('================not_found_param {}========='.format(len(not_found_param)))
|
||||
args.logger.info(not_found_param)
|
||||
args.logger.info('=====load {} successfully ====='.format(ckpt_path))
|
||||
|
||||
return net
|
||||
|
||||
def get_param_groups(network):
|
||||
"""
|
||||
Get param groups
|
||||
"""
|
||||
decay_params = []
|
||||
no_decay_params = []
|
||||
for x in network.trainable_params():
|
||||
parameter_name = x.name
|
||||
if parameter_name.endswith('.bias'):
|
||||
# all bias not using weight decay
|
||||
# print('no decay:{}'.format(parameter_name))
|
||||
no_decay_params.append(x)
|
||||
elif parameter_name.endswith('.gamma'):
|
||||
# bn weight bias not using weight decay, be carefully for now x not include BN
|
||||
# print('no decay:{}'.format(parameter_name))
|
||||
no_decay_params.append(x)
|
||||
elif parameter_name.endswith('.beta'):
|
||||
# bn weight bias not using weight decay, be carefully for now x not include BN
|
||||
# print('no decay:{}'.format(parameter_name))
|
||||
no_decay_params.append(x)
|
||||
else:
|
||||
decay_params.append(x)
|
||||
|
||||
return [{'params': no_decay_params, 'weight_decay': 0.0}, {'params': decay_params}]
|
||||
|
||||
|
||||
class DistributedSampler():
|
||||
"""
|
||||
Distributed sampler
|
||||
"""
|
||||
def __init__(self, dataset, rank, group_size, shuffle=True, seed=0):
|
||||
self.dataset = dataset
|
||||
self.rank = rank
|
||||
self.group_size = group_size
|
||||
self.dataset_length = len(self.dataset)
|
||||
self.num_samples = int(math.ceil(self.dataset_length * 1.0 / self.group_size))
|
||||
self.total_size = self.num_samples * self.group_size
|
||||
self.shuffle = shuffle
|
||||
self.seed = seed
|
||||
|
||||
def __iter__(self):
|
||||
if self.shuffle:
|
||||
self.seed = (self.seed + 1) & 0xffffffff
|
||||
np.random.seed(self.seed)
|
||||
indices = np.random.permutation(self.dataset_length).tolist()
|
||||
else:
|
||||
indices = list(range(len(self.dataset.classes)))
|
||||
|
||||
indices += indices[:(self.total_size - len(indices))]
|
||||
assert len(indices) == self.total_size
|
||||
|
||||
indices = indices[self.rank::self.group_size]
|
||||
assert len(indices) == self.num_samples
|
||||
|
||||
return iter(indices)
|
||||
|
||||
def __len__(self):
|
||||
return self.num_samples
|
||||
|
||||
|
||||
class AverageMeter():
|
||||
"""Computes and stores the average and current value"""
|
||||
|
||||
def __init__(self, name, fmt=':f', tb_writer=None):
|
||||
self.name = name
|
||||
self.fmt = fmt
|
||||
self.reset()
|
||||
self.tb_writer = tb_writer
|
||||
self.cur_step = 1
|
||||
|
||||
def reset(self):
|
||||
self.val = 0
|
||||
self.avg = 0
|
||||
self.sum = 0
|
||||
self.count = 0
|
||||
|
||||
def update(self, val, n=1):
|
||||
self.val = val
|
||||
self.sum += val * n
|
||||
self.count += n
|
||||
self.avg = self.sum / self.count
|
||||
if self.tb_writer is not None:
|
||||
self.tb_writer.add_scalar(self.name, self.val, self.cur_step)
|
||||
self.cur_step += 1
|
||||
|
||||
def __str__(self):
|
||||
fmtstr = '{name}:{avg' + self.fmt + '}'
|
||||
return fmtstr.format(**self.__dict__)
|
||||
|
||||
class LOGGER(logging.Logger):
|
||||
"""
|
||||
Logger class
|
||||
"""
|
||||
def __init__(self, logger_name, rank=0):
|
||||
super(LOGGER, self).__init__(logger_name)
|
||||
if rank % 8 == 0:
|
||||
console = logging.StreamHandler(sys.stdout)
|
||||
console.setLevel(logging.INFO)
|
||||
formatter = logging.Formatter('%(asctime)s:%(levelname)s:%(message)s')
|
||||
console.setFormatter(formatter)
|
||||
self.addHandler(console)
|
||||
|
||||
def setup_logging_file(self, log_dir, rank=0):
|
||||
"""
|
||||
Setup logging file
|
||||
"""
|
||||
self.rank = rank
|
||||
if not os.path.exists(log_dir):
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
log_name = datetime.now().strftime('%Y-%m-%d_time_%H_%M_%S') + '_rank_{}.log'.format(rank)
|
||||
self.log_fn = os.path.join(log_dir, log_name)
|
||||
fh = logging.FileHandler(self.log_fn)
|
||||
fh.setLevel(logging.INFO)
|
||||
formatter = logging.Formatter('%(asctime)s:%(levelname)s:%(message)s')
|
||||
fh.setFormatter(formatter)
|
||||
self.addHandler(fh)
|
||||
|
||||
def info(self, msg, *args, **kwargs):
|
||||
if self.isEnabledFor(logging.INFO):
|
||||
self._log(logging.INFO, msg, args, **kwargs)
|
||||
|
||||
def save_args(self, args):
|
||||
self.info('Args:')
|
||||
args_dict = vars(args)
|
||||
for key in args_dict.keys():
|
||||
# self.info('--> {}: {}'.format(key, args_dict[key]))
|
||||
self.info('--> %s', key)
|
||||
self.info('')
|
||||
|
||||
def important_info(self, msg, *args, **kwargs):
|
||||
if self.isEnabledFor(logging.INFO) and self.rank == 0:
|
||||
line_width = 2
|
||||
important_msg = '\n'
|
||||
important_msg += ('*'*70 + '\n')*line_width
|
||||
important_msg += ('*'*line_width + '\n')*2
|
||||
important_msg += '*'*line_width + ' '*8 + msg + '\n'
|
||||
important_msg += ('*'*line_width + '\n')*2
|
||||
important_msg += ('*'*70 + '\n')*line_width
|
||||
self.info(important_msg, *args, **kwargs)
|
||||
|
||||
def get_logger(path, rank):
|
||||
logger = LOGGER("centerface", rank)
|
||||
logger.setup_logging_file(path, rank)
|
||||
return logger
|
|
@ -0,0 +1,285 @@
|
|||
# Copyright 2020 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
"""weight initilization"""
|
||||
|
||||
import math
|
||||
import numpy as np
|
||||
|
||||
from mindspore.common import initializer as init
|
||||
from mindspore.common.initializer import Initializer as MeInitializer
|
||||
import mindspore.nn as nn
|
||||
from mindspore import Tensor
|
||||
|
||||
def assignment(arr, num):
|
||||
"""Assign the value of `num` to `arr`."""
|
||||
if arr.shape == ():
|
||||
arr = arr.reshape((1))
|
||||
arr[:] = num
|
||||
arr = arr.reshape(())
|
||||
else:
|
||||
if isinstance(num, np.ndarray):
|
||||
arr[:] = num[:]
|
||||
else:
|
||||
arr[:] = num
|
||||
return arr
|
||||
|
||||
def calculate_gain(nonlinearity, param=None):
|
||||
r"""Return the recommended gain value for the given nonlinearity function.
|
||||
The values are as follows:
|
||||
|
||||
================= ====================================================
|
||||
nonlinearity gain
|
||||
================= ====================================================
|
||||
Linear / Identity :math:`1`
|
||||
Conv{1,2,3}D :math:`1`
|
||||
Sigmoid :math:`1`
|
||||
Tanh :math:`\frac{5}{3}`
|
||||
ReLU :math:`\sqrt{2}`
|
||||
Leaky Relu :math:`\sqrt{\frac{2}{1 + \text{negative\_slope}^2}}`
|
||||
================= ====================================================
|
||||
|
||||
Args:
|
||||
nonlinearity: the non-linear function (`nn.functional` name)
|
||||
param: optional parameter for the non-linear function
|
||||
|
||||
Examples:
|
||||
>>> gain = nn.init.calculate_gain('leaky_relu', 0.2) # leaky_relu with negative_slope=0.2
|
||||
"""
|
||||
linear_fns = ['linear', 'conv1d', 'conv2d', 'conv3d', 'conv_transpose1d', 'conv_transpose2d', 'conv_transpose3d']
|
||||
if nonlinearity in linear_fns or nonlinearity == 'sigmoid':
|
||||
return 1
|
||||
if nonlinearity == 'tanh':
|
||||
return 5.0 / 3
|
||||
if nonlinearity == 'relu':
|
||||
return math.sqrt(2.0)
|
||||
if nonlinearity == 'leaky_relu':
|
||||
if param is None:
|
||||
negative_slope = 0.01
|
||||
elif not isinstance(param, bool) and isinstance(param, int) or isinstance(param, float):
|
||||
# True/False are instances of int, hence check above
|
||||
negative_slope = param
|
||||
else:
|
||||
raise ValueError("negative_slope {} not a valid number".format(param))
|
||||
return math.sqrt(2.0 / (1 + negative_slope ** 2))
|
||||
|
||||
raise ValueError("Unsupported nonlinearity {}".format(nonlinearity))
|
||||
|
||||
|
||||
def _calculate_correct_fan(array, mode):
|
||||
mode = mode.lower()
|
||||
valid_modes = ['fan_in', 'fan_out']
|
||||
if mode not in valid_modes:
|
||||
raise ValueError("Mode {} not supported, please use one of {}".format(mode, valid_modes))
|
||||
|
||||
fan_in, fan_out = _calculate_fan_in_and_fan_out(array)
|
||||
return fan_in if mode == 'fan_in' else fan_out
|
||||
|
||||
|
||||
def kaiming_uniform_(arr, a=0, mode='fan_in', nonlinearity='leaky_relu'):
|
||||
r"""Fills the input `Tensor` with values according to the method
|
||||
described in `Delving deep into rectifiers: Surpassing human-level
|
||||
performance on ImageNet classification` - He, K. et al. (2015), using a
|
||||
uniform distribution. The resulting tensor will have values sampled from
|
||||
:math:`\mathcal{U}(-\text{bound}, \text{bound})` where
|
||||
|
||||
.. math::
|
||||
\text{bound} = \text{gain} \times \sqrt{\frac{3}{\text{fan\_mode}}}
|
||||
|
||||
Also known as He initialization.
|
||||
|
||||
Args:
|
||||
tensor: an n-dimensional `Tensor`
|
||||
a: the negative slope of the rectifier used after this layer (only
|
||||
used with ``'leaky_relu'``)
|
||||
mode: either ``'fan_in'`` (default) or ``'fan_out'``. Choosing ``'fan_in'``
|
||||
preserves the magnitude of the variance of the weights in the
|
||||
forward pass. Choosing ``'fan_out'`` preserves the magnitudes in the
|
||||
backwards pass.
|
||||
nonlinearity: the non-linear function (`nn.functional` name),
|
||||
recommended to use only with ``'relu'`` or ``'leaky_relu'`` (default).
|
||||
|
||||
Examples:
|
||||
>>> w = np.empty(3, 5)
|
||||
>>> nn.init.kaiming_uniform_(w, mode='fan_in', nonlinearity='relu')
|
||||
"""
|
||||
fan = _calculate_correct_fan(arr, mode)
|
||||
gain = calculate_gain(nonlinearity, a)
|
||||
std = gain / math.sqrt(fan)
|
||||
bound = math.sqrt(3.0) * std # Calculate uniform bounds from standard deviation
|
||||
return np.random.uniform(-bound, bound, arr.shape)
|
||||
|
||||
|
||||
def kaiming_normal_(arr, a=0, mode='fan_in', nonlinearity='leaky_relu'):
|
||||
r"""Fills the input `Tensor` with values according to the method
|
||||
described in `Delving deep into rectifiers: Surpassing human-level
|
||||
performance on ImageNet classification` - He, K. et al. (2015), using a
|
||||
normal distribution. The resulting tensor will have values sampled from
|
||||
:math:`\mathcal{N}(0, \text{std}^2)` where
|
||||
|
||||
.. math::
|
||||
\text{std} = \frac{\text{gain}}{\sqrt{\text{fan\_mode}}}
|
||||
|
||||
Also known as He initialization.
|
||||
|
||||
Args:
|
||||
tensor: an n-dimensional `Tensor`
|
||||
a: the negative slope of the rectifier used after this layer (only
|
||||
used with ``'leaky_relu'``)
|
||||
mode: either ``'fan_in'`` (default) or ``'fan_out'``. Choosing ``'fan_in'``
|
||||
preserves the magnitude of the variance of the weights in the
|
||||
forward pass. Choosing ``'fan_out'`` preserves the magnitudes in the
|
||||
backwards pass.
|
||||
nonlinearity: the non-linear function (`nn.functional` name),
|
||||
recommended to use only with ``'relu'`` or ``'leaky_relu'`` (default).
|
||||
|
||||
Examples:
|
||||
>>> w = np.empty(3, 5)
|
||||
>>> nn.init.kaiming_normal_(w, mode='fan_out', nonlinearity='relu')
|
||||
"""
|
||||
fan = _calculate_correct_fan(arr, mode)
|
||||
gain = calculate_gain(nonlinearity, a)
|
||||
std = gain / math.sqrt(fan)
|
||||
return np.random.normal(0, std, arr.shape)
|
||||
|
||||
|
||||
def _calculate_fan_in_and_fan_out(arr):
|
||||
"""
|
||||
Calculate fan in and fan out
|
||||
"""
|
||||
dimensions = len(arr.shape)
|
||||
if dimensions < 2:
|
||||
raise ValueError("Fan in and fan out can not be computed for array with fewer than 2 dimensions")
|
||||
|
||||
num_input_fmaps = arr.shape[1]
|
||||
num_output_fmaps = arr.shape[0]
|
||||
receptive_field_size = 1
|
||||
if dimensions > 2:
|
||||
receptive_field_size = arr[0][0].size
|
||||
fan_in = num_input_fmaps * receptive_field_size
|
||||
fan_out = num_output_fmaps * receptive_field_size
|
||||
|
||||
return fan_in, fan_out
|
||||
|
||||
|
||||
def xavier_uniform_(arr, gain=1.):
|
||||
# type: (Tensor, float) -> Tensor
|
||||
r"""Fills the input `Tensor` with values according to the method
|
||||
described in `Understanding the difficulty of training deep feedforward
|
||||
neural networks` - Glorot, X. & Bengio, Y. (2010), using a uniform
|
||||
distribution. The resulting tensor will have values sampled from
|
||||
:math:`\mathcal{U}(-a, a)` where
|
||||
|
||||
.. math::
|
||||
a = \text{gain} \times \sqrt{\frac{6}{\text{fan\_in} + \text{fan\_out}}}
|
||||
|
||||
Also known as Glorot initialization.
|
||||
|
||||
Args:
|
||||
tensor: an n-dimensional `Tensor`
|
||||
gain: an optional scaling factor
|
||||
|
||||
Examples:
|
||||
>>> w = np.empty(3, 5)
|
||||
>>> nn.init.xavier_uniform_(w, gain=nn.init.calculate_gain('relu'))
|
||||
"""
|
||||
fan_in, fan_out = _calculate_fan_in_and_fan_out(arr)
|
||||
std = gain * math.sqrt(2.0 / float(fan_in + fan_out))
|
||||
a = math.sqrt(3.0) * std # Calculate uniform bounds from standard deviation
|
||||
|
||||
return np.random.uniform(-a, a, arr.shape)
|
||||
|
||||
|
||||
class XavierUniform(MeInitializer):
|
||||
def __init__(self, gain=1.):
|
||||
super(XavierUniform, self).__init__()
|
||||
self.gain = gain
|
||||
|
||||
def _initialize(self, arr):
|
||||
tmp = xavier_uniform_(arr, self.gain)
|
||||
assignment(arr, tmp)
|
||||
|
||||
|
||||
class KaimingUniform(MeInitializer):
|
||||
def __init__(self, a=0, mode='fan_in', nonlinearity='leaky_relu'):
|
||||
super(KaimingUniform, self).__init__()
|
||||
self.a = a
|
||||
self.mode = mode
|
||||
self.nonlinearity = nonlinearity
|
||||
|
||||
def _initialize(self, arr):
|
||||
tmp = kaiming_uniform_(arr, self.a, self.mode, self.nonlinearity)
|
||||
assignment(arr, tmp)
|
||||
|
||||
|
||||
class KaimingNormal(MeInitializer):
|
||||
def __init__(self, a=0, mode='fan_in', nonlinearity='leaky_relu'):
|
||||
super(KaimingNormal, self).__init__()
|
||||
self.a = a
|
||||
self.mode = mode
|
||||
self.nonlinearity = nonlinearity
|
||||
|
||||
def _initialize(self, arr):
|
||||
tmp = kaiming_normal_(arr, self.a, self.mode, self.nonlinearity)
|
||||
assignment(arr, tmp)
|
||||
|
||||
class RandomNormal(MeInitializer):
|
||||
def __init__(self, std=0.001):
|
||||
super(RandomNormal, self).__init__()
|
||||
self.std = std
|
||||
|
||||
def _initialize(self, arr):
|
||||
std = self.std
|
||||
tmp = np.random.normal(0, std, arr.shape)
|
||||
assignment(arr, tmp)
|
||||
|
||||
def default_recurisive_init(custom_cell):
|
||||
"""
|
||||
Parameters init
|
||||
"""
|
||||
np.random.seed(123)
|
||||
for name, cell in custom_cell.cells_and_names():
|
||||
if 'hm' in name or 'wh' in name or 'off' in name or 'kps' in name:
|
||||
if isinstance(cell, (nn.Conv2d)):
|
||||
cell.weight.set_data(init.initializer(RandomNormal(), cell.weight.data.shape,
|
||||
cell.weight.data.dtype).to_tensor())
|
||||
if cell.bias is not None:
|
||||
cell.bias.set_data(init.initializer('zeros', cell.bias.data.shape,
|
||||
cell.bias.data.dtype).to_tensor())
|
||||
continue
|
||||
|
||||
if isinstance(cell, (nn.Conv2d)):
|
||||
cell.weight.set_data(init.initializer(KaimingNormal(mode='fan_out'),
|
||||
cell.weight.data.shape,
|
||||
cell.weight.data.dtype).to_tensor())
|
||||
if cell.bias is not None:
|
||||
cell.bias.set_data(init.initializer('zeros', cell.bias.data.shape,
|
||||
cell.bias.data.dtype).to_tensor())
|
||||
elif isinstance(cell, nn.Dense):
|
||||
cell.weight.set_data(init.initializer(KaimingNormal(mode='fan_out'),
|
||||
cell.weight.data.shape,
|
||||
cell.weight.data.dtype).to_tensor())
|
||||
if cell.bias is not None:
|
||||
cell.bias.set_data(init.initializer('zeros', cell.bias.data.shape,
|
||||
cell.bias.data.dtype).to_tensor())
|
||||
elif isinstance(cell, (nn.BatchNorm2d, nn.BatchNorm1d)):
|
||||
cell.gamma.set_data(init.initializer('ones', cell.gamma.data.shape).to_tensor())
|
||||
cell.beta.set_data(init.initializer('zeros', cell.beta.data.shape).to_tensor())
|
||||
elif isinstance(cell, nn.Conv2dTranspose):
|
||||
cell.weight.set_data(init.initializer(KaimingUniform(a=math.sqrt(5), mode='fan_out'),
|
||||
cell.weight.data.shape,
|
||||
cell.weight.data.dtype).to_tensor())
|
||||
if cell.bias is not None:
|
||||
cell.bias.set_data(init.initializer('zeros', cell.bias.data.shape,
|
||||
cell.bias.data.dtype).to_tensor())
|
|
@ -0,0 +1,160 @@
|
|||
# Copyright 2020 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
"""
|
||||
Test centerface example
|
||||
"""
|
||||
import os
|
||||
import time
|
||||
import argparse
|
||||
import datetime
|
||||
import scipy.io as sio
|
||||
|
||||
from mindspore import context
|
||||
from mindspore.train.serialization import load_checkpoint, load_param_into_net
|
||||
|
||||
from src.utils import get_logger
|
||||
from src.var_init import default_recurisive_init
|
||||
from src.centerface import CenterfaceMobilev2, CenterFaceWithNms
|
||||
from src.config import ConfigCenterface
|
||||
|
||||
from dependency.centernet.src.lib.detectors.base_detector import CenterFaceDetector
|
||||
from dependency.evaluate.eval import evaluation
|
||||
|
||||
dev_id = int(os.getenv('DEVICE_ID'))
|
||||
context.set_context(mode=context.GRAPH_MODE, enable_auto_mixed_precision=False,
|
||||
device_target="Ascend", save_graphs=False, device_id=dev_id)
|
||||
|
||||
parser = argparse.ArgumentParser('mindspore coco training')
|
||||
parser.add_argument('--data_dir', type=str, default='', help='train data dir')
|
||||
parser.add_argument('--test_model', type=str, default='', help='test model dir')
|
||||
parser.add_argument('--ground_truth_mat', type=str, default='', help='ground_truth, mat type')
|
||||
parser.add_argument('--save_dir', type=str, default='', help='save_path for evaluate')
|
||||
parser.add_argument('--ground_truth_path', type=str, default='', help='ground_truth path, contain all mat file')
|
||||
parser.add_argument('--eval', type=int, default=0, help='if do eval after test')
|
||||
parser.add_argument('--eval_script_path', type=str, default='', help='evaluate script path')
|
||||
parser.add_argument('--rank', type=int, default=0, help='local rank of distributed')
|
||||
parser.add_argument('--ckpt_path', type=str, default='outputs/', help='checkpoint save location')
|
||||
parser.add_argument('--ckpt_name', type=str, default="", help='input model name')
|
||||
parser.add_argument('--device_num', type=int, default=1, help='device num for testing')
|
||||
parser.add_argument('--steps_per_epoch', type=int, default=198, help='steps for each epoch')
|
||||
parser.add_argument('--start', type=int, default=0, help='start loop number, used to calculate first epoch number')
|
||||
parser.add_argument('--end', type=int, default=18, help='end loop number, used to calculate last epoch number')
|
||||
|
||||
args, _ = parser.parse_known_args()
|
||||
|
||||
if __name__ == "__main__":
|
||||
# logger
|
||||
args.outputs_dir = os.path.join(args.ckpt_path,
|
||||
datetime.datetime.now().strftime('%Y-%m-%d_time_%H_%M_%S'))
|
||||
args.logger = get_logger(args.outputs_dir, args.rank)
|
||||
args.logger.save_args(args)
|
||||
|
||||
if args.ckpt_name != "":
|
||||
args.start = 0
|
||||
args.end = 1
|
||||
|
||||
for loop in range(args.start, args.end, 1):
|
||||
network = CenterfaceMobilev2()
|
||||
default_recurisive_init(network)
|
||||
|
||||
if args.ckpt_name == "":
|
||||
ckpt_num = loop * args.device_num + args.rank + 1
|
||||
ckpt_name = "0-" + str(ckpt_num) + "_" + str(args.steps_per_epoch * ckpt_num) + ".ckpt"
|
||||
else:
|
||||
ckpt_name = args.ckpt_name
|
||||
|
||||
test_model = args.test_model + ckpt_name
|
||||
if not test_model:
|
||||
args.logger.info('load_model {} none'.format(test_model))
|
||||
continue
|
||||
|
||||
if os.path.isfile(test_model):
|
||||
param_dict = load_checkpoint(test_model)
|
||||
param_dict_new = {}
|
||||
for key, values in param_dict.items():
|
||||
if key.startswith('moments.') or key.startswith('moment1.') or key.startswith('moment2.'):
|
||||
continue
|
||||
elif key.startswith('centerface_network.'):
|
||||
param_dict_new[key[19:]] = values
|
||||
else:
|
||||
param_dict_new[key] = values
|
||||
|
||||
load_param_into_net(network, param_dict_new)
|
||||
args.logger.info('load_model {} success'.format(test_model))
|
||||
else:
|
||||
args.logger.info('{} not exists or not a pre-trained file'.format(test_model))
|
||||
continue
|
||||
|
||||
train_network_type_nms = 1 # default with num
|
||||
if train_network_type_nms:
|
||||
network = CenterFaceWithNms(network)
|
||||
args.logger.info('train network type with nms')
|
||||
network.set_train(False)
|
||||
args.logger.info('finish get network')
|
||||
|
||||
config = ConfigCenterface()
|
||||
|
||||
# test network -----------
|
||||
start = time.time()
|
||||
|
||||
ground_truth_mat = sio.loadmat(args.ground_truth_mat)
|
||||
event_list = ground_truth_mat['event_list']
|
||||
file_list = ground_truth_mat['file_list']
|
||||
if args.ckpt_name == "":
|
||||
save_path = args.save_dir + str(ckpt_num) + '/'
|
||||
else:
|
||||
save_path = args.save_dir+ '/'
|
||||
detector = CenterFaceDetector(config, network)
|
||||
|
||||
for index, event in enumerate(event_list):
|
||||
file_list_item = file_list[index][0]
|
||||
im_dir = event[0][0]
|
||||
if not os.path.exists(save_path + im_dir):
|
||||
os.makedirs(save_path + im_dir)
|
||||
args.logger.info('save_path + im_dir={}'.format(save_path + im_dir))
|
||||
for num, file in enumerate(file_list_item):
|
||||
im_name = file[0][0]
|
||||
zip_name = '%s/%s.jpg' % (im_dir, im_name)
|
||||
img_path = os.path.join(args.data_dir, zip_name)
|
||||
args.logger.info('img_path={}'.format(img_path))
|
||||
|
||||
dets = detector.run(img_path)['results']
|
||||
|
||||
f = open(save_path + im_dir + '/' + im_name + '.txt', 'w')
|
||||
f.write('{:s}\n'.format('%s/%s.jpg' % (im_dir, im_name)))
|
||||
f.write('{:d}\n'.format(len(dets)))
|
||||
for b in dets[1]:
|
||||
x1, y1, x2, y2, s = b[0], b[1], b[2], b[3], b[4]
|
||||
f.write('{:.1f} {:.1f} {:.1f} {:.1f} {:.3f}\n'.format(x1, y1, (x2 - x1 + 1), (y2 - y1 + 1), s))
|
||||
f.close()
|
||||
args.logger.info('event:{}, num:{}'.format(index + 1, num + 1))
|
||||
|
||||
end = time.time()
|
||||
args.logger.info("============num {} time {}".format(num, (end-start)*1000))
|
||||
start = end
|
||||
|
||||
if args.eval:
|
||||
args.logger.info('==========start eval===============')
|
||||
args.logger.info("test output path = {}".format(save_path))
|
||||
if os.path.isdir(save_path):
|
||||
evaluation(save_path, args.ground_truth_path)
|
||||
else:
|
||||
args.logger.info('no test output path')
|
||||
args.logger.info('==========end eval===============')
|
||||
|
||||
if args.ckpt_name != "":
|
||||
break
|
||||
|
||||
args.logger.info('==========end testing===============')
|
|
@ -0,0 +1,348 @@
|
|||
# Copyright 2020 Huawei Technologies Co., Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ============================================================================
|
||||
"""
|
||||
Train centerface and get network model files(.ckpt)
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
import argparse
|
||||
import datetime
|
||||
import numpy as np
|
||||
|
||||
from mindspore import context
|
||||
from mindspore.context import ParallelMode
|
||||
from mindspore.nn.optim.adam import Adam
|
||||
from mindspore.nn.optim.momentum import Momentum
|
||||
from mindspore.nn.optim.sgd import SGD
|
||||
from mindspore import Tensor
|
||||
from mindspore.communication.management import init, get_rank, get_group_size
|
||||
from mindspore.train.callback import ModelCheckpoint, RunContext
|
||||
from mindspore.train.callback import _InternalCallbackParam, CheckpointConfig
|
||||
from mindspore.train.serialization import load_checkpoint, load_param_into_net
|
||||
from mindspore.profiler.profiling import Profiler
|
||||
|
||||
from src.utils import get_logger
|
||||
from src.utils import AverageMeter
|
||||
from src.lr_scheduler import warmup_step_lr
|
||||
from src.lr_scheduler import warmup_cosine_annealing_lr, \
|
||||
warmup_cosine_annealing_lr_v2, warmup_cosine_annealing_lr_sample
|
||||
from src.lr_scheduler import MultiStepLR
|
||||
from src.var_init import default_recurisive_init
|
||||
from src.centerface import CenterfaceMobilev2
|
||||
from src.utils import load_backbone, get_param_groups
|
||||
from src.config import ConfigCenterface
|
||||
from src.centerface import CenterFaceWithLossCell, TrainingWrapper
|
||||
from src.dataset import GetDataLoader
|
||||
|
||||
dev_id = int(os.getenv('DEVICE_ID'))
|
||||
context.set_context(mode=context.GRAPH_MODE, enable_auto_mixed_precision=False,
|
||||
device_target="Ascend", save_graphs=False, device_id=dev_id, reserve_class_name_in_scope=False)
|
||||
|
||||
parser = argparse.ArgumentParser('mindspore coco training')
|
||||
|
||||
# dataset related
|
||||
parser.add_argument('--data_dir', type=str, default='', help='train data dir')
|
||||
parser.add_argument('--annot_path', type=str, default='', help='train data annotation path')
|
||||
parser.add_argument('--img_dir', type=str, default='', help='train data img dir')
|
||||
parser.add_argument('--per_batch_size', default=8, type=int, help='batch size for per gpu')
|
||||
|
||||
# network related
|
||||
parser.add_argument('--pretrained_backbone', default='', type=str, help='model_path, local pretrained backbone'
|
||||
' model to load')
|
||||
parser.add_argument('--resume', default='', type=str, help='path of pretrained centerface_model')
|
||||
|
||||
# optimizer and lr related
|
||||
parser.add_argument('--lr_scheduler', default='multistep', type=str,
|
||||
help='lr-scheduler, option type: exponential, cosine_annealing')
|
||||
parser.add_argument('--lr', default=4e-3, type=float, help='learning rate of the training')
|
||||
parser.add_argument('--lr_epochs', type=str, default='90,120', help='epoch of lr changing')
|
||||
parser.add_argument('--lr_gamma', type=float, default=0.1,
|
||||
help='decrease lr by a factor of exponential lr_scheduler')
|
||||
parser.add_argument('--eta_min', type=float, default=0., help='eta_min in cosine_annealing scheduler')
|
||||
parser.add_argument('--t_max', type=int, default=140, help='T-max in cosine_annealing scheduler')
|
||||
parser.add_argument('--max_epoch', type=int, default=140, help='max epoch num to train the model')
|
||||
parser.add_argument('--warmup_epochs', default=0, type=float, help='warmup epoch')
|
||||
parser.add_argument('--weight_decay', type=float, default=0.0005, help='weight decay')
|
||||
parser.add_argument('--momentum', type=float, default=0.9, help='momentum')
|
||||
parser.add_argument('--optimizer', default='adam', type=str,
|
||||
help='optimizer type, default: adam')
|
||||
|
||||
# loss related
|
||||
parser.add_argument('--loss_scale', type=int, default=1024, help='static loss scale')
|
||||
parser.add_argument('--label_smooth', type=int, default=0, help='whether to use label smooth in CE')
|
||||
parser.add_argument('--label_smooth_factor', type=float, default=0.1, help='smooth strength of original one-hot')
|
||||
|
||||
# logging related
|
||||
parser.add_argument('--log_interval', type=int, default=100, help='logging interval')
|
||||
parser.add_argument('--ckpt_path', type=str, default='outputs/', help='checkpoint save location')
|
||||
parser.add_argument('--ckpt_interval', type=int, default=None, help='ckpt_interval')
|
||||
|
||||
parser.add_argument('--is_save_on_master', type=int, default=1, help='save ckpt on master or all rank')
|
||||
|
||||
# distributed related
|
||||
parser.add_argument('--is_distributed', type=int, default=1, help='if multi device')
|
||||
parser.add_argument('--rank', type=int, default=0, help='local rank of distributed')
|
||||
parser.add_argument('--group_size', type=int, default=1, help='world size of distributed')
|
||||
|
||||
# roma obs
|
||||
parser.add_argument('--train_url', type=str, default="", help='train url')
|
||||
|
||||
# profiler init, can open when you debug. if train, donot open, since it cost memory and disk space
|
||||
parser.add_argument('--need_profiler', type=int, default=0, help='whether use profiler')
|
||||
|
||||
# reset default config
|
||||
parser.add_argument('--training_shape', type=str, default="", help='fix training shape')
|
||||
parser.add_argument('--resize_rate', type=int, default=None, help='resize rate for multi-scale training')
|
||||
|
||||
args, _ = parser.parse_known_args()
|
||||
|
||||
if args.lr_scheduler == 'cosine_annealing' and args.max_epoch > args.t_max:
|
||||
args.t_max = args.max_epoch
|
||||
|
||||
args.lr_epochs = list(map(int, args.lr_epochs.split(',')))
|
||||
|
||||
|
||||
def convert_training_shape(args_):
|
||||
"""
|
||||
Convert training shape
|
||||
"""
|
||||
training_shape = [int(args_.training_shape), int(args_.training_shape)]
|
||||
return training_shape
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# init distributed
|
||||
if args.is_distributed:
|
||||
init()
|
||||
args.rank = get_rank()
|
||||
args.group_size = get_group_size()
|
||||
|
||||
# select for master rank save ckpt or all rank save, compatiable for model parallel
|
||||
args.rank_save_ckpt_flag = 0
|
||||
if args.is_save_on_master:
|
||||
if args.rank == 0:
|
||||
args.rank_save_ckpt_flag = 1
|
||||
else:
|
||||
args.rank_save_ckpt_flag = 1
|
||||
|
||||
# logger
|
||||
args.outputs_dir = os.path.join(args.ckpt_path,
|
||||
datetime.datetime.now().strftime('%Y-%m-%d_time_%H_%M_%S'))
|
||||
args.logger = get_logger(args.outputs_dir, args.rank)
|
||||
args.logger.save_args(args)
|
||||
|
||||
if args.need_profiler:
|
||||
profiler = Profiler(output_path=args.outputs_dir)
|
||||
|
||||
loss_meter = AverageMeter('loss')
|
||||
|
||||
context.reset_auto_parallel_context()
|
||||
if args.is_distributed:
|
||||
parallel_mode = ParallelMode.DATA_PARALLEL
|
||||
degree = get_group_size()
|
||||
else:
|
||||
parallel_mode = ParallelMode.STAND_ALONE
|
||||
degree = 1
|
||||
|
||||
# context.set_auto_parallel_context(parallel_mode=parallel_mode, device_num=degree, parameter_broadcast=True, gradients_mean=True)
|
||||
# Notice: parameter_broadcast should be supported, but current version has bugs, thus been disabled.
|
||||
# To make sure the init weight on all npu is the same, we need to set a static seed in default_recurisive_init when weight initialization
|
||||
context.set_auto_parallel_context(parallel_mode=parallel_mode, gradients_mean=True, device_num=degree)
|
||||
network = CenterfaceMobilev2()
|
||||
# init, to avoid overflow, some std of weight should be enough small
|
||||
default_recurisive_init(network)
|
||||
|
||||
if args.pretrained_backbone:
|
||||
network = load_backbone(network, args.pretrained_backbone, args)
|
||||
args.logger.info('load pre-trained backbone {} into network'.format(args.pretrained_backbone))
|
||||
else:
|
||||
args.logger.info('Not load pre-trained backbone, please be careful')
|
||||
|
||||
if os.path.isfile(args.resume):
|
||||
param_dict = load_checkpoint(args.resume)
|
||||
param_dict_new = {}
|
||||
for key, values in param_dict.items():
|
||||
if key.startswith('moments.') or key.startswith('moment1.') or key.startswith('moment2.'):
|
||||
continue
|
||||
elif key.startswith('centerface_network.'):
|
||||
param_dict_new[key[19:]] = values
|
||||
else:
|
||||
param_dict_new[key] = values
|
||||
|
||||
load_param_into_net(network, param_dict_new)
|
||||
args.logger.info('load_model {} success'.format(args.resume))
|
||||
else:
|
||||
args.logger.info('{} not set/exists or not a pre-trained file'.format(args.resume))
|
||||
|
||||
network = CenterFaceWithLossCell(network)
|
||||
args.logger.info('finish get network')
|
||||
|
||||
config = ConfigCenterface()
|
||||
config.data_dir = args.data_dir
|
||||
config.annot_path = args.annot_path
|
||||
config.img_dir = args.img_dir
|
||||
|
||||
config.label_smooth = args.label_smooth
|
||||
config.label_smooth_factor = args.label_smooth_factor
|
||||
# -------------reset config-----------------
|
||||
if args.training_shape:
|
||||
config.multi_scale = [convert_training_shape(args)]
|
||||
|
||||
if args.resize_rate:
|
||||
config.resize_rate = args.resize_rate
|
||||
|
||||
# data loader
|
||||
data_loader, args.steps_per_epoch = GetDataLoader(per_batch_size=args.per_batch_size,
|
||||
max_epoch=args.max_epoch,
|
||||
rank=args.rank,
|
||||
group_size=args.group_size,
|
||||
config=config,
|
||||
split='train')
|
||||
args.steps_per_epoch = args.steps_per_epoch // args.max_epoch
|
||||
args.logger.info('Finish loading dataset')
|
||||
|
||||
if not args.ckpt_interval:
|
||||
args.ckpt_interval = args.steps_per_epoch
|
||||
|
||||
# lr scheduler
|
||||
if args.lr_scheduler == 'multistep':
|
||||
lr_fun = MultiStepLR(args.lr, args.lr_epochs, args.lr_gamma, args.steps_per_epoch, args.max_epoch,
|
||||
args.warmup_epochs)
|
||||
lr = lr_fun.get_lr()
|
||||
elif args.lr_scheduler == 'exponential':
|
||||
lr = warmup_step_lr(args.lr,
|
||||
args.lr_epochs,
|
||||
args.steps_per_epoch,
|
||||
args.warmup_epochs,
|
||||
args.max_epoch,
|
||||
gamma=args.lr_gamma
|
||||
)
|
||||
elif args.lr_scheduler == 'cosine_annealing':
|
||||
lr = warmup_cosine_annealing_lr(args.lr,
|
||||
args.steps_per_epoch,
|
||||
args.warmup_epochs,
|
||||
args.max_epoch,
|
||||
args.t_max,
|
||||
args.eta_min)
|
||||
elif args.lr_scheduler == 'cosine_annealing_V2':
|
||||
lr = warmup_cosine_annealing_lr_v2(args.lr,
|
||||
args.steps_per_epoch,
|
||||
args.warmup_epochs,
|
||||
args.max_epoch,
|
||||
args.t_max,
|
||||
args.eta_min)
|
||||
elif args.lr_scheduler == 'cosine_annealing_sample':
|
||||
lr = warmup_cosine_annealing_lr_sample(args.lr,
|
||||
args.steps_per_epoch,
|
||||
args.warmup_epochs,
|
||||
args.max_epoch,
|
||||
args.t_max,
|
||||
args.eta_min)
|
||||
else:
|
||||
raise NotImplementedError(args.lr_scheduler)
|
||||
|
||||
if args.optimizer == "adam":
|
||||
opt = Adam(params=get_param_groups(network),
|
||||
learning_rate=Tensor(lr),
|
||||
weight_decay=args.weight_decay,
|
||||
loss_scale=args.loss_scale)
|
||||
args.logger.info("use adam optimizer")
|
||||
elif args.optimizer == "sgd":
|
||||
opt = SGD(params=get_param_groups(network),
|
||||
learning_rate=Tensor(lr),
|
||||
momentum=args.momentum,
|
||||
weight_decay=args.weight_decay,
|
||||
loss_scale=args.loss_scale)
|
||||
else:
|
||||
opt = Momentum(params=get_param_groups(network),
|
||||
learning_rate=Tensor(lr),
|
||||
momentum=args.momentum,
|
||||
weight_decay=args.weight_decay,
|
||||
loss_scale=args.loss_scale)
|
||||
|
||||
network = TrainingWrapper(network, opt, sens=args.loss_scale)
|
||||
network.set_train()
|
||||
|
||||
if args.rank_save_ckpt_flag:
|
||||
# checkpoint save
|
||||
ckpt_max_num = args.max_epoch * args.steps_per_epoch // args.ckpt_interval
|
||||
ckpt_config = CheckpointConfig(save_checkpoint_steps=args.ckpt_interval,
|
||||
keep_checkpoint_max=ckpt_max_num)
|
||||
ckpt_cb = ModelCheckpoint(config=ckpt_config,
|
||||
directory=args.outputs_dir,
|
||||
prefix='{}'.format(args.rank))
|
||||
cb_params = _InternalCallbackParam()
|
||||
cb_params.train_network = network
|
||||
cb_params.epoch_num = ckpt_max_num
|
||||
cb_params.cur_epoch_num = 1
|
||||
run_context = RunContext(cb_params)
|
||||
ckpt_cb.begin(run_context)
|
||||
|
||||
args.logger.info('args.steps_per_epoch = {} args.ckpt_interval ={}'.format(args.steps_per_epoch,
|
||||
args.ckpt_interval))
|
||||
|
||||
t_end = time.time()
|
||||
|
||||
for i_all, batch_load in enumerate(data_loader):
|
||||
i = i_all % args.steps_per_epoch
|
||||
epoch = i_all // args.steps_per_epoch + 1
|
||||
images, hm, reg_mask, ind, wh, wight_mask, hm_offset, hps_mask, landmarks = batch_load
|
||||
|
||||
images = Tensor(images)
|
||||
hm = Tensor(hm)
|
||||
reg_mask = Tensor(reg_mask)
|
||||
ind = Tensor(ind)
|
||||
wh = Tensor(wh)
|
||||
wight_mask = Tensor(wight_mask)
|
||||
hm_offset = Tensor(hm_offset)
|
||||
hps_mask = Tensor(hps_mask)
|
||||
landmarks = Tensor(landmarks)
|
||||
|
||||
loss, overflow, scaling = network(images, hm, reg_mask, ind, wh, wight_mask, hm_offset, hps_mask, landmarks)
|
||||
# Tensor to numpy
|
||||
overflow = np.all(overflow.asnumpy())
|
||||
loss = loss.asnumpy()
|
||||
loss_meter.update(loss)
|
||||
args.logger.info('epoch:{}, iter:{}, avg_loss:{}, loss:{}, overflow:{}, loss_scale:{}'.format(epoch,
|
||||
i,
|
||||
loss_meter,
|
||||
loss,
|
||||
overflow,
|
||||
scaling.asnumpy()
|
||||
))
|
||||
|
||||
if args.rank_save_ckpt_flag:
|
||||
# ckpt progress
|
||||
cb_params.cur_epoch_num = epoch
|
||||
cb_params.cur_step_num = i + 1 + (epoch-1)*args.steps_per_epoch
|
||||
cb_params.batch_num = i + 2 + (epoch-1)*args.steps_per_epoch
|
||||
ckpt_cb.step_end(run_context)
|
||||
|
||||
if (i_all+1) % args.steps_per_epoch == 0:
|
||||
time_used = time.time() - t_end
|
||||
fps = args.per_batch_size * args.steps_per_epoch * args.group_size / time_used
|
||||
if args.rank == 0:
|
||||
args.logger.info(
|
||||
'epoch[{}], {}, {:.2f} imgs/sec, lr:{}'
|
||||
.format(epoch, loss_meter, fps, lr[i + (epoch-1)*args.steps_per_epoch])
|
||||
)
|
||||
t_end = time.time()
|
||||
loss_meter.reset()
|
||||
|
||||
if args.need_profiler:
|
||||
profiler.analyse()
|
||||
|
||||
args.logger.info('==========end training===============')
|
Loading…
Reference in New Issue