diff --git a/model_zoo/research/cv/ghostnet/Readme.md b/model_zoo/research/cv/ghostnet/Readme.md new file mode 100644 index 00000000000..a84393eba20 --- /dev/null +++ b/model_zoo/research/cv/ghostnet/Readme.md @@ -0,0 +1,143 @@ +# Contents + +- [GhostNet Description](#ghostnet-description) +- [Model Architecture](#model-architecture) +- [Dataset](#dataset) +- [Environment Requirements](#environment-requirements) +- [Script Description](#script-description) + - [Script and Sample Code](#script-and-sample-code) + - [Training Process](#training-process) + - [Evaluation Process](#evaluation-process) + - [Evaluation](#evaluation) +- [Model Description](#model-description) + - [Performance](#performance) + - [Training Performance](#evaluation-performance) + - [Inference Performance](#evaluation-performance) +- [Description of Random Situation](#description-of-random-situation) +- [ModelZoo Homepage](#modelzoo-homepage) + +# [GhostNet Description](#contents) + +The GhostNet architecture is based on an Ghost module structure which generate more features from cheap operations. Based on a set of intrinsic feature maps, a series of cheap operations are applied to generate many ghost feature maps that could fully reveal information underlying intrinsic features. + +[Paper](https://openaccess.thecvf.com/content_CVPR_2020/papers/Han_GhostNet_More_Features_From_Cheap_Operations_CVPR_2020_paper.pdf): Kai Han, Yunhe Wang, Qi Tian, Jianyuan Guo, Chunjing Xu, Chang Xu. GhostNet: More Features from Cheap Operations. CVPR 2020. + +# [Model architecture](#contents) + +The overall network architecture of GhostNet is show below: + +[Link](https://openaccess.thecvf.com/content_CVPR_2020/papers/Han_GhostNet_More_Features_From_Cheap_Operations_CVPR_2020_paper.pdf) + +# [Dataset](#contents) + +Dataset used: [Oxford-IIIT Pet](https://www.robots.ox.ac.uk/~vgg/data/pets/) + +- Dataset size: 7049 colorful images in 1000 classes + - Train: 3680 images + - Test: 3369 images +- Data format: RGB images. + - Note: Data will be processed in src/dataset.py + +# [Environment Requirements](#contents) + +- Hardware(Ascend/GPU) + - Prepare hardware environment with Ascend or GPU. 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](http://10.90.67.50/mindspore/archive/20200506/OpenSource/me_vm_x86/) +- 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) + +# [Script description](#contents) + +## [Script and sample code](#contents) + +```python +├── GhostNet + ├── Readme.md # descriptions about ghostnet # shell script for evaluation with CPU, GPU or Ascend + ├── src + │ ├──config.py # parameter configuration + │ ├──dataset.py # creating dataset + │ ├──launch.py # start python script + │ ├──lr_generator.py # learning rate config + │ ├──ghostnet.py # GhostNet architecture + │ ├──ghostnet600.py # GhostNet-600M architecture + ├── eval.py # evaluation script + ├── mindspore_hub_conf.py # export model for hub +``` + +## [Training process](#contents) +To Be Done + +## [Eval process](#contents) + +### Usage + +After installing MindSpore via the official website, you can start evaluation as follows: + + +### Launch + +``` +# infer example + + Ascend: python eval.py --model [ghostnet/ghostnet-600] --dataset_path ~/Pets/test.mindrecord --platform Ascend --checkpoint_path [CHECKPOINT_PATH] + GPU: python eval.py --model [ghostnet/ghostnet-600] --dataset_path ~/Pets/test.mindrecord --platform GPU --checkpoint_path [CHECKPOINT_PATH] +``` + +> checkpoint can be produced in training process. + +### Result + +``` +result: {'acc': 0.8113927500681385} ckpt= ./ghostnet_nose_1x_pets.ckpt +result: {'acc': 0.824475333878441} ckpt= ./ghostnet_1x_pets.ckpt +result: {'acc': 0.8691741618969746} ckpt= ./ghostnet600M_pets.ckpt +``` + +# [Model Description](#contents) + +## [Performance](#contents) + +#### Evaluation Performance + +###### GhostNet on ImageNet2012 +| Parameters | | | +| -------------------------- | -------------------------------------- |---------------------------------- | +| Model Version | GhostNet |GhostNet-600| +| uploaded Date | 09/08/2020 (month/day/year) ; | 09/08/2020 (month/day/year) | +| MindSpore Version | 0.6.0-alpha |0.6.0-alpha | +| Dataset | ImageNet2012 | ImageNet2012| +| Parameters (M) | 5.2 | 11.9 | +| FLOPs (M) | 142 | 591 | +| Accuracy (Top1) | 73.9 |80.2 | + +###### GhostNet on Oxford-IIIT Pet +| Parameters | | | +| -------------------------- | -------------------------------------- |---------------------------------- | +| Model Version | GhostNet |GhostNet-600| +| uploaded Date | 09/08/2020 (month/day/year) ; | 09/08/2020 (month/day/year) | +| MindSpore Version | 0.6.0-alpha |0.6.0-alpha | +| Dataset | Oxford-IIIT Pet | Oxford-IIIT Pet| +| Parameters (M) | 3.9 | 10.6 | +| FLOPs (M) | 140 | 590 | +| Accuracy (Top1) | 82.4 |86.9 | + +###### Comparison with other methods on Oxford-IIIT Pet + +|Model|FLOPs (M)|Latency (ms)*|Accuracy (Top1)| +|-|-|-|-| +|MobileNetV2-1x|300|28.2|78.5| +|Ghost-1x w\o SE|138|19.1|81.1| +|Ghost-1x|140|25.3|82.4| +|Ghost-600|590|-|86.9| + +*The latency is measured on Huawei Kirin 990 chip under single-threaded mode with batch size 1. + +# [Description of Random Situation](#contents) + +In dataset.py, we set the seed inside “create_dataset" function. We also use random seed in train.py. + +# [ModelZoo Homepage](#contents) + +Please check the official [homepage](https://gitee.com/mindspore/mindspore/tree/master/model_zoo). diff --git a/model_zoo/research/cv/ghostnet/eval.py b/model_zoo/research/cv/ghostnet/eval.py new file mode 100644 index 00000000000..7317934ec01 --- /dev/null +++ b/model_zoo/research/cv/ghostnet/eval.py @@ -0,0 +1,84 @@ +# 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. +# ============================================================================ +""" +eval. +""" +import os +import argparse +from mindspore import context +from mindspore import nn +from mindspore.train.model import Model +from mindspore.train.serialization import load_checkpoint, load_param_into_net +from mindspore.common import dtype as mstype +from src.dataset import create_dataset +from src.config import config_ascend, config_gpu +from src.ghostnet import ghostnet_1x, ghostnet_nose_1x +from src.ghostnet600 import ghostnet_600m + + +parser = argparse.ArgumentParser(description='Image classification') +parser.add_argument('--checkpoint_path', type=str, default=None, help='Checkpoint file path') +parser.add_argument('--dataset_path', type=str, default=None, help='Dataset path') +parser.add_argument('--platform', type=str, default=None, help='run platform') +parser.add_argument('--model', type=str, default=None, help='ghostnet') +args_opt = parser.parse_args() + + +if __name__ == '__main__': + config_platform = None + if args_opt.platform == "Ascend": + config_platform = config_ascend + device_id = int(os.getenv('DEVICE_ID')) + context.set_context(mode=context.GRAPH_MODE, device_target="Ascend", + device_id=device_id, save_graphs=False) + elif args_opt.platform == "GPU": + config_platform = config_gpu + context.set_context(mode=context.GRAPH_MODE, + device_target="GPU", save_graphs=False) + else: + raise ValueError("Unsupport platform.") + + loss = nn.SoftmaxCrossEntropyWithLogits( + is_grad=False, sparse=True, reduction='mean') + + if args_opt.model == 'ghostnet': + net = ghostnet_1x(num_classes=config_platform.num_classes) + elif args_opt.model == 'ghostnet_nose': + net = ghostnet_nose_1x(num_classes=config_platform.num_classes) + elif args_opt.model == 'ghostnet-600': + net = ghostnet_600m(num_classes=config_platform.num_classes) + + if args_opt.platform == "Ascend": + net.to_float(mstype.float16) + for _, cell in net.cells_and_names(): + if isinstance(cell, nn.Dense): + cell.to_float(mstype.float32) + + dataset = create_dataset(dataset_path=args_opt.dataset_path, + do_train=False, + config=config_platform, + platform=args_opt.platform, + batch_size=config_platform.batch_size, + model=args_opt.model) + step_size = dataset.get_dataset_size() + + if args_opt.checkpoint_path: + param_dict = load_checkpoint(args_opt.checkpoint_path) + load_param_into_net(net, param_dict) + net.set_train(False) + + model = Model(net, loss_fn=loss, metrics={'acc'}) + res = model.eval(dataset) + print("result:", res, "ckpt=", args_opt.checkpoint_path) diff --git a/model_zoo/research/cv/ghostnet/mindpsore_hub_conf.py b/model_zoo/research/cv/ghostnet/mindpsore_hub_conf.py new file mode 100644 index 00000000000..b76e9846924 --- /dev/null +++ b/model_zoo/research/cv/ghostnet/mindpsore_hub_conf.py @@ -0,0 +1,27 @@ +# 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. +# ============================================================================ +"""hub config.""" +from src.ghostnet import ghostnet_1x, ghostnet_nose_1x +from src.ghostnet600 import ghostnet_600m + + +def create_network(name, *args, **kwargs): + if name == 'ghostnet': + return ghostnet_1x(*args, **kwargs) + if name == 'ghostnet_nose': + return ghostnet_nose_1x(*args, **kwargs) + if name == 'ghostnet-600': + return ghostnet_600m(*args, **kwargs) + raise NotImplementedError(f"{name} is not implemented in the repo") diff --git a/model_zoo/research/cv/ghostnet/src/config.py b/model_zoo/research/cv/ghostnet/src/config.py new file mode 100644 index 00000000000..c5428ae53bc --- /dev/null +++ b/model_zoo/research/cv/ghostnet/src/config.py @@ -0,0 +1,54 @@ +# 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. +# ============================================================================ +""" +network config setting, will be used in train.py and eval.py +""" +from easydict import EasyDict as ed + +config_ascend = ed({ + "num_classes": 37, + "image_height": 224, + "image_width": 224, + "batch_size": 256, + "epoch_size": 200, + "warmup_epochs": 4, + "lr": 0.4, + "momentum": 0.9, + "weight_decay": 4e-5, + "label_smooth": 0.1, + "loss_scale": 1024, + "save_checkpoint": True, + "save_checkpoint_epochs": 1, + "keep_checkpoint_max": 200, + "save_checkpoint_path": "./checkpoint", +}) + +config_gpu = ed({ + "num_classes": 37, + "image_height": 224, + "image_width": 224, + "batch_size": 3, + "epoch_size": 370, + "warmup_epochs": 4, + "lr": 0.4, + "momentum": 0.9, + "weight_decay": 4e-5, + "label_smooth": 0.1, + "loss_scale": 1024, + "save_checkpoint": True, + "save_checkpoint_epochs": 1, + "keep_checkpoint_max": 500, + "save_checkpoint_path": "./checkpoint", +}) diff --git a/model_zoo/research/cv/ghostnet/src/dataset.py b/model_zoo/research/cv/ghostnet/src/dataset.py new file mode 100644 index 00000000000..1da50b2bf11 --- /dev/null +++ b/model_zoo/research/cv/ghostnet/src/dataset.py @@ -0,0 +1,110 @@ +# 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. +# ============================================================================ +""" +create train or eval dataset. +""" +import os +import mindspore.common.dtype as mstype +import mindspore.dataset.engine as de +import mindspore.dataset.transforms.vision.c_transforms as C +import mindspore.dataset.transforms.vision.py_transforms as P +import mindspore.dataset.transforms.c_transforms as C2 +from mindspore.dataset.transforms.vision import Inter + + +def create_dataset(dataset_path, do_train, config, platform, repeat_num=1, batch_size=100, model='ghsotnet'): + """ + create a train or eval dataset + + Args: + dataset_path(string): the path of dataset. + do_train(bool): whether dataset is used for train or eval. + repeat_num(int): the repeat times of dataset. Default: 1 + batch_size(int): the batch size of dataset. Default: 32 + + Returns: + dataset + """ + if platform == "Ascend": + rank_size = int(os.getenv("RANK_SIZE")) + rank_id = int(os.getenv("RANK_ID")) + if rank_size == 1: + ds = de.MindDataset( + dataset_path, num_parallel_workers=8, shuffle=True) + else: + ds = de.MindDataset(dataset_path, num_parallel_workers=8, shuffle=True, + num_shards=rank_size, shard_id=rank_id) + elif platform == "GPU": + if do_train: + from mindspore.communication.management import get_rank, get_group_size + ds = de.MindDataset(dataset_path, num_parallel_workers=8, shuffle=True, + num_shards=get_group_size(), shard_id=get_rank()) + else: + ds = de.MindDataset( + dataset_path, num_parallel_workers=8, shuffle=True) + else: + raise ValueError("Unsupport platform.") + + resize_height = config.image_height + buffer_size = 1000 + + # define map operations + resize_crop_op = C.RandomCropDecodeResize( + resize_height, scale=(0.08, 1.0), ratio=(0.75, 1.333)) + horizontal_flip_op = C.RandomHorizontalFlip(prob=0.5) + + color_op = C.RandomColorAdjust( + brightness=0.4, contrast=0.4, saturation=0.4) + rescale_op = C.Rescale(1/255.0, 0) + normalize_op = C.Normalize( + mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + change_swap_op = C.HWC2CHW() + + # define python operations + decode_p = P.Decode() + if model == 'ghostnet-600': + s = 274 + c = 240 + else: + s = 256 + c = 224 + resize_p = P.Resize(s, interpolation=Inter.BICUBIC) + center_crop_p = P.CenterCrop(c) + totensor = P.ToTensor() + normalize_p = P.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)) + composeop = P.ComposeOp( + [decode_p, resize_p, center_crop_p, totensor, normalize_p]) + if do_train: + trans = [resize_crop_op, horizontal_flip_op, color_op, + rescale_op, normalize_op, change_swap_op] + else: + trans = composeop() + type_cast_op = C2.TypeCast(mstype.int32) + + ds = ds.map(input_columns="image", operations=trans, + num_parallel_workers=8) + ds = ds.map(input_columns="label_list", + operations=type_cast_op, num_parallel_workers=8) + + # apply shuffle operations + ds = ds.shuffle(buffer_size=buffer_size) + + # apply batch operations + ds = ds.batch(batch_size, drop_remainder=True) + + # apply dataset repeat operation + ds = ds.repeat(repeat_num) + + return ds diff --git a/model_zoo/research/cv/ghostnet/src/ghostnet.py b/model_zoo/research/cv/ghostnet/src/ghostnet.py new file mode 100644 index 00000000000..14e8613f83a --- /dev/null +++ b/model_zoo/research/cv/ghostnet/src/ghostnet.py @@ -0,0 +1,490 @@ +# 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. +# ============================================================================ +"""GhostNet model define""" +from functools import partial +import math +import numpy as np +import mindspore.nn as nn +from mindspore.ops import operations as P +from mindspore import Tensor + + +__all__ = ['ghostnet'] + + +def _make_divisible(x, divisor=4): + return int(np.ceil(x * 1. / divisor) * divisor) + + +class MyHSigmoid(nn.Cell): + """ + Hard Sigmoid definition. + + Args: + + Returns: + Tensor, output tensor. + + Examples: + >>> MyHSigmoid() + """ + + def __init__(self): + super(MyHSigmoid, self).__init__() + self.relu6 = nn.ReLU6() + + def construct(self, x): + return self.relu6(x + 3.) * 0.16666667 + + +class Activation(nn.Cell): + """ + Activation definition. + + Args: + act_func(string): activation name. + + Returns: + Tensor, output tensor. + """ + + def __init__(self, act_func): + super(Activation, self).__init__() + if act_func == 'relu': + self.act = nn.ReLU() + elif act_func == 'relu6': + self.act = nn.ReLU6() + elif act_func in ('hsigmoid', 'hard_sigmoid'): + self.act = MyHSigmoid() + elif act_func in ('hswish', 'hard_swish'): + self.act = nn.HSwish() + else: + raise NotImplementedError + + def construct(self, x): + return self.act(x) + + +class GlobalAvgPooling(nn.Cell): + """ + Global avg pooling definition. + + Args: + + Returns: + Tensor, output tensor. + + Examples: + >>> GlobalAvgPooling() + """ + + def __init__(self, keep_dims=False): + super(GlobalAvgPooling, self).__init__() + self.mean = P.ReduceMean(keep_dims=keep_dims) + + def construct(self, x): + x = self.mean(x, (2, 3)) + return x + + +class SE(nn.Cell): + """ + SE warpper definition. + + Args: + num_out (int): Output channel. + ratio (int): middle output ratio. + + Returns: + Tensor, output tensor. + + Examples: + >>> SE(4) + """ + + def __init__(self, num_out, ratio=4): + super(SE, self).__init__() + num_mid = _make_divisible(num_out // ratio) + self.pool = GlobalAvgPooling(keep_dims=True) + self.conv_reduce = nn.Conv2d(in_channels=num_out, out_channels=num_mid, + kernel_size=1, has_bias=True, pad_mode='pad') + self.act1 = Activation('relu') + self.conv_expand = nn.Conv2d(in_channels=num_mid, out_channels=num_out, + kernel_size=1, has_bias=True, pad_mode='pad') + self.act2 = Activation('hsigmoid') + self.mul = P.Mul() + + def construct(self, x): + out = self.pool(x) + out = self.conv_reduce(out) + out = self.act1(out) + out = self.conv_expand(out) + out = self.act2(out) + out = self.mul(x, out) + return out + + +class ConvUnit(nn.Cell): + """ + ConvUnit warpper definition. + + Args: + num_in (int): Input channel. + num_out (int): Output channel. + kernel_size (int): Input kernel size. + stride (int): Stride size. + padding (int): Padding number. + num_groups (int): Output num group. + use_act (bool): Used activation or not. + act_type (string): Activation type. + + Returns: + Tensor, output tensor. + + Examples: + >>> ConvUnit(3, 3) + """ + + def __init__(self, num_in, num_out, kernel_size=1, stride=1, padding=0, num_groups=1, + use_act=True, act_type='relu'): + super(ConvUnit, self).__init__() + self.conv = nn.Conv2d(in_channels=num_in, + out_channels=num_out, + kernel_size=kernel_size, + stride=stride, + padding=padding, + group=num_groups, + has_bias=False, + pad_mode='pad') + self.bn = nn.BatchNorm2d(num_out) + self.use_act = use_act + self.act = Activation(act_type) if use_act else None + + def construct(self, x): + out = self.conv(x) + out = self.bn(out) + if self.use_act: + out = self.act(out) + return out + + +class GhostModule(nn.Cell): + """ + GhostModule warpper definition. + + Args: + num_in (int): Input channel. + num_out (int): Output channel. + kernel_size (int): Input kernel size. + stride (int): Stride size. + padding (int): Padding number. + ratio (int): Reduction ratio. + dw_size (int): kernel size of cheap operation. + use_act (bool): Used activation or not. + act_type (string): Activation type. + + Returns: + Tensor, output tensor. + + Examples: + >>> GhostModule(3, 3) + """ + + def __init__(self, num_in, num_out, kernel_size=1, stride=1, padding=0, ratio=2, dw_size=3, + use_act=True, act_type='relu'): + super(GhostModule, self).__init__() + init_channels = math.ceil(num_out / ratio) + new_channels = init_channels * (ratio - 1) + + self.primary_conv = ConvUnit(num_in, init_channels, kernel_size=kernel_size, stride=stride, padding=padding, + num_groups=1, use_act=use_act, act_type='relu') + self.cheap_operation = ConvUnit(init_channels, new_channels, kernel_size=dw_size, stride=1, padding=dw_size//2, + num_groups=init_channels, use_act=use_act, act_type='relu') + self.concat = P.Concat(axis=1) + + def construct(self, x): + x1 = self.primary_conv(x) + x2 = self.cheap_operation(x1) + return self.concat((x1, x2)) + + +class GhostBottleneck(nn.Cell): + """ + GhostBottleneck warpper definition. + + Args: + num_in (int): Input channel. + num_mid (int): Middle channel. + num_out (int): Output channel. + kernel_size (int): Input kernel size. + stride (int): Stride size. + act_type (str): Activation type. + use_se (bool): Use SE warpper or not. + + Returns: + Tensor, output tensor. + + Examples: + >>> GhostBottleneck(16, 3, 1, 1) + """ + + def __init__(self, num_in, num_mid, num_out, kernel_size, stride=1, act_type='relu', use_se=False): + super(GhostBottleneck, self).__init__() + self.ghost1 = GhostModule(num_in, num_mid, kernel_size=1, + stride=1, padding=0, act_type=act_type) + + self.use_dw = stride > 1 + self.dw = None + if self.use_dw: + self.dw = ConvUnit(num_mid, num_mid, kernel_size=kernel_size, stride=stride, + padding=self._get_pad(kernel_size), act_type=act_type, num_groups=num_mid, use_act=False) + + self.use_se = use_se + if use_se: + self.se = SE(num_mid) + + self.ghost2 = GhostModule(num_mid, num_out, kernel_size=1, stride=1, + padding=0, act_type=act_type, use_act=False) + + self.down_sample = False + if num_in != num_out or stride != 1: + self.down_sample = True + self.shortcut = None + if self.down_sample: + self.shortcut = nn.SequentialCell([ + ConvUnit(num_in, num_in, kernel_size=kernel_size, stride=stride, + padding=self._get_pad(kernel_size), num_groups=num_in, use_act=False), + ConvUnit(num_in, num_out, kernel_size=1, stride=1, + padding=0, num_groups=1, use_act=False), + ]) + self.add = P.TensorAdd() + + def construct(self, x): + r"""construct of ghostnet""" + shortcut = x + out = self.ghost1(x) + if self.use_dw: + out = self.dw(out) + if self.use_se: + out = self.se(out) + out = self.ghost2(out) + if self.down_sample: + shortcut = self.shortcut(shortcut) + out = self.add(shortcut, out) + return out + + def _get_pad(self, kernel_size): + """set the padding number""" + pad = 0 + if kernel_size == 1: + pad = 0 + elif kernel_size == 3: + pad = 1 + elif kernel_size == 5: + pad = 2 + elif kernel_size == 7: + pad = 3 + else: + raise NotImplementedError + return pad + + +class GhostNet(nn.Cell): + """ + GhostNet architecture. + + Args: + model_cfgs (Cell): number of classes. + num_classes (int): Output number classes. + multiplier (int): Channels multiplier for round to 8/16 and others. Default is 1. + final_drop (float): Dropout number. + round_nearest (list): Channel round to . Default is 8. + Returns: + Tensor, output tensor. + + Examples: + >>> GhostNet(num_classes=1000) + """ + + def __init__(self, model_cfgs, num_classes=1000, multiplier=1., final_drop=0., round_nearest=8): + super(GhostNet, self).__init__() + self.cfgs = model_cfgs['cfg'] + self.inplanes = 16 + first_conv_in_channel = 3 + first_conv_out_channel = _make_divisible(multiplier * self.inplanes) + + self.conv_stem = nn.Conv2d(in_channels=first_conv_in_channel, + out_channels=first_conv_out_channel, + kernel_size=3, padding=1, stride=2, + has_bias=False, pad_mode='pad') + self.bn1 = nn.BatchNorm2d(first_conv_out_channel) + self.act1 = Activation('relu') + + self.blocks = [] + for layer_cfg in self.cfgs: + self.blocks.append(self._make_layer(kernel_size=layer_cfg[0], + exp_ch=_make_divisible( + multiplier * layer_cfg[1]), + out_channel=_make_divisible( + multiplier * layer_cfg[2]), + use_se=layer_cfg[3], + act_func=layer_cfg[4], + stride=layer_cfg[5])) + output_channel = _make_divisible( + multiplier * model_cfgs["cls_ch_squeeze"]) + self.blocks.append(ConvUnit(_make_divisible(multiplier * self.cfgs[-1][2]), output_channel, + kernel_size=1, stride=1, padding=0, num_groups=1, use_act=True)) + self.blocks = nn.SequentialCell(self.blocks) + + self.global_pool = GlobalAvgPooling(keep_dims=True) + self.conv_head = nn.Conv2d(in_channels=output_channel, + out_channels=model_cfgs['cls_ch_expand'], + kernel_size=1, padding=0, stride=1, + has_bias=True, pad_mode='pad') + self.act2 = Activation('relu') + self.squeeze = P.Flatten() + self.final_drop = final_drop + if self.final_drop > 0: + self.dropout = nn.Dropout(self.final_drop) + + self.classifier = nn.Dense( + model_cfgs['cls_ch_expand'], num_classes, has_bias=True) + + self._initialize_weights() + + def construct(self, x): + r"""construct of GhostNet""" + x = self.conv_stem(x) + x = self.bn1(x) + x = self.act1(x) + x = self.blocks(x) + x = self.global_pool(x) + x = self.conv_head(x) + x = self.act2(x) + x = self.squeeze(x) + if self.final_drop > 0: + x = self.dropout(x) + x = self.classifier(x) + return x + + def _make_layer(self, kernel_size, exp_ch, out_channel, use_se, act_func, stride=1): + mid_planes = exp_ch + out_planes = out_channel + layer = GhostBottleneck(self.inplanes, mid_planes, out_planes, + kernel_size, stride=stride, act_type=act_func, use_se=use_se) + self.inplanes = out_planes + return layer + + def _initialize_weights(self): + """ + Initialize weights. + + Args: + + Returns: + None. + + Examples: + >>> _initialize_weights() + """ + self.init_parameters_data() + for _, m in self.cells_and_names(): + if isinstance(m, (nn.Conv2d)): + n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + m.weight.set_parameter_data(Tensor(np.random.normal(0, np.sqrt(2. / n), + m.weight.data.shape).astype("float32"))) + if m.bias is not None: + m.bias.set_parameter_data( + Tensor(np.zeros(m.bias.data.shape, dtype="float32"))) + elif isinstance(m, nn.BatchNorm2d): + m.gamma.set_parameter_data( + Tensor(np.ones(m.gamma.data.shape, dtype="float32"))) + m.beta.set_parameter_data( + Tensor(np.zeros(m.beta.data.shape, dtype="float32"))) + elif isinstance(m, nn.Dense): + m.weight.set_parameter_data(Tensor(np.random.normal( + 0, 0.01, m.weight.data.shape).astype("float32"))) + if m.bias is not None: + m.bias.set_parameter_data( + Tensor(np.zeros(m.bias.data.shape, dtype="float32"))) + + +def ghostnet(model_name, **kwargs): + """ + Constructs a GhostNet model + """ + model_cfgs = { + "1x": { + "cfg": [ + # k, exp, c, se, nl, s, + # stage1 + [3, 16, 16, False, 'relu', 1], + # stage2 + [3, 48, 24, False, 'relu', 2], + [3, 72, 24, False, 'relu', 1], + # stage3 + [5, 72, 40, True, 'relu', 2], + [5, 120, 40, True, 'relu', 1], + # stage4 + [3, 240, 80, False, 'relu', 2], + [3, 200, 80, False, 'relu', 1], + [3, 184, 80, False, 'relu', 1], + [3, 184, 80, False, 'relu', 1], + [3, 480, 112, True, 'relu', 1], + [3, 672, 112, True, 'relu', 1], + # stage5 + [5, 672, 160, True, 'relu', 2], + [5, 960, 160, False, 'relu', 1], + [5, 960, 160, True, 'relu', 1], + [5, 960, 160, False, 'relu', 1], + [5, 960, 160, True, 'relu', 1]], + "cls_ch_squeeze": 960, + "cls_ch_expand": 1280, + }, + + "nose_1x": { + "cfg": [ + # k, exp, c, se, nl, s, + # stage1 + [3, 16, 16, False, 'relu', 1], + # stage2 + [3, 48, 24, False, 'relu', 2], + [3, 72, 24, False, 'relu', 1], + # stage3 + [5, 72, 40, False, 'relu', 2], + [5, 120, 40, False, 'relu', 1], + # stage4 + [3, 240, 80, False, 'relu', 2], + [3, 200, 80, False, 'relu', 1], + [3, 184, 80, False, 'relu', 1], + [3, 184, 80, False, 'relu', 1], + [3, 480, 112, False, 'relu', 1], + [3, 672, 112, False, 'relu', 1], + # stage5 + [5, 672, 160, False, 'relu', 2], + [5, 960, 160, False, 'relu', 1], + [5, 960, 160, False, 'relu', 1], + [5, 960, 160, False, 'relu', 1], + [5, 960, 160, False, 'relu', 1]], + "cls_ch_squeeze": 960, + "cls_ch_expand": 1280, + } + } + + return GhostNet(model_cfgs[model_name], **kwargs) + + +ghostnet_1x = partial(ghostnet, model_name="1x", final_drop=0.8) +ghostnet_nose_1x = partial(ghostnet, model_name="nose_1x", final_drop=0.8) diff --git a/model_zoo/research/cv/ghostnet/src/ghostnet600.py b/model_zoo/research/cv/ghostnet/src/ghostnet600.py new file mode 100644 index 00000000000..66e673cb8e4 --- /dev/null +++ b/model_zoo/research/cv/ghostnet/src/ghostnet600.py @@ -0,0 +1,466 @@ +# 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. +# ============================================================================ +"""GhostNet model define""" +from functools import partial +import math +import numpy as np +import mindspore.nn as nn +from mindspore.ops import operations as P +from mindspore import Tensor + + +__all__ = ['ghostnet_600m'] + + +def _make_divisible(v, divisor=8, min_value=None): + min_value = min_value or 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 + + +def _round_channels(channels, multiplier=1.0, divisor=8, channel_min=None): + """Round number of filters based on depth multiplier.""" + if not multiplier: + return channels + channels *= multiplier + return _make_divisible(channels, divisor, channel_min) + + +class MyHSigmoid(nn.Cell): + def __init__(self): + super(MyHSigmoid, self).__init__() + self.relu6 = nn.ReLU6() + + def construct(self, x): + return self.relu6(x + 3.) / 6. + + +class Activation(nn.Cell): + """ + Activation definition. + + Args: + act_func(string): activation name. + + Returns: + Tensor, output tensor. + """ + + def __init__(self, act_func): + super(Activation, self).__init__() + if act_func == 'relu': + self.act = nn.ReLU() + elif act_func == 'relu6': + self.act = nn.ReLU6() + elif act_func in ('hsigmoid', 'hard_sigmoid'): + self.act = MyHSigmoid() # nn.HSigmoid() + elif act_func in ('hswish', 'hard_swish'): + self.act = nn.HSwish() + else: + raise NotImplementedError + + def construct(self, x): + return self.act(x) + + +class GlobalAvgPooling(nn.Cell): + """ + Global avg pooling definition. + + Args: + + Returns: + Tensor, output tensor. + + Examples: + >>> GlobalAvgPooling() + """ + + def __init__(self, keep_dims=False): + super(GlobalAvgPooling, self).__init__() + self.mean = P.ReduceMean(keep_dims=keep_dims) + + def construct(self, x): + x = self.mean(x, (2, 3)) + return x + + +class SE(nn.Cell): + """ + SE warpper definition. + + Args: + num_out (int): Output channel. + ratio (int): middle output ratio. + + Returns: + Tensor, output tensor. + + Examples: + >>> SE(4) + """ + + def __init__(self, num_out, ratio=4): + super(SE, self).__init__() + num_mid = _make_divisible(num_out // ratio) + self.pool = GlobalAvgPooling(keep_dims=True) + self.conv_reduce = nn.Conv2d(in_channels=num_out, out_channels=num_mid, + kernel_size=1, has_bias=True, pad_mode='pad') + self.act1 = Activation('relu') + self.conv_expand = nn.Conv2d(in_channels=num_mid, out_channels=num_out, + kernel_size=1, has_bias=True, pad_mode='pad') + self.act2 = Activation('hsigmoid') + self.mul = P.Mul() + + def construct(self, x): + out = self.pool(x) + out = self.conv_reduce(out) + out = self.act1(out) + out = self.conv_expand(out) + out = self.act2(out) + out = self.mul(x, out) + return out + + +class ConvUnit(nn.Cell): + """ + ConvUnit warpper definition. + + Args: + num_in (int): Input channel. + num_out (int): Output channel. + kernel_size (int): Input kernel size. + stride (int): Stride size. + padding (int): Padding number. + num_groups (int): Output num group. + use_act (bool): Used activation or not. + act_type (string): Activation type. + + Returns: + Tensor, output tensor. + + Examples: + >>> ConvUnit(3, 3) + """ + + def __init__(self, num_in, num_out, kernel_size=1, stride=1, padding=0, num_groups=1, + use_act=True, act_type='relu'): + super(ConvUnit, self).__init__() + self.conv = nn.Conv2d(in_channels=num_in, + out_channels=num_out, + kernel_size=kernel_size, + stride=stride, + padding=padding, + group=num_groups, + has_bias=False, + pad_mode='pad') + self.bn = nn.BatchNorm2d(num_out, momentum=0.9) + self.use_act = use_act + self.act = Activation(act_type) if use_act else None + + def construct(self, x): + out = self.conv(x) + out = self.bn(out) + if self.use_act: + out = self.act(out) + return out + + +class GhostModule(nn.Cell): + """ + GhostModule warpper definition. + + Args: + num_in (int): Input channel. + num_out (int): Output channel. + kernel_size (int): Input kernel size. + stride (int): Stride size. + padding (int): Padding number. + use_act (bool): Used activation or not. + act_type (string): Activation type. + + Returns: + Tensor, output tensor. + + Examples: + >>> GhostModule(3, 3) + """ + + def __init__(self, num_in, num_out, kernel_size=1, stride=1, padding=0, ratio=2, dw_size=3, + use_act=True, act_type='relu'): + super(GhostModule, self).__init__() + init_channels = math.ceil(num_out / ratio) + new_channels = init_channels * (ratio - 1) + + self.primary_conv = ConvUnit(num_in, init_channels, kernel_size=kernel_size, stride=stride, padding=padding, + num_groups=1, use_act=use_act, act_type=act_type) + self.cheap_operation = ConvUnit(init_channels, new_channels, kernel_size=dw_size, stride=1, padding=dw_size//2, + num_groups=init_channels, use_act=use_act, act_type=act_type) + self.concat = P.Concat(axis=1) + + def construct(self, x): + x1 = self.primary_conv(x) + x2 = self.cheap_operation(x1) + return self.concat((x1, x2)) + + +class GhostBottleneck(nn.Cell): + """ + GhostBottleneck warpper definition. + + Args: + num_in (int): Input channel. + num_mid (int): Middle channel. + num_out (int): Output channel. + kernel_size (int): Input kernel size. + stride (int): Stride size. + act_type (str): Activation type. + use_se (bool): Use SE warpper or not. + + Returns: + Tensor, output tensor. + + Examples: + >>> GhostBottleneck(16, 3, 1, 1) + """ + + def __init__(self, num_in, num_mid, num_out, kernel_size, stride=1, act_type='relu', use_se=False): + super(GhostBottleneck, self).__init__() + self.ghost1 = GhostModule(num_in, num_mid, kernel_size=1, + stride=1, padding=0, act_type=act_type) + + self.use_dw = stride > 1 + self.dw = None + if self.use_dw: + self.dw = ConvUnit(num_mid, num_mid, kernel_size=kernel_size, stride=stride, + padding=self._get_pad(kernel_size), act_type=act_type, num_groups=num_mid, use_act=False) + if not use_se: + self.use_se = use_se + else: + self.use_se = True + self.se = SE(num_mid, ratio=use_se) + + self.ghost2 = GhostModule(num_mid, num_out, kernel_size=1, stride=1, + padding=0, act_type=act_type, use_act=False) + + self.down_sample = False + if num_in != num_out or stride != 1: + self.down_sample = True + self.shortcut = None + if self.down_sample: + self.shortcut = nn.SequentialCell([ + ConvUnit(num_in, num_in, kernel_size=kernel_size, stride=stride, + padding=self._get_pad(kernel_size), num_groups=num_in, use_act=False), + ConvUnit(num_in, num_out, kernel_size=1, stride=1, + padding=0, num_groups=1, use_act=False), + ]) + self.add = P.TensorAdd() + + def construct(self, x): + """construct""" + shortcut = x + out = self.ghost1(x) + if self.use_dw: + out = self.dw(out) + if self.use_se: + out = self.se(out) + out = self.ghost2(out) + if self.down_sample: + shortcut = self.shortcut(shortcut) + out = self.add(shortcut, out) + return out + + def _get_pad(self, kernel_size): + """set the padding number""" + pad = 0 + if kernel_size == 1: + pad = 0 + elif kernel_size == 3: + pad = 1 + elif kernel_size == 5: + pad = 2 + elif kernel_size == 7: + pad = 3 + else: + raise NotImplementedError + return pad + + +class GhostNet(nn.Cell): + """ + GhostNet architecture. + + Args: + model_cfgs (Cell): number of classes. + num_classes (int): Output number classes. + multiplier (int): Channels multiplier for round to 8/16 and others. Default is 1. + final_drop (float): Dropout number. + round_nearest (list): Channel round to . Default is 8. + Returns: + Tensor, output tensor. + + Examples: + >>> GhostNet(num_classes=1000) + """ + + def __init__(self, model_cfgs, num_classes=1000, multiplier=1., final_drop=0., round_nearest=8): + super(GhostNet, self).__init__() + self.cfgs = model_cfgs['cfg'] + self.inplanes = 16 + first_conv_in_channel = 3 + first_conv_out_channel = _round_channels( + self.inplanes, multiplier, divisor=4) + self.inplanes = first_conv_out_channel + self.conv_stem = nn.Conv2d(in_channels=first_conv_in_channel, + out_channels=first_conv_out_channel, + kernel_size=3, padding=1, stride=2, + has_bias=False, pad_mode='pad') + self.bn1 = nn.BatchNorm2d(first_conv_out_channel, momentum=0.9) + self.act1 = Activation('hswish') + + self.blocks = [] + for layer_cfg in self.cfgs: + #print (layer_cfg) + self.blocks.append(self._make_layer(kernel_size=layer_cfg[0], + exp_ch=_make_divisible( + self.inplanes * layer_cfg[3]), + out_channel=_round_channels( + layer_cfg[2], multiplier, 4), + use_se=layer_cfg[4], + act_func=layer_cfg[5], + stride=layer_cfg[6])) + output_channel = _make_divisible( + multiplier * model_cfgs["cls_ch_squeeze"]) + self.blocks.append(ConvUnit(_make_divisible(multiplier * self.cfgs[-1][2]), output_channel, + kernel_size=1, stride=1, padding=0, num_groups=1, use_act=True, act_type='hswish')) + self.blocks = nn.SequentialCell(self.blocks) + + self.global_pool = GlobalAvgPooling(keep_dims=True) + self.conv_head = nn.Conv2d(in_channels=output_channel, + out_channels=model_cfgs['cls_ch_expand'], + kernel_size=1, padding=0, stride=1, + has_bias=True, pad_mode='pad') + self.act2 = Activation('hswish') + self.squeeze = P.Squeeze(axis=(2, 3)) + self.final_drop = final_drop + if self.final_drop > 0: + self.dropout = nn.Dropout(self.final_drop) + + self.classifier = nn.Dense( + model_cfgs['cls_ch_expand'], num_classes, has_bias=True) + + self._initialize_weights() + + def construct(self, x): + r"""construct of GhostNet""" + x = self.conv_stem(x) + x = self.bn1(x) + x = self.act1(x) + x = self.blocks(x) + x = self.global_pool(x) + x = self.conv_head(x) + x = self.act2(x) + x = self.squeeze(x) + if self.final_drop > 0: + x = self.dropout(x) + x = self.classifier(x) + return x + + def _make_layer(self, kernel_size, exp_ch, out_channel, use_se, act_func, stride=1): + mid_planes = exp_ch + out_planes = out_channel + layer = GhostBottleneck(self.inplanes, mid_planes, out_planes, + kernel_size, stride=stride, act_type=act_func, use_se=use_se) + self.inplanes = out_planes + return layer + + def _initialize_weights(self): + """ + Initialize weights. + + Args: + + Returns: + None. + + Examples: + >>> _initialize_weights() + """ + self.init_parameters_data() + for _, m in self.cells_and_names(): + if isinstance(m, (nn.Conv2d)): + n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + m.weight.set_parameter_data(Tensor(np.random.normal(0, np.sqrt(2. / n), + m.weight.data.shape).astype("float32"))) + if m.bias is not None: + m.bias.set_parameter_data( + Tensor(np.zeros(m.bias.data.shape, dtype="float32"))) + elif isinstance(m, nn.BatchNorm2d): + m.gamma.set_parameter_data( + Tensor(np.ones(m.gamma.data.shape, dtype="float32"))) + m.beta.set_parameter_data( + Tensor(np.zeros(m.beta.data.shape, dtype="float32"))) + elif isinstance(m, nn.Dense): + m.weight.set_parameter_data(Tensor(np.random.normal( + 0, 0.01, m.weight.data.shape).astype("float32"))) + if m.bias is not None: + m.bias.set_parameter_data( + Tensor(np.zeros(m.bias.data.shape, dtype="float32"))) + + +def ghostnet(model_name, **kwargs): + """ + Constructs a GhostNet model + """ + model_cfgs = { + "600M": { + "cfg": [ + # k, exp, c, exp_ratio, se, nl, s, + # stage1 + [3, 16, 16, 1, 10, 'hswish', 1], + [3, 48, 24, 3, 10, 'hswish', 2], + # stage2 + [3, 72, 24, 3, 10, 'hswish', 1], + [5, 72, 40, 3, 10, 'hswish', 2], + # stage3 + [3, 120, 40, 3, 10, 'hswish', 1], + [3, 120, 40, 3, 10, 'hswish', 1], + [3, 240, 80, 6, 10, 'hswish', 2], + # stage4 + [3, 200, 80, 2.5, 10, 'hswish', 1], + [3, 200, 80, 2.5, 10, 'hswish', 1], + [3, 200, 80, 2.5, 10, 'hswish', 1], + [3, 480, 112, 6, 10, 'hswish', 1], + [3, 672, 112, 6, 10, 'hswish', 1], + [3, 672, 112, 6, 10, 'hswish', 1], + [5, 672, 160, 6, 10, 'hswish', 2], + # stage5 + [3, 960, 160, 6, 10, 'hswish', 1], + [3, 960, 160, 6, 10, 'hswish', 1], + [3, 960, 160, 6, 10, 'hswish', 1], + [3, 960, 160, 6, 10, 'hswish', 1], + [3, 960, 160, 6, 10, 'hswish', 1]], + "cls_ch_squeeze": 960, + "cls_ch_expand": 1400, + } + } + return GhostNet(model_cfgs[model_name], **kwargs) + + +ghostnet_600m = partial(ghostnet, model_name="600M", + multiplier=1.75, final_drop=0.8) diff --git a/model_zoo/research/cv/ghostnet/src/launch.py b/model_zoo/research/cv/ghostnet/src/launch.py new file mode 100644 index 00000000000..003221310e1 --- /dev/null +++ b/model_zoo/research/cv/ghostnet/src/launch.py @@ -0,0 +1,165 @@ +# 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. +# ============================================================================ +"""launch train script""" +import os +import sys +import json +import subprocess +import shutil +from argparse import ArgumentParser + + +def parse_args(): + """ + parse args . + + Args: + + Returns: + args. + + Examples: + >>> parse_args() + """ + parser = ArgumentParser(description="mindspore distributed training launch " + "helper utilty that will spawn up " + "multiple distributed processes") + parser.add_argument("--nproc_per_node", type=int, default=1, + help="The number of processes to launch on each node, " + "for D training, this is recommended to be set " + "to the number of D in your system so that " + "each process can be bound to a single D.") + parser.add_argument("--visible_devices", type=str, default="0,1,2,3,4,5,6,7", + help="will use the visible devices sequentially") + parser.add_argument("--server_id", type=str, default="", + help="server ip") + parser.add_argument("--training_script", type=str, + help="The full path to the single D training " + "program/script to be launched in parallel, " + "followed by all the arguments for the " + "training script") + # rest from the training program + args, unknown = parser.parse_known_args() + args.training_script_args = unknown + return args + + +def main(): + print("start", __file__) + args = parse_args() + print(args) + visible_devices = args.visible_devices.split(',') + assert os.path.isfile(args.training_script) + assert len(visible_devices) >= args.nproc_per_node + print('visible_devices:{}'.format(visible_devices)) + if not args.server_id: + print('pleaser input server ip!!!') + exit(0) + print('server_id:{}'.format(args.server_id)) + + # construct hccn_table + hccn_configs = open('/etc/hccn.conf', 'r').readlines() + device_ips = {} + for hccn_item in hccn_configs: + hccn_item = hccn_item.strip() + if hccn_item.startswith('address_'): + device_id, device_ip = hccn_item.split('=') + device_id = device_id.split('_')[1] + device_ips[device_id] = device_ip + print('device_id:{}, device_ip:{}'.format(device_id, device_ip)) + hccn_table = {} + hccn_table['board_id'] = '0x0000' + hccn_table['chip_info'] = '910' + hccn_table['deploy_mode'] = 'lab' + hccn_table['group_count'] = '1' + hccn_table['group_list'] = [] + instance_list = [] + usable_dev = '' + for instance_id in range(args.nproc_per_node): + instance = {} + instance['devices'] = [] + device_id = visible_devices[instance_id] + device_ip = device_ips[device_id] + usable_dev += str(device_id) + instance['devices'].append({ + 'device_id': device_id, + 'device_ip': device_ip, + }) + instance['rank_id'] = str(instance_id) + instance['server_id'] = args.server_id + instance_list.append(instance) + hccn_table['group_list'].append({ + 'device_num': str(args.nproc_per_node), + 'server_num': '1', + 'group_name': '', + 'instance_count': str(args.nproc_per_node), + 'instance_list': instance_list, + }) + hccn_table['para_plane_nic_location'] = 'device' + hccn_table['para_plane_nic_name'] = [] + for instance_id in range(args.nproc_per_node): + eth_id = visible_devices[instance_id] + hccn_table['para_plane_nic_name'].append('eth{}'.format(eth_id)) + hccn_table['para_plane_nic_num'] = str(args.nproc_per_node) + hccn_table['status'] = 'completed' + + # save hccn_table to file + table_path = os.getcwd() + if not os.path.exists(table_path): + os.mkdir(table_path) + table_fn = os.path.join(table_path, + 'rank_table_{}p_{}_{}.json'.format(args.nproc_per_node, usable_dev, args.server_id)) + with open(table_fn, 'w') as table_fp: + json.dump(hccn_table, table_fp, indent=4) + sys.stdout.flush() + + # spawn the processes + processes = [] + cmds = [] + log_files = [] + env = os.environ.copy() + env['RANK_SIZE'] = str(args.nproc_per_node) + cur_path = os.getcwd() + for rank_id in range(0, args.nproc_per_node): + os.chdir(cur_path) + device_id = visible_devices[rank_id] + device_dir = os.path.join(cur_path, 'device{}'.format(rank_id)) + env['RANK_ID'] = str(rank_id) + env['DEVICE_ID'] = str(device_id) + if args.nproc_per_node > 1: + env['RANK_TABLE_FILE'] = table_fn + if os.path.exists(device_dir): + shutil.rmtree(device_dir) + os.mkdir(device_dir) + os.chdir(device_dir) + cmd = [sys.executable, '-u'] + cmd.append(args.training_script) + cmd.extend(args.training_script_args) + log_file = open( + '{dir}/log{id}.log'.format(dir=device_dir, id=rank_id), 'w') + process = subprocess.Popen( + cmd, stdout=log_file, stderr=log_file, env=env) + processes.append(process) + cmds.append(cmd) + log_files.append(log_file) + for process, cmd, log_file in zip(processes, cmds, log_files): + process.wait() + if process.returncode != 0: + raise subprocess.CalledProcessError(returncode=process, cmd=cmd) + log_file.close() + + +if __name__ == "__main__": + main() diff --git a/model_zoo/research/cv/ghostnet/src/lr_generator.py b/model_zoo/research/cv/ghostnet/src/lr_generator.py new file mode 100644 index 00000000000..9c6a591ae2a --- /dev/null +++ b/model_zoo/research/cv/ghostnet/src/lr_generator.py @@ -0,0 +1,55 @@ +# 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 generator""" +import math +import numpy as np + + +def get_lr(global_step, lr_init, lr_end, lr_max, warmup_epochs, total_epochs, steps_per_epoch): + """ + generate learning rate array + + Args: + global_step(int): total steps of the training + lr_init(float): init learning rate + lr_end(float): end learning rate + lr_max(float): max learning rate + warmup_epochs(int): number of warmup epochs + total_epochs(int): total epoch of training + steps_per_epoch(int): steps of one epoch + + Returns: + np.array, learning rate array + """ + lr_each_step = [] + total_steps = steps_per_epoch * total_epochs + warmup_steps = steps_per_epoch * warmup_epochs + for i in range(total_steps): + if i < warmup_steps: + lr = lr_init + (lr_max - lr_init) * i / warmup_steps + else: + lr = lr_end + \ + (lr_max - lr_end) * \ + (1. + math.cos(math.pi * (i - warmup_steps) / + (total_steps - warmup_steps))) / 2. + if lr < 0.0: + lr = 0.0 + lr_each_step.append(lr) + + current_step = global_step + lr_each_step = np.array(lr_each_step).astype(np.float32) + learning_rate = lr_each_step[current_step:] + + return learning_rate diff --git a/model_zoo/research/cv/ghostnet_quant/Readme.md b/model_zoo/research/cv/ghostnet_quant/Readme.md new file mode 100644 index 00000000000..4525ac4cebe --- /dev/null +++ b/model_zoo/research/cv/ghostnet_quant/Readme.md @@ -0,0 +1,136 @@ +# Contents + +- [GhostNet Description](#ghostnet-description) +- [Quantization Description](#ghostnet-quantization-description) +- [Model Architecture](#model-architecture) +- [Dataset](#dataset) +- [Environment Requirements](#environment-requirements) +- [Script Description](#script-description) + - [Script and Sample Code](#script-and-sample-code) + - [Training Process](#training-process) + - [Evaluation Process](#evaluation-process) + - [Evaluation](#evaluation) +- [Model Description](#model-description) + - [Performance](#performance) + - [Training Performance](#evaluation-performance) + - [Inference Performance](#evaluation-performance) +- [Description of Random Situation](#description-of-random-situation) +- [ModelZoo Homepage](#modelzoo-homepage) + +# [GhostNet Description](#contents) + +The GhostNet architecture is based on an Ghost module structure which generate more features from cheap operations. Based on a set of intrinsic feature maps, a series of cheap operations are applied to generate many ghost feature maps that could fully reveal information underlying intrinsic features. + +[Paper](https://openaccess.thecvf.com/content_CVPR_2020/papers/Han_GhostNet_More_Features_From_Cheap_Operations_CVPR_2020_paper.pdf): Kai Han, Yunhe Wang, Qi Tian, Jianyuan Guo, Chunjing Xu, Chang Xu. GhostNet: More Features from Cheap Operations. CVPR 2020. + +# [Quantization Description](#contents) + +Quantization refers to techniques for performing computations and storing tensors at lower bitwidths than floating point precision. For 8bit quantization, we quantize the weights into [-128,127] and the activations into [0,255]. We finetune the model a few epochs after post-quantization to achieve better performance. + +# [Model architecture](#contents) + +The overall network architecture of GhostNet is show below: + +[Link](https://openaccess.thecvf.com/content_CVPR_2020/papers/Han_GhostNet_More_Features_From_Cheap_Operations_CVPR_2020_paper.pdf) + +# [Dataset](#contents) + +Dataset used: [Oxford-IIIT Pet](https://www.robots.ox.ac.uk/~vgg/data/pets/) + +- Dataset size: 7049 colorful images in 1000 classes + - Train: 3680 images + - Test: 3369 images +- Data format: RGB images. + - Note: Data will be processed in src/dataset.py + +# [Environment Requirements](#contents) + +- Hardware(Ascend/GPU) + - Prepare hardware environment with Ascend or GPU 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](http://10.90.67.50/mindspore/archive/20200506/OpenSource/me_vm_x86/) +- 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) + +# [Script description](#contents) + +## [Script and sample code](#contents) + +```python +├── GhostNet + ├── Readme.md # descriptions about GhostNet # shell script for evaluation with CPU, GPU or Ascend + ├── src + │ ├──config.py # parameter configuration + │ ├──dataset.py # creating dataset + │ ├──launch.py # start python script + │ ├──lr_generator.py # learning rate config + │ ├──ghostnet.py # GhostNet architecture + │ ├──quant.py # GhostNet quantization + ├── eval.py # evaluation script + ├── mindspore_hub_conf.py # export model for hub +``` + +## [Training process](#contents) +To Be Done + +## [Eval process](#contents) + +### Usage + +After installing MindSpore via the official website, you can start evaluation as follows: + + +### Launch + +``` +# infer example + + Ascend: python eval.py --dataset_path ~/Pets/test.mindrecord --platform Ascend --checkpoint_path [CHECKPOINT_PATH] + GPU: python eval.py --dataset_path ~/Pets/test.mindrecord --platform GPU --checkpoint_path [CHECKPOINT_PATH] +``` + +> checkpoint can be produced in training process. + +### Result + +``` +result: {'acc': 0.825} ckpt= ./ghostnet_1x_pets_int8.ckpt +``` + +# [Model Description](#contents) + +## [Performance](#contents) + +#### Evaluation Performance + +###### GhostNet on ImageNet2012 +| Parameters | | | +| -------------------------- | -------------------------------------- |---------------------------------- | +| Model Version | GhostNet |GhostNet-int8| +| uploaded Date | 09/08/2020 (month/day/year) ; | 09/08/2020 (month/day/year) | +| MindSpore Version | 0.6.0-alpha |0.6.0-alpha | +| Dataset | ImageNet2012 | ImageNet2012| +| Parameters (M) | 5.2 | / | +| FLOPs (M) | 142 | / | +| Accuracy (Top1) | 73.9 | w/o finetune:72.2, w finetune:73.6 | + +###### GhostNet on Oxford-IIIT Pet +| Parameters | | | +| -------------------------- | -------------------------------------- |---------------------------------- | +| Model Version | GhostNet |GhostNet-int8| +| uploaded Date | 09/08/2020 (month/day/year) ; | 09/08/2020 (month/day/year) | +| MindSpore Version | 0.6.0-alpha |0.6.0-alpha | +| Dataset | Oxford-IIIT Pet | Oxford-IIIT Pet| +| Parameters (M) | 3.9 | / | +| FLOPs (M) | 140 | / | +| Accuracy (Top1) | 82.4 | w/o finetune:81.66, w finetune:82.45 | + + +# [Description of Random Situation](#contents) + +In dataset.py, we set the seed inside “create_dataset" function. We also use random seed in train.py. + +# [ModelZoo Homepage](#contents) + +Please check the official [homepage](https://gitee.com/mindspore/mindspore/tree/master/model_zoo). diff --git a/model_zoo/research/cv/ghostnet_quant/eval.py b/model_zoo/research/cv/ghostnet_quant/eval.py new file mode 100644 index 00000000000..6aa301344ad --- /dev/null +++ b/model_zoo/research/cv/ghostnet_quant/eval.py @@ -0,0 +1,77 @@ +# 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. +# ============================================================================ +""" +eval. +""" +import os +import argparse +from mindspore import context +from mindspore import nn +from mindspore.train.model import Model +from mindspore.train.serialization import load_checkpoint, load_param_into_net +from mindspore.common import dtype as mstype +from src.dataset import create_dataset +from src.config import config_ascend, config_gpu +from src.ghostnet import ghostnet_1x + +parser = argparse.ArgumentParser(description='Image classification') +parser.add_argument('--checkpoint_path', type=str, + default=None, help='Checkpoint file path') +parser.add_argument('--dataset_path', type=str, + default=None, help='Dataset path') +parser.add_argument('--platform', type=str, default=None, help='run platform') +args_opt = parser.parse_args() + + +if __name__ == '__main__': + config_platform = None + if args_opt.platform == "Ascend": + config_platform = config_ascend + device_id = int(os.getenv('DEVICE_ID')) + context.set_context(mode=context.GRAPH_MODE, device_target="Ascend", + device_id=device_id, save_graphs=False) + elif args_opt.platform == "GPU": + config_platform = config_gpu + context.set_context(mode=context.GRAPH_MODE, + device_target="GPU", save_graphs=False) + else: + raise ValueError("Unsupport platform.") + + loss = nn.SoftmaxCrossEntropyWithLogits( + is_grad=False, sparse=True, reduction='mean') + + net = ghostnet_1x(num_classes=config_platform.num_classes) + + if args_opt.platform == "Ascend": + net.to_float(mstype.float16) + for _, cell in net.cells_and_names(): + if isinstance(cell, nn.Dense): + cell.to_float(mstype.float32) + + dataset = create_dataset(dataset_path=args_opt.dataset_path, + do_train=False, + config=config_platform, + platform=args_opt.platform, + batch_size=config_platform.batch_size) + step_size = dataset.get_dataset_size() + + if args_opt.checkpoint_path: + param_dict = load_checkpoint(args_opt.checkpoint_path) + load_param_into_net(net, param_dict) + net.set_train(False) + + model = Model(net, loss_fn=loss, metrics={'acc'}) + res = model.eval(dataset) + print("result:", res, "ckpt=", args_opt.checkpoint_path) diff --git a/model_zoo/research/cv/ghostnet_quant/mindpsore_hub_conf.py b/model_zoo/research/cv/ghostnet_quant/mindpsore_hub_conf.py new file mode 100644 index 00000000000..171e958968d --- /dev/null +++ b/model_zoo/research/cv/ghostnet_quant/mindpsore_hub_conf.py @@ -0,0 +1,22 @@ +# 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. +# ============================================================================ +"""hub config.""" +from src.ghostnet import ghostnet_1x + + +def create_network(name, *args, **kwargs): + if name == 'ghostnet_int8': + return ghostnet_1x(*args, **kwargs) + raise NotImplementedError(f"{name} is not implemented in the repo") diff --git a/model_zoo/research/cv/ghostnet_quant/src/__init__.py b/model_zoo/research/cv/ghostnet_quant/src/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/model_zoo/research/cv/ghostnet_quant/src/config.py b/model_zoo/research/cv/ghostnet_quant/src/config.py new file mode 100644 index 00000000000..c5428ae53bc --- /dev/null +++ b/model_zoo/research/cv/ghostnet_quant/src/config.py @@ -0,0 +1,54 @@ +# 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. +# ============================================================================ +""" +network config setting, will be used in train.py and eval.py +""" +from easydict import EasyDict as ed + +config_ascend = ed({ + "num_classes": 37, + "image_height": 224, + "image_width": 224, + "batch_size": 256, + "epoch_size": 200, + "warmup_epochs": 4, + "lr": 0.4, + "momentum": 0.9, + "weight_decay": 4e-5, + "label_smooth": 0.1, + "loss_scale": 1024, + "save_checkpoint": True, + "save_checkpoint_epochs": 1, + "keep_checkpoint_max": 200, + "save_checkpoint_path": "./checkpoint", +}) + +config_gpu = ed({ + "num_classes": 37, + "image_height": 224, + "image_width": 224, + "batch_size": 3, + "epoch_size": 370, + "warmup_epochs": 4, + "lr": 0.4, + "momentum": 0.9, + "weight_decay": 4e-5, + "label_smooth": 0.1, + "loss_scale": 1024, + "save_checkpoint": True, + "save_checkpoint_epochs": 1, + "keep_checkpoint_max": 500, + "save_checkpoint_path": "./checkpoint", +}) diff --git a/model_zoo/research/cv/ghostnet_quant/src/dataset.py b/model_zoo/research/cv/ghostnet_quant/src/dataset.py new file mode 100644 index 00000000000..1da50b2bf11 --- /dev/null +++ b/model_zoo/research/cv/ghostnet_quant/src/dataset.py @@ -0,0 +1,110 @@ +# 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. +# ============================================================================ +""" +create train or eval dataset. +""" +import os +import mindspore.common.dtype as mstype +import mindspore.dataset.engine as de +import mindspore.dataset.transforms.vision.c_transforms as C +import mindspore.dataset.transforms.vision.py_transforms as P +import mindspore.dataset.transforms.c_transforms as C2 +from mindspore.dataset.transforms.vision import Inter + + +def create_dataset(dataset_path, do_train, config, platform, repeat_num=1, batch_size=100, model='ghsotnet'): + """ + create a train or eval dataset + + Args: + dataset_path(string): the path of dataset. + do_train(bool): whether dataset is used for train or eval. + repeat_num(int): the repeat times of dataset. Default: 1 + batch_size(int): the batch size of dataset. Default: 32 + + Returns: + dataset + """ + if platform == "Ascend": + rank_size = int(os.getenv("RANK_SIZE")) + rank_id = int(os.getenv("RANK_ID")) + if rank_size == 1: + ds = de.MindDataset( + dataset_path, num_parallel_workers=8, shuffle=True) + else: + ds = de.MindDataset(dataset_path, num_parallel_workers=8, shuffle=True, + num_shards=rank_size, shard_id=rank_id) + elif platform == "GPU": + if do_train: + from mindspore.communication.management import get_rank, get_group_size + ds = de.MindDataset(dataset_path, num_parallel_workers=8, shuffle=True, + num_shards=get_group_size(), shard_id=get_rank()) + else: + ds = de.MindDataset( + dataset_path, num_parallel_workers=8, shuffle=True) + else: + raise ValueError("Unsupport platform.") + + resize_height = config.image_height + buffer_size = 1000 + + # define map operations + resize_crop_op = C.RandomCropDecodeResize( + resize_height, scale=(0.08, 1.0), ratio=(0.75, 1.333)) + horizontal_flip_op = C.RandomHorizontalFlip(prob=0.5) + + color_op = C.RandomColorAdjust( + brightness=0.4, contrast=0.4, saturation=0.4) + rescale_op = C.Rescale(1/255.0, 0) + normalize_op = C.Normalize( + mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + change_swap_op = C.HWC2CHW() + + # define python operations + decode_p = P.Decode() + if model == 'ghostnet-600': + s = 274 + c = 240 + else: + s = 256 + c = 224 + resize_p = P.Resize(s, interpolation=Inter.BICUBIC) + center_crop_p = P.CenterCrop(c) + totensor = P.ToTensor() + normalize_p = P.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)) + composeop = P.ComposeOp( + [decode_p, resize_p, center_crop_p, totensor, normalize_p]) + if do_train: + trans = [resize_crop_op, horizontal_flip_op, color_op, + rescale_op, normalize_op, change_swap_op] + else: + trans = composeop() + type_cast_op = C2.TypeCast(mstype.int32) + + ds = ds.map(input_columns="image", operations=trans, + num_parallel_workers=8) + ds = ds.map(input_columns="label_list", + operations=type_cast_op, num_parallel_workers=8) + + # apply shuffle operations + ds = ds.shuffle(buffer_size=buffer_size) + + # apply batch operations + ds = ds.batch(batch_size, drop_remainder=True) + + # apply dataset repeat operation + ds = ds.repeat(repeat_num) + + return ds diff --git a/model_zoo/research/cv/ghostnet_quant/src/ghostnet.py b/model_zoo/research/cv/ghostnet_quant/src/ghostnet.py new file mode 100644 index 00000000000..c9cfa852d6b --- /dev/null +++ b/model_zoo/research/cv/ghostnet_quant/src/ghostnet.py @@ -0,0 +1,491 @@ +# 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. +# ============================================================================ +"""GhostNet model define""" +from functools import partial +import math +import numpy as np +import mindspore.nn as nn +from mindspore.ops import operations as P +from mindspore import Tensor +from .quant import QuanConv + + +__all__ = ['ghostnet'] + + +def _make_divisible(x, divisor=4): + return int(np.ceil(x * 1. / divisor) * divisor) + + +class MyHSigmoid(nn.Cell): + """ + Hard Sigmoid definition. + + Args: + + Returns: + Tensor, output tensor. + + Examples: + >>> MyHSigmoid() + """ + + def __init__(self): + super(MyHSigmoid, self).__init__() + self.relu6 = nn.ReLU6() + + def construct(self, x): + return self.relu6(x + 3.) * 0.16666667 + + +class Activation(nn.Cell): + """ + Activation definition. + + Args: + act_func(string): activation name. + + Returns: + Tensor, output tensor. + """ + + def __init__(self, act_func): + super(Activation, self).__init__() + if act_func == 'relu': + self.act = nn.ReLU() + elif act_func == 'relu6': + self.act = nn.ReLU6() + elif act_func in ('hsigmoid', 'hard_sigmoid'): + self.act = MyHSigmoid() + elif act_func in ('hswish', 'hard_swish'): + self.act = nn.HSwish() + else: + raise NotImplementedError + + def construct(self, x): + return self.act(x) + + +class GlobalAvgPooling(nn.Cell): + """ + Global avg pooling definition. + + Args: + + Returns: + Tensor, output tensor. + + Examples: + >>> GlobalAvgPooling() + """ + + def __init__(self, keep_dims=False): + super(GlobalAvgPooling, self).__init__() + self.mean = P.ReduceMean(keep_dims=keep_dims) + + def construct(self, x): + x = self.mean(x, (2, 3)) + return x + + +class SE(nn.Cell): + """ + SE warpper definition. + + Args: + num_out (int): Output channel. + ratio (int): middle output ratio. + + Returns: + Tensor, output tensor. + + Examples: + >>> SE(4) + """ + + def __init__(self, num_out, ratio=4): + super(SE, self).__init__() + num_mid = _make_divisible(num_out // ratio) + self.pool = GlobalAvgPooling(keep_dims=True) + self.conv_reduce = QuanConv(in_channels=num_out, out_channels=num_mid, + kernel_size=1, has_bias=True, pad_mode='pad') + self.act1 = Activation('relu') + self.conv_expand = QuanConv(in_channels=num_mid, out_channels=num_out, + kernel_size=1, has_bias=True, pad_mode='pad') + self.act2 = Activation('hsigmoid') + self.mul = P.Mul() + + def construct(self, x): + out = self.pool(x) + out = self.conv_reduce(out) + out = self.act1(out) + out = self.conv_expand(out) + out = self.act2(out) + out = self.mul(x, out) + return out + + +class ConvUnit(nn.Cell): + """ + ConvUnit warpper definition. + + Args: + num_in (int): Input channel. + num_out (int): Output channel. + kernel_size (int): Input kernel size. + stride (int): Stride size. + padding (int): Padding number. + num_groups (int): Output num group. + use_act (bool): Used activation or not. + act_type (string): Activation type. + + Returns: + Tensor, output tensor. + + Examples: + >>> ConvUnit(3, 3) + """ + + def __init__(self, num_in, num_out, kernel_size=1, stride=1, padding=0, num_groups=1, + use_act=True, act_type='relu'): + super(ConvUnit, self).__init__() + self.conv = QuanConv(in_channels=num_in, + out_channels=num_out, + kernel_size=kernel_size, + stride=stride, + padding=padding, + group=num_groups, + has_bias=False, + pad_mode='pad') + self.bn = nn.BatchNorm2d(num_out) + self.use_act = use_act + self.act = Activation(act_type) if use_act else None + + def construct(self, x): + out = self.conv(x) + out = self.bn(out) + if self.use_act: + out = self.act(out) + return out + + +class GhostModule(nn.Cell): + """ + GhostModule warpper definition. + + Args: + num_in (int): Input channel. + num_out (int): Output channel. + kernel_size (int): Input kernel size. + stride (int): Stride size. + padding (int): Padding number. + ratio (int): Reduction ratio. + dw_size (int): kernel size of cheap operation. + use_act (bool): Used activation or not. + act_type (string): Activation type. + + Returns: + Tensor, output tensor. + + Examples: + >>> GhostModule(3, 3) + """ + + def __init__(self, num_in, num_out, kernel_size=1, stride=1, padding=0, ratio=2, dw_size=3, + use_act=True, act_type='relu'): + super(GhostModule, self).__init__() + init_channels = math.ceil(num_out / ratio) + new_channels = init_channels * (ratio - 1) + + self.primary_conv = ConvUnit(num_in, init_channels, kernel_size=kernel_size, stride=stride, padding=padding, + num_groups=1, use_act=use_act, act_type='relu') + self.cheap_operation = ConvUnit(init_channels, new_channels, kernel_size=dw_size, stride=1, padding=dw_size//2, + num_groups=init_channels, use_act=use_act, act_type='relu') + self.concat = P.Concat(axis=1) + + def construct(self, x): + x1 = self.primary_conv(x) + x2 = self.cheap_operation(x1) + return self.concat((x1, x2)) + + +class GhostBottleneck(nn.Cell): + """ + GhostBottleneck warpper definition. + + Args: + num_in (int): Input channel. + num_mid (int): Middle channel. + num_out (int): Output channel. + kernel_size (int): Input kernel size. + stride (int): Stride size. + act_type (str): Activation type. + use_se (bool): Use SE warpper or not. + + Returns: + Tensor, output tensor. + + Examples: + >>> GhostBottleneck(16, 3, 1, 1) + """ + + def __init__(self, num_in, num_mid, num_out, kernel_size, stride=1, act_type='relu', use_se=False): + super(GhostBottleneck, self).__init__() + self.ghost1 = GhostModule(num_in, num_mid, kernel_size=1, + stride=1, padding=0, act_type=act_type) + + self.use_dw = stride > 1 + self.dw = None + if self.use_dw: + self.dw = ConvUnit(num_mid, num_mid, kernel_size=kernel_size, stride=stride, + padding=self._get_pad(kernel_size), act_type=act_type, num_groups=num_mid, use_act=False) + + self.use_se = use_se + if use_se: + self.se = SE(num_mid) + + self.ghost2 = GhostModule(num_mid, num_out, kernel_size=1, stride=1, + padding=0, act_type=act_type, use_act=False) + + self.down_sample = False + if num_in != num_out or stride != 1: + self.down_sample = True + self.shortcut = None + if self.down_sample: + self.shortcut = nn.SequentialCell([ + ConvUnit(num_in, num_in, kernel_size=kernel_size, stride=stride, + padding=self._get_pad(kernel_size), num_groups=num_in, use_act=False), + ConvUnit(num_in, num_out, kernel_size=1, stride=1, + padding=0, num_groups=1, use_act=False), + ]) + self.add = P.TensorAdd() + + def construct(self, x): + r"""construct of GhostNet BottleNeck""" + shortcut = x + out = self.ghost1(x) + if self.use_dw: + out = self.dw(out) + if self.use_se: + out = self.se(out) + out = self.ghost2(out) + if self.down_sample: + shortcut = self.shortcut(shortcut) + out = self.add(shortcut, out) + return out + + def _get_pad(self, kernel_size): + """set the padding number""" + pad = 0 + if kernel_size == 1: + pad = 0 + elif kernel_size == 3: + pad = 1 + elif kernel_size == 5: + pad = 2 + elif kernel_size == 7: + pad = 3 + else: + raise NotImplementedError + return pad + + +class GhostNet(nn.Cell): + """ + GhostNet architecture. + + Args: + model_cfgs (Cell): number of classes. + num_classes (int): Output number classes. + multiplier (int): Channels multiplier for round to 8/16 and others. Default is 1. + final_drop (float): Dropout number. + round_nearest (list): Channel round to . Default is 8. + Returns: + Tensor, output tensor. + + Examples: + >>> GhostNet(num_classes=1000) + """ + + def __init__(self, model_cfgs, num_classes=1000, multiplier=1., final_drop=0., round_nearest=8): + super(GhostNet, self).__init__() + self.cfgs = model_cfgs['cfg'] + self.inplanes = 16 + first_conv_in_channel = 3 + first_conv_out_channel = _make_divisible(multiplier * self.inplanes) + + self.conv_stem = QuanConv(in_channels=first_conv_in_channel, + out_channels=first_conv_out_channel, + kernel_size=3, padding=1, stride=2, + has_bias=False, pad_mode='pad') + self.bn1 = nn.BatchNorm2d(first_conv_out_channel) + self.act1 = Activation('relu') + + self.blocks = [] + for layer_cfg in self.cfgs: + self.blocks.append(self._make_layer(kernel_size=layer_cfg[0], + exp_ch=_make_divisible( + multiplier * layer_cfg[1]), + out_channel=_make_divisible( + multiplier * layer_cfg[2]), + use_se=layer_cfg[3], + act_func=layer_cfg[4], + stride=layer_cfg[5])) + output_channel = _make_divisible( + multiplier * model_cfgs["cls_ch_squeeze"]) + self.blocks.append(ConvUnit(_make_divisible(multiplier * self.cfgs[-1][2]), output_channel, + kernel_size=1, stride=1, padding=0, num_groups=1, use_act=True)) + self.blocks = nn.SequentialCell(self.blocks) + + self.global_pool = GlobalAvgPooling(keep_dims=True) + self.conv_head = QuanConv(in_channels=output_channel, + out_channels=model_cfgs['cls_ch_expand'], + kernel_size=1, padding=0, stride=1, + has_bias=True, pad_mode='pad') + self.act2 = Activation('relu') + self.squeeze = P.Flatten() + self.final_drop = final_drop + if self.final_drop > 0: + self.dropout = nn.Dropout(self.final_drop) + + self.classifier = nn.Dense( + model_cfgs['cls_ch_expand'], num_classes, has_bias=True) + + self._initialize_weights() + + def construct(self, x): + r"""construct of GhostNet""" + x = self.conv_stem(x) + x = self.bn1(x) + x = self.act1(x) + x = self.blocks(x) + x = self.global_pool(x) + x = self.conv_head(x) + x = self.act2(x) + x = self.squeeze(x) + if self.final_drop > 0: + x = self.dropout(x) + x = self.classifier(x) + return x + + def _make_layer(self, kernel_size, exp_ch, out_channel, use_se, act_func, stride=1): + mid_planes = exp_ch + out_planes = out_channel + layer = GhostBottleneck(self.inplanes, mid_planes, out_planes, + kernel_size, stride=stride, act_type=act_func, use_se=use_se) + self.inplanes = out_planes + return layer + + def _initialize_weights(self): + """ + Initialize weights. + + Args: + + Returns: + None. + + Examples: + >>> _initialize_weights() + """ + self.init_parameters_data() + for _, m in self.cells_and_names(): + if isinstance(m, (nn.Conv2d)): + n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + m.weight.set_parameter_data(Tensor(np.random.normal(0, np.sqrt(2. / n), + m.weight.data.shape).astype("float32"))) + if m.bias is not None: + m.bias.set_parameter_data( + Tensor(np.zeros(m.bias.data.shape, dtype="float32"))) + elif isinstance(m, nn.BatchNorm2d): + m.gamma.set_parameter_data( + Tensor(np.ones(m.gamma.data.shape, dtype="float32"))) + m.beta.set_parameter_data( + Tensor(np.zeros(m.beta.data.shape, dtype="float32"))) + elif isinstance(m, nn.Dense): + m.weight.set_parameter_data(Tensor(np.random.normal( + 0, 0.01, m.weight.data.shape).astype("float32"))) + if m.bias is not None: + m.bias.set_parameter_data( + Tensor(np.zeros(m.bias.data.shape, dtype="float32"))) + + +def ghostnet(model_name, **kwargs): + """ + Constructs a GhostNet model + """ + model_cfgs = { + "1x": { + "cfg": [ + # k, exp, c, se, nl, s, + # stage1 + [3, 16, 16, False, 'relu', 1], + # stage2 + [3, 48, 24, False, 'relu', 2], + [3, 72, 24, False, 'relu', 1], + # stage3 + [5, 72, 40, True, 'relu', 2], + [5, 120, 40, True, 'relu', 1], + # stage4 + [3, 240, 80, False, 'relu', 2], + [3, 200, 80, False, 'relu', 1], + [3, 184, 80, False, 'relu', 1], + [3, 184, 80, False, 'relu', 1], + [3, 480, 112, True, 'relu', 1], + [3, 672, 112, True, 'relu', 1], + # stage5 + [5, 672, 160, True, 'relu', 2], + [5, 960, 160, False, 'relu', 1], + [5, 960, 160, True, 'relu', 1], + [5, 960, 160, False, 'relu', 1], + [5, 960, 160, True, 'relu', 1]], + "cls_ch_squeeze": 960, + "cls_ch_expand": 1280, + }, + + "nose_1x": { + "cfg": [ + # k, exp, c, se, nl, s, + # stage1 + [3, 16, 16, False, 'relu', 1], + # stage2 + [3, 48, 24, False, 'relu', 2], + [3, 72, 24, False, 'relu', 1], + # stage3 + [5, 72, 40, False, 'relu', 2], + [5, 120, 40, False, 'relu', 1], + # stage4 + [3, 240, 80, False, 'relu', 2], + [3, 200, 80, False, 'relu', 1], + [3, 184, 80, False, 'relu', 1], + [3, 184, 80, False, 'relu', 1], + [3, 480, 112, False, 'relu', 1], + [3, 672, 112, False, 'relu', 1], + # stage5 + [5, 672, 160, False, 'relu', 2], + [5, 960, 160, False, 'relu', 1], + [5, 960, 160, False, 'relu', 1], + [5, 960, 160, False, 'relu', 1], + [5, 960, 160, False, 'relu', 1]], + "cls_ch_squeeze": 960, + "cls_ch_expand": 1280, + } + } + + return GhostNet(model_cfgs[model_name], **kwargs) + + +ghostnet_1x = partial(ghostnet, model_name="1x", final_drop=0.8) +ghostnet_nose_1x = partial(ghostnet, model_name="nose_1x", final_drop=0.8) diff --git a/model_zoo/research/cv/ghostnet_quant/src/launch.py b/model_zoo/research/cv/ghostnet_quant/src/launch.py new file mode 100644 index 00000000000..003221310e1 --- /dev/null +++ b/model_zoo/research/cv/ghostnet_quant/src/launch.py @@ -0,0 +1,165 @@ +# 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. +# ============================================================================ +"""launch train script""" +import os +import sys +import json +import subprocess +import shutil +from argparse import ArgumentParser + + +def parse_args(): + """ + parse args . + + Args: + + Returns: + args. + + Examples: + >>> parse_args() + """ + parser = ArgumentParser(description="mindspore distributed training launch " + "helper utilty that will spawn up " + "multiple distributed processes") + parser.add_argument("--nproc_per_node", type=int, default=1, + help="The number of processes to launch on each node, " + "for D training, this is recommended to be set " + "to the number of D in your system so that " + "each process can be bound to a single D.") + parser.add_argument("--visible_devices", type=str, default="0,1,2,3,4,5,6,7", + help="will use the visible devices sequentially") + parser.add_argument("--server_id", type=str, default="", + help="server ip") + parser.add_argument("--training_script", type=str, + help="The full path to the single D training " + "program/script to be launched in parallel, " + "followed by all the arguments for the " + "training script") + # rest from the training program + args, unknown = parser.parse_known_args() + args.training_script_args = unknown + return args + + +def main(): + print("start", __file__) + args = parse_args() + print(args) + visible_devices = args.visible_devices.split(',') + assert os.path.isfile(args.training_script) + assert len(visible_devices) >= args.nproc_per_node + print('visible_devices:{}'.format(visible_devices)) + if not args.server_id: + print('pleaser input server ip!!!') + exit(0) + print('server_id:{}'.format(args.server_id)) + + # construct hccn_table + hccn_configs = open('/etc/hccn.conf', 'r').readlines() + device_ips = {} + for hccn_item in hccn_configs: + hccn_item = hccn_item.strip() + if hccn_item.startswith('address_'): + device_id, device_ip = hccn_item.split('=') + device_id = device_id.split('_')[1] + device_ips[device_id] = device_ip + print('device_id:{}, device_ip:{}'.format(device_id, device_ip)) + hccn_table = {} + hccn_table['board_id'] = '0x0000' + hccn_table['chip_info'] = '910' + hccn_table['deploy_mode'] = 'lab' + hccn_table['group_count'] = '1' + hccn_table['group_list'] = [] + instance_list = [] + usable_dev = '' + for instance_id in range(args.nproc_per_node): + instance = {} + instance['devices'] = [] + device_id = visible_devices[instance_id] + device_ip = device_ips[device_id] + usable_dev += str(device_id) + instance['devices'].append({ + 'device_id': device_id, + 'device_ip': device_ip, + }) + instance['rank_id'] = str(instance_id) + instance['server_id'] = args.server_id + instance_list.append(instance) + hccn_table['group_list'].append({ + 'device_num': str(args.nproc_per_node), + 'server_num': '1', + 'group_name': '', + 'instance_count': str(args.nproc_per_node), + 'instance_list': instance_list, + }) + hccn_table['para_plane_nic_location'] = 'device' + hccn_table['para_plane_nic_name'] = [] + for instance_id in range(args.nproc_per_node): + eth_id = visible_devices[instance_id] + hccn_table['para_plane_nic_name'].append('eth{}'.format(eth_id)) + hccn_table['para_plane_nic_num'] = str(args.nproc_per_node) + hccn_table['status'] = 'completed' + + # save hccn_table to file + table_path = os.getcwd() + if not os.path.exists(table_path): + os.mkdir(table_path) + table_fn = os.path.join(table_path, + 'rank_table_{}p_{}_{}.json'.format(args.nproc_per_node, usable_dev, args.server_id)) + with open(table_fn, 'w') as table_fp: + json.dump(hccn_table, table_fp, indent=4) + sys.stdout.flush() + + # spawn the processes + processes = [] + cmds = [] + log_files = [] + env = os.environ.copy() + env['RANK_SIZE'] = str(args.nproc_per_node) + cur_path = os.getcwd() + for rank_id in range(0, args.nproc_per_node): + os.chdir(cur_path) + device_id = visible_devices[rank_id] + device_dir = os.path.join(cur_path, 'device{}'.format(rank_id)) + env['RANK_ID'] = str(rank_id) + env['DEVICE_ID'] = str(device_id) + if args.nproc_per_node > 1: + env['RANK_TABLE_FILE'] = table_fn + if os.path.exists(device_dir): + shutil.rmtree(device_dir) + os.mkdir(device_dir) + os.chdir(device_dir) + cmd = [sys.executable, '-u'] + cmd.append(args.training_script) + cmd.extend(args.training_script_args) + log_file = open( + '{dir}/log{id}.log'.format(dir=device_dir, id=rank_id), 'w') + process = subprocess.Popen( + cmd, stdout=log_file, stderr=log_file, env=env) + processes.append(process) + cmds.append(cmd) + log_files.append(log_file) + for process, cmd, log_file in zip(processes, cmds, log_files): + process.wait() + if process.returncode != 0: + raise subprocess.CalledProcessError(returncode=process, cmd=cmd) + log_file.close() + + +if __name__ == "__main__": + main() diff --git a/model_zoo/research/cv/ghostnet_quant/src/lr_generator.py b/model_zoo/research/cv/ghostnet_quant/src/lr_generator.py new file mode 100644 index 00000000000..9c6a591ae2a --- /dev/null +++ b/model_zoo/research/cv/ghostnet_quant/src/lr_generator.py @@ -0,0 +1,55 @@ +# 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 generator""" +import math +import numpy as np + + +def get_lr(global_step, lr_init, lr_end, lr_max, warmup_epochs, total_epochs, steps_per_epoch): + """ + generate learning rate array + + Args: + global_step(int): total steps of the training + lr_init(float): init learning rate + lr_end(float): end learning rate + lr_max(float): max learning rate + warmup_epochs(int): number of warmup epochs + total_epochs(int): total epoch of training + steps_per_epoch(int): steps of one epoch + + Returns: + np.array, learning rate array + """ + lr_each_step = [] + total_steps = steps_per_epoch * total_epochs + warmup_steps = steps_per_epoch * warmup_epochs + for i in range(total_steps): + if i < warmup_steps: + lr = lr_init + (lr_max - lr_init) * i / warmup_steps + else: + lr = lr_end + \ + (lr_max - lr_end) * \ + (1. + math.cos(math.pi * (i - warmup_steps) / + (total_steps - warmup_steps))) / 2. + if lr < 0.0: + lr = 0.0 + lr_each_step.append(lr) + + current_step = global_step + lr_each_step = np.array(lr_each_step).astype(np.float32) + learning_rate = lr_each_step[current_step:] + + return learning_rate diff --git a/model_zoo/research/cv/ghostnet_quant/src/quant.py b/model_zoo/research/cv/ghostnet_quant/src/quant.py new file mode 100644 index 00000000000..1a34d81bc58 --- /dev/null +++ b/model_zoo/research/cv/ghostnet_quant/src/quant.py @@ -0,0 +1,61 @@ +# 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. +# ============================================================================ +"""Quantization define""" +import mindspore as ms +import mindspore.nn as nn +from mindspore import Parameter, Tensor +from mindspore.ops import operations as P +from mindspore.ops import composite as C +from mindspore.common.initializer import initializer + +#------weight symmetric, activation asymmetric------# + + +class QuanConv(nn.Conv2d): + r"""Conv for quantization""" + def __init__(self, in_channels, out_channels, kernel_size, stride=1, pad_mode='same', + padding=0, dilation=1, group=1, has_bias=True): + super(QuanConv, self).__init__(in_channels, out_channels, + kernel_size, stride, pad_mode, padding, dilation, group, has_bias) + self.floor = P.Floor() + self.expand_dims = P.ExpandDims() + self.x_lower_bound = Tensor(0, ms.float32) + self.x_upper_bound = Tensor(2 ** 8 - 1, ms.float32) + self.w_lower_bound = Tensor(-2 ** 7 - 1, ms.float32) + self.w_upper_bound = Tensor(2 ** 7, ms.float32) + self.scale_a = Parameter(initializer('ones', [1]), name='scale_a') + self.scale_w = Parameter(initializer( + 'ones', [out_channels]), name='scale_w') + self.zp_a = Parameter(initializer('ones', [1]), name='zp_a') + + def construct(self, in_data): + r"""construct of QuantConv""" + x = self.floor(in_data / self.scale_a - self.zp_a + 0.5) + x = C.clip_by_value(x, self.x_lower_bound, self.x_upper_bound) + x = (x + self.zp_a) * self.scale_a + + exp_dim_scale_w = self.scale_w + exp_dim_scale_w = self.expand_dims(exp_dim_scale_w, 1) + exp_dim_scale_w = self.expand_dims(exp_dim_scale_w, 2) + exp_dim_scale_w = self.expand_dims(exp_dim_scale_w, 3) + w = self.floor(self.weight / exp_dim_scale_w + 0.5) + w = C.clip_by_value(w, self.w_lower_bound, self.w_upper_bound) + w = w * exp_dim_scale_w + + # forward + output = self.conv2d(x, w) + if self.has_bias: + output = self.bias_add(output, self.bias) + return output diff --git a/model_zoo/research/cv/resnet50_adv_pruning/Readme.md b/model_zoo/research/cv/resnet50_adv_pruning/Readme.md new file mode 100644 index 00000000000..53c87906ddd --- /dev/null +++ b/model_zoo/research/cv/resnet50_adv_pruning/Readme.md @@ -0,0 +1,122 @@ +# Contents + +- [Adversarial Pruning Description](#adversarial-pruning-description) +- [Dataset](#dataset) +- [Environment Requirements](#environment-requirements) +- [Script Description](#script-description) + - [Script and Sample Code](#script-and-sample-code) + - [Training Process](#training-process) + - [Evaluation Process](#evaluation-process) + - [Evaluation](#evaluation) +- [Model Description](#model-description) + - [Performance](#performance) + - [Training Performance](#evaluation-performance) + - [Inference Performance](#evaluation-performance) +- [Description of Random Situation](#description-of-random-situation) +- [ModelZoo Homepage](#modelzoo-homepage) + +# [Adversarial Pruning Description](#contents) + +The Adversarial Pruning method is a reliable neural network pruning algorithm by setting up a scientific control. We prefer to have a more rigorous research design by including a scientific control group as an essential part to minimize the effect of all factors except the association between the filter and expected network output. Acting as a control group, knockoff feature is generated to mimic the feature map produced by the network filter, but they are conditionally independent of the example label given the real feature map. Besides the real feature map on an intermediate layer, the corresponding knockoff feature is brought in as another auxiliary input signal for the subsequent layers. + +[Paper](https://openaccess.thecvf.com/content_CVPR_2020/papers/Han_GhostNet_More_Features_From_Cheap_Operations_CVPR_2020_paper.pdf): Yehui Tang, Yunhe Wang, Yixing Xu, Dacheng Tao, Chunjing Xu, Chao Xu, Chang Xu. Scientific Control for Reliable Neural Network Pruning. Submitted to NeurIPS 2020. + +# [Dataset](#contents) + +Dataset used: [Oxford-IIIT Pet](https://www.robots.ox.ac.uk/~vgg/data/pets/) + +- Dataset size: 7049 colorful images in 1000 classes + - Train: 3680 images + - Test: 3369 images +- Data format: RGB images. + - Note: Data will be processed in src/dataset.py + +# [Environment Requirements](#contents) + +- Hardware(Ascend/GPU) + - Prepare hardware environment with Ascend or GPU 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](http://10.90.67.50/mindspore/archive/20200506/OpenSource/me_vm_x86/) +- 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) + +# [Script description](#contents) + +## [Script and sample code](#contents) + +```python +├── Adversarial Pruning + ├── Readme.md # descriptions about adversarial-pruning # shell script for evaluation with CPU, GPU or Ascend + ├── src + │ ├──config.py # parameter configuration + │ ├──dataset.py # creating dataset + │ ├──resnet_imgnet.py # Pruned ResNet architecture + ├── eval.py # evaluation script + ├── index.txt # channel index of each layer after pruning + ├── mindspore_hub_conf.py # export model for hub +``` + +## [Training process](#contents) +To Be Done + +## [Eval process](#contents) + +### Usage + +After installing MindSpore via the official website, you can start evaluation as follows: + + +### Launch + +``` +# infer example + + Ascend: python eval.py --dataset_path ~/Pets/test.mindrecord --platform Ascend --checkpoint_path [CHECKPOINT_PATH] + GPU: python eval.py --dataset_path ~/Pets/test.mindrecord --platform GPU --checkpoint_path [CHECKPOINT_PATH] +``` + +> checkpoint can be produced in training process. + +### Result + +``` +result: {'acc': 0.8023984736985554} ckpt= ./resnet50-imgnet-0.65x-80.24.ckpt + +``` + +# [Model Description](#contents) + +## [Performance](#contents) + +#### Evaluation Performance + +###### ResNet50-0.65x on ImageNet2012 +| Parameters | | +| -------------------------- | -------------------------------------- | +| Model Version | ResNet50-0.65x | +| uploaded Date | 09/10/2020 (month/day/year) ; | +| MindSpore Version | 0.6.0-alpha | +| Dataset | ImageNet2012 | +| Parameters (M) | 14.6 | +| FLOPs (G) | 2.1 | +| Accuracy (Top1) | 75.80 | + +###### ResNet50-0.65x on Oxford-IIIT Pet +| Parameters | | +| -------------------------- | -------------------------------------- | +| Model Version | ResNet50-0.65x | +| uploaded Date | 09/10/2020 (month/day/year) ; | +| MindSpore Version | 0.6.0-alpha | +| Dataset | Oxford-IIIT Pet | +| Parameters (M) | 14.6 | +| FLOPs (M) | 2.1 | +| Accuracy (Top1) | 80.24 | + +# [Description of Random Situation](#contents) + +In dataset.py, we set the seed inside “create_dataset" function. We also use random seed in train.py. + +# [ModelZoo Homepage](#contents) + +Please check the official [homepage](https://gitee.com/mindspore/mindspore/tree/master/model_zoo). diff --git a/model_zoo/research/cv/resnet50_adv_pruning/eval.py b/model_zoo/research/cv/resnet50_adv_pruning/eval.py new file mode 100644 index 00000000000..ef76a90d151 --- /dev/null +++ b/model_zoo/research/cv/resnet50_adv_pruning/eval.py @@ -0,0 +1,88 @@ +# 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. +# ============================================================================ +""" +eval. +""" +import os +import argparse +import numpy as np + +from mindspore import context, Tensor +from mindspore import nn +from mindspore.train.model import Model +from mindspore.train.serialization import load_checkpoint, load_param_into_net +from mindspore.common import dtype as mstype + +from src.pet_dataset import create_dataset +from src.config import config_ascend, config_gpu +from src.resnet_imgnet import resnet50 + + +parser = argparse.ArgumentParser(description='Image classification') +parser.add_argument('--checkpoint_path', type=str, + default='resnet50-imgnet-0.65x-80.24.ckpt', help='Checkpoint file path') +parser.add_argument('--dataset_path', type=str, + default='/home/hankai/xiaoan/data/test.mindrecord', help='Dataset path') +parser.add_argument('--platform', type=str, default='GPU', help='run platform') +args_opt = parser.parse_args() + + +if __name__ == '__main__': + config_platform = None + if args_opt.platform == "Ascend": + config_platform = config_ascend + device_id = int(os.getenv('DEVICE_ID')) + context.set_context(mode=context.GRAPH_MODE, device_target="Ascend", + device_id=device_id, save_graphs=False) + elif args_opt.platform == "GPU": + config_platform = config_gpu + context.set_context(mode=context.GRAPH_MODE, + device_target="GPU", save_graphs=False) + else: + raise ValueError("Unsupport platform.") + + loss = nn.SoftmaxCrossEntropyWithLogits( + is_grad=False, sparse=True, reduction='mean') + + if args_opt.platform == "Ascend": + net.to_float(mstype.float16) + for _, cell in net.cells_and_names(): + if isinstance(cell, nn.Dense): + cell.to_float(mstype.float32) + + dataset = create_dataset(dataset_path=args_opt.dataset_path, + do_train=False, + config=config_platform, + platform=args_opt.platform, + batch_size=config_platform.batch_size) + step_size = dataset.get_dataset_size() + + index = [] + with open('index.txt', 'r') as f: + for line in f: + ind = Tensor((np.array(line.strip('\n').split(' ')[:-1])).astype(np.int32).reshape(-1, 1)) + index.append(ind) + + net = resnet50( + rate=0.65, class_num=config_platform.num_classes, index=index) + if args_opt.checkpoint_path: + param_dict = load_checkpoint(args_opt.checkpoint_path) + load_param_into_net(net, param_dict) + + net.set_train(False) + + model = Model(net, loss_fn=loss, metrics={'acc'}) + res = model.eval(dataset) + print("result:", res, "ckpt=", args_opt.checkpoint_path) diff --git a/model_zoo/research/cv/resnet50_adv_pruning/index.txt b/model_zoo/research/cv/resnet50_adv_pruning/index.txt new file mode 100644 index 00000000000..2296c62f175 --- /dev/null +++ b/model_zoo/research/cv/resnet50_adv_pruning/index.txt @@ -0,0 +1,16 @@ +91 175 155 144 77 137 0 54 245 140 142 181 224 17 247 150 124 222 66 113 95 152 48 103 46 8 212 157 98 63 44 29 190 195 211 221 248 42 105 169 35 147 20 99 159 6 34 177 33 172 12 255 19 156 53 36 154 102 136 173 179 11 47 196 206 210 131 236 149 86 171 213 132 168 61 227 219 230 15 249 180 79 186 82 55 178 62 30 68 244 88 85 24 225 198 217 120 9 58 193 130 216 129 162 106 59 250 121 28 96 80 209 111 21 50 125 37 13 161 158 197 41 138 118 16 57 14 43 183 145 176 84 114 81 109 188 32 146 104 231 215 100 187 252 200 115 167 67 10 27 72 233 116 122 119 97 243 78 160 7 203 254 74 3 141 201 31 +0 62 116 158 92 184 12 19 223 163 220 189 167 195 78 38 141 26 96 252 186 31 156 115 235 50 200 193 243 7 236 20 129 17 74 55 104 254 49 67 218 227 69 81 72 82 99 83 125 211 113 234 168 41 198 180 161 201 45 176 219 48 175 151 145 52 154 85 134 210 46 248 208 80 149 107 23 37 196 197 136 224 225 132 14 209 140 34 216 86 246 178 143 217 213 16 130 138 91 93 40 244 169 97 148 122 33 59 66 135 114 147 27 25 182 106 222 120 42 117 53 63 126 173 179 133 15 131 4 137 47 229 75 22 11 174 139 249 98 68 199 206 124 183 95 214 157 164 51 247 204 121 166 212 56 194 155 172 165 241 232 6 170 255 152 76 94 +90 26 189 234 171 156 36 233 109 184 13 198 110 96 88 59 1 192 228 124 203 35 37 54 224 30 235 167 103 158 181 190 43 236 239 113 3 254 68 60 41 9 131 244 120 247 91 104 148 51 7 119 147 142 168 161 33 79 99 27 182 34 225 196 95 213 72 183 115 170 187 21 229 17 105 240 157 219 75 207 178 129 121 218 216 155 166 23 126 149 255 15 210 6 57 14 42 106 153 212 136 76 20 179 97 132 102 143 130 118 85 128 94 151 249 217 70 11 175 22 206 221 134 194 53 172 185 137 140 199 152 86 222 164 139 47 66 49 117 204 174 180 4 12 173 211 169 202 45 107 25 83 52 133 246 89 2 48 230 71 87 101 93 123 127 40 64 +290 7 243 298 468 438 492 208 500 145 166 482 505 508 279 347 9 342 276 463 444 477 366 427 144 119 108 198 57 476 97 503 421 416 1 406 56 497 151 399 220 32 266 176 127 233 122 128 42 315 308 323 52 442 483 339 19 214 36 407 120 335 123 20 231 489 135 300 507 329 414 34 424 307 159 152 273 189 388 429 403 134 11 210 425 124 348 84 53 216 378 12 322 169 334 132 61 303 192 221 455 383 328 100 397 376 163 110 302 130 187 260 212 140 419 361 360 226 121 362 155 15 459 327 115 305 356 136 81 62 350 254 224 475 71 244 131 18 485 495 228 184 460 336 423 141 435 3 79 223 398 73 304 504 207 420 98 179 373 65 487 206 181 385 167 129 111 445 405 201 321 257 470 481 48 450 341 253 33 64 355 117 139 510 381 107 271 372 106 301 289 280 24 368 230 113 158 499 92 29 358 316 278 428 326 35 86 149 116 461 78 252 287 205 456 330 377 235 453 147 95 496 410 250 101 162 258 137 248 296 478 502 282 103 239 41 118 345 164 436 457 286 469 67 432 182 195 96 37 264 310 242 47 160 490 225 390 338 142 259 6 411 415 274 85 292 171 394 168 294 359 55 58 237 49 190 45 22 39 465 197 83 211 389 493 384 288 448 367 324 126 150 256 80 69 391 91 262 439 484 447 229 215 293 202 172 295 70 437 352 76 238 177 365 72 40 331 404 374 343 261 417 247 148 175 219 375 402 479 16 380 74 30 +118 200 68 51 299 451 35 402 264 254 478 168 52 372 301 252 1 85 359 336 126 456 350 173 152 502 447 278 369 58 162 371 171 391 481 463 412 448 460 106 282 466 63 453 474 103 238 24 22 262 40 292 499 293 385 341 483 389 224 266 74 150 175 84 201 164 256 420 130 288 259 373 322 384 235 355 410 208 212 80 510 432 30 160 280 422 247 308 41 330 142 39 380 229 71 242 507 323 310 459 191 76 92 210 424 328 155 457 334 117 124 339 381 287 368 501 21 70 98 179 73 407 394 217 406 184 303 11 260 418 65 86 132 489 122 476 269 480 161 337 72 166 431 428 289 357 113 66 120 149 393 271 405 505 139 97 243 257 167 119 180 251 376 314 425 434 154 34 121 109 363 444 333 267 392 326 100 78 135 276 497 246 307 12 396 189 255 351 416 408 419 99 473 128 291 404 494 335 43 442 8 298 503 112 169 206 302 388 93 290 59 203 46 413 360 13 495 0 178 174 353 379 387 4 273 285 401 176 156 283 281 133 42 220 500 482 364 382 25 185 270 102 151 199 317 511 141 492 340 399 54 89 214 104 488 409 313 5 272 105 9 449 509 275 342 284 88 311 50 90 28 464 183 329 60 170 452 344 414 87 198 2 458 32 44 433 240 204 446 23 386 498 222 370 354 94 14 443 125 297 196 346 471 454 194 82 427 232 157 143 7 467 234 426 315 186 421 138 227 395 213 430 486 236 218 312 472 153 318 400 241 332 325 +371 445 338 140 98 82 426 432 61 352 88 293 69 471 175 347 390 470 81 487 267 494 70 333 415 185 476 67 168 11 294 48 54 253 363 387 467 430 285 226 133 466 227 37 2 43 490 459 86 475 434 41 358 40 320 49 197 101 456 353 460 286 184 411 496 336 360 396 236 218 269 116 136 15 183 90 278 240 255 44 398 508 372 428 8 412 12 435 196 222 429 181 275 191 220 447 511 119 179 64 392 310 465 389 305 93 273 47 287 145 493 436 403 158 18 63 477 33 83 355 46 57 21 500 289 130 157 34 248 252 60 107 146 235 463 87 50 75 318 357 328 399 142 105 344 193 121 407 159 359 356 504 312 332 45 201 180 455 258 284 95 296 106 131 420 492 206 51 270 497 100 230 342 502 418 178 251 377 5 297 223 302 260 400 71 132 334 239 343 339 29 9 204 186 335 65 231 254 109 329 205 503 298 488 152 348 327 137 28 169 472 113 386 325 232 56 276 174 299 264 249 23 213 340 376 308 346 326 440 156 17 274 438 234 424 454 351 189 385 27 482 277 138 395 316 84 419 446 170 151 160 225 439 111 26 431 303 443 366 141 450 491 354 313 154 244 94 128 250 192 194 364 498 120 362 452 314 322 271 241 14 408 125 7 73 31 153 474 317 462 383 449 19 451 62 283 341 272 198 216 38 221 507 404 143 3 307 25 233 4 464 59 89 42 378 281 425 413 165 167 306 203 409 323 52 112 104 124 135 291 200 266 209 +446 151 396 23 297 448 301 196 507 335 185 505 445 240 340 269 203 83 236 342 421 246 19 21 434 284 110 38 328 154 182 413 247 117 73 7 192 54 428 370 126 365 467 75 141 52 90 210 198 364 499 329 139 288 162 89 239 159 12 156 93 442 22 379 322 34 331 366 122 488 454 455 149 4 178 373 511 459 474 409 315 369 171 478 491 39 189 495 452 278 53 120 444 158 431 481 420 352 493 403 229 497 353 492 438 164 280 216 419 345 109 14 33 414 487 283 184 124 211 96 145 112 98 77 384 78 453 70 59 177 350 228 42 408 118 76 465 74 404 130 261 233 3 360 295 195 103 282 394 37 399 100 358 25 147 85 355 219 255 416 264 231 424 510 24 440 456 220 152 485 432 287 470 321 65 407 483 232 338 257 508 86 169 67 439 320 193 214 376 461 119 451 49 450 423 148 235 140 258 362 303 221 1 477 18 55 242 271 256 296 496 252 136 167 206 72 385 131 286 377 504 129 437 17 479 262 250 84 389 106 132 307 238 356 40 253 380 116 121 47 137 142 484 56 179 27 429 343 378 245 475 69 274 201 9 293 191 391 113 277 248 304 111 225 316 327 457 276 326 62 29 402 107 92 447 181 175 292 305 502 128 95 310 397 367 336 348 469 436 91 490 334 79 71 375 197 101 417 410 168 61 48 81 289 226 8 449 230 308 45 15 180 205 68 64 359 415 57 244 254 466 480 294 302 260 393 51 223 435 422 299 187 460 +798 809 205 233 39 128 964 358 677 725 711 259 761 248 742 110 617 258 653 406 443 224 651 616 498 972 11 58 69 461 398 111 422 901 851 80 1016 381 216 676 393 303 236 172 100 389 601 444 182 590 174 34 860 993 475 842 122 661 91 9 812 413 1021 135 202 1004 942 883 701 3 27 363 190 105 607 946 173 672 997 703 938 295 87 188 615 767 435 438 879 892 547 850 238 755 399 298 1 127 561 508 136 889 852 35 106 934 448 918 763 1020 317 1017 133 318 1003 242 290 674 861 319 828 262 152 520 40 928 722 336 760 844 432 77 131 32 877 165 553 832 694 134 856 211 821 948 57 792 747 843 904 939 776 434 954 373 285 153 550 539 529 560 752 60 592 337 940 603 577 743 125 191 280 913 411 240 288 253 746 562 1023 167 65 995 230 840 719 922 865 950 143 347 186 95 977 335 919 871 748 84 311 300 6 385 609 376 46 494 386 833 970 415 780 908 929 979 614 862 177 990 658 12 720 327 425 168 166 200 690 594 1005 525 241 252 527 834 390 400 931 998 162 384 735 431 278 409 600 819 55 987 675 679 571 471 507 141 519 501 522 570 56 439 641 640 184 911 484 293 328 340 372 968 655 709 899 509 261 543 724 846 366 994 26 250 829 937 630 387 271 535 523 691 181 1011 544 895 699 192 294 557 768 868 18 275 79 31 874 129 273 378 417 847 800 693 132 430 880 764 404 708 991 178 148 517 467 395 374 945 887 0 289 575 371 817 455 117 631 357 668 585 92 814 476 352 602 88 297 638 36 855 619 1009 706 838 118 383 267 51 245 66 218 957 848 49 732 68 491 292 394 805 222 905 670 930 469 610 707 637 944 257 334 864 176 326 837 78 392 687 935 572 197 849 627 881 898 820 573 296 799 518 654 917 418 662 199 960 808 558 635 391 646 583 365 454 377 712 204 276 219 124 67 530 247 424 512 1022 140 210 751 869 189 963 565 160 796 270 891 659 563 86 526 579 254 48 315 632 524 906 774 21 353 54 83 744 403 622 349 611 685 50 423 107 456 888 496 549 96 1006 14 890 896 951 643 450 239 442 588 410 486 903 581 255 598 859 660 225 64 669 959 277 996 1008 599 729 912 696 532 465 612 307 595 120 474 312 121 304 933 234 419 380 511 567 179 536 93 194 634 606 642 664 75 15 123 779 649 108 149 584 576 459 502 624 452 402 962 872 790 109 332 927 510 341 154 958 169 782 28 466 33 727 952 272 445 813 914 1012 482 537 421 180 771 416 325 72 728 130 822 436 665 368 900 119 420 47 726 667 493 198 457 806 283 228 992 207 495 89 246 282 545 244 249 841 115 770 171 82 541 139 407 587 757 773 923 981 500 633 759 195 802 663 74 99 103 90 61 513 682 331 749 738 830 370 477 793 1018 256 804 63 145 698 702 983 791 446 462 555 717 25 269 625 37 857 150 232 564 414 736 554 683 348 766 170 765 528 263 986 1007 203 364 818 412 1014 5 629 70 647 449 480 +354 689 995 32 785 620 761 309 428 72 507 520 978 253 781 595 933 466 543 698 289 379 800 57 156 306 652 582 850 524 1014 178 774 1015 993 1008 533 48 86 68 764 828 676 917 297 273 925 93 637 625 305 118 901 659 523 417 421 444 808 584 562 411 720 945 957 403 766 856 107 361 947 986 326 1010 888 201 585 204 639 81 345 541 296 760 125 619 746 634 730 1005 936 26 235 615 21 242 813 560 631 896 330 34 206 59 515 685 136 496 399 198 228 598 705 683 112 319 969 868 821 548 51 839 669 105 731 703 554 965 1 372 52 43 355 285 640 851 205 880 846 455 400 848 911 39 522 61 451 213 292 681 215 349 387 413 122 838 985 1002 537 708 1016 120 261 169 622 519 377 20 77 686 340 210 611 914 542 564 357 624 185 743 860 907 391 477 906 728 464 827 895 334 696 536 572 98 994 612 445 247 99 802 275 874 776 873 817 495 594 991 408 849 500 252 979 923 646 509 771 830 718 109 412 1009 276 803 3 742 967 982 577 277 431 314 655 150 882 632 626 665 84 402 739 265 530 467 883 1018 919 627 106 885 974 959 186 784 498 177 232 386 251 346 910 532 517 545 175 199 1000 894 570 807 108 734 898 872 712 240 798 197 638 989 941 92 220 544 648 741 307 604 290 329 151 697 14 90 327 257 1023 815 424 869 511 154 36 884 881 37 948 427 529 423 207 469 341 42 321 621 4 256 1019 35 254 997 262 486 55 130 295 647 534 250 271 799 415 836 805 666 246 159 416 298 1020 605 401 278 908 131 797 75 446 765 113 912 244 282 789 837 317 875 924 664 165 117 671 736 325 71 834 135 248 855 973 79 46 450 174 264 602 700 484 709 161 1013 740 899 935 331 633 1012 127 871 962 695 200 2 576 322 656 951 775 385 40 95 279 958 356 237 753 366 195 501 202 397 426 861 395 217 727 567 229 859 809 904 865 922 280 56 719 857 999 670 966 733 418 561 586 707 315 123 588 8 792 10 149 374 128 332 336 553 243 944 980 891 64 702 635 649 70 589 343 864 591 381 422 85 472 729 284 592 274 162 514 710 404 442 972 926 453 478 900 460 132 365 597 17 115 770 425 180 787 786 339 706 590 661 682 521 58 1017 629 842 22 651 977 224 173 506 429 916 475 449 769 344 440 119 489 1021 303 100 688 375 480 505 73 287 853 288 188 102 350 866 452 139 752 133 525 568 528 497 419 434 462 6 258 212 470 438 97 318 104 473 732 435 190 30 832 788 988 795 847 266 636 886 844 494 502 660 433 578 364 921 16 1001 831 76 101 614 359 482 684 140 782 714 756 953 725 503 825 790 826 338 744 396 196 479 420 126 818 955 214 221 281 11 854 559 53 580 124 382 644 155 187 984 158 623 134 835 499 680 608 299 137 468 227 116 909 920 552 447 806 1007 9 970 551 569 940 824 673 794 13 488 724 269 406 41 645 949 726 5 225 915 996 393 971 878 490 713 437 811 82 678 209 546 616 504 291 157 +857 981 859 682 50 947 836 705 119 500 3 4 362 595 906 729 860 984 955 260 103 287 171 21 235 374 954 307 712 516 950 99 442 49 134 258 567 113 692 988 325 680 593 757 254 652 166 364 311 288 434 221 802 957 780 615 480 909 664 424 415 1006 574 782 973 420 518 344 375 566 304 150 835 281 786 282 433 230 154 753 25 487 378 531 659 269 396 793 963 263 81 765 830 977 991 650 279 999 827 466 655 949 725 538 249 855 266 92 920 568 158 502 762 405 187 118 229 764 701 87 201 95 8 1020 697 105 80 64 885 1002 941 513 212 240 43 610 951 22 853 953 779 89 271 771 937 368 62 898 28 554 732 357 503 66 791 181 423 155 717 995 501 665 651 560 522 611 327 638 810 854 585 39 971 214 78 377 412 847 891 629 387 200 822 831 994 742 587 253 485 315 956 621 944 355 190 346 306 400 718 440 989 213 816 272 497 684 622 776 539 33 631 633 896 505 637 815 69 887 36 174 583 23 385 660 82 597 669 649 707 654 798 553 319 57 877 406 849 917 163 795 79 188 106 125 968 529 797 88 625 262 790 418 317 0 17 473 111 698 843 257 476 384 216 223 547 243 353 525 430 117 534 350 102 640 261 177 972 528 1007 182 391 576 763 904 232 194 527 93 548 559 456 632 645 600 251 172 156 756 919 549 711 161 1021 739 735 623 975 679 805 618 646 958 948 605 224 165 252 277 770 242 552 425 730 12 785 851 848 1014 370 1015 160 46 630 193 53 493 748 594 233 226 862 642 924 792 921 179 129 580 586 942 879 733 883 488 116 9 398 173 389 570 694 804 636 342 142 671 641 484 322 419 811 723 274 394 205 85 926 293 663 428 455 736 1009 677 828 900 104 959 20 609 624 616 639 504 676 416 366 461 264 479 901 542 508 294 141 945 551 801 833 721 519 814 472 946 846 457 758 925 120 459 935 11 506 199 943 1012 867 897 523 852 931 19 241 429 280 244 708 845 915 320 305 324 1016 914 250 774 373 545 365 422 76 463 598 740 217 462 326 687 426 318 211 690 743 997 689 382 338 754 372 987 6 438 874 146 993 291 451 1008 189 337 938 218 381 110 98 1003 535 328 167 808 148 866 940 112 648 145 507 666 713 101 107 157 893 122 286 335 653 30 710 267 65 443 794 135 573 219 54 454 300 895 577 965 2 210 376 537 675 349 714 584 979 136 379 932 821 823 126 960 427 575 933 41 290 15 32 393 59 775 176 133 453 67 681 983 239 35 809 437 913 482 796 715 829 777 789 726 582 209 417 392 540 321 985 781 329 890 308 720 356 340 878 40 238 908 386 285 313 296 674 52 273 236 413 767 270 850 24 892 704 982 489 1001 746 48 184 1010 738 861 882 483 109 299 603 978 607 911 152 839 401 336 722 343 543 351 515 907 259 465 562 643 579 245 470 581 512 47 1013 820 367 410 159 964 755 31 658 703 768 840 613 520 565 517 672 929 699 360 471 208 750 970 354 227 222 34 191 969 +936 151 247 364 137 130 1011 567 605 762 404 920 765 928 814 949 557 204 582 610 498 284 650 496 90 437 962 91 747 888 399 995 307 682 667 589 566 440 786 982 157 554 720 185 959 503 782 638 82 70 166 700 268 391 279 724 224 560 450 999 872 396 115 953 629 533 108 363 955 909 819 362 898 693 376 61 646 973 697 771 512 849 988 267 788 298 541 254 927 725 163 553 468 455 954 458 1004 295 580 515 689 835 257 473 500 830 998 930 282 705 22 538 497 535 743 880 266 122 780 28 763 885 501 93 181 518 698 78 118 187 423 992 836 869 831 158 947 25 630 846 102 148 702 561 77 62 833 370 356 160 480 190 6 570 887 144 792 728 56 597 291 1018 86 684 900 891 857 405 937 119 820 669 769 879 655 984 957 424 452 791 341 622 88 58 712 441 433 713 687 87 380 764 173 265 189 752 479 861 308 603 611 249 368 179 172 387 208 631 131 620 636 848 519 278 855 816 917 805 346 52 466 4 683 65 906 425 873 904 488 262 585 237 168 827 550 235 191 89 522 934 327 502 664 672 11 271 167 389 8 146 916 23 910 277 104 317 972 781 30 140 1009 527 970 676 717 357 507 342 779 353 39 156 21 797 991 722 575 296 229 645 349 274 456 413 981 302 244 182 141 454 243 839 345 675 748 546 526 851 410 35 586 136 57 568 155 918 576 543 366 142 740 20 233 673 116 941 434 188 133 653 469 935 621 632 110 931 594 726 648 978 730 1007 418 960 627 790 19 483 253 451 801 574 914 1012 723 344 926 997 272 883 165 354 322 33 577 679 642 794 852 940 736 623 508 659 377 643 850 258 776 98 651 758 616 641 877 975 85 309 795 989 194 615 324 913 96 2 242 579 565 161 742 145 600 154 126 796 808 714 200 401 60 329 1021 351 649 735 306 945 895 965 504 287 531 739 12 402 125 406 252 217 365 562 707 559 32 481 127 1013 335 746 112 774 1016 1005 13 896 964 485 986 733 804 613 919 767 99 419 915 321 525 382 932 269 1001 911 201 1008 337 1 381 81 967 789 67 563 529 305 1020 259 768 95 300 174 823 193 939 250 482 129 143 624 476 890 218 59 373 213 430 573 394 280 946 882 159 924 671 987 264 69 551 293 371 463 537 15 107 523 9 708 27 738 874 690 320 948 640 694 893 412 666 908 338 703 892 400 313 457 236 397 587 584 319 230 942 285 386 652 47 968 10 318 135 416 240 184 301 674 542 31 943 393 993 868 524 299 64 534 929 581 677 979 323 540 933 210 461 343 251 223 484 44 704 867 921 829 270 120 471 211 417 718 36 583 326 517 692 493 109 837 239 938 809 367 379 798 336 311 273 53 238 225 261 209 548 24 862 17 598 101 355 639 607 985 549 785 384 465 263 340 1010 994 41 426 668 663 372 427 699 76 859 866 227 770 106 432 658 1015 755 205 840 845 221 925 199 294 811 470 691 43 48 443 489 216 539 609 358 226 219 721 392 286 152 360 520 176 516 1006 +900 958 541 177 824 844 912 241 166 498 982 170 374 788 459 310 409 635 393 611 724 905 521 845 399 192 350 408 922 495 984 998 130 695 435 833 973 1009 10 479 637 588 793 822 82 385 185 244 337 298 526 602 753 1011 846 957 872 726 45 1022 118 927 347 105 404 700 139 1003 765 446 633 49 78 455 995 490 967 701 222 741 955 836 357 137 962 56 117 756 456 902 599 1012 860 849 772 732 505 375 277 654 575 622 18 780 712 290 97 480 179 334 608 537 554 807 986 281 331 275 160 683 429 956 413 754 930 697 799 473 906 563 690 88 702 887 450 561 759 1001 959 660 86 583 868 904 830 154 879 682 254 64 552 603 131 267 249 512 847 447 141 743 41 202 420 380 127 667 500 664 999 782 458 861 148 669 190 744 9 705 910 610 272 136 348 598 693 870 560 525 311 508 805 428 260 576 897 907 545 257 585 344 188 28 443 207 133 468 208 920 627 748 396 857 433 318 681 501 953 503 542 794 61 917 696 104 643 687 921 649 630 1018 769 570 265 791 439 573 901 629 423 640 738 288 159 6 502 266 885 624 816 804 882 1008 386 916 898 454 353 796 324 89 639 145 412 513 632 798 893 540 676 13 691 710 651 582 21 263 158 296 27 723 65 58 193 11 854 597 402 828 823 949 125 196 762 728 981 775 368 586 472 960 874 410 22 972 67 523 758 1005 126 549 698 107 295 1006 614 1014 129 452 663 722 66 387 991 366 165 143 102 315 200 946 394 975 692 231 877 99 335 831 69 230 381 384 677 476 488 155 317 471 440 607 191 704 811 909 674 285 795 23 609 217 668 620 948 859 483 978 713 8 457 548 568 147 1021 194 645 345 293 32 580 319 219 546 278 600 438 739 262 284 579 623 965 451 173 250 122 328 915 987 39 156 17 892 270 931 652 112 968 618 752 801 229 31 142 161 684 461 327 19 329 336 587 187 929 301 969 291 338 730 460 718 85 890 286 527 422 808 721 287 354 679 340 517 878 251 400 417 913 116 240 790 223 261 81 299 152 184 453 866 742 35 996 997 302 970 346 504 264 189 883 770 939 673 809 720 176 1015 869 641 519 675 305 493 941 57 109 373 755 372 365 430 485 77 174 551 714 309 462 785 933 774 950 216 940 259 518 543 356 322 747 258 463 671 245 342 989 379 252 383 213 320 529 964 60 15 653 205 862 211 1016 559 398 53 666 574 616 707 827 382 1013 110 703 985 406 306 746 418 621 355 225 550 419 425 269 851 615 101 584 59 76 221 797 470 20 294 594 48 95 943 942 534 776 848 2 908 323 896 52 235 1 947 516 840 936 349 577 789 648 106 867 706 233 273 360 924 321 895 850 919 750 201 434 522 74 243 144 36 172 146 12 432 280 829 87 236 925 316 852 218 740 531 708 605 781 441 636 182 351 839 367 426 343 926 777 538 167 44 994 993 24 209 694 979 481 416 715 427 489 227 226 938 397 1010 238 520 392 945 135 405 199 837 98 253 699 43 120 894 +970 690 853 79 751 190 1015 75 152 391 27 888 955 639 670 887 661 906 929 912 980 494 89 780 535 813 509 819 83 409 918 279 700 491 698 612 569 610 222 149 265 1008 107 631 697 477 346 517 501 995 782 137 724 256 921 26 162 457 352 1005 4 164 898 134 749 325 196 284 772 247 530 283 468 452 514 691 186 956 72 475 142 231 832 842 961 986 769 50 497 592 923 583 381 2 704 808 333 590 884 804 792 395 109 786 507 619 61 752 838 806 920 473 622 759 729 858 787 218 754 901 959 470 966 604 807 915 625 913 298 825 571 899 68 14 80 285 382 900 389 238 726 682 958 735 893 954 588 664 200 650 88 257 565 633 461 1001 424 180 307 802 344 602 560 100 136 549 499 907 483 45 108 249 904 871 430 387 117 194 282 836 863 478 665 356 143 446 896 254 764 793 244 296 28 779 275 757 914 968 540 669 984 454 492 935 778 150 917 885 570 413 816 579 25 51 1011 523 462 559 644 401 76 703 155 811 29 421 949 125 998 412 743 732 640 335 890 902 290 646 746 926 148 791 82 922 868 512 689 713 131 835 182 939 423 32 511 500 728 623 603 377 67 476 502 880 611 1018 105 508 170 874 849 542 714 978 202 911 1009 796 239 641 897 191 263 102 427 112 129 628 744 35 479 69 99 173 411 41 674 30 63 318 696 629 341 822 166 702 353 85 97 204 438 829 846 404 268 354 156 928 154 668 460 140 380 345 84 632 694 543 649 561 119 273 692 882 189 299 176 422 795 177 488 458 368 439 974 135 272 781 805 755 770 981 960 679 599 58 65 800 53 396 693 663 942 587 925 683 77 489 146 31 289 338 393 645 278 524 609 873 309 363 291 859 261 1013 351 748 931 59 705 660 567 558 798 551 250 305 758 723 967 319 621 879 627 163 342 270 193 638 950 618 127 946 548 712 600 485 386 797 504 426 615 373 908 527 742 658 747 767 937 519 133 286 1014 372 785 21 304 34 721 280 777 367 823 355 101 526 262 328 932 366 740 531 394 930 699 993 402 546 652 801 251 320 845 181 159 161 947 576 432 223 975 287 322 943 116 1006 122 300 209 313 481 419 441 651 979 736 240 370 827 400 453 217 36 839 242 869 965 87 594 983 397 730 537 295 417 252 315 1007 406 19 398 259 598 584 463 95 666 126 676 267 630 505 616 311 216 877 336 420 762 1020 11 538 277 144 98 66 545 8 851 518 302 624 243 405 989 1016 340 850 357 648 434 349 188 371 1012 708 165 945 938 264 306 941 466 818 167 221 48 56 258 379 672 718 614 57 213 653 225 987 64 236 862 428 205 577 636 227 790 711 39 210 317 620 895 605 550 522 1002 229 924 789 201 23 671 418 384 520 675 365 327 321 706 852 870 493 174 52 308 933 219 1010 866 316 774 233 425 568 106 848 301 534 837 867 294 677 996 991 226 919 147 673 529 575 894 145 776 574 44 172 235 323 60 855 416 482 269 854 253 883 20 1 940 515 707 74 120 994 +1295 722 351 1562 1409 459 1379 1648 318 1494 1737 68 1158 37 111 14 1220 1335 1536 89 675 1116 1481 210 1272 826 54 1388 632 919 954 848 1411 968 100 1079 1194 1801 1980 1996 1070 1268 226 1951 132 444 194 1850 453 1006 1621 1274 951 1444 1369 998 974 1504 1007 1045 447 1240 1023 1109 16 360 1527 94 1104 2020 245 1921 1440 2043 868 1834 1404 1201 1609 1260 1318 1492 844 397 859 1076 266 1870 803 584 1137 1722 134 1598 1528 9 1933 2004 916 815 148 634 1620 566 1065 2046 1209 1288 1962 181 621 1438 1680 1724 1672 1060 314 941 45 292 990 1824 1555 929 1151 28 553 1282 993 784 1149 1931 471 673 525 1180 449 626 1374 620 2039 356 914 1802 957 619 750 155 446 188 1115 896 994 492 1537 927 1280 604 1943 774 973 1184 1726 526 456 1867 382 378 1386 743 1314 1095 2030 1541 1508 1750 1880 1169 69 320 1140 484 867 1818 1655 658 707 248 545 1550 1829 445 1296 1740 169 311 1443 1978 804 1439 1415 419 1110 1486 1319 1862 522 144 393 241 7 1800 1170 692 391 24 191 1 965 833 696 1094 1636 733 390 1988 125 1812 902 1513 1461 1549 1568 2035 2015 187 1948 1808 1766 638 327 818 470 700 1771 1026 659 1946 1406 612 1822 2027 408 1490 636 1016 1539 517 1936 416 1594 709 1603 1743 1352 461 782 2021 627 1416 217 1163 1001 1529 1365 2014 1484 688 1258 439 1914 900 227 219 237 1910 1233 894 1665 1100 112 123 1906 2002 383 214 885 1420 1923 741 1898 554 488 458 482 1922 814 887 17 1417 1036 827 421 1796 739 1171 909 1186 454 1842 1925 1074 357 1565 505 284 1414 893 923 1947 866 2038 1730 1030 40 641 1817 1183 718 1614 361 172 851 840 1359 220 625 932 508 1437 1385 1718 1763 325 1695 813 122 1881 1788 1927 1047 1483 1342 10 136 1569 1191 1773 309 1653 1141 1466 643 532 1326 349 1370 749 298 1128 565 165 759 1021 83 149 1961 773 1952 1344 1685 1203 1810 1584 1292 1865 199 386 698 1329 474 1658 1547 184 362 1552 682 70 1828 1986 117 735 240 1043 1739 1918 48 1790 714 915 477 1696 186 1593 264 483 1239 422 1050 587 1894 103 928 908 1029 1888 372 616 2011 285 808 742 251 1217 344 1928 618 1054 375 1707 1505 680 1809 1519 1227 224 336 340 1877 1999 737 1617 1969 793 1347 256 585 1053 547 64 1839 204 1860 1478 535 91 2009 1341 242 601 527 1367 892 1787 1719 1218 44 2028 1221 1014 1009 1049 731 1373 1224 1252 1611 1733 1175 708 1900 1465 255 691 498 1524 603 971 1583 1467 1902 1275 294 822 410 1459 1075 1182 976 984 0 1566 1102 1520 595 917 283 1005 1136 1545 20 1057 1310 376 694 1371 2041 2005 1891 1967 1152 1208 1887 1626 110 1488 1592 146 118 423 1027 785 645 303 153 592 1604 2003 358 883 581 1263 602 1977 978 669 395 726 307 513 1038 838 1844 996 1785 1628 1521 622 1453 254 277 1360 212 116 781 1804 1164 145 1041 141 747 1287 1950 1405 1805 1389 792 2033 1091 1129 1853 1688 1723 426 1837 174 1571 841 1328 211 2025 1204 1215 46 209 2047 1573 22 763 1068 206 1366 500 594 987 130 1316 1084 1932 889 775 305 992 490 190 1395 926 1251 1056 1343 107 1205 1901 1989 1567 1644 1580 406 1042 1859 1840 1270 1413 154 26 1694 1286 1289 706 1103 1011 991 1000 1659 755 1803 754 1089 963 1012 960 297 955 1940 1554 1357 1635 1216 1456 1874 1640 201 1113 1361 642 1708 1277 428 1656 790 1214 1745 1235 780 1320 789 1346 788 308 2006 1934 1663 721 1686 1526 409 1018 1238 1052 920 151 1976 1841 1856 1938 787 523 364 1994 176 1875 1945 1871 1125 29 609 1751 578 335 1380 2045 399 1721 75 1534 1271 1267 1421 1166 259 1265 2013 835 1506 77 2 1576 288 630 1145 1321 324 272 373 2036 1496 1303 1651 1704 427 1255 280 608 613 105 124 1683 1192 1671 223 247 478 1882 542 2024 796 1627 403 1811 1709 1897 236 61 1599 1819 164 1308 1959 368 177 1578 911 179 271 1284 1097 1954 1964 352 1155 560 79 934 943 1435 869 1121 1514 1717 1471 1586 824 1791 1960 1394 108 18 657 1546 811 1138 1353 36 1754 2031 800 1885 1250 1645 717 1468 1767 550 1412 203 903 1017 66 685 1120 2040 765 1264 562 1167 863 433 1454 1768 1863 1517 1699 938 1807 972 497 881 1458 950 888 1457 674 1772 672 1247 1744 178 786 1641 801 450 374 333 313 1597 92 921 1425 33 78 1892 1134 757 257 1579 495 1618 1402 1827 1769 109 649 596 101 1692 1917 359 1705 208 452 845 1241 1156 8 1601 438 1193 746 420 2023 1392 1146 1311 432 1081 837 1619 1815 1634 95 1826 1382 607 1381 171 1861 1712 443 1325 817 273 180 644 1397 732 90 396 1693 1711 1830 1473 751 1679 1997 1188 1355 2022 290 1334 88 1232 1401 85 1589 544 906 1525 84 1899 577 1281 882 1336 533 806 1105 1460 1223 1782 87 1926 797 127 1476 745 597 1294 678 1907 1269 1315 1088 1995 50 1760 1082 1972 63 133 1670 861 1836 138 922 401 521 1002 1851 43 1660 1794 1230 1970 166 407 610 964 1487 880 215 611 291 1101 379 425 1479 306 1650 1697 1290 350 1577 228 1480 1248 1736 1033 82 1649 225 348 1543 546 730 467 367 1757 1004 842 1625 586 6 1930 639 183 989 1433 1629 1735 772 328 1020 779 615 2029 956 466 2042 1114 1714 1279 1588 1165 315 1608 858 49 221 1178 67 1246 1973 821 19 1868 404 1498 897 507 697 967 1028 435 1667 1062 252 102 1920 727 56 729 381 1450 723 799 1307 988 1087 1098 1799 196 1364 1778 1482 1148 812 570 1313 1793 810 1613 870 1956 1958 1770 1339 99 1779 828 1849 1338 52 1157 1855 1664 128 319 1777 1953 1507 1384 1278 871 213 1219 1161 143 1048 719 230 1813 676 147 162 637 1408 1231 798 499 1673 353 534 71 51 106 1393 836 1762 1229 1266 543 1455 1993 1975 1835 15 1345 690 561 1893 907 850 949 1127 1556 687 1080 1591 4 53 1677 1469 1398 1069 1646 1301 1689 506 628 710 457 1935 135 157 258 1019 5 1698 1638 572 1139 276 424 1491 1942 1351 1703 1117 1908 192 767 1340 1150 864 1245 222 1130 1852 766 1317 417 940 1010 1472 1657 1715 411 1195 1738 1502 605 666 402 434 369 1798 96 574 468 1225 1816 418 1668 97 1372 1376 985 1987 1761 744 760 1093 1003 816 347 1348 39 278 1111 322 519 1259 1637 819 1463 371 1273 496 1937 1585 21 768 823 1431 380 736 1236 1915 1122 139 711 1966 1337 1706 1847 1858 173 1823 1866 516 504 1283 289 1728 1432 152 394 1349 1179 1607 944 476 13 1832 1322 279 430 1312 473 588 1661 953 167 1753 580 198 805 62 1784 462 12 1106 925 952 849 465 1905 623 1333 599 1064 617 +1825 1950 754 1146 197 999 1411 443 98 838 964 1953 1904 670 1562 1884 921 1488 1390 1963 870 7 1264 1560 747 535 1289 620 1417 803 293 1032 997 109 1978 231 360 1734 185 562 1935 804 1568 1024 481 486 554 1987 1157 326 1780 840 2014 1464 313 148 1638 1298 566 1743 1108 125 2027 1740 596 131 1059 1159 1240 808 830 756 537 1391 166 215 903 1587 180 616 335 1823 1631 977 1934 190 947 837 1445 112 1606 597 1860 1763 446 1933 374 983 657 1287 456 1833 1065 440 611 1453 1768 1926 1103 23 270 142 1944 1507 494 1623 263 771 1530 1820 1837 1328 1413 44 1750 1414 1095 1490 530 865 1472 871 1358 281 714 1754 416 2030 895 373 1861 697 1958 439 1967 160 99 1250 1892 1120 1256 1579 1237 902 813 528 367 1520 594 1482 844 1639 141 1609 1154 845 694 1682 1327 1735 1299 385 1345 1513 219 1988 1832 1831 101 466 1949 1557 1761 1314 104 487 2031 229 1729 83 1045 1787 211 1074 1062 1498 256 905 576 1841 573 907 932 572 1548 922 2032 26 1438 1974 946 1288 333 1922 1664 485 664 673 1716 817 1369 717 1910 791 735 933 1432 575 1881 849 812 1551 833 1197 1637 1063 1181 2017 1564 222 27 1992 2019 1676 1318 398 1799 460 887 1603 1878 1621 513 1599 1999 136 623 784 992 1538 639 60 90 1943 1239 1679 727 1341 1371 378 495 1425 196 629 908 801 1980 954 301 976 687 47 330 1043 2034 938 2039 1897 619 94 929 1133 280 765 338 264 1128 642 1173 144 1998 1156 233 1991 210 1629 981 392 489 521 470 1759 207 1112 563 1898 1845 1033 1383 1363 1615 124 455 290 696 1509 680 671 1158 607 59 836 948 2038 794 135 1176 1829 1677 145 471 1948 400 205 391 730 641 1398 1840 1561 2008 1483 1887 1141 1515 1373 248 867 214 1416 1784 856 1291 1481 1392 1400 709 153 1384 551 269 1144 965 763 1956 2001 1469 1372 1424 1981 962 298 1081 1902 1412 1642 39 1305 900 1274 1641 1996 1198 972 1960 1370 991 2004 1607 285 2033 1406 1965 1310 651 1301 184 1344 120 1208 1474 738 2021 1512 1709 1115 548 1815 1486 785 1982 695 590 53 822 1023 1618 663 1586 1912 985 1592 1622 1979 1162 852 134 220 891 863 1409 1655 498 650 1405 1001 591 719 408 406 2022 742 1691 978 28 130 1084 422 1535 1636 1745 1529 1539 218 1782 1667 1510 506 1858 1131 1407 1701 344 960 395 117 1816 1531 1739 1789 2040 1153 1260 1147 121 1061 1246 676 216 859 686 1168 549 91 1727 1717 275 1685 1989 1675 168 843 1221 1798 479 598 679 743 1357 821 25 1439 1286 879 1145 204 760 1426 274 1410 904 1109 1644 585 1594 1919 2046 1253 1566 1467 379 1210 412 77 524 1315 826 1000 1869 862 1857 767 1913 1397 755 1665 1027 800 321 311 1783 1793 251 128 595 1378 1161 1891 1818 570 761 1921 2007 1762 425 1598 1810 302 1581 539 375 615 1229 974 1522 1882 519 1758 1596 138 1275 1785 543 1387 1465 724 626 847 802 618 1303 672 1317 1526 51 1854 110 1888 637 919 1844 1350 1454 1839 1788 1915 1872 1906 876 358 1476 1569 1292 447 116 1388 1608 22 866 881 698 1908 952 896 1792 159 74 450 775 1728 1868 1440 1093 505 504 156 1932 73 1648 604 1092 581 1018 1085 516 1969 1645 587 2029 1478 1056 288 1694 1169 361 728 1249 1295 122 1152 703 1446 154 1583 1721 973 1856 75 33 860 1626 93 1850 605 390 955 917 2011 1527 520 1646 258 1194 1572 68 1733 1016 884 1182 1879 129 1713 786 855 2013 510 199 832 1757 718 1736 560 1029 1506 32 17 1928 625 578 1697 1738 956 793 337 43 1961 224 1936 1026 152 1137 2024 547 1124 1321 299 421 407 764 304 691 1652 1712 1582 56 541 658 652 523 266 1343 1973 527 352 194 1125 297 11 913 1053 1107 1100 1468 927 603 1170 643 1060 399 1817 2045 64 48 1076 1175 538 1600 1226 436 1220 1235 880 889 325 1749 19 318 1427 542 610 1104 228 1047 201 736 327 1017 1211 132 143 262 517 1036 1005 827 254 309 943 780 850 1113 1683 1463 1803 1068 750 627 1224 16 1764 819 1046 1946 1214 508 1555 906 649 1705 920 1684 1330 1585 3 968 1359 1710 500 1550 386 1265 1136 71 776 1480 1931 1533 1269 705 1457 20 1075 1767 457 797 1039 323 1479 1545 579 1011 418 2025 354 912 234 1925 191 1008 600 1203 1724 1271 340 1670 815 118 161 1167 1201 1028 1339 18 84 438 54 613 482 1332 1393 737 2016 277 1433 1703 1499 1114 419 1263 1205 1191 402 1258 287 188 1257 1707 1696 359 429 420 226 509 1552 177 29 435 1366 1688 0 1765 599 1071 1367 1491 774 206 1351 861 1139 841 835 107 721 1252 1217 164 1319 2043 1420 1375 710 1796 371 2006 1449 307 1202 477 221 1653 202 96 193 1035 1619 886 1567 518 1361 1867 1132 1775 1634 970 1672 1880 1614 934 294 1223 1364 556 1423 1901 1052 609 351 465 1630 892 403 665 1021 935 463 476 1900 1801 1693 1686 608 1802 35 648 314 1443 995 1661 384 630 397 483 957 2015 1511 1444 1778 1656 1804 2042 169 1863 818 744 2041 170 488 480 303 172 915 1554 271 1559 209 123 1649 1080 364 165 734 4 89 1347 1828 1209 1964 67 986 1451 1241 1505 1396 484 213 1766 1516 749 565 961 1278 1460 1195 1899 984 675 526 1822 2005 2036 654 348 401 2018 1019 1307 342 72 1360 532 726 990 1558 731 1909 1492 357 312 462 111 319 553 700 1452 842 273 9 704 1471 661 45 1578 1054 78 1853 529 685 292 1309 739 1657 1030 1283 1589 1151 788 308 37 1349 994 1966 514 355 317 1163 272 707 38 1871 1326 265 1150 1399 291 715 1708 106 1429 1571 1742 445 888 1938 1605 940 706 1975 1352 203 1280 336 2044 926 1687 1110 1187 787 305 782 1690 1335 868 557 1534 1281 998 1437 683 693 1811 247 729 1376 1013 745 1680 751 1473 1323 1747 1015 1907 1875 1916 1268 1334 1746 1781 57 820 133 1200 799 1192 1402 1180 1819 909 212 458 108 783 87 1277 910 242 1290 1238 1791 6 1230 574 36 789 12 396 310 622 245 1851 8 951 187 423 492 1577 1057 987 1003 1723 1889 368 1547 1267 1091 105 1348 1048 178 49 846 644 472 478 427 1800 777 1055 883 217 92 1573 126 1835 225 723 1070 653 1924 383 1951 1836 1595 95 911 1356 1610 86 1259 1517 1706 1602 988 1715 1199 1007 1228 1885 1316 809 634 677 1939 1455 828 157 875 1773 969 382 183 1121 1266 1190 1695 1333 1450 1148 1893 1340 1588 1245 1118 561 240 1049 1540 567 1408 770 708 1415 162 768 1711 1576 877 1957 878 772 442 1368 237 1215 1009 798 1790 959 1020 1972 1993 568 13 1918 1219 773 459 85 289 580 149 113 300 34 1847 182 82 65 1096 666 564 +189 788 611 264 160 218 13 1945 1860 108 337 225 1015 416 614 533 1189 1390 815 365 143 477 350 1429 77 321 1124 1418 409 1455 715 415 1154 960 1715 591 893 792 1579 525 1881 1184 1142 1523 902 32 916 735 597 302 359 1182 822 567 1566 1685 929 153 955 66 1295 1304 1262 1507 78 526 268 1215 1172 1210 1235 1894 1213 1078 2019 1271 1398 1318 235 743 918 1916 1940 1992 512 1543 1568 1613 2023 1199 1485 1654 1240 572 68 1666 1123 596 966 1175 1380 6 2028 696 1063 730 247 911 425 1527 1653 1413 444 2009 1427 423 804 1081 1353 1115 1204 759 320 1808 1013 175 786 986 24 1792 1533 468 710 1560 437 660 116 1895 813 747 1955 1835 521 588 210 1919 290 243 1669 489 1017 1776 3 31 307 1397 1433 244 1047 227 1128 592 1705 1192 1305 620 18 782 203 381 1056 1250 25 1162 1976 1632 1780 1889 1346 1938 785 648 81 394 334 1952 899 294 1658 1287 373 851 1496 821 1608 304 1968 558 424 1740 559 1959 975 799 172 106 633 2010 796 1453 1554 1974 1101 1231 410 1778 1085 1786 286 1308 284 1220 346 1844 953 1503 962 1334 332 1585 1302 351 1941 246 1274 386 1635 352 602 28 1038 713 573 1055 261 1998 1655 1851 794 1689 636 214 1170 1029 1140 1617 1559 570 1239 1772 325 376 316 123 372 1676 1171 797 436 1365 1524 1526 888 1092 1447 780 544 1369 1279 2047 846 102 1537 814 680 1376 340 37 1667 672 1996 1423 249 464 145 838 1042 288 230 820 640 1593 1702 1436 724 1045 1506 1688 1783 1183 1094 1788 1265 146 1937 1985 807 1904 1781 1445 426 1545 668 114 2026 253 1914 855 69 1058 19 1317 1758 324 1697 845 1011 1161 915 659 939 1561 1316 245 852 1843 94 896 1987 1636 1443 634 1927 1859 1356 1618 1734 448 1399 1700 1389 887 1498 1354 1482 118 1883 1149 1448 872 471 1597 1200 1868 708 832 727 204 61 623 1508 973 315 83 746 319 578 689 48 396 50 1525 2006 1646 1633 1505 282 854 1555 992 1865 1245 1911 1733 1392 1435 719 1630 1046 2000 1395 828 1263 1136 1306 1475 100 1567 499 722 232 1528 803 21 1584 367 228 2044 1707 638 112 1896 1963 885 131 853 1264 1476 122 374 202 1294 296 1489 1882 1949 1494 1490 1878 1329 560 555 341 1493 453 349 762 1159 1899 1656 894 615 1037 1232 1763 751 1373 53 1366 98 1155 1793 188 1736 1253 1773 1727 1765 1472 171 1415 1278 255 1601 1819 857 2034 1352 545 461 484 1811 1474 965 47 1284 1258 115 994 616 1105 1661 983 1840 469 1203 1898 1723 240 1135 694 675 1599 9 600 222 608 520 1315 363 1022 1193 366 547 1790 1586 266 1018 981 971 438 817 650 1458 479 508 1989 1513 779 761 1238 1694 1362 933 45 20 1020 582 1003 1283 1714 603 481 330 1107 654 1314 954 1806 1791 1446 1677 1484 1065 206 1424 156 1054 595 1343 397 154 957 1833 733 2024 1338 1536 422 1471 147 1704 120 1871 1152 1706 1500 1696 358 1509 524 1053 1088 1563 1769 181 982 59 1760 1291 1144 311 609 184 1412 861 30 2041 826 1255 411 2003 1522 844 921 876 498 1977 1648 1249 219 1981 1820 871 698 1920 443 908 1464 651 213 75 57 1994 169 483 566 1328 1021 870 238 462 1548 1623 1591 1007 897 1383 1465 333 458 1048 598 576 44 1547 1944 1108 1809 1838 1596 1943 865 827 1631 1796 1125 1749 900 594 472 398 1805 1147 400 716 2039 1735 2040 1326 1347 1622 347 148 1958 1300 549 1616 39 185 1381 1761 1072 642 657 199 1387 126 1815 1980 2038 1888 357 161 336 850 283 429 1712 1917 1798 1026 1921 354 1313 948 618 1607 1922 773 1978 342 1687 1975 995 755 1335 1237 681 121 1181 748 488 1367 80 914 63 941 1745 622 1665 562 385 1825 677 1043 281 1572 8 38 1310 1535 1647 490 0 40 1565 287 841 263 482 1178 447 1906 1217 257 1521 1924 1409 375 550 1114 406 920 1324 830 233 191 866 999 1779 891 802 177 1040 1478 239 1163 1926 961 455 1969 79 1205 1722 1551 664 418 1434 881 1269 275 1041 1001 10 1256 326 1520 840 1112 1103 1293 1512 1460 1953 2045 1405 1419 1787 399 428 382 789 1912 157 1539 709 440 217 1349 1719 113 1785 1491 1089 723 33 446 1737 67 1233 1641 412 568 1268 978 1179 812 23 1222 1739 532 718 972 492 738 1983 220 1742 1214 665 310 256 883 1931 1627 1801 643 777 1728 1573 535 1261 693 442 868 1320 1747 554 2015 728 1957 2043 91 318 1461 1190 691 254 563 193 1515 721 637 1709 607 1342 1156 1121 216 1133 1371 704 1582 1511 1884 1679 669 1990 1330 467 487 1529 970 1530 798 1393 1841 1961 819 485 1829 27 17 742 1104 1671 523 64 1867 1359 700 569 1818 180 1550 1432 1438 187 1821 818 711 1463 946 1626 1690 1951 506 1296 769 1290 1070 811 1672 1950 626 1571 1339 136 1396 1459 2036 401 1997 1982 141 1169 221 630 834 1766 1440 1377 1073 863 990 1660 671 912 515 475 823 1141 1479 105 764 1640 132 1456 1879 1907 1710 878 1167 159 701 831 1588 1372 901 625 1713 1303 707 1932 1724 1109 1267 1578 1590 903 1726 312 1218 2037 1558 463 574 439 1777 1875 808 923 432 408 317 1716 1583 297 848 1918 1360 1358 1683 62 958 224 2021 968 1027 1731 1280 379 1600 1016 1970 905 1874 125 766 1146 1486 392 1110 964 1363 164 517 583 46 1452 1972 42 1285 361 740 1810 546 2001 1019 1518 258 90 925 262 581 306 1221 11 1473 561 987 1370 587 1076 793 494 527 1936 1139 1775 829 2008 1008 1675 456 504 1570 241 309 886 1071 459 1039 209 619 2011 1923 103 976 2042 869 1289 949 1344 553 2029 729 910 1309 151 557 314 1137 579 1030 476 1411 2031 168 1067 165 685 1720 1850 1410 1822 1939 856 211 328 344 1856 1857 645 510 1286 1564 251 1880 1925 509 1748 1928 378 371 1708 1247 162 1060 835 1876 486 1605 1292 1004 702 144 1487 641 1625 627 1670 1468 1005 430 1644 1717 1782 1301 757 1441 348 1014 1826 862 190 1332 1466 1086 1812 1615 1386 913 1891 1967 178 749 906 1900 2005 1130 338 1224 450 298 1208 1686 1855 452 1594 917 390 421 1350 1948 1614 56 277 1100 76 71 1589 1148 1102 1288 403 1947 1517 1991 1374 142 1323 1807 864 1746 1973 1942 1887 273 1979 1275 1909 362 1693 1964 2027 644 192 1477 1984 907 984 974 364 107 384 688 1116 128 890 1241 1495 1971 1862 1400 1870 1480 787 1211 770 26 1576 305 158 1817 927 1252 1488 935 763 267 1457 176 678 513 84 1402 183 1260 1341 892 658 493 22 1824 1827 480 540 272 413 1097 538 92 110 909 1229 293 790 1225 1954 1202 495 1151 1035 744 717 1692 898 1219 1913 1319 1634 2035 867 65 1732 1556 924 1847 1023 2018 diff --git a/model_zoo/research/cv/resnet50_adv_pruning/mindpsore_hub_conf.py b/model_zoo/research/cv/resnet50_adv_pruning/mindpsore_hub_conf.py new file mode 100644 index 00000000000..e38b765e6cb --- /dev/null +++ b/model_zoo/research/cv/resnet50_adv_pruning/mindpsore_hub_conf.py @@ -0,0 +1,22 @@ +# 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. +# ============================================================================ +"""hub config.""" +from src.resnet_imgnet import resnet50 + + +def create_network(name, *args, **kwargs): + if name == 'resnet-0.65x': + return resnet50(*args, **kwargs) + raise NotImplementedError(f"{name} is not implemented in the repo") diff --git a/model_zoo/research/cv/resnet50_adv_pruning/src/config.py b/model_zoo/research/cv/resnet50_adv_pruning/src/config.py new file mode 100644 index 00000000000..6eb39510bfd --- /dev/null +++ b/model_zoo/research/cv/resnet50_adv_pruning/src/config.py @@ -0,0 +1,54 @@ +# 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. +# ============================================================================ +""" +network config setting, will be used in train.py and eval.py +""" +from easydict import EasyDict as ed + +config_ascend = ed({ + "num_classes": 438, + "image_height": 224, + "image_width": 224, + "batch_size": 256, + "epoch_size": 200, + "warmup_epochs": 1, + "lr": 0.02, + "momentum": 0.9, + "weight_decay": 4e-5, + "label_smooth": 0.1, + "loss_scale": 1024, + "save_checkpoint": True, + "save_checkpoint_epochs": 5, + "keep_checkpoint_max": 200, + "save_checkpoint_path": "./checkpoint", +}) + +config_gpu = ed({ + "num_classes": 37, + "image_height": 224, + "image_width": 224, + "batch_size": 1, + "epoch_size": 200, + "warmup_epochs": 0, + "lr": 0.8, + "momentum": 0.9, + "weight_decay": 4e-5, + # "label_smooth": 0.1, + "loss_scale": 1024, + "save_checkpoint": True, + "save_checkpoint_epochs": 1, + "keep_checkpoint_max": 200, + "save_checkpoint_path": "./checkpoint", +}) diff --git a/model_zoo/research/cv/resnet50_adv_pruning/src/pet_dataset.py b/model_zoo/research/cv/resnet50_adv_pruning/src/pet_dataset.py new file mode 100644 index 00000000000..0f73e7311a4 --- /dev/null +++ b/model_zoo/research/cv/resnet50_adv_pruning/src/pet_dataset.py @@ -0,0 +1,106 @@ +# 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. +# ============================================================================ +""" +create train or eval dataset. +""" +import os +import mindspore.common.dtype as mstype +import mindspore.dataset.engine as de +import mindspore.dataset.transforms.vision.c_transforms as C +import mindspore.dataset.transforms.vision.py_transforms as P +import mindspore.dataset.transforms.c_transforms as C2 +from mindspore.dataset.transforms.vision import Inter + + +def create_dataset(dataset_path, do_train, config, platform, repeat_num=1, batch_size=100): + """ + create a train or eval dataset + + Args: + dataset_path(string): the path of dataset. + do_train(bool): whether dataset is used for train or eval. + repeat_num(int): the repeat times of dataset. Default: 1 + batch_size(int): the batch size of dataset. Default: 32 + + Returns: + dataset + """ + if platform == "Ascend": + rank_size = int(os.getenv("RANK_SIZE")) + rank_id = int(os.getenv("RANK_ID")) + if rank_size == 1: + ds = de.MindDataset( + dataset_path, num_parallel_workers=8, shuffle=True) + else: + ds = de.MindDataset(dataset_path, num_parallel_workers=8, shuffle=True, + num_shards=rank_size, shard_id=rank_id) + elif platform == "GPU": + if do_train: + from mindspore.communication.management import get_rank, get_group_size + ds = de.MindDataset(dataset_path, num_parallel_workers=8, shuffle=True, + num_shards=get_group_size(), shard_id=get_rank()) + else: + ds = de.MindDataset( + dataset_path, num_parallel_workers=8, shuffle=False) + else: + raise ValueError("Unsupport platform.") + + resize_height = config.image_height + buffer_size = 1000 + + # define map operations + resize_crop_op = C.RandomCropDecodeResize( + resize_height, scale=(0.08, 1.0), ratio=(0.75, 1.333)) + horizontal_flip_op = C.RandomHorizontalFlip(prob=0.5) + + color_op = C.RandomColorAdjust( + brightness=0.4, contrast=0.4, saturation=0.4) + rescale_op = C.Rescale(1/255.0, 0) + normalize_op = C.Normalize( + mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + change_swap_op = C.HWC2CHW() + + # define python operations + decode_p = P.Decode() + resize_p = P.Resize(256, interpolation=Inter.BILINEAR) + center_crop_p = P.CenterCrop(224) + totensor = P.ToTensor() + normalize_p = P.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)) + #change_op_p = P.HWC2CHW() + composeop = P.ComposeOp( + [decode_p, resize_p, center_crop_p, totensor, normalize_p]) + if do_train: + trans = [resize_crop_op, horizontal_flip_op, color_op, + rescale_op, normalize_op, change_swap_op] + else: + #trans = [decode_op, resize_op, center_crop, rescale_op, normalize_op, change_swap_op] + trans = composeop() + type_cast_op = C2.TypeCast(mstype.int32) + + ds = ds.map(input_columns="image", operations=trans, + num_parallel_workers=8) + ds = ds.map(input_columns="label_list", + operations=type_cast_op, num_parallel_workers=8) + + # apply shuffle operations + ds = ds.shuffle(buffer_size=buffer_size) + + # apply batch operations + ds = ds.batch(batch_size, drop_remainder=True) + + # apply dataset repeat operation + ds = ds.repeat(repeat_num) + + return ds diff --git a/model_zoo/research/cv/resnet50_adv_pruning/src/resnet_imgnet.py b/model_zoo/research/cv/resnet50_adv_pruning/src/resnet_imgnet.py new file mode 100644 index 00000000000..05cf4abd006 --- /dev/null +++ b/model_zoo/research/cv/resnet50_adv_pruning/src/resnet_imgnet.py @@ -0,0 +1,391 @@ +# 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. +# ============================================================================ +"""ResNet.""" +import math +import numpy as np +import mindspore.nn as nn +from mindspore.ops import operations as P +from mindspore.common.tensor import Tensor + + +def _weight_variable(shape, factor=0.01): + init_value = np.random.randn(*shape).astype(np.float32) * factor + return Tensor(init_value) + + +def _conv3x3(in_channel, out_channel, stride=1): + weight_shape = (out_channel, in_channel, 3, 3) + weight = _weight_variable(weight_shape) + return nn.Conv2d(in_channel, out_channel, + kernel_size=3, stride=stride, padding=1, pad_mode='pad', weight_init=weight) + + +def _conv1x1(in_channel, out_channel, stride=1): + weight_shape = (out_channel, in_channel, 1, 1) + weight = _weight_variable(weight_shape) + return nn.Conv2d(in_channel, out_channel, + kernel_size=1, stride=stride, padding=0, pad_mode='same', weight_init=weight) + + +def _conv7x7(in_channel, out_channel, stride=1): + weight_shape = (out_channel, in_channel, 7, 7) + weight = _weight_variable(weight_shape) + return nn.Conv2d(in_channel, out_channel, + kernel_size=7, stride=stride, padding=3, pad_mode='pad', weight_init=weight) + + +def _bn(channel): + return nn.BatchNorm2d(channel) + + +def _bn_last(channel): + return nn.BatchNorm2d(channel) + + +def _fc(in_channel, out_channel): + weight_shape = (out_channel, in_channel) + weight = _weight_variable(weight_shape) + return nn.Dense(in_channel, out_channel, has_bias=True, weight_init=weight, bias_init=0) + + +class ResidualBlock(nn.Cell): + """ + ResNet V1 residual block definition. + + Args: + in_channel (int): Input channel. + out_channel (int): Output channel. + stride (int): Stride size for the first convolutional layer. Default: 1. + + Returns: + Tensor, output tensor. + + Examples: + >>> ResidualBlock(3, 256, stride=2) + """ + expansion = 4 + + def __init__(self, + in_channel, + out_channel, + rate, + stride=1, + num=0, + index=None): + super(ResidualBlock, self).__init__() + + channel = out_channel // self.expansion + prune_channel = math.ceil(channel*rate) + self.conv1 = _conv1x1(in_channel, prune_channel, stride=1) + self.bn1 = _bn(prune_channel) + + self.conv2 = _conv3x3(prune_channel, prune_channel, stride=stride) + self.bn2 = _bn(prune_channel) + + self.conv3 = _conv1x1(prune_channel, math.ceil( + channel*rate*self.expansion), stride=1) + self.bn3 = _bn_last(math.ceil(channel*rate*self.expansion)) + + self.relu = nn.ReLU() + + self.down_sample = False + + if stride != 1 or in_channel != out_channel: + self.down_sample = True + self.down_sample_layer = None + + if self.down_sample: + self.down_sample_layer = nn.SequentialCell([_conv1x1(in_channel, out_channel, stride), + _bn(out_channel)]) + self.add = P.TensorAdd() + + self.op = P.ScatterNd() + self.transpose = P.Transpose() + self.idx = None + self.shape = None + if out_channel == 256: + self.shape = (out_channel, 1, 56, 56) + if num == 0: + self.idx = index[0] + elif num == 1: + self.idx = index[1] + elif num == 2: + self.idx = index[2] + elif out_channel == 512: + self.shape = (out_channel, 1, 28, 28) + if num == 0: + self.idx = index[3] + elif num == 1: + self.idx = index[4] + elif num == 2: + self.idx = index[5] + elif num == 3: + self.idx = index[6] + elif out_channel == 1024: + self.shape = (out_channel, 1, 14, 14) + if num == 0: + self.idx = index[7] + elif num == 1: + self.idx = index[8] + elif num == 2: + self.idx = index[9] + elif num == 3: + self.idx = index[10] + elif num == 4: + self.idx = index[11] + elif num == 5: + self.idx = index[12] + elif out_channel == 2048: + self.shape = (out_channel, 1, 7, 7) + if num == 0: + self.idx = index[13] + elif num == 1: + self.idx = index[14] + elif num == 2: + self.idx = index[15] + + def construct(self, x): + r"""construct of ResidualBlock""" + identity = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + out = self.relu(out) + + out = self.conv3(out) + out = self.bn3(out) + + if self.down_sample: + identity = self.down_sample_layer(identity) + + output = self.transpose(out, (1, 0, 2, 3)) + output = self.op(self.idx, output, self.shape) + output = self.transpose(output, (1, 0, 2, 3)) + + output = self.add(identity, output) + output = self.relu(output) + + return output + + +class ResNet(nn.Cell): + """ + ResNet architecture. + + Args: + block (Cell): Block for network. + layer_nums (list): Numbers of block in different layers. + in_channels (list): Input channel in each layer. + out_channels (list): Output channel in each layer. + strides (list): Stride size in each layer. + num_classes (int): The number of classes that the training images are belonging to. + Returns: + Tensor, output tensor. + + Examples: + >>> ResNet(ResidualBlock, + >>> [3, 4, 6, 3], + >>> [64, 256, 512, 1024], + >>> [256, 512, 1024, 2048], + >>> [1, 2, 2, 2], + >>> 10) + """ + + def __init__(self, + rate, + block, + layer_nums, + in_channels, + out_channels, + strides, + num_classes, + index): + super(ResNet, self).__init__() + + if not len(layer_nums) == len(in_channels) == len(out_channels) == 4: + raise ValueError( + "the length of layer_num, in_channels, out_channels list must be 4!") + + self.conv1 = _conv7x7(3, 64, stride=2) + self.bn1 = _bn(64) + self.relu = P.ReLU() + self.pad_op = P.Pad(((0, 0), (0, 0), (1, 1), (1, 1))) + self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, pad_mode="valid") + + self.layer1 = self._make_layer(block, + rate, + layer_nums[0], + in_channel=in_channels[0], + out_channel=out_channels[0], + stride=strides[0], + index=index) + self.layer2 = self._make_layer(block, + rate, + layer_nums[1], + in_channel=in_channels[1], + out_channel=out_channels[1], + stride=strides[1], + index=index) + self.layer3 = self._make_layer(block, + rate, + layer_nums[2], + in_channel=in_channels[2], + out_channel=out_channels[2], + stride=strides[2], + index=index) + self.layer4 = self._make_layer(block, + rate, + layer_nums[3], + in_channel=in_channels[3], + out_channel=out_channels[3], + stride=strides[3], + index=index) + + self.mean = P.ReduceMean(keep_dims=True) + self.flatten = nn.Flatten() + self.end_point = _fc(out_channels[3], num_classes) + + self._initialize_weights() + + def _make_layer(self, block, rate, layer_num, in_channel, out_channel, stride, index): + """ + Make stage network of ResNet. + + Args: + block (Cell): Resnet block. + layer_num (int): Layer number. + in_channel (int): Input channel. + out_channel (int): Output channel. + stride (int): Stride size for the first convolutional layer. + + Returns: + SequentialCell, the output layer. + + Examples: + >>> _make_layer(ResidualBlock, 3, 128, 256, 2) + """ + layers = [] + + resnet_block = block(in_channel, out_channel, rate, + stride=stride, num=0, index=index) + layers.append(resnet_block) + + for _ in range(1, layer_num): + resnet_block = block(out_channel, out_channel, + rate, stride=1, num=_, index=index) + layers.append(resnet_block) + + return nn.SequentialCell(layers) + + def construct(self, x): + r"""construct of ResNet""" + x1 = self.conv1(x) + x2 = self.bn1(x1) + x3 = self.relu(x2) + x4 = self.pad_op(x3) + c1 = self.maxpool(x4) + + c2 = self.layer1(c1) + c3 = self.layer2(c2) + c4 = self.layer3(c3) + c5 = self.layer4(c4) + + out = self.mean(c5, (2, 3)) + out = self.flatten(out) + out = self.end_point(out) + return out + + def _initialize_weights(self): + """ + Initialize weights. + + Args: + + Returns: + None. + + Examples: + >>> _initialize_weights() + """ + self.init_parameters_data() + for _, m in self.cells_and_names(): + if isinstance(m, (nn.Conv2d)): + n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + m.weight.set_parameter_data(Tensor(np.random.normal(0, np.sqrt(2. / n), + m.weight.data.shape).astype("float32"))) + if m.bias is not None: + m.bias.set_parameter_data( + Tensor(np.zeros(m.bias.data.shape, dtype="float32"))) + elif isinstance(m, nn.BatchNorm2d): + m.gamma.set_parameter_data( + Tensor(np.ones(m.gamma.data.shape, dtype="float32"))) + m.beta.set_parameter_data( + Tensor(np.zeros(m.beta.data.shape, dtype="float32"))) + elif isinstance(m, nn.Dense): + m.weight.set_parameter_data(Tensor(np.random.normal( + 0, 0.01, m.weight.data.shape).astype("float32"))) + if m.bias is not None: + m.bias.set_parameter_data( + Tensor(np.zeros(m.bias.data.shape, dtype="float32"))) + + +def resnet50(rate=1, class_num=10, index=None): + """ + Get ResNet50 neural network. + + Args: + class_num (int): Class number. + + Returns: + Cell, cell instance of ResNet50 neural network. + + Examples: + >>> net = resnet50(10) + """ + return ResNet(rate, + ResidualBlock, + [3, 4, 6, 3], + [64, 256, 512, 1024], + [256, 512, 1024, 2048], + [1, 2, 2, 2], + class_num, + index) + + +def resnet101(rate=1, class_num=10, index=None): + """ + Get ResNet101 neural network. + + Args: + class_num (int): Class number. + + Returns: + Cell, cell instance of ResNet101 neural network. + + Examples: + >>> net = resnet101(1001) + """ + return ResNet(rate, + ResidualBlock, + [3, 4, 23, 3], + [64, 256, 512, 1024], + [256, 512, 1024, 2048], + [1, 2, 2, 2], + class_num, + index) diff --git a/model_zoo/research/cv/ssd_ghostnet/README.md b/model_zoo/research/cv/ssd_ghostnet/README.md new file mode 100644 index 00000000000..5126fa12824 --- /dev/null +++ b/model_zoo/research/cv/ssd_ghostnet/README.md @@ -0,0 +1,193 @@ +# [SSD Description](#contents) + +SSD discretizes the output space of bounding boxes into a set of default boxes over different aspect ratios and scales per feature map location. At prediction time, the network generates scores for the presence of each object category in each default box and produces adjustments to the box to better match the object shape.Additionally, the network combines predictions from multiple feature maps with different resolutions to naturally handle objects of various sizes. + +[Paper](https://arxiv.org/abs/1512.02325): Wei Liu, Dragomir Anguelov, Dumitru Erhan, Christian Szegedy, Scott Reed, Cheng-Yang Fu, Alexander C. Berg.European Conference on Computer Vision (ECCV), 2016 (In press). + +# [Model Architecture](#contents) + +The SSD approach is based on a feed-forward convolutional network that produces a fixed-size collection of bounding boxes and scores for the presence of object class instances in those boxes, followed by a non-maximum suppression step to produce the final detections. The early network layers are based on a standard architecture used for high quality image classification, which is called the base network. Then add auxiliary structure to the network to produce detections. + +# [Dataset](#contents) +Dataset used: [COCO2017]() + +- Dataset size:19G + - Train:18G,118000 images + - Val:1G,5000 images + - Annotations:241M,instances,captions,person_keypoints etc +- Data format:image and json files + - Note:Data will be processed in dataset.py + +# [Environment Requirements](#contents) + +- Hardware(Ascend/GPU) + - Prepare hardware environment with Ascend or GPU 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](http://10.90.67.50/mindspore/archive/20200506/OpenSource/me_vm_x86/) +- 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) + +- Install [MindSpore](https://www.mindspore.cn/install/en). + +- Download the dataset COCO2017. + +- We use COCO2017 as training dataset in this example by default, and you can also use your own datasets. + + 1. If coco dataset is used. **Select dataset to coco when run script.** + Install Cython and pycocotool, and you can also install mmcv to process data. + + ``` + pip install Cython + + pip install pycocotools + + ``` + And change the COCO_ROOT and other settings you need in `config.py`. The directory structure is as follows: + + ``` + . + └─cocodataset + ├─annotations + ├─instance_train2017.json + └─instance_val2017.json + ├─val2017 + └─train2017 + + ``` + + 2. If your own dataset is used. **Select dataset to other when run script.** + Organize the dataset infomation into a TXT file, each row in the file is as follows: + + ``` + train2017/0000001.jpg 0,259,401,459,7 35,28,324,201,2 0,30,59,80,2 + + ``` + + Each row is an image annotation which split by space, the first column is a relative path of image, the others are box and class infomations of the format [xmin,ymin,xmax,ymax,class]. We read image from an image path joined by the `IMAGE_DIR`(dataset directory) and the relative path in `ANNO_PATH`(the TXT file path), `IMAGE_DIR` and `ANNO_PATH` are setting in `config.py`. + +# [Quick Start](#contents) + +After installing MindSpore via the official website, you can start training and evaluation on Ascend as follows: + +``` +# single npu training on Ascend +python train.py + +# distributed training on Ascend +sh run_distribute_train_ghostnet.sh [DEVICE_NUM] [EPOCH_SIZE] [LR] [DATASET] [RANK_TABLE_FILE] + +# run eval on Ascend +python eval.py --device_id 0 --dataset coco --checkpoint_path LOG4/ssd-500_458.ckpt +``` + +# [Script Description](#contents) + +## [Script and Sample Code](#contents) + +```shell + + ├── ssd_ghostnet + ├── README.md ## readme file of ssd_ghostnet + ├── scripts + └─ run_distribute_train_ghostnet.sh ## shell script for distributed on ascend + ├── src + ├─ box_util.py ## bbox utils + ├─ coco_eval.py ## coco metrics utils + ├─ config_ghostnet_13x.py ## total config + ├─ dataset.py ## create dataset and process dataset + ├─ init_params.py ## parameters utils + ├─ lr_schedule.py ## learning ratio generator + └─ ssd_ghostnet.py ## ssd architecture + ├── eval.py ## eval scripts + ├── train.py ## train scripts + ├── mindspore_hub_conf.py # export model for hub +``` + +## [Script Parameters](#contents) + + ``` + Major parameters in train.py and config_ghostnet_13x.py as follows: + + "device_num": 1 # Use device nums + "lr": 0.05 # Learning rate init value + "dataset": coco # Dataset name + "epoch_size": 500 # Epoch size + "batch_size": 32 # Batch size of input tensor + "pre_trained": None # Pretrained checkpoint file path + "pre_trained_epoch_size": 0 # Pretrained epoch size + "save_checkpoint_epochs": 10 # The epoch interval between two checkpoints. By default, the checkpoint will be saved per 10 epochs + "loss_scale": 1024 # Loss scale + + "class_num": 81 # Dataset class number + "image_shape": [300, 300] # Image height and width used as input to the model + "mindrecord_dir": "/data/MindRecord_COCO" # MindRecord path + "coco_root": "/data/coco2017" # COCO2017 dataset path + "voc_root": "" # VOC original dataset path + "image_dir": "" # Other dataset image path, if coco or voc used, it will be useless + "anno_path": "" # Other dataset annotation path, if coco or voc used, it will be useless + + ``` + + +## [Training Process](#contents) + +### Training on Ascend + +To train the model, run `train.py`. If the `mindrecord_dir` is empty, it will generate [mindrecord](https://www.mindspore.cn/tutorial/en/master/use/data_preparation/converting_datasets.html) files by `coco_root`(coco dataset) or `iamge_dir` and `anno_path`(own dataset). **Note if mindrecord_dir isn't empty, it will use mindrecord_dir instead of raw images.** + + +- Distribute mode + +``` + sh run_distribute_train_ghostnet.sh [DEVICE_NUM] [EPOCH_SIZE] [LR] [DATASET] [RANK_TABLE_FILE] [PRE_TRAINED](optional) [PRE_TRAINED_EPOCH_SIZE](optional) +``` +We need five or seven parameters for this scripts. +- `DEVICE_NUM`: the device number for distributed train. +- `EPOCH_NUM`: epoch num for distributed train. +- `LR`: learning rate init value for distributed train. +- `DATASET`:the dataset mode for distributed train. +- `RANK_TABLE_FILE :` the path of [rank_table.json](https://gitee.com/mindspore/mindspore/tree/master/model_zoo/utils/hccl_tools), it is better to use absolute path. +- `PRE_TRAINED :` the path of pretrained checkpoint file, it is better to use absolute path. +- `PRE_TRAINED_EPOCH_SIZE :` the epoch num of pretrained. + + Training result will be stored in the current path, whose folder name begins with "LOG". Under this, you can find checkpoint file together with result like the followings in LOG4/log.txt. + +## [Evaluation Process](#contents) + +### Evaluation on Ascend + +``` +python eval.py --device_id 0 --dataset coco --checkpoint_path LOG4/ssd-500_458.ckpt +``` + +# [Model Description](#contents) +## [Performance](#contents) + +### Evaluation Performance + +| Parameters | Ascend | +| -------------------------- | -------------------------------------------------------------| +| Model Version | SSD ghostnet | +| Resource | Ascend 910 ;CPU 2.60GHz,56cores;Memory,314G | +| MindSpore Version | 0.7.0 | +| Dataset | COCO2017 | +| Training Parameters | epoch = 500, batch_size = 32 | +| Optimizer | Momentum | +| Loss Function | Sigmoid Cross Entropy,SmoothL1Loss | +| Total time | 8pcs: 12hours | + + +### Inference Performance + +| Parameters | Ascend | +| ------------------- | ----------------------------| +| Model Version | SSD ghostnet | +| Resource | Ascend 910 | +| Uploaded Date | 09/08/2020 (month/day/year) | +| MindSpore Version | 0.7.0 | +| Dataset | COCO2017 | +| batch_size | 1 | +| outputs | mAP | +| Accuracy | IoU=0.50: 24.1% | +| Model for inference | 55M(.ckpt file) | \ No newline at end of file diff --git a/model_zoo/research/cv/ssd_ghostnet/eval.py b/model_zoo/research/cv/ssd_ghostnet/eval.py new file mode 100644 index 00000000000..d4336c57262 --- /dev/null +++ b/model_zoo/research/cv/ssd_ghostnet/eval.py @@ -0,0 +1,116 @@ +# 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 +# +# less 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. +# ============================================================================ + +"""Evaluation for SSD""" + +import os +import argparse +import time +import numpy as np +from mindspore import context, Tensor +from mindspore.train.serialization import load_checkpoint, load_param_into_net +from src.ssd_ghostnet import SSD300, ssd_ghostnet +from src.dataset import create_ssd_dataset, data_to_mindrecord_byte_image, voc_data_to_mindrecord +# from src.config_ghostnet import config +from src.config_ghostnet_13x import config +from src.coco_eval import metrics + + +def ssd_eval(dataset_path, ckpt_path): + """SSD evaluation.""" + batch_size = 1 + ds = create_ssd_dataset( + dataset_path, batch_size=batch_size, repeat_num=1, is_training=False) + net = SSD300(ssd_ghostnet(), config, is_training=False) + print("Load Checkpoint!") + param_dict = load_checkpoint(ckpt_path) + net.init_parameters_data() + load_param_into_net(net, param_dict) + + net.set_train(False) + i = batch_size + total = ds.get_dataset_size() * batch_size + start = time.time() + pred_data = [] + print("\n========================================\n") + print("total images num: ", total) + print("Processing, please wait a moment.") + for data in ds.create_dict_iterator(): + img_id = data['img_id'] + img_np = data['image'] + image_shape = data['image_shape'] + + output = net(Tensor(img_np)) + for batch_idx in range(img_np.shape[0]): + pred_data.append({"boxes": output[0].asnumpy()[batch_idx], + "box_scores": output[1].asnumpy()[batch_idx], + "img_id": int(np.squeeze(img_id[batch_idx])), + "image_shape": image_shape[batch_idx]}) + percent = round(i / total * 100., 2) + + print(f' {str(percent)} [{i}/{total}]', end='\r') + i += batch_size + cost_time = int((time.time() - start) * 1000) + print(f' 100% [{total}/{total}] cost {cost_time} ms') + mAP = metrics(pred_data) + print("\n========================================\n") + print(f"mAP: {mAP}") + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='SSD evaluation') + parser.add_argument("--device_id", type=int, default=0, + help="Device id, default is 0.") + parser.add_argument("--dataset", type=str, default="coco", + help="Dataset, default is coco.") + parser.add_argument("--checkpoint_path", type=str, + required=True, help="Checkpoint file path.") + args_opt = parser.parse_args() + + context.set_context(mode=context.GRAPH_MODE, + device_target="Ascend", device_id=args_opt.device_id) + + prefix = "ssd_eval.mindrecord" + mindrecord_dir = config.mindrecord_dir + mindrecord_file = os.path.join(mindrecord_dir, prefix + "0") + if args_opt.dataset == "voc": + config.coco_root = config.voc_root + if not os.path.exists(mindrecord_file): + if not os.path.isdir(mindrecord_dir): + os.makedirs(mindrecord_dir) + if args_opt.dataset == "coco": + if os.path.isdir(config.coco_root): + print("Create Mindrecord.") + data_to_mindrecord_byte_image("coco", False, prefix) + print("Create Mindrecord Done, at {}".format(mindrecord_dir)) + else: + print("coco_root not exits.") + elif args_opt.dataset == "voc": + if os.path.isdir(config.voc_dir) and os.path.isdir(config.voc_root): + print("Create Mindrecord.") + voc_data_to_mindrecord(mindrecord_dir, False, prefix) + print("Create Mindrecord Done, at {}".format(mindrecord_dir)) + else: + print("voc_root or voc_dir not exits.") + else: + if os.path.isdir(config.image_dir) and os.path.exists(config.anno_path): + print("Create Mindrecord.") + data_to_mindrecord_byte_image("other", False, prefix) + print("Create Mindrecord Done, at {}".format(mindrecord_dir)) + else: + print("IMAGE_DIR or ANNO_PATH not exits.") + + print("Start Eval!") + ssd_eval(mindrecord_file, args_opt.checkpoint_path) diff --git a/model_zoo/research/cv/ssd_ghostnet/mindspore_hub_conf.py b/model_zoo/research/cv/ssd_ghostnet/mindspore_hub_conf.py new file mode 100644 index 00000000000..c06cc1c8c6f --- /dev/null +++ b/model_zoo/research/cv/ssd_ghostnet/mindspore_hub_conf.py @@ -0,0 +1,22 @@ +# 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. +# ============================================================================ +"""hub config.""" +from src.ssd_ghostnet import SSD300, ssd_ghostnet +from src.config_ghostnet_13x import config + +def create_network(name, *args, **kwargs): + if name == 'ghostnet_ssd': + return SSD300(ssd_ghostnet(), config, **kwargs) + raise NotImplementedError(f"{name} is not implemented in the repo") diff --git a/model_zoo/research/cv/ssd_ghostnet/scripts/run_distribute_train_ghostnet.sh b/model_zoo/research/cv/ssd_ghostnet/scripts/run_distribute_train_ghostnet.sh new file mode 100644 index 00000000000..5d113c1ca80 --- /dev/null +++ b/model_zoo/research/cv/ssd_ghostnet/scripts/run_distribute_train_ghostnet.sh @@ -0,0 +1,82 @@ +#!/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. +# ============================================================================ + +echo "==============================================================================================================" +echo "Please run the scipt as: " +echo "sh run_distribute_train_ghostnet.sh DEVICE_NUM EPOCH_SIZE LR DATASET RANK_TABLE_FILE PRE_TRAINED PRE_TRAINED_EPOCH_SIZE" +echo "for example: sh run_distribute_train_ghostnet.sh 8 500 0.2 coco /data/hccl.json /opt/ssd-300.ckpt(optional) 200(optional)" +echo "It is better to use absolute path." +echo "=================================================================================================================" + +if [ $# != 5 ] && [ $# != 7 ] +then + echo "Usage: sh run_distribute_train_ghostnet.sh [DEVICE_NUM] [EPOCH_SIZE] [LR] [DATASET] \ +[RANK_TABLE_FILE] [PRE_TRAINED](optional) [PRE_TRAINED_EPOCH_SIZE](optional)" + exit 1 +fi + +# Before start distribute train, first create mindrecord files. +BASE_PATH=$(cd "`dirname $0`" || exit; pwd) +cd $BASE_PATH/../ || exit +python train.py --only_create_dataset=True + +echo "After running the scipt, the network runs in the background. The log will be generated in LOGx/log.txt" + +export RANK_SIZE=$1 +EPOCH_SIZE=$2 +LR=$3 +DATASET=$4 +PRE_TRAINED=$6 +PRE_TRAINED_EPOCH_SIZE=$7 +export RANK_TABLE_FILE=$5 + +for((i=0;i env.log + if [ $# == 5 ] + then + python train.py \ + --distribute=True \ + --lr=$LR \ + --dataset=$DATASET \ + --device_num=$RANK_SIZE \ + --device_id=$DEVICE_ID \ + --epoch_size=$EPOCH_SIZE > log.txt 2>&1 & + fi + + if [ $# == 7 ] + then + python train.py \ + --distribute=True \ + --lr=$LR \ + --dataset=$DATASET \ + --device_num=$RANK_SIZE \ + --device_id=$DEVICE_ID \ + --pre_trained=$PRE_TRAINED \ + --pre_trained_epoch_size=$PRE_TRAINED_EPOCH_SIZE \ + --epoch_size=$EPOCH_SIZE > log.txt 2>&1 & + fi + + cd ../ +done diff --git a/model_zoo/research/cv/ssd_ghostnet/src/__init__.py b/model_zoo/research/cv/ssd_ghostnet/src/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/model_zoo/research/cv/ssd_ghostnet/src/box_utils.py b/model_zoo/research/cv/ssd_ghostnet/src/box_utils.py new file mode 100644 index 00000000000..af309036fd0 --- /dev/null +++ b/model_zoo/research/cv/ssd_ghostnet/src/box_utils.py @@ -0,0 +1,175 @@ +# 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. +# ============================================================================ + +"""Bbox utils""" + +import math +import itertools as it +import numpy as np +from .config_ghostnet_13x import config + + +class GeneratDefaultBoxes(): + """ + Generate Default boxes for SSD, follows the order of (W, H, archor_sizes). + `self.default_boxes` has a shape of [archor_sizes, H, W, 4], the last dimension is [y, x, h, w]. + `self.default_boxes_ltrb` has a shape as `self.default_boxes`, the last dimension is [y1, x1, y2, x2]. + """ + + def __init__(self): + fk = config.img_shape[0] / np.array(config.steps) + scale_rate = (config.max_scale - config.min_scale) / \ + (len(config.num_default) - 1) + scales = [config.min_scale + scale_rate * + i for i in range(len(config.num_default))] + [1.0] + self.default_boxes = [] + for idex, feature_size in enumerate(config.feature_size): + sk1 = scales[idex] + sk2 = scales[idex + 1] + sk3 = math.sqrt(sk1 * sk2) + if idex == 0: + w, h = sk1 * math.sqrt(2), sk1 / math.sqrt(2) + all_sizes = [(0.1, 0.1), (w, h), (h, w)] + else: + all_sizes = [(sk1, sk1)] + for aspect_ratio in config.aspect_ratios[idex]: + w, h = sk1 * math.sqrt(aspect_ratio), sk1 / \ + math.sqrt(aspect_ratio) + all_sizes.append((w, h)) + all_sizes.append((h, w)) + all_sizes.append((sk3, sk3)) + + assert len(all_sizes) == config.num_default[idex] + + for i, j in it.product(range(feature_size), repeat=2): + for w, h in all_sizes: + cx, cy = (j + 0.5) / fk[idex], (i + 0.5) / fk[idex] + self.default_boxes.append([cy, cx, h, w]) + + def to_ltrb(cy, cx, h, w): + return cy - h / 2, cx - w / 2, cy + h / 2, cx + w / 2 + + # For IoU calculation + self.default_boxes_ltrb = np.array( + tuple(to_ltrb(*i) for i in self.default_boxes), dtype='float32') + self.default_boxes = np.array(self.default_boxes, dtype='float32') + + +default_boxes_ltrb = GeneratDefaultBoxes().default_boxes_ltrb +default_boxes = GeneratDefaultBoxes().default_boxes +y1, x1, y2, x2 = np.split(default_boxes_ltrb[:, :4], 4, axis=-1) +vol_anchors = (x2 - x1) * (y2 - y1) +matching_threshold = config.match_thershold + + +def ssd_bboxes_encode(boxes): + """ + Labels anchors with ground truth inputs. + + Args: + boxex: ground truth with shape [N, 5], for each row, it stores [y, x, h, w, cls]. + + Returns: + gt_loc: location ground truth with shape [num_anchors, 4]. + gt_label: class ground truth with shape [num_anchors, 1]. + num_matched_boxes: number of positives in an image. + """ + + def jaccard_with_anchors(bbox): + """Compute jaccard score a box and the anchors.""" + # Intersection bbox and volume. + ymin = np.maximum(y1, bbox[0]) + xmin = np.maximum(x1, bbox[1]) + ymax = np.minimum(y2, bbox[2]) + xmax = np.minimum(x2, bbox[3]) + w = np.maximum(xmax - xmin, 0.) + h = np.maximum(ymax - ymin, 0.) + + # Volumes. + inter_vol = h * w + union_vol = vol_anchors + \ + (bbox[2] - bbox[0]) * (bbox[3] - bbox[1]) - inter_vol + jaccard = inter_vol / union_vol + return np.squeeze(jaccard) + + pre_scores = np.zeros((config.num_ssd_boxes), dtype=np.float32) + t_boxes = np.zeros((config.num_ssd_boxes, 4), dtype=np.float32) + t_label = np.zeros((config.num_ssd_boxes), dtype=np.int64) + for bbox in boxes: + label = int(bbox[4]) + scores = jaccard_with_anchors(bbox) + idx = np.argmax(scores) + scores[idx] = 2.0 + mask = (scores > matching_threshold) + mask = mask & (scores > pre_scores) + pre_scores = np.maximum(pre_scores, scores * mask) + t_label = mask * label + (1 - mask) * t_label + for i in range(4): + t_boxes[:, i] = mask * bbox[i] + (1 - mask) * t_boxes[:, i] + + index = np.nonzero(t_label) + + # Transform to ltrb. + bboxes = np.zeros((config.num_ssd_boxes, 4), dtype=np.float32) + bboxes[:, [0, 1]] = (t_boxes[:, [0, 1]] + t_boxes[:, [2, 3]]) / 2 + bboxes[:, [2, 3]] = t_boxes[:, [2, 3]] - t_boxes[:, [0, 1]] + + # Encode features. + bboxes_t = bboxes[index] + default_boxes_t = default_boxes[index] + bboxes_t[:, :2] = (bboxes_t[:, :2] - default_boxes_t[:, :2]) / \ + (default_boxes_t[:, 2:] * config.prior_scaling[0]) + bboxes_t[:, 2:4] = np.log( + bboxes_t[:, 2:4] / default_boxes_t[:, 2:4]) / config.prior_scaling[1] + bboxes[index] = bboxes_t + + num_match = np.array([len(np.nonzero(t_label)[0])], dtype=np.int32) + return bboxes, t_label.astype(np.int32), num_match + + +def ssd_bboxes_decode(boxes): + """Decode predict boxes to [y, x, h, w]""" + boxes_t = boxes.copy() + default_boxes_t = default_boxes.copy() + boxes_t[:, :2] = boxes_t[:, :2] * config.prior_scaling[0] * \ + default_boxes_t[:, 2:] + default_boxes_t[:, :2] + boxes_t[:, 2:4] = np.exp( + boxes_t[:, 2:4] * config.prior_scaling[1]) * default_boxes_t[:, 2:4] + + bboxes = np.zeros((len(boxes_t), 4), dtype=np.float32) + + bboxes[:, [0, 1]] = boxes_t[:, [0, 1]] - boxes_t[:, [2, 3]] / 2 + bboxes[:, [2, 3]] = boxes_t[:, [0, 1]] + boxes_t[:, [2, 3]] / 2 + + return np.clip(bboxes, 0, 1) + + +def intersect(box_a, box_b): + """Compute the intersect of two sets of boxes.""" + max_yx = np.minimum(box_a[:, 2:4], box_b[2:4]) + min_yx = np.maximum(box_a[:, :2], box_b[:2]) + inter = np.clip((max_yx - min_yx), a_min=0, a_max=np.inf) + return inter[:, 0] * inter[:, 1] + + +def jaccard_numpy(box_a, box_b): + """Compute the jaccard overlap of two sets of boxes.""" + inter = intersect(box_a, box_b) + area_a = ((box_a[:, 2] - box_a[:, 0]) * + (box_a[:, 3] - box_a[:, 1])) + area_b = ((box_b[2] - box_b[0]) * + (box_b[3] - box_b[1])) + union = area_a + area_b - inter + return inter / union diff --git a/model_zoo/research/cv/ssd_ghostnet/src/coco_eval.py b/model_zoo/research/cv/ssd_ghostnet/src/coco_eval.py new file mode 100644 index 00000000000..011fa7e2afb --- /dev/null +++ b/model_zoo/research/cv/ssd_ghostnet/src/coco_eval.py @@ -0,0 +1,129 @@ +# 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. +# ============================================================================ +"""Coco metrics utils""" + +import os +import json +import numpy as np +from .config_ghostnet_13x import config +from .box_utils import ssd_bboxes_decode + + +def apply_nms(all_boxes, all_scores, thres, max_boxes): + """Apply NMS to bboxes.""" + y1 = all_boxes[:, 0] + x1 = all_boxes[:, 1] + y2 = all_boxes[:, 2] + x2 = all_boxes[:, 3] + areas = (x2 - x1 + 1) * (y2 - y1 + 1) + + order = all_scores.argsort()[::-1] + keep = [] + + while order.size > 0: + i = order[0] + keep.append(i) + + if len(keep) >= max_boxes: + break + + xx1 = np.maximum(x1[i], x1[order[1:]]) + yy1 = np.maximum(y1[i], y1[order[1:]]) + xx2 = np.minimum(x2[i], x2[order[1:]]) + yy2 = np.minimum(y2[i], y2[order[1:]]) + + w = np.maximum(0.0, xx2 - xx1 + 1) + h = np.maximum(0.0, yy2 - yy1 + 1) + inter = w * h + + ovr = inter / (areas[i] + areas[order[1:]] - inter) + + inds = np.where(ovr <= thres)[0] + + order = order[inds + 1] + return keep + + +def metrics(pred_data): + """Calculate mAP of predicted bboxes.""" + from pycocotools.coco import COCO + from pycocotools.cocoeval import COCOeval + num_classes = config.num_classes + + coco_root = config.coco_root + data_type = config.val_data_type + + # Classes need to train or test. + val_cls = config.coco_classes + val_cls_dict = {} + for i, cls in enumerate(val_cls): + val_cls_dict[i] = cls + + anno_json = os.path.join(coco_root, config.instances_set.format(data_type)) + coco_gt = COCO(anno_json) + classs_dict = {} + cat_ids = coco_gt.loadCats(coco_gt.getCatIds()) + for cat in cat_ids: + classs_dict[cat["name"]] = cat["id"] + + predictions = [] + img_ids = [] + + for sample in pred_data: + pred_boxes = sample['boxes'] + box_scores = sample['box_scores'] + img_id = sample['img_id'] + h, w = sample['image_shape'] + + pred_boxes = ssd_bboxes_decode(pred_boxes) + final_boxes = [] + final_label = [] + final_score = [] + img_ids.append(img_id) + + for c in range(1, num_classes): + class_box_scores = box_scores[:, c] + score_mask = class_box_scores > config.min_score + class_box_scores = class_box_scores[score_mask] + class_boxes = pred_boxes[score_mask] * [h, w, h, w] + + if score_mask.any(): + nms_index = apply_nms( + class_boxes, class_box_scores, config.nms_thershold, config.max_boxes) + class_boxes = class_boxes[nms_index] + class_box_scores = class_box_scores[nms_index] + + final_boxes += class_boxes.tolist() + final_score += class_box_scores.tolist() + final_label += [classs_dict[val_cls_dict[c]]] * \ + len(class_box_scores) + + for loc, label, score in zip(final_boxes, final_label, final_score): + res = {} + res['image_id'] = img_id + res['bbox'] = [loc[1], loc[0], loc[3] - loc[1], loc[2] - loc[0]] + res['score'] = score + res['category_id'] = label + predictions.append(res) + with open('predictions.json', 'w') as f: + json.dump(predictions, f) + + coco_dt = coco_gt.loadRes('predictions.json') + E = COCOeval(coco_gt, coco_dt, iouType='bbox') + E.params.imgIds = img_ids + E.evaluate() + E.accumulate() + E.summarize() + return E.stats[0] diff --git a/model_zoo/research/cv/ssd_ghostnet/src/config_ghostnet_13x.py b/model_zoo/research/cv/ssd_ghostnet/src/config_ghostnet_13x.py new file mode 100644 index 00000000000..86a5a9083e0 --- /dev/null +++ b/model_zoo/research/cv/ssd_ghostnet/src/config_ghostnet_13x.py @@ -0,0 +1,81 @@ +# 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. +# " ============================================================================ + +"""Config parameters for SSD models.""" + +from easydict import EasyDict as ed + +config = ed({ + "img_shape": [300, 300], + "num_ssd_boxes": 1917, + "neg_pre_positive": 3, + "match_thershold": 0.5, + "nms_thershold": 0.6, + "min_score": 0.1, + "max_boxes": 100, + + # learing rate settings + "global_step": 0, + "lr_init": 0.001, + "lr_end_rate": 0.001, + "warmup_epochs": 2, + "momentum": 0.9, + "weight_decay": 1.5e-4, + + # network + "num_default": [3, 6, 6, 6, 6, 6], + "extras_in_channels": [256, 864, 1248, 512, 256, 256], + "extras_out_channels": [864, 1248, 512, 256, 256, 128], + "extras_srides": [1, 1, 2, 2, 2, 2], + "extras_ratio": [0.2, 0.2, 0.2, 0.25, 0.5, 0.25], + "feature_size": [19, 10, 5, 3, 2, 1], + "min_scale": 0.2, + "max_scale": 0.95, + "aspect_ratios": [(2,), (2, 3), (2, 3), (2, 3), (2, 3), (2, 3)], + "steps": (16, 32, 64, 100, 150, 300), + "prior_scaling": (0.1, 0.2), + "gamma": 2.0, + "alpha": 0.75, + + # `mindrecord_dir` and `coco_root` are better to use absolute path. + "mindrecord_dir": "/ssd0/liuchuanjian/mscoco2017/MindRecord_COCO", + "coco_root": "/ssd0/liuchuanjian/mscoco2017", + "train_data_type": "train2017", + "val_data_type": "val2017", + "instances_set": "annotations/instances_{}.json", + "coco_classes": ('background', 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', + 'train', 'truck', 'boat', 'traffic light', 'fire hydrant', + 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', + 'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', + 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', + 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball', + 'kite', 'baseball bat', 'baseball glove', 'skateboard', + 'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', + 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', + 'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', + 'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', + 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', + 'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', 'sink', + 'refrigerator', 'book', 'clock', 'vase', 'scissors', + 'teddy bear', 'hair drier', 'toothbrush'), + "num_classes": 81, + # The annotation.json position of voc validation dataset. + "voc_root": "", + # voc original dataset. + "voc_dir": "", + # if coco or voc used, `image_dir` and `anno_path` are useless. + "image_dir": "", + "anno_path": "", +}) diff --git a/model_zoo/research/cv/ssd_ghostnet/src/dataset.py b/model_zoo/research/cv/ssd_ghostnet/src/dataset.py new file mode 100644 index 00000000000..165f4eb9c25 --- /dev/null +++ b/model_zoo/research/cv/ssd_ghostnet/src/dataset.py @@ -0,0 +1,424 @@ +# 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. +# ============================================================================ + +"""SSD dataset""" + +from __future__ import division + +import os +import json +import xml.etree.ElementTree as et +import numpy as np +import cv2 + +import mindspore.dataset as de +import mindspore.dataset.transforms.vision.c_transforms as C +from mindspore.mindrecord import FileWriter +from .config_ghostnet_13x import config +from .box_utils import jaccard_numpy, ssd_bboxes_encode + + +def _rand(a=0., b=1.): + """Generate random.""" + return np.random.rand() * (b - a) + a + + +def get_imageId_from_fileName(filename): + """Get imageID from fileName""" + try: + filename = os.path.splitext(filename)[0] + return int(filename) + except: + raise NotImplementedError( + 'Filename %s is supposed to be an integer.' % (filename)) + + +def random_sample_crop(image, boxes): + """Random Crop the image and boxes""" + height, width, _ = image.shape + min_iou = np.random.choice([None, 0.1, 0.3, 0.5, 0.7, 0.9]) + + if min_iou is None: + return image, boxes + + # max trails (50) + for _ in range(50): + image_t = image + + w = _rand(0.3, 1.0) * width + h = _rand(0.3, 1.0) * height + + # aspect ratio constraint b/t .5 & 2 + if h / w < 0.5 or h / w > 2: + continue + + left = _rand() * (width - w) + top = _rand() * (height - h) + + rect = np.array([int(top), int(left), int(top+h), int(left+w)]) + overlap = jaccard_numpy(boxes, rect) + + # dropout some boxes + drop_mask = overlap > 0 + if not drop_mask.any(): + continue + + if overlap[drop_mask].min() < min_iou and overlap[drop_mask].max() > (min_iou + 0.2): + continue + + image_t = image_t[rect[0]:rect[2], rect[1]:rect[3], :] + + centers = (boxes[:, :2] + boxes[:, 2:4]) / 2.0 + + m1 = (rect[0] < centers[:, 0]) * (rect[1] < centers[:, 1]) + m2 = (rect[2] > centers[:, 0]) * (rect[3] > centers[:, 1]) + + # mask in that both m1 and m2 are true + mask = m1 * m2 * drop_mask + + # have any valid boxes? try again if not + if not mask.any(): + continue + + # take only matching gt boxes + boxes_t = boxes[mask, :].copy() + + boxes_t[:, :2] = np.maximum(boxes_t[:, :2], rect[:2]) + boxes_t[:, :2] -= rect[:2] + boxes_t[:, 2:4] = np.minimum(boxes_t[:, 2:4], rect[2:4]) + boxes_t[:, 2:4] -= rect[:2] + + return image_t, boxes_t + return image, boxes + + +def preprocess_fn(img_id, image, box, is_training): + """Preprocess function for dataset.""" + def _infer_data(image, input_shape): + img_h, img_w, _ = image.shape + input_h, input_w = input_shape + + image = cv2.resize(image, (input_w, input_h)) + + # When the channels of image is 1 + if len(image.shape) == 2: + image = np.expand_dims(image, axis=-1) + image = np.concatenate([image, image, image], axis=-1) + + return img_id, image, np.array((img_h, img_w), np.float32) + + def _data_aug(image, box, is_training, image_size=(300, 300)): + """Data augmentation function.""" + ih, iw, _ = image.shape + w, h = image_size + + if not is_training: + return _infer_data(image, image_size) + + # Random crop + box = box.astype(np.float32) + image, box = random_sample_crop(image, box) + ih, iw, _ = image.shape + + # Resize image + image = cv2.resize(image, (w, h)) + + # Flip image or not + flip = _rand() < .5 + if flip: + image = cv2.flip(image, 1, dst=None) + + # When the channels of image is 1 + if len(image.shape) == 2: + image = np.expand_dims(image, axis=-1) + image = np.concatenate([image, image, image], axis=-1) + + box[:, [0, 2]] = box[:, [0, 2]] / ih + box[:, [1, 3]] = box[:, [1, 3]] / iw + + if flip: + box[:, [1, 3]] = 1 - box[:, [3, 1]] + + box, label, num_match = ssd_bboxes_encode(box) + return image, box, label, num_match + return _data_aug(image, box, is_training, image_size=config.img_shape) + + +def create_voc_label(is_training): + """Get image path and annotation from VOC.""" + voc_dir = config.voc_dir + cls_map = {name: i for i, name in enumerate(config.coco_classes)} + sub_dir = 'train' if is_training else 'eval' + #sub_dir = 'train' + voc_dir = os.path.join(voc_dir, sub_dir) + if not os.path.isdir(voc_dir): + raise ValueError(f'Cannot find {sub_dir} dataset path.') + + image_dir = anno_dir = voc_dir + if os.path.isdir(os.path.join(voc_dir, 'Images')): + image_dir = os.path.join(voc_dir, 'Images') + if os.path.isdir(os.path.join(voc_dir, 'Annotations')): + anno_dir = os.path.join(voc_dir, 'Annotations') + + if not is_training: + data_dir = config.voc_root + json_file = os.path.join( + data_dir, config.instances_set.format(sub_dir)) + file_dir = os.path.split(json_file)[0] + if not os.path.isdir(file_dir): + os.makedirs(file_dir) + json_dict = {"images": [], "type": "instances", "annotations": [], + "categories": []} + bnd_id = 1 + + image_files_dict = {} + image_anno_dict = {} + images = [] + for anno_file in os.listdir(anno_dir): + print(anno_file) + if not anno_file.endswith('xml'): + continue + tree = et.parse(os.path.join(anno_dir, anno_file)) + root_node = tree.getroot() + file_name = root_node.find('filename').text + img_id = get_imageId_from_fileName(file_name) + image_path = os.path.join(image_dir, file_name) + print(image_path) + if not os.path.isfile(image_path): + print(f'Cannot find image {file_name} according to annotations.') + continue + + labels = [] + for obj in root_node.iter('object'): + cls_name = obj.find('name').text + if cls_name not in cls_map: + print(f'Label "{cls_name}" not in "{config.coco_classes}"') + continue + bnd_box = obj.find('bndbox') + x_min = int(bnd_box.find('xmin').text) - 1 + y_min = int(bnd_box.find('ymin').text) - 1 + x_max = int(bnd_box.find('xmax').text) - 1 + y_max = int(bnd_box.find('ymax').text) - 1 + labels.append([y_min, x_min, y_max, x_max, cls_map[cls_name]]) + + if not is_training: + o_width = abs(x_max - x_min) + o_height = abs(y_max - y_min) + ann = {'area': o_width * o_height, 'iscrowd': 0, 'image_id': + img_id, 'bbox': [x_min, y_min, o_width, o_height], + 'category_id': cls_map[cls_name], 'id': bnd_id, + 'ignore': 0, + 'segmentation': []} + json_dict['annotations'].append(ann) + bnd_id = bnd_id + 1 + + if labels: + images.append(img_id) + image_files_dict[img_id] = image_path + image_anno_dict[img_id] = np.array(labels) + + if not is_training: + size = root_node.find("size") + width = int(size.find('width').text) + height = int(size.find('height').text) + image = {'file_name': file_name, 'height': height, 'width': width, + 'id': img_id} + json_dict['images'].append(image) + + if not is_training: + for cls_name, cid in cls_map.items(): + cat = {'supercategory': 'none', 'id': cid, 'name': cls_name} + json_dict['categories'].append(cat) + json_fp = open(json_file, 'w') + json_str = json.dumps(json_dict) + json_fp.write(json_str) + json_fp.close() + + return images, image_files_dict, image_anno_dict + + +def create_coco_label(is_training): + """Get image path and annotation from COCO.""" + from pycocotools.coco import COCO + + coco_root = config.coco_root + data_type = config.val_data_type + if is_training: + data_type = config.train_data_type + + # Classes need to train or test. + train_cls = config.coco_classes + train_cls_dict = {} + for i, cls in enumerate(train_cls): + train_cls_dict[cls] = i + + anno_json = os.path.join(coco_root, config.instances_set.format(data_type)) + + coco = COCO(anno_json) + classs_dict = {} + cat_ids = coco.loadCats(coco.getCatIds()) + for cat in cat_ids: + classs_dict[cat["id"]] = cat["name"] + + image_ids = coco.getImgIds() + images = [] + image_path_dict = {} + image_anno_dict = {} + + for img_id in image_ids: + image_info = coco.loadImgs(img_id) + file_name = image_info[0]["file_name"] + anno_ids = coco.getAnnIds(imgIds=img_id, iscrowd=None) + anno = coco.loadAnns(anno_ids) + image_path = os.path.join(coco_root, data_type, file_name) + annos = [] + iscrowd = False + for label in anno: + bbox = label["bbox"] + class_name = classs_dict[label["category_id"]] + iscrowd = iscrowd or label["iscrowd"] + if class_name in train_cls: + x_min, x_max = bbox[0], bbox[0] + bbox[2] + y_min, y_max = bbox[1], bbox[1] + bbox[3] + annos.append( + list(map(round, [y_min, x_min, y_max, x_max])) + [train_cls_dict[class_name]]) + + if not is_training and iscrowd: + continue + if len(annos) >= 1: + images.append(img_id) + image_path_dict[img_id] = image_path + image_anno_dict[img_id] = np.array(annos) + + return images, image_path_dict, image_anno_dict + + +def anno_parser(annos_str): + """Parse annotation from string to list.""" + annos = [] + for anno_str in annos_str: + anno = list(map(int, anno_str.strip().split(','))) + annos.append(anno) + return annos + + +def filter_valid_data(image_dir, anno_path): + """Filter valid image file, which both in image_dir and anno_path.""" + images = [] + image_path_dict = {} + image_anno_dict = {} + if not os.path.isdir(image_dir): + raise RuntimeError("Path given is not valid.") + if not os.path.isfile(anno_path): + raise RuntimeError("Annotation file is not valid.") + + with open(anno_path, "rb") as f: + lines = f.readlines() + for img_id, line in enumerate(lines): + line_str = line.decode("utf-8").strip() + line_split = str(line_str).split(' ') + file_name = line_split[0] + image_path = os.path.join(image_dir, file_name) + if os.path.isfile(image_path): + images.append(img_id) + image_path_dict[img_id] = image_path + image_anno_dict[img_id] = anno_parser(line_split[1:]) + + return images, image_path_dict, image_anno_dict + + +def voc_data_to_mindrecord(mindrecord_dir, is_training, prefix="ssd.mindrecord", file_num=8): + """Create MindRecord file by image_dir and anno_path.""" + mindrecord_path = os.path.join(mindrecord_dir, prefix) + writer = FileWriter(mindrecord_path, file_num) + images, image_path_dict, image_anno_dict = create_voc_label(is_training) + + ssd_json = { + "img_id": {"type": "int32", "shape": [1]}, + "image": {"type": "bytes"}, + "annotation": {"type": "int32", "shape": [-1, 5]}, + } + writer.add_schema(ssd_json, "ssd_json") + + for img_id in images: + image_path = image_path_dict[img_id] + with open(image_path, 'rb') as f: + img = f.read() + annos = np.array(image_anno_dict[img_id], dtype=np.int32) + img_id = np.array([img_id], dtype=np.int32) + row = {"img_id": img_id, "image": img, "annotation": annos} + writer.write_raw_data([row]) + writer.commit() + + +def data_to_mindrecord_byte_image(dataset="coco", is_training=True, prefix="ssd.mindrecord", file_num=8): + """Create MindRecord file.""" + mindrecord_dir = config.mindrecord_dir + mindrecord_path = os.path.join(mindrecord_dir, prefix) + writer = FileWriter(mindrecord_path, file_num) + if dataset == "coco": + images, image_path_dict, image_anno_dict = create_coco_label( + is_training) + else: + images, image_path_dict, image_anno_dict = filter_valid_data( + config.image_dir, config.anno_path) + + ssd_json = { + "img_id": {"type": "int32", "shape": [1]}, + "image": {"type": "bytes"}, + "annotation": {"type": "int32", "shape": [-1, 5]}, + } + writer.add_schema(ssd_json, "ssd_json") + + for img_id in images: + image_path = image_path_dict[img_id] + with open(image_path, 'rb') as f: + img = f.read() + annos = np.array(image_anno_dict[img_id], dtype=np.int32) + img_id = np.array([img_id], dtype=np.int32) + row = {"img_id": img_id, "image": img, "annotation": annos} + writer.write_raw_data([row]) + writer.commit() + + +def create_ssd_dataset(mindrecord_file, batch_size=32, repeat_num=10, device_num=1, rank=0, + is_training=True, num_parallel_workers=4): + """Creatr SSD dataset with MindDataset.""" + ds = de.MindDataset(mindrecord_file, columns_list=["img_id", "image", "annotation"], num_shards=device_num, + shard_id=rank, num_parallel_workers=num_parallel_workers, shuffle=is_training) + decode = C.Decode() + ds = ds.map(input_columns=["image"], operations=decode) + change_swap_op = C.HWC2CHW() + normalize_op = C.Normalize( + mean=[0.485*255, 0.456*255, 0.406*255], std=[0.229*255, 0.224*255, 0.225*255]) + color_adjust_op = C.RandomColorAdjust( + brightness=0.4, contrast=0.4, saturation=0.4) + compose_map_func = (lambda img_id, image, annotation: preprocess_fn( + img_id, image, annotation, is_training)) + if is_training: + output_columns = ["image", "box", "label", "num_match"] + trans = [color_adjust_op, normalize_op, change_swap_op] + else: + output_columns = ["img_id", "image", "image_shape"] + trans = [normalize_op, change_swap_op] + ds = ds.map(input_columns=["img_id", "image", "annotation"], + output_columns=output_columns, columns_order=output_columns, + operations=compose_map_func, python_multiprocessing=is_training, + num_parallel_workers=num_parallel_workers) + ds = ds.map(input_columns=["image"], operations=trans, python_multiprocessing=is_training, + num_parallel_workers=num_parallel_workers) + ds = ds.batch(batch_size, drop_remainder=True) + ds = ds.repeat(repeat_num) + return ds diff --git a/model_zoo/research/cv/ssd_ghostnet/src/init_params.py b/model_zoo/research/cv/ssd_ghostnet/src/init_params.py new file mode 100644 index 00000000000..3eb078179b2 --- /dev/null +++ b/model_zoo/research/cv/ssd_ghostnet/src/init_params.py @@ -0,0 +1,53 @@ +# 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. +# ============================================================================ +"""Parameters utils""" + +import numpy as np +from mindspore.common.initializer import initializer, TruncatedNormal + + +def init_net_param(network, initialize_mode='TruncatedNormal'): + """Init the parameters in net.""" + params = network.trainable_params() + for p in params: + if 'beta' not in p.name and 'gamma' not in p.name and 'bias' not in p.name: + np.random.seed(seed=1) + if initialize_mode == 'TruncatedNormal': + p.set_parameter_data(initializer( + TruncatedNormal(), p.data.shape, p.data.dtype)) + else: + p.set_parameter_data( + initialize_mode, p.data.shape, p.data.dtype) + + +def load_backbone_params(network, param_dict): + """Init the parameters from pre-train model, default is mobilenetv2.""" + for _, param in net.parameters_and_names(): + param_name = param.name.replace('network.backbone.', '') + name_split = param_name.split('.') + if 'features_1' in param_name: + param_name = param_name.replace('features_1', 'features') + if 'features_2' in param_name: + param_name = '.'.join( + ['features', str(int(name_split[1]) + 14)] + name_split[2:]) + if param_name in param_dict: + param.set_parameter_data(param_dict[param_name].data) + + +def filter_checkpoint_parameter(param_dict): + """remove useless parameters""" + for key in list(param_dict.keys()): + if 'multi_loc_layers' in key or 'multi_cls_layers' in key: + del param_dict[key] diff --git a/model_zoo/research/cv/ssd_ghostnet/src/lr_schedule.py b/model_zoo/research/cv/ssd_ghostnet/src/lr_schedule.py new file mode 100644 index 00000000000..eba31f3f5dc --- /dev/null +++ b/model_zoo/research/cv/ssd_ghostnet/src/lr_schedule.py @@ -0,0 +1,57 @@ +# 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 schedule""" + +import math +import numpy as np + + +def get_lr(global_step, lr_init, lr_end, lr_max, warmup_epochs, total_epochs, steps_per_epoch): + """ + generate learning rate array + + Args: + global_step(int): total steps of the training + lr_init(float): init learning rate + lr_end(float): end learning rate + lr_max(float): max learning rate + warmup_epochs(float): number of warmup epochs + total_epochs(int): total epoch of training + steps_per_epoch(int): steps of one epoch + + Returns: + np.array, learning rate array + """ + lr_each_step = [] + total_steps = steps_per_epoch * total_epochs + warmup_steps = steps_per_epoch * warmup_epochs + for i in range(total_steps): + if i < warmup_steps: + lr = lr_init + (lr_max - lr_init) * i / warmup_steps + else: + lr = lr_end + \ + (lr_max - lr_end) * \ + (1. + math.cos(math.pi * (i - warmup_steps) / + (total_steps - warmup_steps))) / 2. + if lr < 0.0: + lr = 0.0 + lr_each_step.append(lr) + + current_step = global_step + lr_each_step = np.array(lr_each_step).astype(np.float32) + learning_rate = lr_each_step[current_step:] + + return learning_rate diff --git a/model_zoo/research/cv/ssd_ghostnet/src/ssd_ghostnet.py b/model_zoo/research/cv/ssd_ghostnet/src/ssd_ghostnet.py new file mode 100644 index 00000000000..5c3e3d4e728 --- /dev/null +++ b/model_zoo/research/cv/ssd_ghostnet/src/ssd_ghostnet.py @@ -0,0 +1,785 @@ +# 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. +# ============================================================================ + +"""SSD net based MobilenetV2.""" +import math +import numpy as np + +import mindspore.common.dtype as mstype +import mindspore as ms +import mindspore.nn as nn +from mindspore import Parameter, context, Tensor +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.initializer import initializer + + +def _make_divisible(x, divisor=4): + return int(np.ceil(x * 1. / divisor) * divisor) + + +def _conv2d(in_channel, out_channel, kernel_size=3, stride=1, pad_mod='same'): + return nn.Conv2d(in_channel, out_channel, kernel_size=kernel_size, stride=stride, + padding=0, pad_mode=pad_mod, has_bias=True) + + +def _bn(channel): + return nn.BatchNorm2d(channel, eps=1e-3, momentum=0.97, + gamma_init=1, beta_init=0, moving_mean_init=0, moving_var_init=1) + + +def _last_conv2d(in_channel, out_channel, kernel_size=3, stride=1, pad_mod='same', pad=0): + depthwise_conv = DepthwiseConv( + in_channel, kernel_size, stride, pad_mode='same', pad=pad) + conv = _conv2d(in_channel, out_channel, kernel_size=1) + return nn.SequentialCell([depthwise_conv, _bn(in_channel), nn.ReLU6(), conv]) + + +class ConvBNReLU(nn.Cell): + """ + Convolution/Depthwise fused with Batchnorm and ReLU block definition. + + Args: + in_planes (int): Input channel. + out_planes (int): Output channel. + kernel_size (int): Input kernel size. + stride (int): Stride size for the first convolutional layer. Default: 1. + groups (int): channel group. Convolution is 1 while Depthiwse is input channel. Default: 1. + + Returns: + Tensor, output tensor. + + Examples: + >>> ConvBNReLU(16, 256, kernel_size=1, stride=1, groups=1) + """ + + def __init__(self, in_planes, out_planes, kernel_size=3, stride=1, groups=1, use_act=True, act_type='relu'): + super(ConvBNReLU, self).__init__() + padding = 0 + if groups == 1: + conv = nn.Conv2d(in_planes, out_planes, kernel_size, stride, pad_mode='same', + padding=padding) + else: + conv = DepthwiseConv(in_planes, kernel_size, + stride, pad_mode='same', pad=padding) + layers = [conv, _bn(out_planes)] + if use_act: + layers.append(Activation(act_type)) + self.features = nn.SequentialCell(layers) + + def construct(self, x): + output = self.features(x) + # print(output.shape) + return output + + +class DepthwiseConv(nn.Cell): + """ + Depthwise Convolution warpper definition. + + Args: + in_planes (int): Input channel. + kernel_size (int): Input kernel size. + stride (int): Stride size. + pad_mode (str): pad mode in (pad, same, valid) + channel_multiplier (int): Output channel multiplier + has_bias (bool): has bias or not + + Returns: + Tensor, output tensor. + + Examples: + >>> DepthwiseConv(16, 3, 1, 'pad', 1, channel_multiplier=1) + """ + + 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.in_channels = in_planes + self.channel_multiplier = channel_multiplier + self.out_channels = in_planes * channel_multiplier + self.kernel_size = (kernel_size, kernel_size) + self.depthwise_conv = P.DepthwiseConv2dNative(channel_multiplier=channel_multiplier, + kernel_size=self.kernel_size, + stride=stride, pad_mode=pad_mode, pad=pad) + self.bias_add = P.BiasAdd() + weight_shape = [channel_multiplier, in_planes, *self.kernel_size] + self.weight = Parameter(initializer( + 'ones', 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 MyHSigmoid(nn.Cell): + def __init__(self): + super(MyHSigmoid, self).__init__() + self.relu6 = nn.ReLU6() + + def construct(self, x): + return self.relu6(x + 3.) / 6. + + +class Activation(nn.Cell): + """ + Activation definition. + + Args: + act_func(string): activation name. + + Returns: + Tensor, output tensor. + """ + + def __init__(self, act_func): + super(Activation, self).__init__() + if act_func == 'relu': + self.act = nn.ReLU() + elif act_func == 'relu6': + self.act = nn.ReLU6() + elif act_func in ('hsigmoid', 'hard_sigmoid'): + self.act = MyHSigmoid() # nn.HSigmoid() + elif act_func in ('hswish', 'hard_swish'): + self.act = nn.HSwish() + else: + raise NotImplementedError + + def construct(self, x): + return self.act(x) + + +class GlobalAvgPooling(nn.Cell): + """ + Global avg pooling definition. + + Args: + + Returns: + Tensor, output tensor. + + Examples: + >>> GlobalAvgPooling() + """ + + def __init__(self, keep_dims=False): + super(GlobalAvgPooling, self).__init__() + self.mean = P.ReduceMean(keep_dims=keep_dims) + + def construct(self, x): + x = self.mean(x, (2, 3)) + return x + + +class SE(nn.Cell): + """ + SE warpper definition. + + Args: + num_out (int): Output channel. + ratio (int): middle output ratio. + + Returns: + Tensor, output tensor. + + Examples: + >>> SE(4) + """ + + def __init__(self, num_out, ratio=4): + super(SE, self).__init__() + num_mid = _make_divisible(num_out // ratio) + self.pool = GlobalAvgPooling(keep_dims=True) + self.conv_reduce = nn.Conv2d(in_channels=num_out, out_channels=num_mid, + kernel_size=1, has_bias=True, pad_mode='pad') + self.act1 = Activation('relu') + self.conv_expand = nn.Conv2d(in_channels=num_mid, out_channels=num_out, + kernel_size=1, has_bias=True, pad_mode='pad') + self.act2 = Activation('hsigmoid') + self.mul = P.Mul() + + def construct(self, x): + out = self.pool(x) + out = self.conv_reduce(out) + out = self.act1(out) + out = self.conv_expand(out) + out = self.act2(out) + out = self.mul(x, out) + return out + + +class GhostModule(nn.Cell): + """ + GhostModule warpper definition. + + Args: + num_in (int): Input channel. + num_out (int): Output channel. + kernel_size (int): Input kernel size. + stride (int): Stride size. + padding (int): Padding number. + use_act (bool): Used activation or not. + act_type (string): Activation type. + + Returns: + Tensor, output tensor. + + Examples: + >>> GhostModule(3, 3) + """ + + def __init__(self, num_in, num_out, kernel_size=1, stride=1, padding=0, ratio=2, dw_size=3, + use_act=True, act_type='relu'): + super(GhostModule, self).__init__() + init_channels = math.ceil(num_out / ratio) + new_channels = init_channels * (ratio - 1) + + self.primary_conv = ConvBNReLU(num_in, init_channels, kernel_size=kernel_size, stride=stride, + groups=1, use_act=use_act, act_type='relu') + self.cheap_operation = ConvBNReLU(init_channels, new_channels, kernel_size=dw_size, stride=1, + groups=init_channels, use_act=use_act, act_type='relu') + self.concat = P.Concat(axis=1) + + def construct(self, x): + x1 = self.primary_conv(x) + x2 = self.cheap_operation(x1) + # print(x1.shape) + # print(x2.shape) + return self.concat((x1, x2)) + + +class GhostBottleneck(nn.Cell): + """ + GhostBottleneck warpper definition. + + Args: + num_in (int): Input channel. + num_mid (int): Middle channel. + num_out (int): Output channel. + kernel_size (int): Input kernel size. + stride (int): Stride size. + act_type (str): Activation type. + use_se (bool): Use SE warpper or not. + + Returns: + Tensor, output tensor. + + Examples: + >>> GhostBottleneck(16, 3, 1, 1) + """ + + def __init__(self, num_in, num_mid, num_out, kernel_size, stride=1, act_type='relu', use_se=False, + use_res_connect=True, last_relu=False): + super(GhostBottleneck, self).__init__() + self.ghost1 = GhostModule(num_in, num_mid, kernel_size=1, + stride=1, padding=0, act_type=act_type) + + self.use_res_connect = use_res_connect + self.last_relu = last_relu + self.use_dw = stride > 1 + self.dw = None + if self.use_dw: + self.dw = ConvBNReLU(num_mid, num_mid, kernel_size=kernel_size, stride=stride, + act_type=act_type, groups=num_mid, use_act=False) + + self.use_se = use_se + if use_se: + self.se = SE(num_mid) + + self.ghost2 = GhostModule(num_mid, num_out, kernel_size=1, stride=1, + padding=0, act_type=act_type, use_act=False) + self.relu = nn.ReLU() + if self.use_res_connect: + self.down_sample = False + if num_in != num_out or stride != 1: + self.down_sample = True + self.shortcut = None + if self.down_sample: + self.shortcut = nn.SequentialCell([ + ConvBNReLU(num_in, num_in, kernel_size=kernel_size, stride=stride, + groups=num_in, use_act=False), + ConvBNReLU(num_in, num_out, kernel_size=1, stride=1, + groups=1, use_act=False), + ]) + self.add = P.TensorAdd() + + def construct(self, x): + """construct""" + shortcut = x + out = self.ghost1(x) + if self.use_dw: + out = self.dw(out) + if self.use_se: + out = self.se(out) + out = self.ghost2(out) + if self.use_res_connect: + if self.down_sample: + shortcut = self.shortcut(shortcut) + out = self.add(shortcut, out) + if self.last_relu: + out = self.relu(out) + # print(out.shape) + return out + + def _get_pad(self, kernel_size): + """set the padding number""" + pad = 0 + if kernel_size == 1: + pad = 0 + elif kernel_size == 3: + pad = 1 + elif kernel_size == 5: + pad = 2 + elif kernel_size == 7: + pad = 3 + else: + raise NotImplementedError + return pad + + +class InvertedResidual(nn.Cell): + """ + Residual block definition. + + Args: + inp (int): Input channel. + oup (int): Output channel. + stride (int): Stride size for the first convolutional layer. Default: 1. + expand_ratio (int): expand ration of input channel + + Returns: + Tensor, output tensor. + + Examples: + >>> ResidualBlock(3, 256, 1, 1) + """ + + def __init__(self, inp, oup, stride, expand_ratio, last_relu=False): + super(InvertedResidual, self).__init__() + assert stride in [1, 2] + + hidden_dim = int(round(inp * expand_ratio)) + self.use_res_connect = stride == 1 and inp == oup + + layers = [] + if expand_ratio != 1: + 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), + _bn(oup), + ]) + self.conv = nn.SequentialCell(layers) + self.add = P.TensorAdd() + self.cast = P.Cast() + self.last_relu = last_relu + self.relu = nn.ReLU6() + + def construct(self, x): + identity = x + x = self.conv(x) + if self.use_res_connect: + x = self.add(identity, x) + if self.last_relu: + x = self.relu(x) + # print(x.shape) + return x + + +class FlattenConcat(nn.Cell): + """ + Concatenate predictions into a single tensor. + + Args: + config (dict): The default config of SSD. + + Returns: + Tensor, flatten predictions. + """ + + def __init__(self, config): + super(FlattenConcat, self).__init__() + self.num_ssd_boxes = config.num_ssd_boxes + self.concat = P.Concat(axis=1) + self.transpose = P.Transpose() + + def construct(self, inputs): + output = () + batch_size = F.shape(inputs[0])[0] + for x in inputs: + x = self.transpose(x, (0, 2, 3, 1)) + output += (F.reshape(x, (batch_size, -1)),) + res = self.concat(output) + return F.reshape(res, (batch_size, self.num_ssd_boxes, -1)) + + +class MultiBox(nn.Cell): + """ + Multibox conv layers. Each multibox layer contains class conf scores and localization predictions. + + Args: + config (dict): The default config of SSD. + + Returns: + Tensor, localization predictions. + Tensor, class conf scores. + """ + + def __init__(self, config): + super(MultiBox, self).__init__() + num_classes = config.num_classes + out_channels = config.extras_out_channels + num_default = config.num_default + + loc_layers = [] + cls_layers = [] + for k, out_channel in enumerate(out_channels): + loc_layers += [_last_conv2d(out_channel, 4 * num_default[k], + kernel_size=3, stride=1, pad_mod='same', pad=0)] + cls_layers += [_last_conv2d(out_channel, num_classes * num_default[k], + kernel_size=3, stride=1, pad_mod='same', pad=0)] + + self.multi_loc_layers = nn.layer.CellList(loc_layers) + self.multi_cls_layers = nn.layer.CellList(cls_layers) + self.flatten_concat = FlattenConcat(config) + + def construct(self, inputs): + loc_outputs = () + cls_outputs = () + for i in range(len(self.multi_loc_layers)): + loc_outputs += (self.multi_loc_layers[i](inputs[i]),) + cls_outputs += (self.multi_cls_layers[i](inputs[i]),) + return self.flatten_concat(loc_outputs), self.flatten_concat(cls_outputs) + + +class SSD300(nn.Cell): + """ + SSD300 Network. Default backbone is resnet34. + + Args: + backbone (Cell): Backbone Network. + config (dict): The default config of SSD. + + Returns: + Tensor, localization predictions. + Tensor, class conf scores. + + Examples:backbone + SSD300(backbone=resnet34(num_classes=None), + config=config). + """ + + def __init__(self, backbone, config, is_training=True, **kwargs): + super(SSD300, self).__init__() + + self.backbone = backbone + in_channels = config.extras_in_channels + out_channels = config.extras_out_channels + ratios = config.extras_ratio + strides = config.extras_srides + residual_list = [] + for i in range(2, len(in_channels)): + residual = InvertedResidual(in_channels[i], out_channels[i], stride=strides[i], + expand_ratio=ratios[i], last_relu=True) + residual_list.append(residual) + self.multi_residual = nn.layer.CellList(residual_list) + self.multi_box = MultiBox(config) + self.is_training = is_training + if not is_training: + self.activation = P.Sigmoid() + + def construct(self, x): + r"""construct of SSD300""" + layer_out_11, output = self.backbone(x) + multi_feature = (layer_out_11, output) + feature = output + for residual in self.multi_residual: + feature = residual(feature) + multi_feature += (feature,) + pred_loc, pred_label = self.multi_box(multi_feature) + if not self.is_training: + pred_label = self.activation(pred_label) + return pred_loc, pred_label + + +class SigmoidFocalClassificationLoss(nn.Cell): + """" + Sigmoid focal-loss for classification. + + Args: + gamma (float): Hyper-parameter to balance the easy and hard examples. Default: 2.0 + alpha (float): Hyper-parameter to balance the positive and negative example. Default: 0.25 + + Returns: + Tensor, the focal loss. + """ + + def __init__(self, gamma=2.0, alpha=0.25): + super(SigmoidFocalClassificationLoss, self).__init__() + self.sigmiod_cross_entropy = P.SigmoidCrossEntropyWithLogits() + self.sigmoid = P.Sigmoid() + self.pow = P.Pow() + self.onehot = P.OneHot() + self.on_value = Tensor(1.0, mstype.float32) + self.off_value = Tensor(0.0, mstype.float32) + self.gamma = gamma + self.alpha = alpha + + def construct(self, logits, label): + r"""construct of SigmoidFocalClassificationLoss""" + label = self.onehot(label, F.shape( + logits)[-1], self.on_value, self.off_value) + sigmiod_cross_entropy = self.sigmiod_cross_entropy(logits, label) + sigmoid = self.sigmoid(logits) + label = F.cast(label, mstype.float32) + p_t = label * sigmoid + (1 - label) * (1 - sigmoid) + modulating_factor = self.pow(1 - p_t, self.gamma) + alpha_weight_factor = label * self.alpha + \ + (1 - label) * (1 - self.alpha) + focal_loss = modulating_factor * alpha_weight_factor * sigmiod_cross_entropy + return focal_loss + + +class SSDWithLossCell(nn.Cell): + """" + Provide SSD training loss through network. + + Args: + network (Cell): The training network. + config (dict): SSD config. + + Returns: + Tensor, the loss of the network. + """ + + def __init__(self, network, config): + super(SSDWithLossCell, self).__init__() + self.network = network + self.less = P.Less() + self.tile = P.Tile() + self.reduce_sum = P.ReduceSum() + self.reduce_mean = P.ReduceMean() + self.expand_dims = P.ExpandDims() + self.class_loss = SigmoidFocalClassificationLoss( + config.gamma, config.alpha) + self.loc_loss = nn.SmoothL1Loss() + + def construct(self, x, gt_loc, gt_label, num_matched_boxes): + r"""construct of SSDWithLossCell""" + pred_loc, pred_label = self.network(x) + mask = F.cast(self.less(0, gt_label), mstype.float32) + num_matched_boxes = self.reduce_sum( + F.cast(num_matched_boxes, mstype.float32)) + + # Localization Loss + mask_loc = self.tile(self.expand_dims(mask, -1), (1, 1, 4)) + smooth_l1 = self.loc_loss(pred_loc, gt_loc) * mask_loc + loss_loc = self.reduce_sum(self.reduce_mean(smooth_l1, -1), -1) + + # Classification Loss + loss_cls = self.class_loss(pred_label, gt_label) + loss_cls = self.reduce_sum(loss_cls, (1, 2)) + + return self.reduce_sum((loss_cls + loss_loc) / num_matched_boxes) + + +class TrainingWrapper(nn.Cell): + """ + Encapsulation class of SSD network training. + + Append an optimizer to the training network after that the construct + function can be called to create the backward graph. + + Args: + network (Cell): The training network. Note that loss function should have been added. + optimizer (Optimizer): Optimizer for updating the weights. + sens (Number): The adjust parameter. Default: 1.0. + """ + + def __init__(self, network, optimizer, sens=1.0): + super(TrainingWrapper, self).__init__(auto_prefix=False) + self.network = network + self.weights = ms.ParameterTuple(network.trainable_params()) + self.optimizer = optimizer + self.grad = C.GradOperation('grad', 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 [ms.ParallelMode.DATA_PARALLEL, ms.ParallelMode.HYBRID_PARALLEL]: + self.reducer_flag = True + if self.reducer_flag: + mean = context.get_auto_parallel_context("mirror_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) + + def construct(self, *args): + weights = self.weights + loss = self.network(*args) + sens = P.Fill()(P.DType()(loss), P.Shape()(loss), self.sens) + grads = self.grad(self.network, weights)(*args, sens) + if self.reducer_flag: + # apply grad reducer on grads + grads = self.grad_reducer(grads) + return F.depend(loss, self.optimizer(grads)) + + +class SSDWithGhostNet(nn.Cell): + """ + Ghostnett architecture for SSD backbone. + + Args: + model_cfgs (list): model config + multiplier (int): Channels multiplier for round to 8/16 and others. Default is 1. + round_nearest (list): Channel round to. Default is 8 + Returns: + Tensor, the 11th feature after ConvBNReLU in MobileNetV2. + Tensor, the last feature in MobileNetV2. + + Examples: + >>> SSDWithGhostNet() + """ + + def __init__(self, model_cfgs, multiplier=1., round_nearest=8): + super(SSDWithGhostNet, self).__init__() + self.cfgs = model_cfgs['cfg'] + # self.inplanes = 16 ## for "1x" + self.inplanes = 20 # for "1.3x" + first_conv_in_channel = 3 + first_conv_out_channel = _make_divisible(multiplier * self.inplanes) + + blocks = [] + blocks.append(ConvBNReLU(first_conv_in_channel, first_conv_out_channel, + kernel_size=3, stride=2, groups=1, use_act=True, act_type='relu')) + + layer_index = 0 + for layer_cfg in self.cfgs: + # print(layer_cfg) + if layer_index == 11: + hidden_dim = int(round(self.inplanes * 6)) + self.expand_layer_conv_11 = ConvBNReLU( + self.inplanes, hidden_dim, kernel_size=1) + blocks.append(self._make_layer(kernel_size=layer_cfg[0], + exp_ch=_make_divisible( + multiplier * layer_cfg[1]), + out_channel=_make_divisible( + multiplier * layer_cfg[2]), + use_se=layer_cfg[3], + act_func=layer_cfg[4], + stride=layer_cfg[5])) + layer_index += 1 + output_channel = _make_divisible( + multiplier * model_cfgs["cls_ch_squeeze"]) + blocks.append(ConvBNReLU(_make_divisible(multiplier * self.cfgs[-1][2]), output_channel, + kernel_size=1, stride=1, groups=1, use_act=True)) + + self.features_1 = nn.SequentialCell(blocks[:12]) + self.features_2 = nn.SequentialCell(blocks[12:]) + + def _make_layer(self, kernel_size, exp_ch, out_channel, use_se, act_func, stride=1): + mid_planes = exp_ch + out_planes = out_channel + # num_in, num_mid, num_out, kernel_size, stride=1, act_type='relu', use_se=False): + layer = GhostBottleneck(self.inplanes, mid_planes, out_planes, + kernel_size, stride=stride, act_type=act_func, use_se=use_se) + self.inplanes = out_planes + return layer + + def construct(self, x): + out = self.features_1(x) + expand_layer_conv_11 = self.expand_layer_conv_11(out) + out = self.features_2(out) + return expand_layer_conv_11, out + + +def ssd_ghostnet(**kwargs): + """ + Constructs a SSD GhostNet model + """ + model_cfgs = { + "1x": { + "cfg": [ + # k, exp, c, se, nl, s, + # stage1 + [3, 16, 16, False, 'relu', 1], + # stage2 + [3, 48, 24, False, 'relu', 2], + [3, 72, 24, False, 'relu', 1], + # stage3 + [5, 72, 40, True, 'relu', 2], + [5, 120, 40, True, 'relu', 1], + # stage4 + [3, 240, 80, False, 'relu', 2], + [3, 200, 80, False, 'relu', 1], + [3, 184, 80, False, 'relu', 1], + [3, 184, 80, False, 'relu', 1], + [3, 480, 112, True, 'relu', 1], + [3, 672, 112, True, 'relu', 1], + # stage5 + [5, 672, 160, True, 'relu', 2], + [5, 960, 160, False, 'relu', 1], + [5, 960, 160, True, 'relu', 1], + [5, 960, 160, False, 'relu', 1], + [5, 960, 160, True, 'relu', 1]], + "cls_ch_squeeze": 960, + }, + "1.3x": { + "cfg": [ + # k, exp, c, se, nl, s + # stage1 + [3, 24, 20, False, 'relu', 1], + # stage2 + [3, 64, 32, False, 'relu', 2], + [3, 96, 32, False, 'relu', 1], + # stage3 + [5, 96, 52, True, 'relu', 2], + [5, 160, 52, True, 'relu', 1], + # stage4 + [3, 312, 104, False, 'relu', 2], + [3, 264, 104, False, 'relu', 1], + [3, 240, 104, False, 'relu', 1], + [3, 240, 104, False, 'relu', 1], + [3, 624, 144, True, 'relu', 1], + [3, 864, 144, True, 'relu', 1], + # stage5 + [5, 864, 208, True, 'relu', 2], + [5, 1248, 208, False, 'relu', 1], + [5, 1248, 208, True, 'relu', 1], + [5, 1248, 208, False, 'relu', 1], + [5, 1248, 208, True, 'relu', 1]], + "cls_ch_squeeze": 1248, + } + } + return SSDWithGhostNet(model_cfgs["1.3x"], **kwargs) diff --git a/model_zoo/research/cv/ssd_ghostnet/train.py b/model_zoo/research/cv/ssd_ghostnet/train.py new file mode 100644 index 00000000000..85c7d86f1a7 --- /dev/null +++ b/model_zoo/research/cv/ssd_ghostnet/train.py @@ -0,0 +1,169 @@ +# 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 +# +# less 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 SSD and get checkpoint files.""" + +import os +import argparse +import ast +import mindspore.nn as nn +from mindspore import context, Tensor +from mindspore.communication.management import init +from mindspore.train.callback import CheckpointConfig, ModelCheckpoint, LossMonitor, TimeMonitor +from mindspore.train import Model, ParallelMode +# from mindspore.context import ParallelMode +from mindspore.train.serialization import load_checkpoint, load_param_into_net +from src.ssd_ghostnet import SSD300, SSDWithLossCell, TrainingWrapper, ssd_ghostnet +# from src.config_ghostnet_1x import config +from src.config_ghostnet_13x import config +from src.dataset import create_ssd_dataset, data_to_mindrecord_byte_image, voc_data_to_mindrecord +from src.lr_schedule import get_lr +from src.init_params import init_net_param, filter_checkpoint_parameter + + +def main(): + parser = argparse.ArgumentParser(description="SSD training") + parser.add_argument("--only_create_dataset", type=ast.literal_eval, default=False, + help="If set it true, only create Mindrecord, default is False.") + parser.add_argument("--distribute", type=ast.literal_eval, default=False, + help="Run distribute, default is False.") + parser.add_argument("--device_id", type=int, default=4, + help="Device id, default is 0.") + parser.add_argument("--device_num", type=int, default=1, + help="Use device nums, default is 1.") + parser.add_argument("--lr", type=float, default=0.05, + help="Learning rate, default is 0.05.") + parser.add_argument("--mode", type=str, default="sink", + help="Run sink mode or not, default is sink.") + parser.add_argument("--dataset", type=str, default="coco", + help="Dataset, defalut is coco.") + parser.add_argument("--epoch_size", type=int, default=500, + help="Epoch size, default is 500.") + parser.add_argument("--batch_size", type=int, default=32, + help="Batch size, default is 32.") + parser.add_argument("--pre_trained", type=str, default=None, + help="Pretrained Checkpoint file path.") + parser.add_argument("--pre_trained_epoch_size", type=int, + default=0, help="Pretrained epoch size.") + parser.add_argument("--save_checkpoint_epochs", type=int, + default=10, help="Save checkpoint epochs, default is 10.") + parser.add_argument("--loss_scale", type=int, default=1024, + help="Loss scale, default is 1024.") + parser.add_argument("--filter_weight", type=ast.literal_eval, default=False, + help="Filter weight parameters, default is False.") + args_opt = parser.parse_args() + + context.set_context(mode=context.GRAPH_MODE, + device_target="Ascend", device_id=args_opt.device_id) + + if args_opt.distribute: + device_num = args_opt.device_num + context.reset_auto_parallel_context() + context.set_auto_parallel_context(parallel_mode=ParallelMode.DATA_PARALLEL, mirror_mean=True, + device_num=device_num) + init() + rank = args_opt.device_id % device_num + else: + rank = 0 + device_num = 1 + + print("Start create dataset!") + + # It will generate mindrecord file in args_opt.mindrecord_dir, + # and the file name is ssd.mindrecord0, 1, ... file_num. + + prefix = "ssd.mindrecord" + mindrecord_dir = config.mindrecord_dir + mindrecord_file = os.path.join(mindrecord_dir, prefix + "0") + if not os.path.exists(mindrecord_file): + if not os.path.isdir(mindrecord_dir): + os.makedirs(mindrecord_dir) + if args_opt.dataset == "coco": + if os.path.isdir(config.coco_root): + print("Create Mindrecord.") + data_to_mindrecord_byte_image("coco", True, prefix) + print("Create Mindrecord Done, at {}".format(mindrecord_dir)) + else: + print("coco_root not exits.") + elif args_opt.dataset == "voc": + if os.path.isdir(config.voc_dir): + print("Create Mindrecord.") + voc_data_to_mindrecord(mindrecord_dir, True, prefix) + print("Create Mindrecord Done, at {}".format(mindrecord_dir)) + else: + print("voc_dir not exits.") + else: + if os.path.isdir(config.image_dir) and os.path.exists(config.anno_path): + print("Create Mindrecord.") + data_to_mindrecord_byte_image("other", True, prefix) + print("Create Mindrecord Done, at {}".format(mindrecord_dir)) + else: + print("image_dir or anno_path not exits.") + + if not args_opt.only_create_dataset: + loss_scale = float(args_opt.loss_scale) + + # When create MindDataset, using the fitst mindrecord file, such as ssd.mindrecord0. + dataset = create_ssd_dataset(mindrecord_file, repeat_num=1, + batch_size=args_opt.batch_size, device_num=device_num, rank=rank) + + dataset_size = dataset.get_dataset_size() + print("Create dataset done!") + + backbone = ssd_ghostnet() + ssd = SSD300(backbone=backbone, config=config) + # print(ssd) + net = SSDWithLossCell(ssd, config) + init_net_param(net) + + # checkpoint + ckpt_config = CheckpointConfig( + save_checkpoint_steps=dataset_size * args_opt.save_checkpoint_epochs, keep_checkpoint_max=60) + ckpoint_cb = ModelCheckpoint( + prefix="ssd", directory=None, config=ckpt_config) + + if args_opt.pre_trained: + if args_opt.pre_trained_epoch_size <= 0: + raise KeyError( + "pre_trained_epoch_size must be greater than 0.") + param_dict = load_checkpoint(args_opt.pre_trained) + if args_opt.filter_weight: + filter_checkpoint_parameter(param_dict) + load_param_into_net(net, param_dict) + + lr = Tensor(get_lr(global_step=config.global_step, + lr_init=config.lr_init, lr_end=config.lr_end_rate * args_opt.lr, lr_max=args_opt.lr, + warmup_epochs=config.warmup_epochs, + total_epochs=args_opt.epoch_size, + steps_per_epoch=dataset_size)) + opt = nn.Momentum(filter(lambda x: x.requires_grad, net.get_parameters()), lr, + config.momentum, config.weight_decay, loss_scale) + net = TrainingWrapper(net, opt, loss_scale) + + callback = [TimeMonitor(data_size=dataset_size), + LossMonitor(), ckpoint_cb] + + model = Model(net) + dataset_sink_mode = False + if args_opt.mode == "sink": + print("In sink mode, one epoch return a loss.") + dataset_sink_mode = True + print("Start train SSD, the first epoch will be slower because of the graph compilation.") + model.train(args_opt.epoch_size, dataset, + callbacks=callback, dataset_sink_mode=dataset_sink_mode) + + +if __name__ == '__main__': + main()