+
+[](https://pypi.org/project/mmcls)
+[](https://mmclassification.readthedocs.io/en/latest/)
+[](https://github.com/open-mmlab/mmclassification/actions)
+[](https://codecov.io/gh/open-mmlab/mmclassification)
+[](https://github.com/open-mmlab/mmclassification/blob/master/LICENSE)
+[](https://github.com/open-mmlab/mmclassification/issues)
+[](https://github.com/open-mmlab/mmclassification/issues)
+
+[📘 Documentation](https://mmclassification.readthedocs.io/en/latest/) |
+[🛠️ Installation](https://mmclassification.readthedocs.io/en/latest/install.html) |
+[👀 Model Zoo](https://mmclassification.readthedocs.io/en/latest/model_zoo.html) |
+[🆕 Update News](https://mmclassification.readthedocs.io/en/latest/changelog.html) |
+[🤔 Reporting Issues](https://github.com/open-mmlab/mmclassification/issues/new/choose)
+
+:point_right: **MMClassification 1.0 branch is in trial, welcome every to [try it](https://github.com/open-mmlab/mmclassification/tree/1.x) and [discuss with us](https://github.com/open-mmlab/mmclassification/discussions)!** :point_left:
+
+
+
+## Introduction
+
+English | [简体中文](/README_zh-CN.md) | [模型的测试方法及测试步骤](train.md)
+
+MMClassification is an open source image classification toolbox based on PyTorch. It is
+a part of the [OpenMMLab](https://openmmlab.com/) project.
+
+The master branch works with **PyTorch 1.5+**.
+
+
+
+
+
+### Major features
+
+- Various backbones and pretrained models
+- Bag of training tricks
+- Large-scale training configs
+- High efficiency and extensibility
+- Powerful toolkits
+
+## What's new
+
+The MMClassification 1.0 has released! It's still unstable and in release candidate. If you want to try it, go
+to [the 1.x branch](https://github.com/open-mmlab/mmclassification/tree/1.x) and discuss it with us in
+[the discussion](https://github.com/open-mmlab/mmclassification/discussions).
+
+v0.24.1 was released in 31/10/2022.
+Highlights of the new version:
+
+- Support HUAWEI Ascend device.
+
+v0.24.0 was released in 30/9/2022.
+Highlights of the new version:
+
+- Support **HorNet**, **EfficientFormerm**, **SwinTransformer V2** and **MViT** backbones.
+- Support Standford Cars dataset.
+
+v0.23.0 was released in 1/5/2022.
+Highlights of the new version:
+
+- Support **DenseNet**, **VAN** and **PoolFormer**, and provide pre-trained models.
+- Support training on IPU.
+- New style API docs, welcome [view it](https://mmclassification.readthedocs.io/en/master/api/models.html).
+
+Please refer to [changelog.md](docs/en/changelog.md) for more details and other release history.
+
+## Installation
+
+Below are quick steps for installation:
+
+```shell
+conda create -n open-mmlab python=3.8 pytorch=1.10 cudatoolkit=11.3 torchvision==0.11.0 -c pytorch -y
+conda activate open-mmlab
+pip3 install openmim
+mim install mmcv-full
+git clone https://github.com/open-mmlab/mmclassification.git
+cd mmclassification
+pip3 install -e .
+```
+
+Please refer to [install.md](https://mmclassification.readthedocs.io/en/latest/install.html) for more detailed installation and dataset preparation.
+
+## Getting Started
+
+Please see [Getting Started](https://mmclassification.readthedocs.io/en/latest/getting_started.html) for the basic usage of MMClassification. There are also tutorials:
+
+- [Learn about Configs](https://mmclassification.readthedocs.io/en/latest/tutorials/config.html)
+- [Fine-tune Models](https://mmclassification.readthedocs.io/en/latest/tutorials/finetune.html)
+- [Add New Dataset](https://mmclassification.readthedocs.io/en/latest/tutorials/new_dataset.html)
+- [Customizie Data Pipeline](https://mmclassification.readthedocs.io/en/latest/tutorials/data_pipeline.html)
+- [Add New Modules](https://mmclassification.readthedocs.io/en/latest/tutorials/new_modules.html)
+- [Customizie Schedule](https://mmclassification.readthedocs.io/en/latest/tutorials/schedule.html)
+- [Customizie Runtime Settings](https://mmclassification.readthedocs.io/en/latest/tutorials/runtime.html)
+
+Colab tutorials are also provided:
+
+- Learn about MMClassification **Python API**: [Preview the notebook](https://github.com/open-mmlab/mmclassification/blob/master/docs/en/tutorials/MMClassification_python.ipynb) or directly [run on Colab](https://colab.research.google.com/github/open-mmlab/mmclassification/blob/master/docs/en/tutorials/MMClassification_python.ipynb).
+- Learn about MMClassification **CLI tools**: [Preview the notebook](https://github.com/open-mmlab/mmclassification/blob/master/docs/en/tutorials/MMClassification_tools.ipynb) or directly [run on Colab](https://colab.research.google.com/github/open-mmlab/mmclassification/blob/master/docs/en/tutorials/MMClassification_tools.ipynb).
+
+## Model zoo
+
+Results and models are available in the [model zoo](https://mmclassification.readthedocs.io/en/latest/model_zoo.html).
+
+
+Supported backbones
+
+- [x] [VGG](https://github.com/open-mmlab/mmclassification/tree/master/configs/vgg)
+- [x] [ResNet](https://github.com/open-mmlab/mmclassification/tree/master/configs/resnet)
+- [x] [ResNeXt](https://github.com/open-mmlab/mmclassification/tree/master/configs/resnext)
+- [x] [SE-ResNet](https://github.com/open-mmlab/mmclassification/tree/master/configs/seresnet)
+- [x] [SE-ResNeXt](https://github.com/open-mmlab/mmclassification/tree/master/configs/seresnet)
+- [x] [RegNet](https://github.com/open-mmlab/mmclassification/tree/master/configs/regnet)
+- [x] [ShuffleNetV1](https://github.com/open-mmlab/mmclassification/tree/master/configs/shufflenet_v1)
+- [x] [ShuffleNetV2](https://github.com/open-mmlab/mmclassification/tree/master/configs/shufflenet_v2)
+- [x] [MobileNetV2](https://github.com/open-mmlab/mmclassification/tree/master/configs/mobilenet_v2)
+- [x] [MobileNetV3](https://github.com/open-mmlab/mmclassification/tree/master/configs/mobilenet_v3)
+- [x] [Swin-Transformer](https://github.com/open-mmlab/mmclassification/tree/master/configs/swin_transformer)
+- [x] [RepVGG](https://github.com/open-mmlab/mmclassification/tree/master/configs/repvgg)
+- [x] [Vision-Transformer](https://github.com/open-mmlab/mmclassification/tree/master/configs/vision_transformer)
+- [x] [Transformer-in-Transformer](https://github.com/open-mmlab/mmclassification/tree/master/configs/tnt)
+- [x] [Res2Net](https://github.com/open-mmlab/mmclassification/tree/master/configs/res2net)
+- [x] [MLP-Mixer](https://github.com/open-mmlab/mmclassification/tree/master/configs/mlp_mixer)
+- [x] [DeiT](https://github.com/open-mmlab/mmclassification/tree/master/configs/deit)
+- [x] [Conformer](https://github.com/open-mmlab/mmclassification/tree/master/configs/conformer)
+- [x] [T2T-ViT](https://github.com/open-mmlab/mmclassification/tree/master/configs/t2t_vit)
+- [x] [Twins](https://github.com/open-mmlab/mmclassification/tree/master/configs/twins)
+- [x] [EfficientNet](https://github.com/open-mmlab/mmclassification/tree/master/configs/efficientnet)
+- [x] [ConvNeXt](https://github.com/open-mmlab/mmclassification/tree/master/configs/convnext)
+- [x] [HRNet](https://github.com/open-mmlab/mmclassification/tree/master/configs/hrnet)
+- [x] [VAN](https://github.com/open-mmlab/mmclassification/tree/master/configs/van)
+- [x] [ConvMixer](https://github.com/open-mmlab/mmclassification/tree/master/configs/convmixer)
+- [x] [CSPNet](https://github.com/open-mmlab/mmclassification/tree/master/configs/cspnet)
+- [x] [PoolFormer](https://github.com/open-mmlab/mmclassification/tree/master/configs/poolformer)
+- [x] [MViT](https://github.com/open-mmlab/mmclassification/tree/master/configs/mvit)
+- [x] [EfficientFormer](https://github.com/open-mmlab/mmclassification/tree/master/configs/efficientformer)
+- [x] [HorNet](https://github.com/open-mmlab/mmclassification/tree/master/configs/hornet)
+
+
+
+## Contributing
+
+We appreciate all contributions to improve MMClassification.
+Please refer to [CONTRUBUTING.md](https://mmclassification.readthedocs.io/en/latest/community/CONTRIBUTING.html) for the contributing guideline.
+
+## Acknowledgement
+
+MMClassification is an open source project that is contributed by researchers and engineers from various colleges and companies. We appreciate all the contributors who implement their methods or add new features, as well as users who give valuable feedbacks.
+We wish that the toolbox and benchmark could serve the growing research community by providing a flexible toolkit to reimplement existing methods and develop their own new classifiers.
+
+## Citation
+
+If you find this project useful in your research, please consider cite:
+
+```BibTeX
+@misc{2020mmclassification,
+ title={OpenMMLab's Image Classification Toolbox and Benchmark},
+ author={MMClassification Contributors},
+ howpublished = {\url{https://github.com/open-mmlab/mmclassification}},
+ year={2020}
+}
+```
+
+## License
+
+This project is released under the [Apache 2.0 license](LICENSE).
+
+## Projects in OpenMMLab
+
+- [MMCV](https://github.com/open-mmlab/mmcv): OpenMMLab foundational library for computer vision.
+- [MIM](https://github.com/open-mmlab/mim): MIM installs OpenMMLab packages.
+- [MMClassification](https://github.com/open-mmlab/mmclassification): OpenMMLab image classification toolbox and benchmark.
+- [MMDetection](https://github.com/open-mmlab/mmdetection): OpenMMLab detection toolbox and benchmark.
+- [MMDetection3D](https://github.com/open-mmlab/mmdetection3d): OpenMMLab's next-generation platform for general 3D object detection.
+- [MMRotate](https://github.com/open-mmlab/mmrotate): OpenMMLab rotated object detection toolbox and benchmark.
+- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation): OpenMMLab semantic segmentation toolbox and benchmark.
+- [MMOCR](https://github.com/open-mmlab/mmocr): OpenMMLab text detection, recognition, and understanding toolbox.
+- [MMPose](https://github.com/open-mmlab/mmpose): OpenMMLab pose estimation toolbox and benchmark.
+- [MMHuman3D](https://github.com/open-mmlab/mmhuman3d): OpenMMLab 3D human parametric model toolbox and benchmark.
+- [MMSelfSup](https://github.com/open-mmlab/mmselfsup): OpenMMLab self-supervised learning toolbox and benchmark.
+- [MMRazor](https://github.com/open-mmlab/mmrazor): OpenMMLab model compression toolbox and benchmark.
+- [MMFewShot](https://github.com/open-mmlab/mmfewshot): OpenMMLab fewshot learning toolbox and benchmark.
+- [MMAction2](https://github.com/open-mmlab/mmaction2): OpenMMLab's next-generation action understanding toolbox and benchmark.
+- [MMTracking](https://github.com/open-mmlab/mmtracking): OpenMMLab video perception toolbox and benchmark.
+- [MMFlow](https://github.com/open-mmlab/mmflow): OpenMMLab optical flow toolbox and benchmark.
+- [MMEditing](https://github.com/open-mmlab/mmediting): OpenMMLab image and video editing toolbox.
+- [MMGeneration](https://github.com/open-mmlab/mmgeneration): OpenMMLab image and video generative models toolbox.
+- [MMDeploy](https://github.com/open-mmlab/mmdeploy): OpenMMLab model deployment framework.
diff --git a/openmmlab_test/mmclassification-0.24.1/README_zh-CN.md b/openmmlab_test/mmclassification-0.24.1/README_zh-CN.md
new file mode 100644
index 0000000000000000000000000000000000000000..60f06209d78abb4e9b6fd3060632819321c59be8
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/README_zh-CN.md
@@ -0,0 +1,222 @@
+
+
+## Results and models
+
+### ImageNet-1k
+
+| Model | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download |
+| :-------------------: | :-------: | :------: | :-------: | :-------: | :---------------------------------------------------------------------: | :-----------------------------------------------------------------------: |
+| Conformer-tiny-p16\* | 23.52 | 4.90 | 81.31 | 95.60 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/conformer/conformer-tiny-p16_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/conformer/conformer-tiny-p16_3rdparty_8xb128_in1k_20211206-f6860372.pth) |
+| Conformer-small-p32\* | 38.85 | 7.09 | 81.96 | 96.02 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/conformer/conformer-small-p32_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/conformer/conformer-small-p32_8xb128_in1k_20211206-947a0816.pth) |
+| Conformer-small-p16\* | 37.67 | 10.31 | 83.32 | 96.46 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/conformer/conformer-small-p16_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/conformer/conformer-small-p16_3rdparty_8xb128_in1k_20211206-3065dcf5.pth) |
+| Conformer-base-p16\* | 83.29 | 22.89 | 83.82 | 96.59 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/conformer/conformer-base-p16_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/conformer/conformer-base-p16_3rdparty_8xb128_in1k_20211206-bfdf8637.pth) |
+
+*Models with * are converted from the [official repo](https://github.com/pengzhiliang/Conformer). The config files of these models are only for validation. We don't ensure these config files' training accuracy and welcome you to contribute your reproduction results.*
+
+## Citation
+
+```
+@article{peng2021conformer,
+ title={Conformer: Local Features Coupling Global Representations for Visual Recognition},
+ author={Zhiliang Peng and Wei Huang and Shanzhi Gu and Lingxi Xie and Yaowei Wang and Jianbin Jiao and Qixiang Ye},
+ journal={arXiv preprint arXiv:2105.03889},
+ year={2021},
+}
+```
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/conformer/conformer-base-p16_8xb128_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/conformer/conformer-base-p16_8xb128_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..29ed58be5d8bfcc23246ce013dd46f1da56d726d
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/conformer/conformer-base-p16_8xb128_in1k.py
@@ -0,0 +1,9 @@
+_base_ = [
+ '../_base_/models/conformer/base-p16.py',
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_conformer.py',
+ '../_base_/default_runtime.py'
+]
+
+data = dict(samples_per_gpu=128)
+evaluation = dict(interval=1, metric='accuracy')
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/conformer/conformer-small-p16_8xb128_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/conformer/conformer-small-p16_8xb128_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..c40ed0419e92ff4fd4fc6796b9ecad95fb6a9677
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/conformer/conformer-small-p16_8xb128_in1k.py
@@ -0,0 +1,9 @@
+_base_ = [
+ '../_base_/models/conformer/small-p16.py',
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_conformer.py',
+ '../_base_/default_runtime.py'
+]
+
+data = dict(samples_per_gpu=128)
+evaluation = dict(interval=1, metric='accuracy')
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/conformer/conformer-small-p32_8xb128_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/conformer/conformer-small-p32_8xb128_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..aaa11895e650dd76a098561b7a80af2c2afa8044
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/conformer/conformer-small-p32_8xb128_in1k.py
@@ -0,0 +1,9 @@
+_base_ = [
+ '../_base_/models/conformer/small-p32.py',
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_conformer.py',
+ '../_base_/default_runtime.py'
+]
+
+data = dict(samples_per_gpu=128)
+evaluation = dict(interval=1, metric='accuracy')
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/conformer/conformer-tiny-p16_8xb128_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/conformer/conformer-tiny-p16_8xb128_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..76a264c656d143739f6d1d186284266c21bd771d
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/conformer/conformer-tiny-p16_8xb128_in1k.py
@@ -0,0 +1,9 @@
+_base_ = [
+ '../_base_/models/conformer/tiny-p16.py',
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_conformer.py',
+ '../_base_/default_runtime.py'
+]
+
+data = dict(samples_per_gpu=128)
+evaluation = dict(interval=1, metric='accuracy')
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/conformer/metafile.yml b/openmmlab_test/mmclassification-0.24.1/configs/conformer/metafile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4efe05fb8fd858876a0f6469a2a22c07406c9fdc
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/conformer/metafile.yml
@@ -0,0 +1,78 @@
+Collections:
+ - Name: Conformer
+ Metadata:
+ Training Data: ImageNet-1k
+ Architecture:
+ - Layer Normalization
+ - Scaled Dot-Product Attention
+ - Dropout
+ Paper:
+ URL: https://arxiv.org/abs/2105.03889
+ Title: "Conformer: Local Features Coupling Global Representations for Visual Recognition"
+ README: configs/conformer/README.md
+ Code:
+ URL: https://github.com/open-mmlab/mmclassification/blob/v0.19.0/mmcls/models/backbones/conformer.py
+ Version: v0.19.0
+
+Models:
+ - Name: conformer-tiny-p16_3rdparty_8xb128_in1k
+ In Collection: Conformer
+ Config: configs/conformer/conformer-tiny-p16_8xb128_in1k.py
+ Metadata:
+ FLOPs: 4899611328
+ Parameters: 23524704
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 81.31
+ Top 5 Accuracy: 95.60
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/conformer/conformer-tiny-p16_3rdparty_8xb128_in1k_20211206-f6860372.pth
+ Converted From:
+ Weights: https://drive.google.com/file/d/19SxGhKcWOR5oQSxNUWUM2MGYiaWMrF1z/view?usp=sharing
+ Code: https://github.com/pengzhiliang/Conformer/blob/main/models.py#L65
+ - Name: conformer-small-p16_3rdparty_8xb128_in1k
+ In Collection: Conformer
+ Config: configs/conformer/conformer-small-p16_8xb128_in1k.py
+ Metadata:
+ FLOPs: 10311309312
+ Parameters: 37673424
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 83.32
+ Top 5 Accuracy: 96.46
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/conformer/conformer-small-p16_3rdparty_8xb128_in1k_20211206-3065dcf5.pth
+ Converted From:
+ Weights: https://drive.google.com/file/d/1mpOlbLaVxOfEwV4-ha78j_1Ebqzj2B83/view?usp=sharing
+ Code: https://github.com/pengzhiliang/Conformer/blob/main/models.py#L73
+ - Name: conformer-small-p32_8xb128_in1k
+ In Collection: Conformer
+ Config: configs/conformer/conformer-small-p32_8xb128_in1k.py
+ Metadata:
+ FLOPs: 7087281792
+ Parameters: 38853072
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 81.96
+ Top 5 Accuracy: 96.02
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/conformer/conformer-small-p32_8xb128_in1k_20211206-947a0816.pth
+ - Name: conformer-base-p16_3rdparty_8xb128_in1k
+ In Collection: Conformer
+ Config: configs/conformer/conformer-base-p16_8xb128_in1k.py
+ Metadata:
+ FLOPs: 22892078080
+ Parameters: 83289136
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 83.82
+ Top 5 Accuracy: 96.59
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/conformer/conformer-base-p16_3rdparty_8xb128_in1k_20211206-bfdf8637.pth
+ Converted From:
+ Weights: https://drive.google.com/file/d/1oeQ9LSOGKEUaYGu7WTlUGl3KDsQIi0MA/view?usp=sharing
+ Code: https://github.com/pengzhiliang/Conformer/blob/main/models.py#L89
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/convmixer/README.md b/openmmlab_test/mmclassification-0.24.1/configs/convmixer/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..763bad3ccf6620fb0c5497c657f00f77076e0f11
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/convmixer/README.md
@@ -0,0 +1,42 @@
+# ConvMixer
+
+> [Patches Are All You Need?](https://arxiv.org/abs/2201.09792)
+
+
+
+## Abstract
+
+
+
+Although convolutional networks have been the dominant architecture for vision tasks for many years, recent experiments have shown that Transformer-based models, most notably the Vision Transformer (ViT), may exceed their performance in some settings. However, due to the quadratic runtime of the self-attention layers in Transformers, ViTs require the use of patch embeddings, which group together small regions of the image into single input features, in order to be applied to larger image sizes. This raises a question: Is the performance of ViTs due to the inherently-more-powerful Transformer architecture, or is it at least partly due to using patches as the input representation? In this paper, we present some evidence for the latter: specifically, we propose the ConvMixer, an extremely simple model that is similar in spirit to the ViT and the even-more-basic MLP-Mixer in that it operates directly on patches as input, separates the mixing of spatial and channel dimensions, and maintains equal size and resolution throughout the network. In contrast, however, the ConvMixer uses only standard convolutions to achieve the mixing steps. Despite its simplicity, we show that the ConvMixer outperforms the ViT, MLP-Mixer, and some of their variants for similar parameter counts and data set sizes, in addition to outperforming classical vision models such as the ResNet.
+
+
+
+
+
+
+
+## Results and models
+
+### ImageNet-1k
+
+| Model | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download |
+| :-----------------: | :-------: | :------: | :-------: | :-------: | :----------------------------------------------------------------------: | :------------------------------------------------------------------------: |
+| ConvMixer-768/32\* | 21.11 | 19.62 | 80.16 | 95.08 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/convmixer/convmixer-768-32_10xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/convmixer/convmixer-768-32_3rdparty_10xb64_in1k_20220323-bca1f7b8.pth) |
+| ConvMixer-1024/20\* | 24.38 | 5.55 | 76.94 | 93.36 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/convmixer/convmixer-1024-20_10xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/convmixer/convmixer-1024-20_3rdparty_10xb64_in1k_20220323-48f8aeba.pth) |
+| ConvMixer-1536/20\* | 51.63 | 48.71 | 81.37 | 95.61 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/convmixer/convmixer-1536-20_10xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/convmixer/convmixer-1536_20_3rdparty_10xb64_in1k_20220323-ea5786f3.pth) |
+
+*Models with * are converted from the [official repo](https://github.com/locuslab/convmixer). The config files of these models are only for inference. We don't ensure these config files' training accuracy and welcome you to contribute your reproduction results.*
+
+## Citation
+
+```bibtex
+@misc{trockman2022patches,
+ title={Patches Are All You Need?},
+ author={Asher Trockman and J. Zico Kolter},
+ year={2022},
+ eprint={2201.09792},
+ archivePrefix={arXiv},
+ primaryClass={cs.CV}
+}
+```
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/convmixer/convmixer-1024-20_10xb64_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/convmixer/convmixer-1024-20_10xb64_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..58694d6e27a74dc79b77a68306cb69343c44fdba
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/convmixer/convmixer-1024-20_10xb64_in1k.py
@@ -0,0 +1,10 @@
+_base_ = [
+ '../_base_/models/convmixer/convmixer-1024-20.py',
+ '../_base_/datasets/imagenet_bs64_convmixer_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py',
+]
+
+optimizer = dict(lr=0.01)
+
+runner = dict(type='EpochBasedRunner', max_epochs=150)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/convmixer/convmixer-1536-20_10xb64_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/convmixer/convmixer-1536-20_10xb64_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..17a7559585fffbc26f14a215fd074c27f3a4a5e4
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/convmixer/convmixer-1536-20_10xb64_in1k.py
@@ -0,0 +1,10 @@
+_base_ = [
+ '../_base_/models/convmixer/convmixer-1536-20.py',
+ '../_base_/datasets/imagenet_bs64_convmixer_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py',
+]
+
+optimizer = dict(lr=0.01)
+
+runner = dict(type='EpochBasedRunner', max_epochs=150)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/convmixer/convmixer-768-32_10xb64_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/convmixer/convmixer-768-32_10xb64_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..fa4c0602edf798f8e7ee21dbf248d95f5c9cba4b
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/convmixer/convmixer-768-32_10xb64_in1k.py
@@ -0,0 +1,10 @@
+_base_ = [
+ '../_base_/models/convmixer/convmixer-768-32.py',
+ '../_base_/datasets/imagenet_bs64_convmixer_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py',
+]
+
+optimizer = dict(lr=0.01)
+
+runner = dict(type='EpochBasedRunner', max_epochs=300)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/convmixer/metafile.yml b/openmmlab_test/mmclassification-0.24.1/configs/convmixer/metafile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7831d7464748f2a9e34ccfaad7606d8605b568d8
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/convmixer/metafile.yml
@@ -0,0 +1,61 @@
+Collections:
+ - Name: ConvMixer
+ Metadata:
+ Training Data: ImageNet-1k
+ Architecture:
+ - 1x1 Convolution
+ - LayerScale
+ Paper:
+ URL: https://arxiv.org/abs/2201.09792
+ Title: Patches Are All You Need?
+ README: configs/convmixer/README.md
+
+Models:
+ - Name: convmixer-768-32_10xb64_in1k
+ Metadata:
+ FLOPs: 19623051264
+ Parameters: 21110248
+ In Collections: ConvMixer
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 80.16
+ Top 5 Accuracy: 95.08
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/convmixer/convmixer-768-32_3rdparty_10xb64_in1k_20220323-bca1f7b8.pth
+ Config: configs/convmixer/convmixer-768-32_10xb64_in1k.py
+ Converted From:
+ Weights: https://github.com/tmp-iclr/convmixer/releases/download/v1.0/convmixer_768_32_ks7_p7_relu.pth.tar
+ Code: https://github.com/locuslab/convmixer
+ - Name: convmixer-1024-20_10xb64_in1k
+ Metadata:
+ FLOPs: 5550112768
+ Parameters: 24383464
+ In Collections: ConvMixer
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 76.94
+ Top 5 Accuracy: 93.36
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/convmixer/convmixer-1024-20_3rdparty_10xb64_in1k_20220323-48f8aeba.pth
+ Config: configs/convmixer/convmixer-1024-20_10xb64_in1k.py
+ Converted From:
+ Weights: https://github.com/tmp-iclr/convmixer/releases/download/v1.0/convmixer_1024_20_ks9_p14.pth.tar
+ Code: https://github.com/locuslab/convmixer
+ - Name: convmixer-1536-20_10xb64_in1k
+ Metadata:
+ FLOPs: 48713170944
+ Parameters: 51625960
+ In Collections: ConvMixer
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 81.37
+ Top 5 Accuracy: 95.61
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/convmixer/convmixer-1536_20_3rdparty_10xb64_in1k_20220323-ea5786f3.pth
+ Config: configs/convmixer/convmixer-1536-20_10xb64_in1k.py
+ Converted From:
+ Weights: https://github.com/tmp-iclr/convmixer/releases/download/v1.0/convmixer_1536_20_ks9_p7.pth.tar
+ Code: https://github.com/locuslab/convmixer
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/convnext/README.md b/openmmlab_test/mmclassification-0.24.1/configs/convnext/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..7db81366aa47ad92771247bbfa5364dba952df21
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/convnext/README.md
@@ -0,0 +1,59 @@
+# ConvNeXt
+
+> [A ConvNet for the 2020s](https://arxiv.org/abs/2201.03545v1)
+
+
+
+## Abstract
+
+
+
+The "Roaring 20s" of visual recognition began with the introduction of Vision Transformers (ViTs), which quickly superseded ConvNets as the state-of-the-art image classification model. A vanilla ViT, on the other hand, faces difficulties when applied to general computer vision tasks such as object detection and semantic segmentation. It is the hierarchical Transformers (e.g., Swin Transformers) that reintroduced several ConvNet priors, making Transformers practically viable as a generic vision backbone and demonstrating remarkable performance on a wide variety of vision tasks. However, the effectiveness of such hybrid approaches is still largely credited to the intrinsic superiority of Transformers, rather than the inherent inductive biases of convolutions. In this work, we reexamine the design spaces and test the limits of what a pure ConvNet can achieve. We gradually "modernize" a standard ResNet toward the design of a vision Transformer, and discover several key components that contribute to the performance difference along the way. The outcome of this exploration is a family of pure ConvNet models dubbed ConvNeXt. Constructed entirely from standard ConvNet modules, ConvNeXts compete favorably with Transformers in terms of accuracy and scalability, achieving 87.8% ImageNet top-1 accuracy and outperforming Swin Transformers on COCO detection and ADE20K segmentation, while maintaining the simplicity and efficiency of standard ConvNets.
+
+
+
+
+
+
+
+## Results and models
+
+### ImageNet-1k
+
+| Model | Pretrain | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download |
+| :-----------: | :----------: | :-------: | :------: | :-------: | :-------: | :-------------------------------------------------------------------: | :---------------------------------------------------------------------: |
+| ConvNeXt-T\* | From scratch | 28.59 | 4.46 | 82.05 | 95.86 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/convnext/convnext-tiny_32xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/convnext/convnext-tiny_3rdparty_32xb128_in1k_20220124-18abde00.pth) |
+| ConvNeXt-S\* | From scratch | 50.22 | 8.69 | 83.13 | 96.44 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/convnext/convnext-small_32xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/convnext/convnext-small_3rdparty_32xb128_in1k_20220124-d39b5192.pth) |
+| ConvNeXt-B\* | From scratch | 88.59 | 15.36 | 83.85 | 96.74 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/convnext/convnext-base_32xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/convnext/convnext-base_3rdparty_32xb128_in1k_20220124-d0915162.pth) |
+| ConvNeXt-B\* | ImageNet-21k | 88.59 | 15.36 | 85.81 | 97.86 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/convnext/convnext-base_32xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/convnext/convnext-base_in21k-pre-3rdparty_32xb128_in1k_20220124-eb2d6ada.pth) |
+| ConvNeXt-L\* | From scratch | 197.77 | 34.37 | 84.30 | 96.89 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/convnext/convnext-large_64xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/convnext/convnext-large_3rdparty_64xb64_in1k_20220124-f8a0ded0.pth) |
+| ConvNeXt-L\* | ImageNet-21k | 197.77 | 34.37 | 86.61 | 98.04 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/convnext/convnext-large_64xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/convnext/convnext-large_in21k-pre-3rdparty_64xb64_in1k_20220124-2412403d.pth) |
+| ConvNeXt-XL\* | ImageNet-21k | 350.20 | 60.93 | 86.97 | 98.20 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/convnext/convnext-xlarge_64xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/convnext/convnext-xlarge_in21k-pre-3rdparty_64xb64_in1k_20220124-76b6863d.pth) |
+
+*Models with * are converted from the [official repo](https://github.com/facebookresearch/ConvNeXt). The config files of these models are only for inference. We don't ensure these config files' training accuracy and welcome you to contribute your reproduction results.*
+
+### Pre-trained Models
+
+The pre-trained models on ImageNet-1k or ImageNet-21k are used to fine-tune on the downstream tasks.
+
+| Model | Training Data | Params(M) | Flops(G) | Download |
+| :-----------: | :-----------: | :-------: | :------: | :-----------------------------------------------------------------------------------------------------------------------------------: |
+| ConvNeXt-T\* | ImageNet-1k | 28.59 | 4.46 | [model](https://download.openmmlab.com/mmclassification/v0/convnext/convnext-tiny_3rdparty_32xb128-noema_in1k_20220222-2908964a.pth) |
+| ConvNeXt-S\* | ImageNet-1k | 50.22 | 8.69 | [model](https://download.openmmlab.com/mmclassification/v0/convnext/convnext-small_3rdparty_32xb128-noema_in1k_20220222-fa001ca5.pth) |
+| ConvNeXt-B\* | ImageNet-1k | 88.59 | 15.36 | [model](https://download.openmmlab.com/mmclassification/v0/convnext/convnext-base_3rdparty_32xb128-noema_in1k_20220222-dba4f95f.pth) |
+| ConvNeXt-B\* | ImageNet-21k | 88.59 | 15.36 | [model](https://download.openmmlab.com/mmclassification/v0/convnext/convnext-base_3rdparty_in21k_20220124-13b83eec.pth) |
+| ConvNeXt-L\* | ImageNet-21k | 197.77 | 34.37 | [model](https://download.openmmlab.com/mmclassification/v0/convnext/convnext-large_3rdparty_in21k_20220124-41b5a79f.pth) |
+| ConvNeXt-XL\* | ImageNet-21k | 350.20 | 60.93 | [model](https://download.openmmlab.com/mmclassification/v0/convnext/convnext-xlarge_3rdparty_in21k_20220124-f909bad7.pth) |
+
+*Models with * are converted from the [official repo](https://github.com/facebookresearch/ConvNeXt).*
+
+## Citation
+
+```bibtex
+@Article{liu2022convnet,
+ author = {Zhuang Liu and Hanzi Mao and Chao-Yuan Wu and Christoph Feichtenhofer and Trevor Darrell and Saining Xie},
+ title = {A ConvNet for the 2020s},
+ journal = {arXiv preprint arXiv:2201.03545},
+ year = {2022},
+}
+```
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/convnext/convnext-base_32xb128_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/convnext/convnext-base_32xb128_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..6c0450a4341e27f04dbfac5d6a841f9fdd94c56d
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/convnext/convnext-base_32xb128_in1k.py
@@ -0,0 +1,12 @@
+_base_ = [
+ '../_base_/models/convnext/convnext-base.py',
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py',
+]
+
+data = dict(samples_per_gpu=128)
+
+optimizer = dict(lr=4e-3)
+
+custom_hooks = [dict(type='EMAHook', momentum=4e-5, priority='ABOVE_NORMAL')]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/convnext/convnext-large_64xb64_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/convnext/convnext-large_64xb64_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..1faae253436fe180db7b88c066ecfdc5df266429
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/convnext/convnext-large_64xb64_in1k.py
@@ -0,0 +1,12 @@
+_base_ = [
+ '../_base_/models/convnext/convnext-large.py',
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py',
+]
+
+data = dict(samples_per_gpu=64)
+
+optimizer = dict(lr=4e-3)
+
+custom_hooks = [dict(type='EMAHook', momentum=4e-5, priority='ABOVE_NORMAL')]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/convnext/convnext-small_32xb128_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/convnext/convnext-small_32xb128_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..d820fc6cac93623ad665001a89c0dab0d6cc6a5c
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/convnext/convnext-small_32xb128_in1k.py
@@ -0,0 +1,12 @@
+_base_ = [
+ '../_base_/models/convnext/convnext-small.py',
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py',
+]
+
+data = dict(samples_per_gpu=128)
+
+optimizer = dict(lr=4e-3)
+
+custom_hooks = [dict(type='EMAHook', momentum=4e-5, priority='ABOVE_NORMAL')]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/convnext/convnext-tiny_32xb128_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/convnext/convnext-tiny_32xb128_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..46d0185d8ab25b8c591c5d820b48278565b9e228
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/convnext/convnext-tiny_32xb128_in1k.py
@@ -0,0 +1,12 @@
+_base_ = [
+ '../_base_/models/convnext/convnext-tiny.py',
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py',
+]
+
+data = dict(samples_per_gpu=128)
+
+optimizer = dict(lr=4e-3)
+
+custom_hooks = [dict(type='EMAHook', momentum=4e-5, priority='ABOVE_NORMAL')]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/convnext/convnext-xlarge_64xb64_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/convnext/convnext-xlarge_64xb64_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..72849013df6d1506f3f4d3c353a4e06f15c8d258
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/convnext/convnext-xlarge_64xb64_in1k.py
@@ -0,0 +1,12 @@
+_base_ = [
+ '../_base_/models/convnext/convnext-xlarge.py',
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py',
+]
+
+data = dict(samples_per_gpu=64)
+
+optimizer = dict(lr=4e-3)
+
+custom_hooks = [dict(type='EMAHook', momentum=4e-5, priority='ABOVE_NORMAL')]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/convnext/metafile.yml b/openmmlab_test/mmclassification-0.24.1/configs/convnext/metafile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..823f33270d04b430675c05c5dcaf95b93cc26c93
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/convnext/metafile.yml
@@ -0,0 +1,221 @@
+Collections:
+ - Name: ConvNeXt
+ Metadata:
+ Training Data: ImageNet-1k
+ Architecture:
+ - 1x1 Convolution
+ - LayerScale
+ Paper:
+ URL: https://arxiv.org/abs/2201.03545v1
+ Title: A ConvNet for the 2020s
+ README: configs/convnext/README.md
+ Code:
+ Version: v0.20.1
+ URL: https://github.com/open-mmlab/mmclassification/blob/v0.20.1/mmcls/models/backbones/convnext.py
+
+Models:
+ - Name: convnext-tiny_3rdparty_32xb128_in1k
+ Metadata:
+ FLOPs: 4457472768
+ Parameters: 28589128
+ In Collections: ConvNeXt
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 82.05
+ Top 5 Accuracy: 95.86
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/convnext/convnext-tiny_3rdparty_32xb128_in1k_20220124-18abde00.pth
+ Config: configs/convnext/convnext-tiny_32xb128_in1k.py
+ Converted From:
+ Weights: https://dl.fbaipublicfiles.com/convnext/convnext_tiny_1k_224_ema.pth
+ Code: https://github.com/facebookresearch/ConvNeXt
+ - Name: convnext-tiny_3rdparty_32xb128-noema_in1k
+ Metadata:
+ Training Data: ImageNet-1k
+ FLOPs: 4457472768
+ Parameters: 28589128
+ In Collections: ConvNeXt
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 81.81
+ Top 5 Accuracy: 95.67
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/convnext/convnext-tiny_3rdparty_32xb128-noema_in1k_20220222-2908964a.pth
+ Config: configs/convnext/convnext-tiny_32xb128_in1k.py
+ Converted From:
+ Weights: https://dl.fbaipublicfiles.com/convnext/convnext_tiny_1k_224.pth
+ Code: https://github.com/facebookresearch/ConvNeXt
+ - Name: convnext-small_3rdparty_32xb128_in1k
+ Metadata:
+ FLOPs: 8687008512
+ Parameters: 50223688
+ In Collections: ConvNeXt
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 83.13
+ Top 5 Accuracy: 96.44
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/convnext/convnext-small_3rdparty_32xb128_in1k_20220124-d39b5192.pth
+ Config: configs/convnext/convnext-small_32xb128_in1k.py
+ Converted From:
+ Weights: https://dl.fbaipublicfiles.com/convnext/convnext_small_1k_224_ema.pth
+ Code: https://github.com/facebookresearch/ConvNeXt
+ - Name: convnext-small_3rdparty_32xb128-noema_in1k
+ Metadata:
+ Training Data: ImageNet-1k
+ FLOPs: 8687008512
+ Parameters: 50223688
+ In Collections: ConvNeXt
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 83.11
+ Top 5 Accuracy: 96.34
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/convnext/convnext-small_3rdparty_32xb128-noema_in1k_20220222-fa001ca5.pth
+ Config: configs/convnext/convnext-small_32xb128_in1k.py
+ Converted From:
+ Weights: https://dl.fbaipublicfiles.com/convnext/convnext_small_1k_224.pth
+ Code: https://github.com/facebookresearch/ConvNeXt
+ - Name: convnext-base_3rdparty_32xb128_in1k
+ Metadata:
+ FLOPs: 15359124480
+ Parameters: 88591464
+ In Collections: ConvNeXt
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 83.85
+ Top 5 Accuracy: 96.74
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/convnext/convnext-base_3rdparty_32xb128_in1k_20220124-d0915162.pth
+ Config: configs/convnext/convnext-base_32xb128_in1k.py
+ Converted From:
+ Weights: https://dl.fbaipublicfiles.com/convnext/convnext_base_1k_224_ema.pth
+ Code: https://github.com/facebookresearch/ConvNeXt
+ - Name: convnext-base_3rdparty_32xb128-noema_in1k
+ Metadata:
+ Training Data: ImageNet-1k
+ FLOPs: 15359124480
+ Parameters: 88591464
+ In Collections: ConvNeXt
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 83.71
+ Top 5 Accuracy: 96.60
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/convnext/convnext-base_3rdparty_32xb128-noema_in1k_20220222-dba4f95f.pth
+ Config: configs/convnext/convnext-base_32xb128_in1k.py
+ Converted From:
+ Weights: https://dl.fbaipublicfiles.com/convnext/convnext_base_1k_224.pth
+ Code: https://github.com/facebookresearch/ConvNeXt
+ - Name: convnext-base_3rdparty_in21k
+ Metadata:
+ Training Data: ImageNet-21k
+ FLOPs: 15359124480
+ Parameters: 88591464
+ In Collections: ConvNeXt
+ Results: null
+ Weights: https://download.openmmlab.com/mmclassification/v0/convnext/convnext-base_3rdparty_in21k_20220124-13b83eec.pth
+ Converted From:
+ Weights: https://dl.fbaipublicfiles.com/convnext/convnext_base_22k_224.pth
+ Code: https://github.com/facebookresearch/ConvNeXt
+ - Name: convnext-base_in21k-pre-3rdparty_32xb128_in1k
+ Metadata:
+ Training Data:
+ - ImageNet-21k
+ - ImageNet-1k
+ FLOPs: 15359124480
+ Parameters: 88591464
+ In Collections: ConvNeXt
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 85.81
+ Top 5 Accuracy: 97.86
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/convnext/convnext-base_in21k-pre-3rdparty_32xb128_in1k_20220124-eb2d6ada.pth
+ Config: configs/convnext/convnext-base_32xb128_in1k.py
+ Converted From:
+ Weights: https://dl.fbaipublicfiles.com/convnext/convnext_base_22k_1k_224.pth
+ Code: https://github.com/facebookresearch/ConvNeXt
+ - Name: convnext-large_3rdparty_64xb64_in1k
+ Metadata:
+ FLOPs: 34368026112
+ Parameters: 197767336
+ In Collections: ConvNeXt
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 84.30
+ Top 5 Accuracy: 96.89
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/convnext/convnext-large_3rdparty_64xb64_in1k_20220124-f8a0ded0.pth
+ Config: configs/convnext/convnext-large_64xb64_in1k.py
+ Converted From:
+ Weights: https://dl.fbaipublicfiles.com/convnext/convnext_large_1k_224_ema.pth
+ Code: https://github.com/facebookresearch/ConvNeXt
+ - Name: convnext-large_3rdparty_in21k
+ Metadata:
+ Training Data: ImageNet-21k
+ FLOPs: 34368026112
+ Parameters: 197767336
+ In Collections: ConvNeXt
+ Results: null
+ Weights: https://download.openmmlab.com/mmclassification/v0/convnext/convnext-large_3rdparty_in21k_20220124-41b5a79f.pth
+ Converted From:
+ Weights: https://dl.fbaipublicfiles.com/convnext/convnext_large_22k_224.pth
+ Code: https://github.com/facebookresearch/ConvNeXt
+ - Name: convnext-large_in21k-pre-3rdparty_64xb64_in1k
+ Metadata:
+ Training Data:
+ - ImageNet-21k
+ - ImageNet-1k
+ FLOPs: 34368026112
+ Parameters: 197767336
+ In Collections: ConvNeXt
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 86.61
+ Top 5 Accuracy: 98.04
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/convnext/convnext-large_in21k-pre-3rdparty_64xb64_in1k_20220124-2412403d.pth
+ Config: configs/convnext/convnext-large_64xb64_in1k.py
+ Converted From:
+ Weights: https://dl.fbaipublicfiles.com/convnext/convnext_large_22k_1k_224.pth
+ Code: https://github.com/facebookresearch/ConvNeXt
+ - Name: convnext-xlarge_3rdparty_in21k
+ Metadata:
+ Training Data: ImageNet-21k
+ FLOPs: 60929820672
+ Parameters: 350196968
+ In Collections: ConvNeXt
+ Results: null
+ Weights: https://download.openmmlab.com/mmclassification/v0/convnext/convnext-xlarge_3rdparty_in21k_20220124-f909bad7.pth
+ Converted From:
+ Weights: https://dl.fbaipublicfiles.com/convnext/convnext_xlarge_22k_224.pth
+ Code: https://github.com/facebookresearch/ConvNeXt
+ - Name: convnext-xlarge_in21k-pre-3rdparty_64xb64_in1k
+ Metadata:
+ Training Data:
+ - ImageNet-21k
+ - ImageNet-1k
+ FLOPs: 60929820672
+ Parameters: 350196968
+ In Collections: ConvNeXt
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 86.97
+ Top 5 Accuracy: 98.20
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/convnext/convnext-xlarge_in21k-pre-3rdparty_64xb64_in1k_20220124-76b6863d.pth
+ Config: configs/convnext/convnext-xlarge_64xb64_in1k.py
+ Converted From:
+ Weights: https://dl.fbaipublicfiles.com/convnext/convnext_xlarge_22k_1k_224_ema.pth
+ Code: https://github.com/facebookresearch/ConvNeXt
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/cspnet/README.md b/openmmlab_test/mmclassification-0.24.1/configs/cspnet/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..10eb9d0d505e05daba1fe7181efe0cd4b8f8ffaf
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/cspnet/README.md
@@ -0,0 +1,41 @@
+# CSPNet
+
+> [CSPNet: A New Backbone that can Enhance Learning Capability of CNN](https://arxiv.org/abs/1911.11929)
+
+
+
+## Abstract
+
+
+
+Neural networks have enabled state-of-the-art approaches to achieve incredible results on computer vision tasks such as object detection. However, such success greatly relies on costly computation resources, which hinders people with cheap devices from appreciating the advanced technology. In this paper, we propose Cross Stage Partial Network (CSPNet) to mitigate the problem that previous works require heavy inference computations from the network architecture perspective. We attribute the problem to the duplicate gradient information within network optimization. The proposed networks respect the variability of the gradients by integrating feature maps from the beginning and the end of a network stage, which, in our experiments, reduces computations by 20% with equivalent or even superior accuracy on the ImageNet dataset, and significantly outperforms state-of-the-art approaches in terms of AP50 on the MS COCO object detection dataset. The CSPNet is easy to implement and general enough to cope with architectures based on ResNet, ResNeXt, and DenseNet. Source code is at this https URL.
+
+
+
+
+
+
+
+## Results and models
+
+### ImageNet-1k
+
+| Model | Pretrain | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download |
+| :------------: | :----------: | :-------: | :------: | :-------: | :-------: | :------------------------------------------------------------------: | :---------------------------------------------------------------------: |
+| CSPDarkNet50\* | From scratch | 27.64 | 5.04 | 80.05 | 95.07 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/cspnet/cspdarknet50_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/cspnet/cspdarknet50_3rdparty_8xb32_in1k_20220329-bd275287.pth) |
+| CSPResNet50\* | From scratch | 21.62 | 3.48 | 79.55 | 94.68 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/cspnet/cspresnet50_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/cspnet/cspresnet50_3rdparty_8xb32_in1k_20220329-dd6dddfb.pth) |
+| CSPResNeXt50\* | From scratch | 20.57 | 3.11 | 79.96 | 94.96 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/cspnet/cspresnext50_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/cspnet/cspresnext50_3rdparty_8xb32_in1k_20220329-2cc84d21.pth) |
+
+*Models with * are converted from the [timm repo](https://github.com/rwightman/pytorch-image-models). The config files of these models are only for inference. We don't ensure these config files' training accuracy and welcome you to contribute your reproduction results.*
+
+## Citation
+
+```bibtex
+@inproceedings{wang2020cspnet,
+ title={CSPNet: A new backbone that can enhance learning capability of CNN},
+ author={Wang, Chien-Yao and Liao, Hong-Yuan Mark and Wu, Yueh-Hua and Chen, Ping-Yang and Hsieh, Jun-Wei and Yeh, I-Hau},
+ booktitle={Proceedings of the IEEE/CVF conference on computer vision and pattern recognition workshops},
+ pages={390--391},
+ year={2020}
+}
+```
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/cspnet/cspdarknet50_8xb32_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/cspnet/cspdarknet50_8xb32_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..cf2ce731f0c38a86520a445cde459a0bcddb3f65
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/cspnet/cspdarknet50_8xb32_in1k.py
@@ -0,0 +1,65 @@
+_base_ = [
+ '../_base_/schedules/imagenet_bs256.py', '../_base_/default_runtime.py'
+]
+
+# model settings
+model = dict(
+ type='ImageClassifier',
+ backbone=dict(type='CSPDarkNet', depth=53),
+ neck=dict(type='GlobalAveragePooling'),
+ head=dict(
+ type='LinearClsHead',
+ num_classes=1000,
+ in_channels=1024,
+ loss=dict(type='CrossEntropyLoss', loss_weight=1.0),
+ topk=(1, 5),
+ ))
+
+# dataset settings
+dataset_type = 'ImageNet'
+img_norm_cfg = dict(
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='RandomResizedCrop',
+ size=224,
+ backend='pillow',
+ interpolation='bicubic'),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='Collect', keys=['img', 'gt_label'])
+]
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='Resize',
+ size=(288, -1),
+ backend='pillow',
+ interpolation='bicubic'),
+ dict(type='CenterCrop', crop_size=256),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img'])
+]
+data = dict(
+ samples_per_gpu=32,
+ workers_per_gpu=2,
+ train=dict(
+ type=dataset_type,
+ data_prefix='data/imagenet/train',
+ pipeline=train_pipeline),
+ val=dict(
+ type=dataset_type,
+ data_prefix='data/imagenet/val',
+ ann_file='data/imagenet/meta/val.txt',
+ pipeline=test_pipeline),
+ test=dict(
+ # replace `data/val` with `data/test` for standard test
+ type=dataset_type,
+ data_prefix='data/imagenet/val',
+ ann_file='data/imagenet/meta/val.txt',
+ pipeline=test_pipeline))
+evaluation = dict(interval=1, metric='accuracy')
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/cspnet/cspresnet50_8xb32_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/cspnet/cspresnet50_8xb32_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..f4cfbf8a6a7b96a1c243938a939488b4225a8f33
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/cspnet/cspresnet50_8xb32_in1k.py
@@ -0,0 +1,66 @@
+_base_ = [
+ '../_base_/datasets/imagenet_bs32_pil_resize.py',
+ '../_base_/schedules/imagenet_bs256.py', '../_base_/default_runtime.py'
+]
+
+# model settings
+model = dict(
+ type='ImageClassifier',
+ backbone=dict(type='CSPResNet', depth=50),
+ neck=dict(type='GlobalAveragePooling'),
+ head=dict(
+ type='LinearClsHead',
+ num_classes=1000,
+ in_channels=1024,
+ loss=dict(type='CrossEntropyLoss', loss_weight=1.0),
+ topk=(1, 5),
+ ))
+
+# dataset settings
+dataset_type = 'ImageNet'
+img_norm_cfg = dict(
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='RandomResizedCrop',
+ size=224,
+ backend='pillow',
+ interpolation='bicubic'),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='Collect', keys=['img', 'gt_label'])
+]
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='Resize',
+ size=(288, -1),
+ backend='pillow',
+ interpolation='bicubic'),
+ dict(type='CenterCrop', crop_size=256),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img'])
+]
+data = dict(
+ samples_per_gpu=32,
+ workers_per_gpu=2,
+ train=dict(
+ type=dataset_type,
+ data_prefix='data/imagenet/train',
+ pipeline=train_pipeline),
+ val=dict(
+ type=dataset_type,
+ data_prefix='data/imagenet/val',
+ ann_file='data/imagenet/meta/val.txt',
+ pipeline=test_pipeline),
+ test=dict(
+ # replace `data/val` with `data/test` for standard test
+ type=dataset_type,
+ data_prefix='data/imagenet/val',
+ ann_file='data/imagenet/meta/val.txt',
+ pipeline=test_pipeline))
+evaluation = dict(interval=1, metric='accuracy')
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/cspnet/cspresnext50_8xb32_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/cspnet/cspresnext50_8xb32_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..a82ab75115f8245355b4551de0be4b19a4748450
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/cspnet/cspresnext50_8xb32_in1k.py
@@ -0,0 +1,65 @@
+_base_ = [
+ '../_base_/schedules/imagenet_bs256.py', '../_base_/default_runtime.py'
+]
+
+# model settings
+model = dict(
+ type='ImageClassifier',
+ backbone=dict(type='CSPResNeXt', depth=50),
+ neck=dict(type='GlobalAveragePooling'),
+ head=dict(
+ type='LinearClsHead',
+ num_classes=1000,
+ in_channels=2048,
+ loss=dict(type='CrossEntropyLoss', loss_weight=1.0),
+ topk=(1, 5),
+ ))
+
+# dataset settings
+dataset_type = 'ImageNet'
+img_norm_cfg = dict(
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='RandomResizedCrop',
+ size=224,
+ backend='pillow',
+ interpolation='bicubic'),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='Collect', keys=['img', 'gt_label'])
+]
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='Resize',
+ size=(256, -1),
+ backend='pillow',
+ interpolation='bicubic'),
+ dict(type='CenterCrop', crop_size=224),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img'])
+]
+data = dict(
+ samples_per_gpu=32,
+ workers_per_gpu=2,
+ train=dict(
+ type=dataset_type,
+ data_prefix='data/imagenet/train',
+ pipeline=train_pipeline),
+ val=dict(
+ type=dataset_type,
+ data_prefix='data/imagenet/val',
+ ann_file='data/imagenet/meta/val.txt',
+ pipeline=test_pipeline),
+ test=dict(
+ # replace `data/val` with `data/test` for standard test
+ type=dataset_type,
+ data_prefix='data/imagenet/val',
+ ann_file='data/imagenet/meta/val.txt',
+ pipeline=test_pipeline))
+evaluation = dict(interval=1, metric='accuracy')
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/cspnet/metafile.yml b/openmmlab_test/mmclassification-0.24.1/configs/cspnet/metafile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8c4a78edde5df74eb964f9a37baa4f4784c869a0
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/cspnet/metafile.yml
@@ -0,0 +1,64 @@
+Collections:
+ - Name: CSPNet
+ Metadata:
+ Training Data: ImageNet-1k
+ Architecture:
+ - Cross Stage Partia Stage
+ Paper:
+ URL: https://arxiv.org/abs/1911.11929
+ Title: 'CSPNet: A New Backbone that can Enhance Learning Capability of CNN'
+ README: configs/cspnet/README.md
+ Code:
+ Version: v0.22.0
+ URL: https://github.com/open-mmlab/mmclassification/blob/v0.22.0/mmcls/models/backbones/cspnet.py
+
+Models:
+ - Name: cspdarknet50_3rdparty_8xb32_in1k
+ Metadata:
+ FLOPs: 5040000000
+ Parameters: 27640000
+ In Collections: CSPNet
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 80.05
+ Top 5 Accuracy: 95.07
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/cspnet/cspdarknet50_3rdparty_8xb32_in1k_20220329-bd275287.pth
+ Config: configs/cspnet/cspdarknet50_8xb32_in1k.py
+ Converted From:
+ Weights: https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/cspdarknet53_ra_256-d05c7c21.pth
+ Code: https://github.com/rwightman/pytorch-image-models
+ - Name: cspresnet50_3rdparty_8xb32_in1k
+ Metadata:
+ Training Data: ImageNet-1k
+ FLOPs: 3480000000
+ Parameters: 21620000
+ In Collections: CSPNet
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 79.55
+ Top 5 Accuracy: 94.68
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/cspnet/cspresnet50_3rdparty_8xb32_in1k_20220329-dd6dddfb.pth
+ Config: configs/cspnet/cspresnet50_8xb32_in1k.py
+ Converted From:
+ Weights: https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/cspresnet50_ra-d3e8d487.pth
+ Code: https://github.com/rwightman/pytorch-image-models
+ - Name: cspresnext50_3rdparty_8xb32_in1k
+ Metadata:
+ FLOPs: 3110000000
+ Parameters: 20570000
+ In Collections: CSPNet
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 79.96
+ Top 5 Accuracy: 94.96
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/cspnet/cspresnext50_3rdparty_8xb32_in1k_20220329-2cc84d21.pth
+ Config: configs/cspnet/cspresnext50_8xb32_in1k.py
+ Converted From:
+ Weights: https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/cspresnext50_ra_224-648b4713.pth
+ Code: https://github.com/rwightman/pytorch-image-models
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/csra/README.md b/openmmlab_test/mmclassification-0.24.1/configs/csra/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..fa677cfca1b3ff99d54c9cbebd36a8704f973a12
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/csra/README.md
@@ -0,0 +1,36 @@
+# CSRA
+
+> [Residual Attention: A Simple but Effective Method for Multi-Label Recognition](https://arxiv.org/abs/2108.02456)
+
+
+
+## Abstract
+
+Multi-label image recognition is a challenging computer vision task of practical use. Progresses in this area, however, are often characterized by complicated methods, heavy computations, and lack of intuitive explanations. To effectively capture different spatial regions occupied by objects from different categories, we propose an embarrassingly simple module, named class-specific residual attention (CSRA). CSRA generates class-specific features for every category by proposing a simple spatial attention score, and then combines it with the class-agnostic average pooling feature. CSRA achieves state-of-the-art results on multilabel recognition, and at the same time is much simpler than them. Furthermore, with only 4 lines of code, CSRA also leads to consistent improvement across many diverse pretrained models and datasets without any extra training. CSRA is both easy to implement and light in computations, which also enjoys intuitive explanations and visualizations.
+
+
+
+
+
+## Results and models
+
+### VOC2007
+
+| Model | Pretrain | Params(M) | Flops(G) | mAP | OF1 (%) | CF1 (%) | Config | Download |
+| :------------: | :------------------------------------------------: | :-------: | :------: | :---: | :-----: | :-----: | :-----------------------------------------------: | :-------------------------------------------------: |
+| Resnet101-CSRA | [ImageNet-1k](https://download.openmmlab.com/mmclassification/v0/resnet/resnet101_8xb32_in1k_20210831-539c63f8.pth) | 23.55 | 4.12 | 94.98 | 90.80 | 89.16 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/csra/resnet101-csra_1xb16_voc07-448px.py) | [model](https://download.openmmlab.com/mmclassification/v0/csra/resnet101-csra_1xb16_voc07-448px_20220722-29efb40a.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/csra/resnet101-csra_1xb16_voc07-448px_20220722-29efb40a.log.json) |
+
+## Citation
+
+```bibtex
+@misc{https://doi.org/10.48550/arxiv.2108.02456,
+ doi = {10.48550/ARXIV.2108.02456},
+ url = {https://arxiv.org/abs/2108.02456},
+ author = {Zhu, Ke and Wu, Jianxin},
+ keywords = {Computer Vision and Pattern Recognition (cs.CV), FOS: Computer and information sciences, FOS: Computer and information sciences},
+ title = {Residual Attention: A Simple but Effective Method for Multi-Label Recognition},
+ publisher = {arXiv},
+ year = {2021},
+ copyright = {arXiv.org perpetual, non-exclusive license}
+}
+```
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/csra/metafile.yml b/openmmlab_test/mmclassification-0.24.1/configs/csra/metafile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f1fa62289c1999a298fec7ff52103880e4075188
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/csra/metafile.yml
@@ -0,0 +1,29 @@
+Collections:
+ - Name: CSRA
+ Metadata:
+ Training Data: PASCAL VOC 2007
+ Architecture:
+ - Class-specific Residual Attention
+ Paper:
+ URL: https://arxiv.org/abs/1911.11929
+ Title: 'Residual Attention: A Simple but Effective Method for Multi-Label Recognition'
+ README: configs/csra/README.md
+ Code:
+ Version: v0.24.0
+ URL: https://github.com/open-mmlab/mmclassification/blob/v0.24.0/mmcls/models/heads/multi_label_csra_head.py
+
+Models:
+ - Name: resnet101-csra_1xb16_voc07-448px
+ Metadata:
+ FLOPs: 4120000000
+ Parameters: 23550000
+ In Collections: CSRA
+ Results:
+ - Dataset: PASCAL VOC 2007
+ Metrics:
+ mAP: 94.98
+ OF1: 90.80
+ CF1: 89.16
+ Task: Multi-Label Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/csra/resnet101-csra_1xb16_voc07-448px_20220722-29efb40a.pth
+ Config: configs/csra/resnet101-csra_1xb16_voc07-448px.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/csra/resnet101-csra_1xb16_voc07-448px.py b/openmmlab_test/mmclassification-0.24.1/configs/csra/resnet101-csra_1xb16_voc07-448px.py
new file mode 100644
index 0000000000000000000000000000000000000000..5dc5dd62aad05d5e00f3dbd3ea3a89e1315ff37f
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/csra/resnet101-csra_1xb16_voc07-448px.py
@@ -0,0 +1,75 @@
+_base_ = ['../_base_/datasets/voc_bs16.py', '../_base_/default_runtime.py']
+
+# Pre-trained Checkpoint Path
+checkpoint = 'https://download.openmmlab.com/mmclassification/v0/resnet/resnet101_8xb32_in1k_20210831-539c63f8.pth' # noqa
+# If you want to use the pre-trained weight of ResNet101-CutMix from
+# the originary repo(https://github.com/Kevinz-code/CSRA). Script of
+# 'tools/convert_models/torchvision_to_mmcls.py' can help you convert weight
+# into mmcls format. The mAP result would hit 95.5 by using the weight.
+# checkpoint = 'PATH/TO/PRE-TRAINED_WEIGHT'
+
+# model settings
+model = dict(
+ type='ImageClassifier',
+ backbone=dict(
+ type='ResNet',
+ depth=101,
+ num_stages=4,
+ out_indices=(3, ),
+ style='pytorch',
+ init_cfg=dict(
+ type='Pretrained', checkpoint=checkpoint, prefix='backbone')),
+ neck=None,
+ head=dict(
+ type='CSRAClsHead',
+ num_classes=20,
+ in_channels=2048,
+ num_heads=1,
+ lam=0.1,
+ loss=dict(type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0)))
+
+# dataset setting
+img_norm_cfg = dict(mean=[0, 0, 0], std=[255, 255, 255], to_rgb=True)
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='RandomResizedCrop', size=448, scale=(0.7, 1.0)),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='Collect', keys=['img', 'gt_label'])
+]
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='Resize', size=448),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img'])
+]
+data = dict(
+ # map the difficult examples as negative ones(0)
+ train=dict(pipeline=train_pipeline, difficult_as_postive=False),
+ val=dict(pipeline=test_pipeline),
+ test=dict(pipeline=test_pipeline))
+
+# optimizer
+# the lr of classifier.head is 10 * base_lr, which help convergence.
+optimizer = dict(
+ type='SGD',
+ lr=0.0002,
+ momentum=0.9,
+ weight_decay=0.0001,
+ paramwise_cfg=dict(custom_keys={'head': dict(lr_mult=10)}))
+
+optimizer_config = dict(grad_clip=None)
+
+# learning policy
+lr_config = dict(
+ policy='step',
+ step=6,
+ gamma=0.1,
+ warmup='linear',
+ warmup_iters=1,
+ warmup_ratio=1e-7,
+ warmup_by_epoch=True)
+runner = dict(type='EpochBasedRunner', max_epochs=20)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/deit/README.md b/openmmlab_test/mmclassification-0.24.1/configs/deit/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..e3103658a2a756ad74208cbc8147d1a237c28474
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/deit/README.md
@@ -0,0 +1,52 @@
+# DeiT
+
+> [Training data-efficient image transformers & distillation through attention](https://arxiv.org/abs/2012.12877)
+
+
+
+## Abstract
+
+Recently, neural networks purely based on attention were shown to address image understanding tasks such as image classification. However, these visual transformers are pre-trained with hundreds of millions of images using an expensive infrastructure, thereby limiting their adoption. In this work, we produce a competitive convolution-free transformer by training on Imagenet only. We train them on a single computer in less than 3 days. Our reference vision transformer (86M parameters) achieves top-1 accuracy of 83.1% (single-crop evaluation) on ImageNet with no external data. More importantly, we introduce a teacher-student strategy specific to transformers. It relies on a distillation token ensuring that the student learns from the teacher through attention. We show the interest of this token-based distillation, especially when using a convnet as a teacher. This leads us to report results competitive with convnets for both Imagenet (where we obtain up to 85.2% accuracy) and when transferring to other tasks. We share our code and models.
+
+
+
+
+
+## Results and models
+
+### ImageNet-1k
+
+The teacher of the distilled version DeiT is RegNetY-16GF.
+
+| Model | Pretrain | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download |
+| :-------------------------: | :----------: | :-------: | :------: | :-------: | :-------: | :------------------------------------------------------------: | :--------------------------------------------------------------: |
+| DeiT-tiny | From scratch | 5.72 | 1.08 | 74.50 | 92.24 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/deit/deit-tiny_pt-4xb256_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/deit/deit-tiny_pt-4xb256_in1k_20220218-13b382a0.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/deit/deit-tiny_pt-4xb256_in1k_20220218-13b382a0.log.json) |
+| DeiT-tiny distilled\* | From scratch | 5.72 | 1.08 | 74.51 | 91.90 | [config](https://github.com/open-mmlab/mmclassification/tree/master/configs/deit/deit-tiny-distilled_pt-4xb256_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/deit/deit-tiny-distilled_3rdparty_pt-4xb256_in1k_20211216-c429839a.pth) |
+| DeiT-small | From scratch | 22.05 | 4.24 | 80.69 | 95.06 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/deit/deit-small_pt-4xb256_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/deit/deit-small_pt-4xb256_in1k_20220218-9425b9bb.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/deit/deit-small_pt-4xb256_in1k_20220218-9425b9bb.log.json) |
+| DeiT-small distilled\* | From scratch | 22.05 | 4.24 | 81.17 | 95.40 | [config](https://github.com/open-mmlab/mmclassification/tree/master/configs/deit/deit-small-distilled_pt-4xb256_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/deit/deit-small-distilled_3rdparty_pt-4xb256_in1k_20211216-4de1d725.pth) |
+| DeiT-base | From scratch | 86.57 | 16.86 | 81.76 | 95.81 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/deit/deit-base_pt-16xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/deit/deit-base_pt-16xb64_in1k_20220216-db63c16c.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/deit/deit-base_pt-16xb64_in1k_20220216-db63c16c.log.json) |
+| DeiT-base\* | From scratch | 86.57 | 16.86 | 81.79 | 95.59 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/deit/deit-base_pt-16xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/deit/deit-base_3rdparty_pt-16xb64_in1k_20211124-6f40c188.pth) |
+| DeiT-base distilled\* | From scratch | 86.57 | 16.86 | 83.33 | 96.49 | [config](https://github.com/open-mmlab/mmclassification/tree/master/configs/deit/deit-base-distilled_pt-16xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/deit/deit-base-distilled_3rdparty_pt-16xb64_in1k_20211216-42891296.pth) |
+| DeiT-base 384px\* | ImageNet-1k | 86.86 | 49.37 | 83.04 | 96.31 | [config](https://github.com/open-mmlab/mmclassification/tree/master/configs/deit/deit-base_ft-16xb32_in1k-384px.py) | [model](https://download.openmmlab.com/mmclassification/v0/deit/deit-base_3rdparty_ft-16xb32_in1k-384px_20211124-822d02f2.pth) |
+| DeiT-base distilled 384px\* | ImageNet-1k | 86.86 | 49.37 | 85.55 | 97.35 | [config](https://github.com/open-mmlab/mmclassification/tree/master/configs/deit/deit-base-distilled_ft-16xb32_in1k-384px.py) | [model](https://download.openmmlab.com/mmclassification/v0/deit/deit-base-distilled_3rdparty_ft-16xb32_in1k-384px_20211216-e48d6000.pth) |
+
+*Models with * are converted from the [official repo](https://github.com/facebookresearch/deit). The config files of these models are only for validation. We don't ensure these config files' training accuracy and welcome you to contribute your reproduction results.*
+
+```{warning}
+MMClassification doesn't support training the distilled version DeiT.
+And we provide distilled version checkpoints for inference only.
+```
+
+## Citation
+
+```
+@InProceedings{pmlr-v139-touvron21a,
+ title = {Training data-efficient image transformers & distillation through attention},
+ author = {Touvron, Hugo and Cord, Matthieu and Douze, Matthijs and Massa, Francisco and Sablayrolles, Alexandre and Jegou, Herve},
+ booktitle = {International Conference on Machine Learning},
+ pages = {10347--10357},
+ year = {2021},
+ volume = {139},
+ month = {July}
+}
+```
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/deit/deit-base-distilled_ft-16xb32_in1k-384px.py b/openmmlab_test/mmclassification-0.24.1/configs/deit/deit-base-distilled_ft-16xb32_in1k-384px.py
new file mode 100644
index 0000000000000000000000000000000000000000..c8bdfb537bd4883203750a983d1c67173447fb88
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/deit/deit-base-distilled_ft-16xb32_in1k-384px.py
@@ -0,0 +1,9 @@
+_base_ = './deit-base_ft-16xb32_in1k-384px.py'
+
+# model settings
+model = dict(
+ backbone=dict(type='DistilledVisionTransformer'),
+ head=dict(type='DeiTClsHead'),
+ # Change to the path of the pretrained model
+ # init_cfg=dict(type='Pretrained', checkpoint=''),
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/deit/deit-base-distilled_pt-16xb64_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/deit/deit-base-distilled_pt-16xb64_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..671658383aee8a0c7feb7283731116d873276f0c
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/deit/deit-base-distilled_pt-16xb64_in1k.py
@@ -0,0 +1,10 @@
+_base_ = './deit-small_pt-4xb256_in1k.py'
+
+# model settings
+model = dict(
+ backbone=dict(type='DistilledVisionTransformer', arch='deit-base'),
+ head=dict(type='DeiTClsHead', in_channels=768),
+)
+
+# data settings
+data = dict(samples_per_gpu=64, workers_per_gpu=5)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/deit/deit-base_ft-16xb32_in1k-384px.py b/openmmlab_test/mmclassification-0.24.1/configs/deit/deit-base_ft-16xb32_in1k-384px.py
new file mode 100644
index 0000000000000000000000000000000000000000..db444168d43b5ce970110bf725bf801a2355c442
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/deit/deit-base_ft-16xb32_in1k-384px.py
@@ -0,0 +1,29 @@
+_base_ = [
+ '../_base_/datasets/imagenet_bs64_swin_384.py',
+ '../_base_/schedules/imagenet_bs4096_AdamW.py',
+ '../_base_/default_runtime.py'
+]
+
+# model settings
+model = dict(
+ type='ImageClassifier',
+ backbone=dict(
+ type='VisionTransformer',
+ arch='deit-base',
+ img_size=384,
+ patch_size=16,
+ ),
+ neck=None,
+ head=dict(
+ type='VisionTransformerClsHead',
+ num_classes=1000,
+ in_channels=768,
+ loss=dict(
+ type='LabelSmoothLoss', label_smooth_val=0.1, mode='original'),
+ ),
+ # Change to the path of the pretrained model
+ # init_cfg=dict(type='Pretrained', checkpoint=''),
+)
+
+# data settings
+data = dict(samples_per_gpu=32, workers_per_gpu=5)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/deit/deit-base_pt-16xb64_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/deit/deit-base_pt-16xb64_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..24c13dca489b46d0afd272a569afd1c7c19d940a
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/deit/deit-base_pt-16xb64_in1k.py
@@ -0,0 +1,13 @@
+_base_ = './deit-small_pt-4xb256_in1k.py'
+
+# model settings
+model = dict(
+ backbone=dict(
+ type='VisionTransformer', arch='deit-base', drop_path_rate=0.1),
+ head=dict(type='VisionTransformerClsHead', in_channels=768),
+)
+
+# data settings
+data = dict(samples_per_gpu=64, workers_per_gpu=5)
+
+custom_hooks = [dict(type='EMAHook', momentum=4e-5, priority='ABOVE_NORMAL')]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/deit/deit-small-distilled_pt-4xb256_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/deit/deit-small-distilled_pt-4xb256_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..3b1fac224905bfddb5803a1c569c38cce8a73250
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/deit/deit-small-distilled_pt-4xb256_in1k.py
@@ -0,0 +1,7 @@
+_base_ = './deit-small_pt-4xb256_in1k.py'
+
+# model settings
+model = dict(
+ backbone=dict(type='DistilledVisionTransformer', arch='deit-small'),
+ head=dict(type='DeiTClsHead', in_channels=384),
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/deit/deit-small_pt-4xb256_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/deit/deit-small_pt-4xb256_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..550f08015e17618f7b1eede1ad64185ebdfaf954
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/deit/deit-small_pt-4xb256_in1k.py
@@ -0,0 +1,44 @@
+# In small and tiny arch, remove drop path and EMA hook comparing with the
+# original config
+_base_ = [
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py'
+]
+
+# model settings
+model = dict(
+ type='ImageClassifier',
+ backbone=dict(
+ type='VisionTransformer',
+ arch='deit-small',
+ img_size=224,
+ patch_size=16),
+ neck=None,
+ head=dict(
+ type='VisionTransformerClsHead',
+ num_classes=1000,
+ in_channels=384,
+ loss=dict(
+ type='LabelSmoothLoss', label_smooth_val=0.1, mode='original'),
+ ),
+ init_cfg=[
+ dict(type='TruncNormal', layer='Linear', std=.02),
+ dict(type='Constant', layer='LayerNorm', val=1., bias=0.),
+ ],
+ train_cfg=dict(augments=[
+ dict(type='BatchMixup', alpha=0.8, num_classes=1000, prob=0.5),
+ dict(type='BatchCutMix', alpha=1.0, num_classes=1000, prob=0.5)
+ ]))
+
+# data settings
+data = dict(samples_per_gpu=256, workers_per_gpu=5)
+
+paramwise_cfg = dict(
+ norm_decay_mult=0.0,
+ bias_decay_mult=0.0,
+ custom_keys={
+ '.cls_token': dict(decay_mult=0.0),
+ '.pos_embed': dict(decay_mult=0.0)
+ })
+optimizer = dict(paramwise_cfg=paramwise_cfg)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/deit/deit-tiny-distilled_pt-4xb256_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/deit/deit-tiny-distilled_pt-4xb256_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..175f980445d351bd78ba5eab7ca13aa7e15f7197
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/deit/deit-tiny-distilled_pt-4xb256_in1k.py
@@ -0,0 +1,7 @@
+_base_ = './deit-small_pt-4xb256_in1k.py'
+
+# model settings
+model = dict(
+ backbone=dict(type='DistilledVisionTransformer', arch='deit-tiny'),
+ head=dict(type='DeiTClsHead', in_channels=192),
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/deit/deit-tiny_pt-4xb256_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/deit/deit-tiny_pt-4xb256_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..43df6e13823ee0ae8da50c63acea0efa716e8a42
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/deit/deit-tiny_pt-4xb256_in1k.py
@@ -0,0 +1,7 @@
+_base_ = './deit-small_pt-4xb256_in1k.py'
+
+# model settings
+model = dict(
+ backbone=dict(type='VisionTransformer', arch='deit-tiny'),
+ head=dict(type='VisionTransformerClsHead', in_channels=192),
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/deit/metafile.yml b/openmmlab_test/mmclassification-0.24.1/configs/deit/metafile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ddd4c6744d7ea7ae61f20e67899c508004abea60
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/deit/metafile.yml
@@ -0,0 +1,153 @@
+Collections:
+ - Name: DeiT
+ Metadata:
+ Training Data: ImageNet-1k
+ Architecture:
+ - Layer Normalization
+ - Scaled Dot-Product Attention
+ - Attention Dropout
+ - Multi-Head Attention
+ Paper:
+ URL: https://arxiv.org/abs/2012.12877
+ Title: "Training data-efficient image transformers & distillation through attention"
+ README: configs/deit/README.md
+ Code:
+ URL: v0.19.0
+ Version: https://github.com/open-mmlab/mmclassification/blob/v0.19.0/mmcls/models/backbones/deit.py
+
+Models:
+ - Name: deit-tiny_pt-4xb256_in1k
+ Metadata:
+ FLOPs: 1080000000
+ Parameters: 5720000
+ In Collection: DeiT
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 74.50
+ Top 5 Accuracy: 92.24
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/deit/deit-tiny_pt-4xb256_in1k_20220218-13b382a0.pth
+ Config: configs/deit/deit-tiny_pt-4xb256_in1k.py
+ - Name: deit-tiny-distilled_3rdparty_pt-4xb256_in1k
+ Metadata:
+ FLOPs: 1080000000
+ Parameters: 5720000
+ In Collection: DeiT
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 74.51
+ Top 5 Accuracy: 91.90
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/deit/deit-tiny-distilled_3rdparty_pt-4xb256_in1k_20211216-c429839a.pth
+ Converted From:
+ Weights: https://dl.fbaipublicfiles.com/deit/deit_tiny_distilled_patch16_224-b40b3cf7.pth
+ Code: https://github.com/facebookresearch/deit/blob/f5123946205daf72a88783dae94cabff98c49c55/models.py#L108
+ Config: configs/deit/deit-tiny-distilled_pt-4xb256_in1k.py
+ - Name: deit-small_pt-4xb256_in1k
+ Metadata:
+ FLOPs: 4240000000
+ Parameters: 22050000
+ In Collection: DeiT
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 80.69
+ Top 5 Accuracy: 95.06
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/deit/deit-small_pt-4xb256_in1k_20220218-9425b9bb.pth
+ Config: configs/deit/deit-small_pt-4xb256_in1k.py
+ - Name: deit-small-distilled_3rdparty_pt-4xb256_in1k
+ Metadata:
+ FLOPs: 4240000000
+ Parameters: 22050000
+ In Collection: DeiT
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 81.17
+ Top 5 Accuracy: 95.40
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/deit/deit-small-distilled_3rdparty_pt-4xb256_in1k_20211216-4de1d725.pth
+ Converted From:
+ Weights: https://dl.fbaipublicfiles.com/deit/deit_small_distilled_patch16_224-649709d9.pth
+ Code: https://github.com/facebookresearch/deit/blob/f5123946205daf72a88783dae94cabff98c49c55/models.py#L123
+ Config: configs/deit/deit-small-distilled_pt-4xb256_in1k.py
+ - Name: deit-base_pt-16xb64_in1k
+ Metadata:
+ FLOPs: 16860000000
+ Parameters: 86570000
+ In Collection: DeiT
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 81.76
+ Top 5 Accuracy: 95.81
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/deit/deit-base_pt-16xb64_in1k_20220216-db63c16c.pth
+ Config: configs/deit/deit-base_pt-16xb64_in1k.py
+ - Name: deit-base_3rdparty_pt-16xb64_in1k
+ Metadata:
+ FLOPs: 16860000000
+ Parameters: 86570000
+ In Collection: DeiT
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 81.79
+ Top 5 Accuracy: 95.59
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/deit/deit-base_3rdparty_pt-16xb64_in1k_20211124-6f40c188.pth
+ Converted From:
+ Weights: https://dl.fbaipublicfiles.com/deit/deit_base_patch16_224-b5f2ef4d.pth
+ Code: https://github.com/facebookresearch/deit/blob/f5123946205daf72a88783dae94cabff98c49c55/models.py#L93
+ Config: configs/deit/deit-base_pt-16xb64_in1k.py
+ - Name: deit-base-distilled_3rdparty_pt-16xb64_in1k
+ Metadata:
+ FLOPs: 16860000000
+ Parameters: 86570000
+ In Collection: DeiT
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 83.33
+ Top 5 Accuracy: 96.49
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/deit/deit-base-distilled_3rdparty_pt-16xb64_in1k_20211216-42891296.pth
+ Converted From:
+ Weights: https://dl.fbaipublicfiles.com/deit/deit_base_distilled_patch16_224-df68dfff.pth
+ Code: https://github.com/facebookresearch/deit/blob/f5123946205daf72a88783dae94cabff98c49c55/models.py#L138
+ Config: configs/deit/deit-base-distilled_pt-16xb64_in1k.py
+ - Name: deit-base_3rdparty_ft-16xb32_in1k-384px
+ Metadata:
+ FLOPs: 49370000000
+ Parameters: 86860000
+ In Collection: DeiT
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 83.04
+ Top 5 Accuracy: 96.31
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/deit/deit-base_3rdparty_ft-16xb32_in1k-384px_20211124-822d02f2.pth
+ Converted From:
+ Weights: https://dl.fbaipublicfiles.com/deit/deit_base_patch16_384-8de9b5d1.pth
+ Code: https://github.com/facebookresearch/deit/blob/f5123946205daf72a88783dae94cabff98c49c55/models.py#L153
+ Config: configs/deit/deit-base_ft-16xb32_in1k-384px.py
+ - Name: deit-base-distilled_3rdparty_ft-16xb32_in1k-384px
+ Metadata:
+ FLOPs: 49370000000
+ Parameters: 86860000
+ In Collection: DeiT
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 85.55
+ Top 5 Accuracy: 97.35
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/deit/deit-base-distilled_3rdparty_ft-16xb32_in1k-384px_20211216-e48d6000.pth
+ Converted From:
+ Weights: https://dl.fbaipublicfiles.com/deit/deit_base_distilled_patch16_384-d0272ac0.pth
+ Code: https://github.com/facebookresearch/deit/blob/f5123946205daf72a88783dae94cabff98c49c55/models.py#L168
+ Config: configs/deit/deit-base-distilled_ft-16xb32_in1k-384px.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/densenet/README.md b/openmmlab_test/mmclassification-0.24.1/configs/densenet/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..f07f25c9fdb6fe57a8dc3d6f2af95d820ab21736
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/densenet/README.md
@@ -0,0 +1,41 @@
+# DenseNet
+
+> [Densely Connected Convolutional Networks](https://arxiv.org/abs/1608.06993)
+
+
+
+## Abstract
+
+Recent work has shown that convolutional networks can be substantially deeper, more accurate, and efficient to train if they contain shorter connections between layers close to the input and those close to the output. In this paper, we embrace this observation and introduce the Dense Convolutional Network (DenseNet), which connects each layer to every other layer in a feed-forward fashion. Whereas traditional convolutional networks with L layers have L connections - one between each layer and its subsequent layer - our network has L(L+1)/2 direct connections. For each layer, the feature-maps of all preceding layers are used as inputs, and its own feature-maps are used as inputs into all subsequent layers. DenseNets have several compelling advantages: they alleviate the vanishing-gradient problem, strengthen feature propagation, encourage feature reuse, and substantially reduce the number of parameters. We evaluate our proposed architecture on four highly competitive object recognition benchmark tasks (CIFAR-10, CIFAR-100, SVHN, and ImageNet). DenseNets obtain significant improvements over the state-of-the-art on most of them, whilst requiring less computation to achieve high performance.
+
+
+
+
+
+## Results and models
+
+### ImageNet-1k
+
+| Model | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download |
+| :-----------: | :-------: | :------: | :-------: | :-------: | :-------------------------------------------------------------------------: | :---------------------------------------------------------------------------: |
+| DenseNet121\* | 7.98 | 2.88 | 74.96 | 92.21 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/densenet/densenet121_4xb256_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/densenet/densenet121_4xb256_in1k_20220426-07450f99.pth) |
+| DenseNet169\* | 14.15 | 3.42 | 76.08 | 93.11 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/densenet/densenet169_4xb256_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/densenet/densenet169_4xb256_in1k_20220426-a2889902.pth) |
+| DenseNet201\* | 20.01 | 4.37 | 77.32 | 93.64 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/densenet/densenet201_4xb256_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/densenet/densenet201_4xb256_in1k_20220426-05cae4ef.pth) |
+| DenseNet161\* | 28.68 | 7.82 | 77.61 | 93.83 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/densenet/densenet161_4xb256_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/densenet/densenet161_4xb256_in1k_20220426-ee6a80a9.pth) |
+
+*Models with * are converted from [pytorch](https://pytorch.org/vision/stable/models.html), guided by [original repo](https://github.com/liuzhuang13/DenseNet). The config files of these models are only for inference. We don't ensure these config files' training accuracy and welcome you to contribute your reproduction results.*
+
+## Citation
+
+```bibtex
+@misc{https://doi.org/10.48550/arxiv.1608.06993,
+ doi = {10.48550/ARXIV.1608.06993},
+ url = {https://arxiv.org/abs/1608.06993},
+ author = {Huang, Gao and Liu, Zhuang and van der Maaten, Laurens and Weinberger, Kilian Q.},
+ keywords = {Computer Vision and Pattern Recognition (cs.CV), Machine Learning (cs.LG), FOS: Computer and information sciences, FOS: Computer and information sciences},
+ title = {Densely Connected Convolutional Networks},
+ publisher = {arXiv},
+ year = {2016},
+ copyright = {arXiv.org perpetual, non-exclusive license}
+}
+```
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/densenet/densenet121_4xb256_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/densenet/densenet121_4xb256_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..08d65ae24acae4758c26c3f1f13ec648671560ec
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/densenet/densenet121_4xb256_in1k.py
@@ -0,0 +1,10 @@
+_base_ = [
+ '../_base_/models/densenet/densenet121.py',
+ '../_base_/datasets/imagenet_bs64.py',
+ '../_base_/schedules/imagenet_bs256.py',
+ '../_base_/default_runtime.py',
+]
+
+data = dict(samples_per_gpu=256)
+
+runner = dict(type='EpochBasedRunner', max_epochs=90)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/densenet/densenet161_4xb256_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/densenet/densenet161_4xb256_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..4581d1dec6915db973316453fb8e2eee5c9d981a
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/densenet/densenet161_4xb256_in1k.py
@@ -0,0 +1,10 @@
+_base_ = [
+ '../_base_/models/densenet/densenet161.py',
+ '../_base_/datasets/imagenet_bs64.py',
+ '../_base_/schedules/imagenet_bs256.py',
+ '../_base_/default_runtime.py',
+]
+
+data = dict(samples_per_gpu=256)
+
+runner = dict(type='EpochBasedRunner', max_epochs=90)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/densenet/densenet169_4xb256_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/densenet/densenet169_4xb256_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..6179293beba840548f0e6f07fee612466b2863c6
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/densenet/densenet169_4xb256_in1k.py
@@ -0,0 +1,10 @@
+_base_ = [
+ '../_base_/models/densenet/densenet169.py',
+ '../_base_/datasets/imagenet_bs64.py',
+ '../_base_/schedules/imagenet_bs256.py',
+ '../_base_/default_runtime.py',
+]
+
+data = dict(samples_per_gpu=256)
+
+runner = dict(type='EpochBasedRunner', max_epochs=90)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/densenet/densenet201_4xb256_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/densenet/densenet201_4xb256_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..897a141dba1ddc845563c752df702165718ce6df
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/densenet/densenet201_4xb256_in1k.py
@@ -0,0 +1,10 @@
+_base_ = [
+ '../_base_/models/densenet/densenet201.py',
+ '../_base_/datasets/imagenet_bs64.py',
+ '../_base_/schedules/imagenet_bs256.py',
+ '../_base_/default_runtime.py',
+]
+
+data = dict(samples_per_gpu=256)
+
+runner = dict(type='EpochBasedRunner', max_epochs=90)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/densenet/metafile.yml b/openmmlab_test/mmclassification-0.24.1/configs/densenet/metafile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..84366b23a35d7ab3ce42bd0725b6a8e204bbd988
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/densenet/metafile.yml
@@ -0,0 +1,76 @@
+Collections:
+ - Name: DenseNet
+ Metadata:
+ Training Data: ImageNet-1k
+ Architecture:
+ - DenseBlock
+ Paper:
+ URL: https://arxiv.org/abs/1608.06993
+ Title: Densely Connected Convolutional Networks
+ README: configs/densenet/README.md
+
+Models:
+ - Name: densenet121_4xb256_in1k
+ Metadata:
+ FLOPs: 2881695488
+ Parameters: 7978856
+ In Collections: DenseNet
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 74.96
+ Top 5 Accuracy: 92.21
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/densenet/densenet121_4xb256_in1k_20220426-07450f99.pth
+ Config: configs/densenet/densenet121_4xb256_in1k.py
+ Converted From:
+ Weights: https://download.pytorch.org/models/densenet121-a639ec97.pth
+ Code: https://github.com/pytorch/vision/blob/main/torchvision/models/densenet.py
+ - Name: densenet169_4xb256_in1k
+ Metadata:
+ FLOPs: 3416860160
+ Parameters: 14149480
+ In Collections: DenseNet
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 76.08
+ Top 5 Accuracy: 93.11
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/densenet/densenet169_4xb256_in1k_20220426-a2889902.pth
+ Config: configs/densenet/densenet169_4xb256_in1k.py
+ Converted From:
+ Weights: https://download.pytorch.org/models/densenet169-b2777c0a.pth
+ Code: https://github.com/pytorch/vision/blob/main/torchvision/models/densenet.py
+ - Name: densenet201_4xb256_in1k
+ Metadata:
+ FLOPs: 4365236736
+ Parameters: 20013928
+ In Collections: DenseNet
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 77.32
+ Top 5 Accuracy: 93.64
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/densenet/densenet201_4xb256_in1k_20220426-05cae4ef.pth
+ Config: configs/densenet/densenet201_4xb256_in1k.py
+ Converted From:
+ Weights: https://download.pytorch.org/models/densenet201-c1103571.pth
+ Code: https://github.com/pytorch/vision/blob/main/torchvision/models/densenet.py
+ - Name: densenet161_4xb256_in1k
+ Metadata:
+ FLOPs: 7816363968
+ Parameters: 28681000
+ In Collections: DenseNet
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 77.61
+ Top 5 Accuracy: 93.83
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/densenet/densenet161_4xb256_in1k_20220426-ee6a80a9.pth
+ Config: configs/densenet/densenet161_4xb256_in1k.py
+ Converted From:
+ Weights: https://download.pytorch.org/models/densenet161-8d451a50.pth
+ Code: https://github.com/pytorch/vision/blob/main/torchvision/models/densenet.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/efficientformer/README.md b/openmmlab_test/mmclassification-0.24.1/configs/efficientformer/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..ecd6b4927e5147e6360d1509ba356f1b56fdb074
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/efficientformer/README.md
@@ -0,0 +1,47 @@
+# EfficientFormer
+
+> [EfficientFormer: Vision Transformers at MobileNet Speed](https://arxiv.org/abs/2206.01191)
+
+
+
+## Abstract
+
+Vision Transformers (ViT) have shown rapid progress in computer vision tasks, achieving promising results on various benchmarks. However, due to the massive number of parameters and model design, e.g., attention mechanism, ViT-based models are generally times slower than lightweight convolutional networks. Therefore, the deployment of ViT for real-time applications is particularly challenging, especially on resource-constrained hardware such as mobile devices. Recent efforts try to reduce the computation complexity of ViT through network architecture search or hybrid design with MobileNet block, yet the inference speed is still unsatisfactory. This leads to an important question: can transformers run as fast as MobileNet while obtaining high performance? To answer this, we first revisit the network architecture and operators used in ViT-based models and identify inefficient designs. Then we introduce a dimension-consistent pure transformer (without MobileNet blocks) as a design paradigm. Finally, we perform latency-driven slimming to get a series of final models dubbed EfficientFormer. Extensive experiments show the superiority of EfficientFormer in performance and speed on mobile devices. Our fastest model, EfficientFormer-L1, achieves 79.2% top-1 accuracy on ImageNet-1K with only 1.6 ms inference latency on iPhone 12 (compiled with CoreML), which runs as fast as MobileNetV2×1.4 (1.6 ms, 74.7% top-1), and our largest model, EfficientFormer-L7, obtains 83.3% accuracy with only 7.0 ms latency. Our work proves that properly designed transformers can reach extremely low latency on mobile devices while maintaining high performance.
+
+
+
+
+
+## Results and models
+
+### ImageNet-1k
+
+| Model | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download |
+| :------------------: | :-------: | :------: | :-------: | :-------: | :---------------------------------------------------------------------: | :------------------------------------------------------------------------: |
+| EfficientFormer-l1\* | 12.19 | 1.30 | 80.46 | 94.99 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientformer/efficientformer-l1_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientformer/efficientformer-l1_3rdparty_in1k_20220803-d66e61df.pth) |
+| EfficientFormer-l3\* | 31.41 | 3.93 | 82.45 | 96.18 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientformer/efficientformer-l3_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientformer/efficientformer-l3_3rdparty_in1k_20220803-dde1c8c5.pth) |
+| EfficientFormer-l7\* | 82.23 | 10.16 | 83.40 | 96.60 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientformer/efficientformer-l7_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientformer/efficientformer-l7_3rdparty_in1k_20220803-41a552bb.pth) |
+
+*Models with * are converted from the [official repo](https://github.com/snap-research/EfficientFormer). The config files of these models are only for inference. We don't ensure these config files' training accuracy and welcome you to contribute your reproduction results.*
+
+## Citation
+
+```bibtex
+@misc{https://doi.org/10.48550/arxiv.2206.01191,
+ doi = {10.48550/ARXIV.2206.01191},
+
+ url = {https://arxiv.org/abs/2206.01191},
+
+ author = {Li, Yanyu and Yuan, Geng and Wen, Yang and Hu, Eric and Evangelidis, Georgios and Tulyakov, Sergey and Wang, Yanzhi and Ren, Jian},
+
+ keywords = {Computer Vision and Pattern Recognition (cs.CV), FOS: Computer and information sciences, FOS: Computer and information sciences},
+
+ title = {EfficientFormer: Vision Transformers at MobileNet Speed},
+
+ publisher = {arXiv},
+
+ year = {2022},
+
+ copyright = {Creative Commons Attribution 4.0 International}
+}
+```
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/efficientformer/efficientformer-l1_8xb128_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/efficientformer/efficientformer-l1_8xb128_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..f5db2bfc63bc0f95d07eb72d74541c16b34a66d0
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/efficientformer/efficientformer-l1_8xb128_in1k.py
@@ -0,0 +1,24 @@
+_base_ = [
+ '../_base_/datasets/imagenet_bs128_poolformer_small_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py',
+]
+
+model = dict(
+ type='ImageClassifier',
+ backbone=dict(
+ type='EfficientFormer',
+ arch='l1',
+ drop_path_rate=0,
+ init_cfg=[
+ dict(
+ type='TruncNormal',
+ layer=['Conv2d', 'Linear'],
+ std=.02,
+ bias=0.),
+ dict(type='Constant', layer=['GroupNorm'], val=1., bias=0.),
+ dict(type='Constant', layer=['LayerScale'], val=1e-5)
+ ]),
+ neck=dict(type='GlobalAveragePooling', dim=1),
+ head=dict(
+ type='EfficientFormerClsHead', in_channels=448, num_classes=1000))
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/efficientformer/efficientformer-l3_8xb128_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/efficientformer/efficientformer-l3_8xb128_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..e920f785d843e838435ef80040ea08133cd9e0b3
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/efficientformer/efficientformer-l3_8xb128_in1k.py
@@ -0,0 +1,24 @@
+_base_ = [
+ '../_base_/datasets/imagenet_bs128_poolformer_small_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py',
+]
+
+model = dict(
+ type='ImageClassifier',
+ backbone=dict(
+ type='EfficientFormer',
+ arch='l3',
+ drop_path_rate=0,
+ init_cfg=[
+ dict(
+ type='TruncNormal',
+ layer=['Conv2d', 'Linear'],
+ std=.02,
+ bias=0.),
+ dict(type='Constant', layer=['GroupNorm'], val=1., bias=0.),
+ dict(type='Constant', layer=['LayerScale'], val=1e-5)
+ ]),
+ neck=dict(type='GlobalAveragePooling', dim=1),
+ head=dict(
+ type='EfficientFormerClsHead', in_channels=512, num_classes=1000))
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/efficientformer/efficientformer-l7_8xb128_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/efficientformer/efficientformer-l7_8xb128_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..a59e3a7ed5a33914478abb2f3354d7bf6a11e256
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/efficientformer/efficientformer-l7_8xb128_in1k.py
@@ -0,0 +1,24 @@
+_base_ = [
+ '../_base_/datasets/imagenet_bs128_poolformer_small_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py',
+]
+
+model = dict(
+ type='ImageClassifier',
+ backbone=dict(
+ type='EfficientFormer',
+ arch='l7',
+ drop_path_rate=0,
+ init_cfg=[
+ dict(
+ type='TruncNormal',
+ layer=['Conv2d', 'Linear'],
+ std=.02,
+ bias=0.),
+ dict(type='Constant', layer=['GroupNorm'], val=1., bias=0.),
+ dict(type='Constant', layer=['LayerScale'], val=1e-5)
+ ]),
+ neck=dict(type='GlobalAveragePooling', dim=1),
+ head=dict(
+ type='EfficientFormerClsHead', in_channels=768, num_classes=1000))
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/efficientformer/metafile.yml b/openmmlab_test/mmclassification-0.24.1/configs/efficientformer/metafile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..33c47865e9fcfb472a3a69bd4e4c9e22534040b9
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/efficientformer/metafile.yml
@@ -0,0 +1,67 @@
+Collections:
+ - Name: EfficientFormer
+ Metadata:
+ Training Data: ImageNet-1k
+ Architecture:
+ - Pooling
+ - 1x1 Convolution
+ - LayerScale
+ - MetaFormer
+ Paper:
+ URL: https://arxiv.org/pdf/2206.01191.pdf
+ Title: "EfficientFormer: Vision Transformers at MobileNet Speed"
+ README: configs/efficientformer/README.md
+ Code:
+ Version: v0.24.0
+ URL: https://github.com/open-mmlab/mmclassification/blob/v0.24.0/mmcls/models/backbones/efficientformer.py
+
+Models:
+ - Name: efficientformer-l1_3rdparty_8xb128_in1k
+ Metadata:
+ FLOPs: 1304601088 # 1.3G
+ Parameters: 12278696 # 12M
+ In Collections: EfficientFormer
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 80.46
+ Top 5 Accuracy: 94.99
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/efficientformer/efficientformer-l1_3rdparty_in1k_20220803-d66e61df.pth
+ Config: configs/efficientformer/efficientformer-l1_8xb128_in1k.py
+ Converted From:
+ Weights: https://drive.google.com/file/d/11SbX-3cfqTOc247xKYubrAjBiUmr818y/view?usp=sharing
+ Code: https://github.com/snap-research/EfficientFormer
+ - Name: efficientformer-l3_3rdparty_8xb128_in1k
+ Metadata:
+ Training Data: ImageNet-1k
+ FLOPs: 3737045760 # 3.7G
+ Parameters: 31406000 # 31M
+ In Collections: EfficientFormer
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 82.45
+ Top 5 Accuracy: 96.18
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/efficientformer/efficientformer-l3_3rdparty_in1k_20220803-dde1c8c5.pth
+ Config: configs/efficientformer/efficientformer-l3_8xb128_in1k.py
+ Converted From:
+ Weights: https://drive.google.com/file/d/1OyyjKKxDyMj-BcfInp4GlDdwLu3hc30m/view?usp=sharing
+ Code: https://github.com/snap-research/EfficientFormer
+ - Name: efficientformer-l7_3rdparty_8xb128_in1k
+ Metadata:
+ FLOPs: 10163951616 # 10.2G
+ Parameters: 82229328 # 82M
+ In Collections: EfficientFormer
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 83.40
+ Top 5 Accuracy: 96.60
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/efficientformer/efficientformer-l7_3rdparty_in1k_20220803-41a552bb.pth
+ Config: configs/efficientformer/efficientformer-l7_8xb128_in1k.py
+ Converted From:
+ Weights: https://drive.google.com/file/d/1cVw-pctJwgvGafeouynqWWCwgkcoFMM5/view?usp=sharing
+ Code: https://github.com/snap-research/EfficientFormer
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/efficientnet/README.md b/openmmlab_test/mmclassification-0.24.1/configs/efficientnet/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..832f5c6b2f9d65acb9dcc920547a4e82292a4da8
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/efficientnet/README.md
@@ -0,0 +1,62 @@
+# EfficientNet
+
+> [Rethinking Model Scaling for Convolutional Neural Networks](https://arxiv.org/abs/1905.11946v5)
+
+
+
+## Abstract
+
+Convolutional Neural Networks (ConvNets) are commonly developed at a fixed resource budget, and then scaled up for better accuracy if more resources are available. In this paper, we systematically study model scaling and identify that carefully balancing network depth, width, and resolution can lead to better performance. Based on this observation, we propose a new scaling method that uniformly scales all dimensions of depth/width/resolution using a simple yet highly effective compound coefficient. We demonstrate the effectiveness of this method on scaling up MobileNets and ResNet. To go even further, we use neural architecture search to design a new baseline network and scale it up to obtain a family of models, called EfficientNets, which achieve much better accuracy and efficiency than previous ConvNets. In particular, our EfficientNet-B7 achieves state-of-the-art 84.3% top-1 accuracy on ImageNet, while being 8.4x smaller and 6.1x faster on inference than the best existing ConvNet. Our EfficientNets also transfer well and achieve state-of-the-art accuracy on CIFAR-100 (91.7%), Flowers (98.8%), and 3 other transfer learning datasets, with an order of magnitude fewer parameters.
+
+
+
+## Results and models
+
+### ImageNet-1k
+
+| Model | Pretrain | resolution | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download |
+| :-----------: | :----------: | :--------: | :-------: | :------: | :-------: | :-------: | :--------------------------------------------------------------: | :----------------------------------------------------------------: |
+| HorNet-T\* | From scratch | 224x224 | 22.41 | 3.98 | 82.84 | 96.24 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/hornet/hornet-tiny_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/hornet/hornet-tiny_3rdparty_in1k_20220915-0e8eedff.pth) |
+| HorNet-T-GF\* | From scratch | 224x224 | 22.99 | 3.9 | 82.98 | 96.38 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/hornet/hornet-tiny-gf_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/hornet/hornet-tiny-gf_3rdparty_in1k_20220915-4c35a66b.pth) |
+| HorNet-S\* | From scratch | 224x224 | 49.53 | 8.83 | 83.79 | 96.75 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/hornet/hornet-small_8xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/hornet/hornet-small_3rdparty_in1k_20220915-5935f60f.pth) |
+| HorNet-S-GF\* | From scratch | 224x224 | 50.4 | 8.71 | 83.98 | 96.77 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/hornet/hornet-small-gf_8xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/hornet/hornet-small-gf_3rdparty_in1k_20220915-649ca492.pth) |
+| HorNet-B\* | From scratch | 224x224 | 87.26 | 15.59 | 84.24 | 96.94 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/hornet/hornet-base_8xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/hornet/hornet-base_3rdparty_in1k_20220915-a06176bb.pth) |
+| HorNet-B-GF\* | From scratch | 224x224 | 88.42 | 15.42 | 84.32 | 96.95 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/hornet/hornet-base-gf_8xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/hornet/hornet-base-gf_3rdparty_in1k_20220915-82c06fa7.pth) |
+
+\*Models with * are converted from [the official repo](https://github.com/raoyongming/HorNet). The config files of these models are only for validation. We don't ensure these config files' training accuracy and welcome you to contribute your reproduction results.
+
+### Pre-trained Models
+
+The pre-trained models on ImageNet-21k are used to fine-tune on the downstream tasks.
+
+| Model | Pretrain | resolution | Params(M) | Flops(G) | Download |
+| :--------------: | :----------: | :--------: | :-------: | :------: | :------------------------------------------------------------------------------------------------------------------------: |
+| HorNet-L\* | ImageNet-21k | 224x224 | 194.54 | 34.83 | [model](https://download.openmmlab.com/mmclassification/v0/hornet/hornet-large_3rdparty_in21k_20220909-9ccef421.pth) |
+| HorNet-L-GF\* | ImageNet-21k | 224x224 | 196.29 | 34.58 | [model](https://download.openmmlab.com/mmclassification/v0/hornet/hornet-large-gf_3rdparty_in21k_20220909-3aea3b61.pth) |
+| HorNet-L-GF384\* | ImageNet-21k | 384x384 | 201.23 | 101.63 | [model](https://download.openmmlab.com/mmclassification/v0/hornet/hornet-large-gf384_3rdparty_in21k_20220909-80894290.pth) |
+
+\*Models with * are converted from [the official repo](https://github.com/raoyongming/HorNet).
+
+## Citation
+
+```
+@article{rao2022hornet,
+ title={HorNet: Efficient High-Order Spatial Interactions with Recursive Gated Convolutions},
+ author={Rao, Yongming and Zhao, Wenliang and Tang, Yansong and Zhou, Jie and Lim, Ser-Lam and Lu, Jiwen},
+ journal={arXiv preprint arXiv:2207.14284},
+ year={2022}
+}
+```
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/hornet/hornet-base-gf_8xb64_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/hornet/hornet-base-gf_8xb64_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..6c29de66b62641c21c2f1e2e82881333ad438bba
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/hornet/hornet-base-gf_8xb64_in1k.py
@@ -0,0 +1,13 @@
+_base_ = [
+ '../_base_/models/hornet/hornet-base-gf.py',
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py',
+]
+
+data = dict(samples_per_gpu=64)
+
+optimizer = dict(lr=4e-3)
+optimizer_config = dict(grad_clip=dict(max_norm=1.0), _delete_=True)
+
+custom_hooks = [dict(type='EMAHook', momentum=4e-5, priority='ABOVE_NORMAL')]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/hornet/hornet-base_8xb64_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/hornet/hornet-base_8xb64_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..969d8b95b6ee1a06ecf257d162050982d4d5d698
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/hornet/hornet-base_8xb64_in1k.py
@@ -0,0 +1,13 @@
+_base_ = [
+ '../_base_/models/hornet/hornet-base.py',
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py',
+]
+
+data = dict(samples_per_gpu=64)
+
+optimizer = dict(lr=4e-3)
+optimizer_config = dict(grad_clip=dict(max_norm=5.0), _delete_=True)
+
+custom_hooks = [dict(type='EMAHook', momentum=4e-5, priority='ABOVE_NORMAL')]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/hornet/hornet-small-gf_8xb64_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/hornet/hornet-small-gf_8xb64_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..deb570eba0e2b76ec92650774540966db7a5d895
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/hornet/hornet-small-gf_8xb64_in1k.py
@@ -0,0 +1,13 @@
+_base_ = [
+ '../_base_/models/hornet/hornet-small-gf.py',
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py',
+]
+
+data = dict(samples_per_gpu=64)
+
+optimizer = dict(lr=4e-3)
+optimizer_config = dict(grad_clip=dict(max_norm=1.0), _delete_=True)
+
+custom_hooks = [dict(type='EMAHook', momentum=4e-5, priority='ABOVE_NORMAL')]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/hornet/hornet-small_8xb64_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/hornet/hornet-small_8xb64_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..c07fa60dbd7b04cb9669ecd49da952b376da9feb
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/hornet/hornet-small_8xb64_in1k.py
@@ -0,0 +1,13 @@
+_base_ = [
+ '../_base_/models/hornet/hornet-small.py',
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py',
+]
+
+data = dict(samples_per_gpu=64)
+
+optimizer = dict(lr=4e-3)
+optimizer_config = dict(grad_clip=dict(max_norm=5.0), _delete_=True)
+
+custom_hooks = [dict(type='EMAHook', momentum=4e-5, priority='ABOVE_NORMAL')]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/hornet/hornet-tiny-gf_8xb128_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/hornet/hornet-tiny-gf_8xb128_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..3a1d1a7a5110247a9ad515d93eb2ca6241556412
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/hornet/hornet-tiny-gf_8xb128_in1k.py
@@ -0,0 +1,13 @@
+_base_ = [
+ '../_base_/models/hornet/hornet-tiny-gf.py',
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py',
+]
+
+data = dict(samples_per_gpu=128)
+
+optimizer = dict(lr=4e-3)
+optimizer_config = dict(grad_clip=dict(max_norm=1.0), _delete_=True)
+
+custom_hooks = [dict(type='EMAHook', momentum=4e-5, priority='ABOVE_NORMAL')]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/hornet/hornet-tiny_8xb128_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/hornet/hornet-tiny_8xb128_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..69a7cdf07cebe095adaf6713a84e4eb810af707a
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/hornet/hornet-tiny_8xb128_in1k.py
@@ -0,0 +1,13 @@
+_base_ = [
+ '../_base_/models/hornet/hornet-tiny.py',
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py',
+]
+
+data = dict(samples_per_gpu=128)
+
+optimizer = dict(lr=4e-3)
+optimizer_config = dict(grad_clip=dict(max_norm=100.0), _delete_=True)
+
+custom_hooks = [dict(type='EMAHook', momentum=4e-5, priority='ABOVE_NORMAL')]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/hornet/metafile.yml b/openmmlab_test/mmclassification-0.24.1/configs/hornet/metafile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..712077220e3048e7e2859109368ce08e443660d9
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/hornet/metafile.yml
@@ -0,0 +1,97 @@
+Collections:
+ - Name: HorNet
+ Metadata:
+ Training Data: ImageNet-1k
+ Training Techniques:
+ - AdamW
+ - Weight Decay
+ Architecture:
+ - HorNet
+ - gnConv
+ Paper:
+ URL: https://arxiv.org/pdf/2207.14284v2.pdf
+ Title: "HorNet: Efficient High-Order Spatial Interactions with Recursive Gated Convolutions"
+ README: configs/hornet/README.md
+ Code:
+ Version: v0.24.0
+ URL: https://github.com/open-mmlab/mmclassification/blob/v0.24.0/mmcls/models/backbones/hornet.py
+
+Models:
+ - Name: hornet-tiny_3rdparty_in1k
+ Metadata:
+ FLOPs: 3980000000 # 3.98G
+ Parameters: 22410000 # 22.41M
+ In Collection: HorNet
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 82.84
+ Top 5 Accuracy: 96.24
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/hornet/hornet-tiny_3rdparty_in1k_20220915-0e8eedff.pth
+ Config: configs/hornet/hornet-tiny_8xb128_in1k.py
+ - Name: hornet-tiny-gf_3rdparty_in1k
+ Metadata:
+ FLOPs: 3900000000 # 3.9G
+ Parameters: 22990000 # 22.99M
+ In Collection: HorNet
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 82.98
+ Top 5 Accuracy: 96.38
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/hornet/hornet-tiny-gf_3rdparty_in1k_20220915-4c35a66b.pth
+ Config: configs/hornet/hornet-tiny-gf_8xb128_in1k.py
+ - Name: hornet-small_3rdparty_in1k
+ Metadata:
+ FLOPs: 8830000000 # 8.83G
+ Parameters: 49530000 # 49.53M
+ In Collection: HorNet
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 83.79
+ Top 5 Accuracy: 96.75
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/hornet/hornet-small_3rdparty_in1k_20220915-5935f60f.pth
+ Config: configs/hornet/hornet-small_8xb64_in1k.py
+ - Name: hornet-small-gf_3rdparty_in1k
+ Metadata:
+ FLOPs: 8710000000 # 8.71G
+ Parameters: 50400000 # 50.4M
+ In Collection: HorNet
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 83.98
+ Top 5 Accuracy: 96.77
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/hornet/hornet-small-gf_3rdparty_in1k_20220915-649ca492.pth
+ Config: configs/hornet/hornet-small-gf_8xb64_in1k.py
+ - Name: hornet-base_3rdparty_in1k
+ Metadata:
+ FLOPs: 15590000000 # 15.59G
+ Parameters: 87260000 # 87.26M
+ In Collection: HorNet
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 84.24
+ Top 5 Accuracy: 96.94
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/hornet/hornet-base_3rdparty_in1k_20220915-a06176bb.pth
+ Config: configs/hornet/hornet-base_8xb64_in1k.py
+ - Name: hornet-base-gf_3rdparty_in1k
+ Metadata:
+ FLOPs: 15420000000 # 15.42G
+ Parameters: 88420000 # 88.42M
+ In Collection: HorNet
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 84.32
+ Top 5 Accuracy: 96.95
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/hornet/hornet-base-gf_3rdparty_in1k_20220915-82c06fa7.pth
+ Config: configs/hornet/hornet-base-gf_8xb64_in1k.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/hrnet/README.md b/openmmlab_test/mmclassification-0.24.1/configs/hrnet/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..0a30ccd16d449f660a3cb5def19485b19abc3c43
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/hrnet/README.md
@@ -0,0 +1,44 @@
+# HRNet
+
+> [Deep High-Resolution Representation Learning for Visual Recognition](https://arxiv.org/abs/1908.07919v2)
+
+
+
+## Abstract
+
+High-resolution representations are essential for position-sensitive vision problems, such as human pose estimation, semantic segmentation, and object detection. Existing state-of-the-art frameworks first encode the input image as a low-resolution representation through a subnetwork that is formed by connecting high-to-low resolution convolutions *in series* (e.g., ResNet, VGGNet), and then recover the high-resolution representation from the encoded low-resolution representation. Instead, our proposed network, named as High-Resolution Network (HRNet), maintains high-resolution representations through the whole process. There are two key characteristics: (i) Connect the high-to-low resolution convolution streams *in parallel*; (ii) Repeatedly exchange the information across resolutions. The benefit is that the resulting representation is semantically richer and spatially more precise. We show the superiority of the proposed HRNet in a wide range of applications, including human pose estimation, semantic segmentation, and object detection, suggesting that the HRNet is a stronger backbone for computer vision problems.
+
+
+
+
+
+## Results and models
+
+## ImageNet-1k
+
+| Model | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download |
+| :----------------: | :-------: | :------: | :-------: | :-------: | :----------------------------------------------------------------------: | :-------------------------------------------------------------------------: |
+| HRNet-W18\* | 21.30 | 4.33 | 76.75 | 93.44 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/hrnet/hrnet-w18_4xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w18_3rdparty_8xb32_in1k_20220120-0c10b180.pth) |
+| HRNet-W30\* | 37.71 | 8.17 | 78.19 | 94.22 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/hrnet/hrnet-w30_4xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w30_3rdparty_8xb32_in1k_20220120-8aa3832f.pth) |
+| HRNet-W32\* | 41.23 | 8.99 | 78.44 | 94.19 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/hrnet/hrnet-w32_4xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w32_3rdparty_8xb32_in1k_20220120-c394f1ab.pth) |
+| HRNet-W40\* | 57.55 | 12.77 | 78.94 | 94.47 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/hrnet/hrnet-w40_4xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w40_3rdparty_8xb32_in1k_20220120-9a2dbfc5.pth) |
+| HRNet-W44\* | 67.06 | 14.96 | 78.88 | 94.37 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/hrnet/hrnet-w44_4xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w44_3rdparty_8xb32_in1k_20220120-35d07f73.pth) |
+| HRNet-W48\* | 77.47 | 17.36 | 79.32 | 94.52 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/hrnet/hrnet-w48_4xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w48_3rdparty_8xb32_in1k_20220120-e555ef50.pth) |
+| HRNet-W64\* | 128.06 | 29.00 | 79.46 | 94.65 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/hrnet/hrnet-w64_4xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w64_3rdparty_8xb32_in1k_20220120-19126642.pth) |
+| HRNet-W18 (ssld)\* | 21.30 | 4.33 | 81.06 | 95.70 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/hrnet/hrnet-w18_4xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w18_3rdparty_8xb32-ssld_in1k_20220120-455f69ea.pth) |
+| HRNet-W48 (ssld)\* | 77.47 | 17.36 | 83.63 | 96.79 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/hrnet/hrnet-w48_4xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w48_3rdparty_8xb32-ssld_in1k_20220120-d0459c38.pth) |
+
+*Models with * are converted from the [official repo](https://github.com/HRNet/HRNet-Image-Classification). The config files of these models are only for inference. We don't ensure these config files' training accuracy and welcome you to contribute your reproduction results.*
+
+## Citation
+
+```
+@article{WangSCJDZLMTWLX19,
+ title={Deep High-Resolution Representation Learning for Visual Recognition},
+ author={Jingdong Wang and Ke Sun and Tianheng Cheng and
+ Borui Jiang and Chaorui Deng and Yang Zhao and Dong Liu and Yadong Mu and
+ Mingkui Tan and Xinggang Wang and Wenyu Liu and Bin Xiao},
+ journal = {TPAMI}
+ year={2019}
+}
+```
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/hrnet/hrnet-w18_4xb32_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/hrnet/hrnet-w18_4xb32_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..a84fe67fb6c4af4af147c6a0d9b6c6308762aa6e
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/hrnet/hrnet-w18_4xb32_in1k.py
@@ -0,0 +1,6 @@
+_base_ = [
+ '../_base_/models/hrnet/hrnet-w18.py',
+ '../_base_/datasets/imagenet_bs32_pil_resize.py',
+ '../_base_/schedules/imagenet_bs256_coslr.py',
+ '../_base_/default_runtime.py'
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/hrnet/hrnet-w30_4xb32_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/hrnet/hrnet-w30_4xb32_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..d2a9c0ddbe327604032901d6d17e7a5cb8200d00
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/hrnet/hrnet-w30_4xb32_in1k.py
@@ -0,0 +1,6 @@
+_base_ = [
+ '../_base_/models/hrnet/hrnet-w30.py',
+ '../_base_/datasets/imagenet_bs32_pil_resize.py',
+ '../_base_/schedules/imagenet_bs256_coslr.py',
+ '../_base_/default_runtime.py'
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/hrnet/hrnet-w32_4xb32_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/hrnet/hrnet-w32_4xb32_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..91380a965b833d9d46dcf318c72af20e4a2748b3
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/hrnet/hrnet-w32_4xb32_in1k.py
@@ -0,0 +1,6 @@
+_base_ = [
+ '../_base_/models/hrnet/hrnet-w32.py',
+ '../_base_/datasets/imagenet_bs32_pil_resize.py',
+ '../_base_/schedules/imagenet_bs256_coslr.py',
+ '../_base_/default_runtime.py'
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/hrnet/hrnet-w40_4xb32_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/hrnet/hrnet-w40_4xb32_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..5d35cecd76fc802a45c1a5a49027a11ea0a23ba6
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/hrnet/hrnet-w40_4xb32_in1k.py
@@ -0,0 +1,6 @@
+_base_ = [
+ '../_base_/models/hrnet/hrnet-w40.py',
+ '../_base_/datasets/imagenet_bs32_pil_resize.py',
+ '../_base_/schedules/imagenet_bs256_coslr.py',
+ '../_base_/default_runtime.py'
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/hrnet/hrnet-w44_4xb32_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/hrnet/hrnet-w44_4xb32_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..ce6bb41ac051210399b6e656e967eb5452d6623e
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/hrnet/hrnet-w44_4xb32_in1k.py
@@ -0,0 +1,6 @@
+_base_ = [
+ '../_base_/models/hrnet/hrnet-w44.py',
+ '../_base_/datasets/imagenet_bs32_pil_resize.py',
+ '../_base_/schedules/imagenet_bs256_coslr.py',
+ '../_base_/default_runtime.py'
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/hrnet/hrnet-w48_4xb32_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/hrnet/hrnet-w48_4xb32_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..6943892e6d274f618468c04fa16928872871a493
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/hrnet/hrnet-w48_4xb32_in1k.py
@@ -0,0 +1,6 @@
+_base_ = [
+ '../_base_/models/hrnet/hrnet-w48.py',
+ '../_base_/datasets/imagenet_bs32_pil_resize.py',
+ '../_base_/schedules/imagenet_bs256_coslr.py',
+ '../_base_/default_runtime.py'
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/hrnet/hrnet-w64_4xb32_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/hrnet/hrnet-w64_4xb32_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..0009bc67b0cd36cb17ae8b116d7403aa83d0b639
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/hrnet/hrnet-w64_4xb32_in1k.py
@@ -0,0 +1,6 @@
+_base_ = [
+ '../_base_/models/hrnet/hrnet-w64.py',
+ '../_base_/datasets/imagenet_bs32_pil_resize.py',
+ '../_base_/schedules/imagenet_bs256_coslr.py',
+ '../_base_/default_runtime.py'
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/hrnet/metafile.yml b/openmmlab_test/mmclassification-0.24.1/configs/hrnet/metafile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..64fe14223a1b33c075c4682c3b244915b2c453e3
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/hrnet/metafile.yml
@@ -0,0 +1,162 @@
+Collections:
+ - Name: HRNet
+ Metadata:
+ Training Data: ImageNet-1k
+ Architecture:
+ - Batch Normalization
+ - Convolution
+ - ReLU
+ - Residual Connection
+ Paper:
+ URL: https://arxiv.org/abs/1908.07919v2
+ Title: "Deep High-Resolution Representation Learning for Visual Recognition"
+ README: configs/hrnet/README.md
+ Code:
+ URL: https://github.com/open-mmlab/mmclassification/blob/v0.20.1/mmcls/models/backbones/hrnet.py
+ Version: v0.20.1
+
+Models:
+ - Name: hrnet-w18_3rdparty_8xb32_in1k
+ Metadata:
+ FLOPs: 4330397932
+ Parameters: 21295164
+ In Collection: HRNet
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 76.75
+ Top 5 Accuracy: 93.44
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w18_3rdparty_8xb32_in1k_20220120-0c10b180.pth
+ Config: configs/hrnet/hrnet-w18_4xb32_in1k.py
+ Converted From:
+ Weights: https://1drv.ms/u/s!Aus8VCZ_C_33cMkPimlmClRvmpw
+ Code: https://github.com/HRNet/HRNet-Image-Classification
+ - Name: hrnet-w30_3rdparty_8xb32_in1k
+ Metadata:
+ FLOPs: 8168305684
+ Parameters: 37708380
+ In Collection: HRNet
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 78.19
+ Top 5 Accuracy: 94.22
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w30_3rdparty_8xb32_in1k_20220120-8aa3832f.pth
+ Config: configs/hrnet/hrnet-w30_4xb32_in1k.py
+ Converted From:
+ Weights: https://1drv.ms/u/s!Aus8VCZ_C_33cQoACCEfrzcSaVI
+ Code: https://github.com/HRNet/HRNet-Image-Classification
+ - Name: hrnet-w32_3rdparty_8xb32_in1k
+ Metadata:
+ FLOPs: 8986267584
+ Parameters: 41228840
+ In Collection: HRNet
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 78.44
+ Top 5 Accuracy: 94.19
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w32_3rdparty_8xb32_in1k_20220120-c394f1ab.pth
+ Config: configs/hrnet/hrnet-w32_4xb32_in1k.py
+ Converted From:
+ Weights: https://1drv.ms/u/s!Aus8VCZ_C_33dYBMemi9xOUFR0w
+ Code: https://github.com/HRNet/HRNet-Image-Classification
+ - Name: hrnet-w40_3rdparty_8xb32_in1k
+ Metadata:
+ FLOPs: 12767574064
+ Parameters: 57553320
+ In Collection: HRNet
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 78.94
+ Top 5 Accuracy: 94.47
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w40_3rdparty_8xb32_in1k_20220120-9a2dbfc5.pth
+ Config: configs/hrnet/hrnet-w40_4xb32_in1k.py
+ Converted From:
+ Weights: https://1drv.ms/u/s!Aus8VCZ_C_33ck0gvo5jfoWBOPo
+ Code: https://github.com/HRNet/HRNet-Image-Classification
+ - Name: hrnet-w44_3rdparty_8xb32_in1k
+ Metadata:
+ FLOPs: 14963902632
+ Parameters: 67061144
+ In Collection: HRNet
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 78.88
+ Top 5 Accuracy: 94.37
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w44_3rdparty_8xb32_in1k_20220120-35d07f73.pth
+ Config: configs/hrnet/hrnet-w44_4xb32_in1k.py
+ Converted From:
+ Weights: https://1drv.ms/u/s!Aus8VCZ_C_33czZQ0woUb980gRs
+ Code: https://github.com/HRNet/HRNet-Image-Classification
+ - Name: hrnet-w48_3rdparty_8xb32_in1k
+ Metadata:
+ FLOPs: 17364014752
+ Parameters: 77466024
+ In Collection: HRNet
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 79.32
+ Top 5 Accuracy: 94.52
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w48_3rdparty_8xb32_in1k_20220120-e555ef50.pth
+ Config: configs/hrnet/hrnet-w48_4xb32_in1k.py
+ Converted From:
+ Weights: https://1drv.ms/u/s!Aus8VCZ_C_33dKvqI6pBZlifgJk
+ Code: https://github.com/HRNet/HRNet-Image-Classification
+ - Name: hrnet-w64_3rdparty_8xb32_in1k
+ Metadata:
+ FLOPs: 29002298752
+ Parameters: 128056104
+ In Collection: HRNet
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 79.46
+ Top 5 Accuracy: 94.65
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w64_3rdparty_8xb32_in1k_20220120-19126642.pth
+ Config: configs/hrnet/hrnet-w64_4xb32_in1k.py
+ Converted From:
+ Weights: https://1drv.ms/u/s!Aus8VCZ_C_33gQbJsUPTIj3rQu99
+ Code: https://github.com/HRNet/HRNet-Image-Classification
+ - Name: hrnet-w18_3rdparty_8xb32-ssld_in1k
+ Metadata:
+ FLOPs: 4330397932
+ Parameters: 21295164
+ In Collection: HRNet
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 81.06
+ Top 5 Accuracy: 95.7
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w18_3rdparty_8xb32-ssld_in1k_20220120-455f69ea.pth
+ Config: configs/hrnet/hrnet-w18_4xb32_in1k.py
+ Converted From:
+ Weights: https://github.com/HRNet/HRNet-Image-Classification/releases/download/PretrainedWeights/HRNet_W18_C_ssld_pretrained.pth
+ Code: https://github.com/HRNet/HRNet-Image-Classification
+ - Name: hrnet-w48_3rdparty_8xb32-ssld_in1k
+ Metadata:
+ FLOPs: 17364014752
+ Parameters: 77466024
+ In Collection: HRNet
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 83.63
+ Top 5 Accuracy: 96.79
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w48_3rdparty_8xb32-ssld_in1k_20220120-d0459c38.pth
+ Config: configs/hrnet/hrnet-w48_4xb32_in1k.py
+ Converted From:
+ Weights: https://github.com/HRNet/HRNet-Image-Classification/releases/download/PretrainedWeights/HRNet_W48_C_ssld_pretrained.pth
+ Code: https://github.com/HRNet/HRNet-Image-Classification
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/lenet/README.md b/openmmlab_test/mmclassification-0.24.1/configs/lenet/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..2cd68eac42ed7fa1d0167fe1f7b9ad917e5ce735
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/lenet/README.md
@@ -0,0 +1,28 @@
+# LeNet
+
+> [Backpropagation Applied to Handwritten Zip Code Recognition](https://ieeexplore.ieee.org/document/6795724)
+
+
+
+## Abstract
+
+The ability of learning networks to generalize can be greatly enhanced by providing constraints from the task domain. This paper demonstrates how such constraints can be integrated into a backpropagation network through the architecture of the network. This approach has been successfully applied to the recognition of handwritten zip code digits provided by the U.S. Postal Service. A single network learns the entire recognition operation, going from the normalized image of the character to the final classification.
+
+
+
+
+
+## Citation
+
+```
+@ARTICLE{6795724,
+ author={Y. {LeCun} and B. {Boser} and J. S. {Denker} and D. {Henderson} and R. E. {Howard} and W. {Hubbard} and L. D. {Jackel}},
+ journal={Neural Computation},
+ title={Backpropagation Applied to Handwritten Zip Code Recognition},
+ year={1989},
+ volume={1},
+ number={4},
+ pages={541-551},
+ doi={10.1162/neco.1989.1.4.541}}
+}
+```
diff --git a/openmmlab_test/mmclassification-speed-benchmark/configs/lenet/lenet5_mnist.py b/openmmlab_test/mmclassification-0.24.1/configs/lenet/lenet5_mnist.py
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/configs/lenet/lenet5_mnist.py
rename to openmmlab_test/mmclassification-0.24.1/configs/lenet/lenet5_mnist.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/mlp_mixer/README.md b/openmmlab_test/mmclassification-0.24.1/configs/mlp_mixer/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..5ec98871b6da4406551100c617200104f478860d
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/mlp_mixer/README.md
@@ -0,0 +1,37 @@
+# Mlp-Mixer
+
+> [MLP-Mixer: An all-MLP Architecture for Vision](https://arxiv.org/abs/2105.01601)
+
+
+
+## Abstract
+
+Convolutional Neural Networks (CNNs) are the go-to model for computer vision. Recently, attention-based networks, such as the Vision Transformer, have also become popular. In this paper we show that while convolutions and attention are both sufficient for good performance, neither of them are necessary. We present MLP-Mixer, an architecture based exclusively on multi-layer perceptrons (MLPs). MLP-Mixer contains two types of layers: one with MLPs applied independently to image patches (i.e. "mixing" the per-location features), and one with MLPs applied across patches (i.e. "mixing" spatial information). When trained on large datasets, or with modern regularization schemes, MLP-Mixer attains competitive scores on image classification benchmarks, with pre-training and inference cost comparable to state-of-the-art models. We hope that these results spark further research beyond the realms of well established CNNs and Transformers.
+
+
+
+
+
+## Results and models
+
+### ImageNet-1k
+
+| Model | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download |
+| :----------: | :-------: | :------: | :-------: | :-------: | :-------------------------------------------------------------------------: | :----------------------------------------------------------------------------: |
+| Mixer-B/16\* | 59.88 | 12.61 | 76.68 | 92.25 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/mlp_mixer/mlp-mixer-base-p16_64xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mlp-mixer/mixer-base-p16_3rdparty_64xb64_in1k_20211124-1377e3e0.pth) |
+| Mixer-L/16\* | 208.2 | 44.57 | 72.34 | 88.02 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/mlp_mixer/mlp-mixer-large-p16_64xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mlp-mixer/mixer-large-p16_3rdparty_64xb64_in1k_20211124-5a2519d2.pth) |
+
+*Models with * are converted from [timm](https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/mlp_mixer.py). The config files of these models are only for validation. We don't ensure these config files' training accuracy and welcome you to contribute your reproduction results.*
+
+## Citation
+
+```
+@misc{tolstikhin2021mlpmixer,
+ title={MLP-Mixer: An all-MLP Architecture for Vision},
+ author={Ilya Tolstikhin and Neil Houlsby and Alexander Kolesnikov and Lucas Beyer and Xiaohua Zhai and Thomas Unterthiner and Jessica Yung and Andreas Steiner and Daniel Keysers and Jakob Uszkoreit and Mario Lucic and Alexey Dosovitskiy},
+ year={2021},
+ eprint={2105.01601},
+ archivePrefix={arXiv},
+ primaryClass={cs.CV}
+}
+```
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/mlp_mixer/metafile.yml b/openmmlab_test/mmclassification-0.24.1/configs/mlp_mixer/metafile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e8efa0850133f43ac6d0e1329099254ce5e34c8d
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/mlp_mixer/metafile.yml
@@ -0,0 +1,50 @@
+Collections:
+ - Name: MLP-Mixer
+ Metadata:
+ Training Data: ImageNet-1k
+ Architecture:
+ - MLP
+ - Layer Normalization
+ - Dropout
+ Paper:
+ URL: https://arxiv.org/abs/2105.01601
+ Title: "MLP-Mixer: An all-MLP Architecture for Vision"
+ README: configs/mlp_mixer/README.md
+ Code:
+ URL: https://github.com/open-mmlab/mmclassification/blob/v0.18.0/mmcls/models/backbones/mlp_mixer.py
+ Version: v0.18.0
+
+Models:
+ - Name: mlp-mixer-base-p16_3rdparty_64xb64_in1k
+ In Collection: MLP-Mixer
+ Config: configs/mlp_mixer/mlp-mixer-base-p16_64xb64_in1k.py
+ Metadata:
+ FLOPs: 12610000000 # 12.61 G
+ Parameters: 59880000 # 59.88 M
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 76.68
+ Top 5 Accuracy: 92.25
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/mlp-mixer/mixer-base-p16_3rdparty_64xb64_in1k_20211124-1377e3e0.pth
+ Converted From:
+ Weights: https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-vitjx/jx_mixer_b16_224-76587d61.pth
+ Code: https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/mlp_mixer.py#L70
+
+ - Name: mlp-mixer-large-p16_3rdparty_64xb64_in1k
+ In Collection: MLP-Mixer
+ Config: configs/mlp_mixer/mlp-mixer-large-p16_64xb64_in1k.py
+ Metadata:
+ FLOPs: 44570000000 # 44.57 G
+ Parameters: 208200000 # 208.2 M
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 72.34
+ Top 5 Accuracy: 88.02
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/mlp-mixer/mixer-large-p16_3rdparty_64xb64_in1k_20211124-5a2519d2.pth
+ Converted From:
+ Weights: https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-vitjx/jx_mixer_b16_224_in21k-617b3de2.pth
+ Code: https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/mlp_mixer.py#L73
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/mlp_mixer/mlp-mixer-base-p16_64xb64_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/mlp_mixer/mlp-mixer-base-p16_64xb64_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..e35dae553506389bf4ef80f3c1b5d6324c53d779
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/mlp_mixer/mlp-mixer-base-p16_64xb64_in1k.py
@@ -0,0 +1,6 @@
+_base_ = [
+ '../_base_/models/mlp_mixer_base_patch16.py',
+ '../_base_/datasets/imagenet_bs64_mixer_224.py',
+ '../_base_/schedules/imagenet_bs4096_AdamW.py',
+ '../_base_/default_runtime.py',
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/mlp_mixer/mlp-mixer-large-p16_64xb64_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/mlp_mixer/mlp-mixer-large-p16_64xb64_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..459563c8545ea4802ee7daef78cb7d005085eb11
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/mlp_mixer/mlp-mixer-large-p16_64xb64_in1k.py
@@ -0,0 +1,6 @@
+_base_ = [
+ '../_base_/models/mlp_mixer_large_patch16.py',
+ '../_base_/datasets/imagenet_bs64_mixer_224.py',
+ '../_base_/schedules/imagenet_bs4096_AdamW.py',
+ '../_base_/default_runtime.py',
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/mobilenet_v2/README.md b/openmmlab_test/mmclassification-0.24.1/configs/mobilenet_v2/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..675c8dd4d439140dbbef4805b56c27234b49bc45
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/mobilenet_v2/README.md
@@ -0,0 +1,38 @@
+# MobileNet V2
+
+> [MobileNetV2: Inverted Residuals and Linear Bottlenecks](https://arxiv.org/abs/1801.04381)
+
+
+
+## Abstract
+
+In this paper we describe a new mobile architecture, MobileNetV2, that improves the state of the art performance of mobile models on multiple tasks and benchmarks as well as across a spectrum of different model sizes. We also describe efficient ways of applying these mobile models to object detection in a novel framework we call SSDLite. Additionally, we demonstrate how to build mobile semantic segmentation models through a reduced form of DeepLabv3 which we call Mobile DeepLabv3.
+
+The MobileNetV2 architecture is based on an inverted residual structure where the input and output of the residual block are thin bottleneck layers opposite to traditional residual models which use expanded representations in the input an MobileNetV2 uses lightweight depthwise convolutions to filter features in the intermediate expansion layer. Additionally, we find that it is important to remove non-linearities in the narrow layers in order to maintain representational power. We demonstrate that this improves performance and provide an intuition that led to this design. Finally, our approach allows decoupling of the input/output domains from the expressiveness of the transformation, which provides a convenient framework for further analysis. We measure our performance on Imagenet classification, COCO object detection, VOC image segmentation. We evaluate the trade-offs between accuracy, and number of operations measured by multiply-adds (MAdd), as well as the number of parameters
+
+
+
+
+
+## Results and models
+
+### ImageNet-1k
+
+| Model | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download |
+| :----------: | :-------: | :------: | :-------: | :-------: | :-------------------------------------------------------------------------: | :----------------------------------------------------------------------------: |
+| MobileNet V2 | 3.5 | 0.319 | 71.86 | 90.42 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/mobilenet_v2/mobilenet-v2_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobilenet_v2/mobilenet_v2_batch256_imagenet_20200708-3b2dc3af.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobilenet_v2/mobilenet_v2_batch256_imagenet_20200708-3b2dc3af.log.json) |
+
+## Citation
+
+```
+@INPROCEEDINGS{8578572,
+ author={M. {Sandler} and A. {Howard} and M. {Zhu} and A. {Zhmoginov} and L. {Chen}},
+ booktitle={2018 IEEE/CVF Conference on Computer Vision and Pattern Recognition},
+ title={MobileNetV2: Inverted Residuals and Linear Bottlenecks},
+ year={2018},
+ volume={},
+ number={},
+ pages={4510-4520},
+ doi={10.1109/CVPR.2018.00474}}
+}
+```
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/mobilenet_v2/metafile.yml b/openmmlab_test/mmclassification-0.24.1/configs/mobilenet_v2/metafile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e16557fb973a12b473dab030a6d7855df9c2e6a2
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/mobilenet_v2/metafile.yml
@@ -0,0 +1,34 @@
+Collections:
+ - Name: MobileNet V2
+ Metadata:
+ Training Data: ImageNet-1k
+ Training Techniques:
+ - SGD with Momentum
+ - Weight Decay
+ Training Resources: 8x V100 GPUs
+ Epochs: 300
+ Batch Size: 256
+ Architecture:
+ - MobileNet V2
+ Paper:
+ URL: https://arxiv.org/abs/1801.04381
+ Title: "MobileNetV2: Inverted Residuals and Linear Bottlenecks"
+ README: configs/mobilenet_v2/README.md
+ Code:
+ URL: https://github.com/open-mmlab/mmclassification/blob/v0.15.0/mmcls/models/backbones/mobilenet_v2.py#L101
+ Version: v0.15.0
+
+Models:
+ - Name: mobilenet-v2_8xb32_in1k
+ Metadata:
+ FLOPs: 319000000
+ Parameters: 3500000
+ In Collection: MobileNet V2
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 71.86
+ Top 5 Accuracy: 90.42
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/mobilenet_v2/mobilenet_v2_batch256_imagenet_20200708-3b2dc3af.pth
+ Config: configs/mobilenet_v2/mobilenet-v2_8xb32_in1k.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/mobilenet_v2/mobilenet-v2_8xb32_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/mobilenet_v2/mobilenet-v2_8xb32_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..88eaad52e795dff26fbf3cbd8d950f60c45efd2f
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/mobilenet_v2/mobilenet-v2_8xb32_in1k.py
@@ -0,0 +1,8 @@
+_base_ = [
+ '../_base_/models/mobilenet_v2_1x.py',
+ '../_base_/datasets/imagenet_bs32_pil_resize.py',
+ '../_base_/schedules/imagenet_bs256_epochstep.py',
+ '../_base_/default_runtime.py'
+]
+
+#fp16 = dict(loss_scale=512.)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/mobilenet_v2/mobilenet_v2_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/mobilenet_v2/mobilenet_v2_b32x8_imagenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..26c2b6ded4fa12dcb8e5522435547b7263d92309
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/mobilenet_v2/mobilenet_v2_b32x8_imagenet.py
@@ -0,0 +1,6 @@
+_base_ = 'mobilenet-v2_8xb32_in1k.py'
+
+_deprecation_ = dict(
+ expected='mobilenet-v2_8xb32_in1k.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/mobilenet_v3/README.md b/openmmlab_test/mmclassification-0.24.1/configs/mobilenet_v3/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..737c4d32ec01e65f464f73cc68cbb245021c3a99
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/mobilenet_v3/README.md
@@ -0,0 +1,36 @@
+# MobileNet V3
+
+> [Searching for MobileNetV3](https://arxiv.org/abs/1905.02244)
+
+
+
+## Abstract
+
+We present the next generation of MobileNets based on a combination of complementary search techniques as well as a novel architecture design. MobileNetV3 is tuned to mobile phone CPUs through a combination of hardware-aware network architecture search (NAS) complemented by the NetAdapt algorithm and then subsequently improved through novel architecture advances. This paper starts the exploration of how automated search algorithms and network design can work together to harness complementary approaches improving the overall state of the art. Through this process we create two new MobileNet models for release: MobileNetV3-Large and MobileNetV3-Small which are targeted for high and low resource use cases. These models are then adapted and applied to the tasks of object detection and semantic segmentation. For the task of semantic segmentation (or any dense pixel prediction), we propose a new efficient segmentation decoder Lite Reduced Atrous Spatial Pyramid Pooling (LR-ASPP). We achieve new state of the art results for mobile classification, detection and segmentation. MobileNetV3-Large is 3.2% more accurate on ImageNet classification while reducing latency by 15% compared to MobileNetV2. MobileNetV3-Small is 4.6% more accurate while reducing latency by 5% compared to MobileNetV2. MobileNetV3-Large detection is 25% faster at roughly the same accuracy as MobileNetV2 on COCO detection. MobileNetV3-Large LR-ASPP is 30% faster than MobileNetV2 R-ASPP at similar accuracy for Cityscapes segmentation.
+
+
+
+## Results and models
+
+### ImageNet-1k
+
+| Model | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download |
+| :---------------: | :-------: | :------: | :-------: | :-------: | :-----------------------------------------------------------------------: | :-------------------------------------------------------------------------: |
+| ResNeXt-32x4d-50 | 25.03 | 4.27 | 77.90 | 93.66 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnext/resnext50-32x4d_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/resnext/resnext50_32x4d_b32x8_imagenet_20210429-56066e27.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/resnext/resnext50_32x4d_b32x8_imagenet_20210429-56066e27.log.json) |
+| ResNeXt-32x4d-101 | 44.18 | 8.03 | 78.61 | 94.17 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnext/resnext101-32x4d_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/resnext/resnext101_32x4d_b32x8_imagenet_20210506-e0fa3dd5.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/resnext/resnext101_32x4d_b32x8_imagenet_20210506-e0fa3dd5.log.json) |
+| ResNeXt-32x8d-101 | 88.79 | 16.5 | 79.27 | 94.58 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnext/resnext101-32x8d_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/resnext/resnext101_32x8d_b32x8_imagenet_20210506-23a247d5.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/resnext/resnext101_32x8d_b32x8_imagenet_20210506-23a247d5.log.json) |
+| ResNeXt-32x4d-152 | 59.95 | 11.8 | 78.88 | 94.33 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnext/resnext152-32x4d_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/resnext/resnext152_32x4d_b32x8_imagenet_20210524-927787be.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/resnext/resnext152_32x4d_b32x8_imagenet_20210524-927787be.log.json) |
+
+## Citation
+
+```
+@inproceedings{xie2017aggregated,
+ title={Aggregated residual transformations for deep neural networks},
+ author={Xie, Saining and Girshick, Ross and Doll{\'a}r, Piotr and Tu, Zhuowen and He, Kaiming},
+ booktitle={Proceedings of the IEEE conference on computer vision and pattern recognition},
+ pages={1492--1500},
+ year={2017}
+}
+```
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/resnext/metafile.yml b/openmmlab_test/mmclassification-0.24.1/configs/resnext/metafile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c68e7f9d9ab4fd2954c06b2c4a9fb6816e1de03b
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/resnext/metafile.yml
@@ -0,0 +1,73 @@
+Collections:
+ - Name: ResNeXt
+ Metadata:
+ Training Data: ImageNet-1k
+ Training Techniques:
+ - SGD with Momentum
+ - Weight Decay
+ Training Resources: 8x V100 GPUs
+ Epochs: 100
+ Batch Size: 256
+ Architecture:
+ - ResNeXt
+ Paper:
+ URL: https://openaccess.thecvf.com/content_cvpr_2017/html/Xie_Aggregated_Residual_Transformations_CVPR_2017_paper.html
+ Title: "Aggregated Residual Transformations for Deep Neural Networks"
+ README: configs/resnext/README.md
+ Code:
+ URL: https://github.com/open-mmlab/mmclassification/blob/v0.15.0/mmcls/models/backbones/resnext.py#L90
+ Version: v0.15.0
+
+Models:
+ - Name: resnext50-32x4d_8xb32_in1k
+ Metadata:
+ FLOPs: 4270000000
+ Parameters: 25030000
+ In Collection: ResNeXt
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 77.90
+ Top 5 Accuracy: 93.66
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/resnext/resnext50_32x4d_b32x8_imagenet_20210429-56066e27.pth
+ Config: configs/resnext/resnext50-32x4d_8xb32_in1k.py
+ - Name: resnext101-32x4d_8xb32_in1k
+ Metadata:
+ FLOPs: 8030000000
+ Parameters: 44180000
+ In Collection: ResNeXt
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 78.61
+ Top 5 Accuracy: 94.17
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/resnext/resnext101_32x4d_b32x8_imagenet_20210506-e0fa3dd5.pth
+ Config: configs/resnext/resnext101-32x4d_8xb32_in1k.py
+ - Name: resnext101-32x8d_8xb32_in1k
+ Metadata:
+ FLOPs: 16500000000
+ Parameters: 88790000
+ In Collection: ResNeXt
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 79.27
+ Top 5 Accuracy: 94.58
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/resnext/resnext101_32x8d_b32x8_imagenet_20210506-23a247d5.pth
+ Config: configs/resnext/resnext101-32x8d_8xb32_in1k.py
+ - Name: resnext152-32x4d_8xb32_in1k
+ Metadata:
+ FLOPs: 11800000000
+ Parameters: 59950000
+ In Collection: ResNeXt
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 78.88
+ Top 5 Accuracy: 94.33
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/resnext/resnext152_32x4d_b32x8_imagenet_20210524-927787be.pth
+ Config: configs/resnext/resnext152-32x4d_8xb32_in1k.py
diff --git a/openmmlab_test/mmclassification-speed-benchmark/configs/resnext/resnext101_32x4d_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/resnext/resnext101-32x4d_8xb32_in1k.py
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/configs/resnext/resnext101_32x4d_b32x8_imagenet.py
rename to openmmlab_test/mmclassification-0.24.1/configs/resnext/resnext101-32x4d_8xb32_in1k.py
diff --git a/openmmlab_test/mmclassification-speed-benchmark/configs/resnext/resnext101_32x8d_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/resnext/resnext101-32x8d_8xb32_in1k.py
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/configs/resnext/resnext101_32x8d_b32x8_imagenet.py
rename to openmmlab_test/mmclassification-0.24.1/configs/resnext/resnext101-32x8d_8xb32_in1k.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/resnext/resnext101_32x4d_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/resnext/resnext101_32x4d_b32x8_imagenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..07d66c356f66e657280f90d8d343dee074393c16
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/resnext/resnext101_32x4d_b32x8_imagenet.py
@@ -0,0 +1,6 @@
+_base_ = 'resnext101-32x4d_8xb32_in1k.py'
+
+_deprecation_ = dict(
+ expected='resnext101-32x4d_8xb32_in1k.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/resnext/resnext101_32x8d_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/resnext/resnext101_32x8d_b32x8_imagenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..071ca60f21c308aa48d6037b7d58ca1d4ad0cfce
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/resnext/resnext101_32x8d_b32x8_imagenet.py
@@ -0,0 +1,6 @@
+_base_ = 'resnext101-32x8d_8xb32_in1k.py'
+
+_deprecation_ = dict(
+ expected='resnext101-32x8d_8xb32_in1k.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/configs/resnext/resnext152_32x4d_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/resnext/resnext152-32x4d_8xb32_in1k.py
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/configs/resnext/resnext152_32x4d_b32x8_imagenet.py
rename to openmmlab_test/mmclassification-0.24.1/configs/resnext/resnext152-32x4d_8xb32_in1k.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/resnext/resnext152_32x4d_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/resnext/resnext152_32x4d_b32x8_imagenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..6d05c8b3a1ebcffa2bd201280cb5306b47510985
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/resnext/resnext152_32x4d_b32x8_imagenet.py
@@ -0,0 +1,6 @@
+_base_ = 'resnext152-32x4d_8xb32_in1k.py'
+
+_deprecation_ = dict(
+ expected='resnext152-32x4d_8xb32_in1k.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/configs/resnext/resnext50_32x4d_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/resnext/resnext50-32x4d_8xb32_in1k.py
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/configs/resnext/resnext50_32x4d_b32x8_imagenet.py
rename to openmmlab_test/mmclassification-0.24.1/configs/resnext/resnext50-32x4d_8xb32_in1k.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/resnext/resnext50_32x4d_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/resnext/resnext50_32x4d_b32x8_imagenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..92ae0639941e9d95bf42156eef600ccf19924a4f
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/resnext/resnext50_32x4d_b32x8_imagenet.py
@@ -0,0 +1,6 @@
+_base_ = 'resnext50-32x4d_8xb32_in1k.py'
+
+_deprecation_ = dict(
+ expected='resnext50-32x4d_8xb32_in1k.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/seresnet/README.md b/openmmlab_test/mmclassification-0.24.1/configs/seresnet/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..ccfd1d156ed3dc0c2d392069f80d0f88669bd2e7
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/seresnet/README.md
@@ -0,0 +1,34 @@
+# SE-ResNet
+
+> [Squeeze-and-Excitation Networks](https://openaccess.thecvf.com/content_cvpr_2018/html/Hu_Squeeze-and-Excitation_Networks_CVPR_2018_paper.html)
+
+
+
+## Abstract
+
+The central building block of convolutional neural networks (CNNs) is the convolution operator, which enables networks to construct informative features by fusing both spatial and channel-wise information within local receptive fields at each layer. A broad range of prior research has investigated the spatial component of this relationship, seeking to strengthen the representational power of a CNN by enhancing the quality of spatial encodings throughout its feature hierarchy. In this work, we focus instead on the channel relationship and propose a novel architectural unit, which we term the "Squeeze-and-Excitation" (SE) block, that adaptively recalibrates channel-wise feature responses by explicitly modelling interdependencies between channels. We show that these blocks can be stacked together to form SENet architectures that generalise extremely effectively across different datasets. We further demonstrate that SE blocks bring significant improvements in performance for existing state-of-the-art CNNs at slight additional computational cost. Squeeze-and-Excitation Networks formed the foundation of our ILSVRC 2017 classification submission which won first place and reduced the top-5 error to 2.251%, surpassing the winning entry of 2016 by a relative improvement of ~25%.
+
+
+
+
+
+## Results and models
+
+### ImageNet-1k
+
+| Model | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download |
+| :-----------: | :-------: | :------: | :-------: | :-------: | :-------------------------------------------------------------------------: | :---------------------------------------------------------------------------: |
+| SE-ResNet-50 | 28.09 | 4.13 | 77.74 | 93.84 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/seresnet/seresnet50_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/se-resnet/se-resnet50_batch256_imagenet_20200804-ae206104.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/se-resnet/se-resnet50_batch256_imagenet_20200708-657b3c36.log.json) |
+| SE-ResNet-101 | 49.33 | 7.86 | 78.26 | 94.07 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/seresnet/seresnet101_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/se-resnet/se-resnet101_batch256_imagenet_20200804-ba5b51d4.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/se-resnet/se-resnet101_batch256_imagenet_20200708-038a4d04.log.json) |
+
+## Citation
+
+```
+@inproceedings{hu2018squeeze,
+ title={Squeeze-and-excitation networks},
+ author={Hu, Jie and Shen, Li and Sun, Gang},
+ booktitle={Proceedings of the IEEE conference on computer vision and pattern recognition},
+ pages={7132--7141},
+ year={2018}
+}
+```
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/seresnet/metafile.yml b/openmmlab_test/mmclassification-0.24.1/configs/seresnet/metafile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7d2a38109ef54537ad6188b3b5bfe351a45e1570
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/seresnet/metafile.yml
@@ -0,0 +1,47 @@
+Collections:
+ - Name: SEResNet
+ Metadata:
+ Training Data: ImageNet-1k
+ Training Techniques:
+ - SGD with Momentum
+ - Weight Decay
+ Training Resources: 8x V100 GPUs
+ Epochs: 140
+ Batch Size: 256
+ Architecture:
+ - ResNet
+ Paper:
+ URL: https://openaccess.thecvf.com/content_cvpr_2018/html/Hu_Squeeze-and-Excitation_Networks_CVPR_2018_paper.html
+ Title: "Squeeze-and-Excitation Networks"
+ README: configs/seresnet/README.md
+ Code:
+ URL: https://github.com/open-mmlab/mmclassification/blob/v0.15.0/mmcls/models/backbones/seresnet.py#L58
+ Version: v0.15.0
+
+Models:
+ - Name: seresnet50_8xb32_in1k
+ Metadata:
+ FLOPs: 4130000000
+ Parameters: 28090000
+ In Collection: SEResNet
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 77.74
+ Top 5 Accuracy: 93.84
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/se-resnet/se-resnet50_batch256_imagenet_20200804-ae206104.pth
+ Config: configs/seresnet/seresnet50_8xb32_in1k.py
+ - Name: seresnet101_8xb32_in1k
+ Metadata:
+ FLOPs: 7860000000
+ Parameters: 49330000
+ In Collection: SEResNet
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 78.26
+ Top 5 Accuracy: 94.07
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/se-resnet/se-resnet101_batch256_imagenet_20200804-ba5b51d4.pth
+ Config: configs/seresnet/seresnet101_8xb32_in1k.py
diff --git a/openmmlab_test/mmclassification-speed-benchmark/configs/seresnet/seresnet101_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/seresnet/seresnet101_8xb32_in1k.py
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/configs/seresnet/seresnet101_b32x8_imagenet.py
rename to openmmlab_test/mmclassification-0.24.1/configs/seresnet/seresnet101_8xb32_in1k.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/seresnet/seresnet101_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/seresnet/seresnet101_b32x8_imagenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..46daa09a3c3f825f727cdc55022dd0597f204858
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/seresnet/seresnet101_b32x8_imagenet.py
@@ -0,0 +1,6 @@
+_base_ = 'seresnet101_8xb32_in1k.py'
+
+_deprecation_ = dict(
+ expected='seresnet101_8xb32_in1k.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/configs/seresnet/seresnet50_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/seresnet/seresnet50_8xb32_in1k.py
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/configs/seresnet/seresnet50_b32x8_imagenet.py
rename to openmmlab_test/mmclassification-0.24.1/configs/seresnet/seresnet50_8xb32_in1k.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/seresnet/seresnet50_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/seresnet/seresnet50_b32x8_imagenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..0fb9df39d518df91a59c066c0bec64a86b695eba
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/seresnet/seresnet50_b32x8_imagenet.py
@@ -0,0 +1,6 @@
+_base_ = 'seresnet50_8xb32_in1k.py'
+
+_deprecation_ = dict(
+ expected='seresnet50_8xb32_in1k.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/configs/seresnext/seresnext101_32x4d_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/seresnet/seresnext101-32x4d_8xb32_in1k.py
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/configs/seresnext/seresnext101_32x4d_b32x8_imagenet.py
rename to openmmlab_test/mmclassification-0.24.1/configs/seresnet/seresnext101-32x4d_8xb32_in1k.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/seresnet/seresnext101_32x4d_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/seresnet/seresnext101_32x4d_b32x8_imagenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..cb99ec661b39ab21749c999eedc96068d7eebdca
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/seresnet/seresnext101_32x4d_b32x8_imagenet.py
@@ -0,0 +1,6 @@
+_base_ = 'seresnext101-32x4d_8xb32_in1k.py'
+
+_deprecation_ = dict(
+ expected='seresnext101-32x4d_8xb32_in1k.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/configs/seresnext/seresnext50_32x4d_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/seresnet/seresnext50-32x4d_8xb32_in1k.py
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/configs/seresnext/seresnext50_32x4d_b32x8_imagenet.py
rename to openmmlab_test/mmclassification-0.24.1/configs/seresnet/seresnext50-32x4d_8xb32_in1k.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/seresnet/seresnext50_32x4d_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/seresnet/seresnext50_32x4d_b32x8_imagenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..49229604f4a4d9d0449054948abb2d4a0ad175cb
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/seresnet/seresnext50_32x4d_b32x8_imagenet.py
@@ -0,0 +1,6 @@
+_base_ = 'seresnext50-32x4d_8xb32_in1k.py'
+
+_deprecation_ = dict(
+ expected='seresnext50-32x4d_8xb32_in1k.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/shufflenet_v1/README.md b/openmmlab_test/mmclassification-0.24.1/configs/shufflenet_v1/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..fd131279210a4e9c8f44f372b02daa76d8c88a15
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/shufflenet_v1/README.md
@@ -0,0 +1,33 @@
+# ShuffleNet V1
+
+> [ShuffleNet: An Extremely Efficient Convolutional Neural Network for Mobile Devices](https://openaccess.thecvf.com/content_cvpr_2018/html/Zhang_ShuffleNet_An_Extremely_CVPR_2018_paper.html)
+
+
+
+## Abstract
+
+We introduce an extremely computation-efficient CNN architecture named ShuffleNet, which is designed specially for mobile devices with very limited computing power (e.g., 10-150 MFLOPs). The new architecture utilizes two new operations, pointwise group convolution and channel shuffle, to greatly reduce computation cost while maintaining accuracy. Experiments on ImageNet classification and MS COCO object detection demonstrate the superior performance of ShuffleNet over other structures, e.g. lower top-1 error (absolute 7.8%) than recent MobileNet on ImageNet classification task, under the computation budget of 40 MFLOPs. On an ARM-based mobile device, ShuffleNet achieves ~13x actual speedup over AlexNet while maintaining comparable accuracy.
+
+
+
+
+
+## Results and models
+
+### ImageNet-1k
+
+| Model | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download |
+| :-------------------------: | :-------: | :------: | :-------: | :-------: | :------------------------------------------------------------------: | :--------------------------------------------------------------------: |
+| ShuffleNetV1 1.0x (group=3) | 1.87 | 0.146 | 68.13 | 87.81 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/shufflenet_v1/shufflenet-v1-1x_16xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/shufflenet_v1/shufflenet_v1_batch1024_imagenet_20200804-5d6cec73.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/shufflenet_v1/shufflenet_v1_batch1024_imagenet_20200804-5d6cec73.log.json) |
+
+## Citation
+
+```
+@inproceedings{zhang2018shufflenet,
+ title={Shufflenet: An extremely efficient convolutional neural network for mobile devices},
+ author={Zhang, Xiangyu and Zhou, Xinyu and Lin, Mengxiao and Sun, Jian},
+ booktitle={Proceedings of the IEEE conference on computer vision and pattern recognition},
+ pages={6848--6856},
+ year={2018}
+}
+```
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/shufflenet_v1/metafile.yml b/openmmlab_test/mmclassification-0.24.1/configs/shufflenet_v1/metafile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2cfffa103f98c632e512c0a30d1ccf3cc9dcb806
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/shufflenet_v1/metafile.yml
@@ -0,0 +1,35 @@
+Collections:
+ - Name: Shufflenet V1
+ Metadata:
+ Training Data: ImageNet-1k
+ Training Techniques:
+ - SGD with Momentum
+ - Weight Decay
+ - No BN decay
+ Training Resources: 8x 1080 GPUs
+ Epochs: 300
+ Batch Size: 1024
+ Architecture:
+ - Shufflenet V1
+ Paper:
+ URL: https://openaccess.thecvf.com/content_cvpr_2018/html/Zhang_ShuffleNet_An_Extremely_CVPR_2018_paper.html
+ Title: "ShuffleNet: An Extremely Efficient Convolutional Neural Network for Mobile Devices"
+ README: configs/shufflenet_v1/README.md
+ Code:
+ URL: https://github.com/open-mmlab/mmclassification/blob/v0.15.0/mmcls/models/backbones/shufflenet_v1.py#L152
+ Version: v0.15.0
+
+Models:
+ - Name: shufflenet-v1-1x_16xb64_in1k
+ Metadata:
+ FLOPs: 146000000
+ Parameters: 1870000
+ In Collection: Shufflenet V1
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 68.13
+ Top 5 Accuracy: 87.81
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/shufflenet_v1/shufflenet_v1_batch1024_imagenet_20200804-5d6cec73.pth
+ Config: configs/shufflenet_v1/shufflenet-v1-1x_16xb64_in1k.py
diff --git a/openmmlab_test/mmclassification-speed-benchmark/configs/shufflenet_v1/shufflenet_v1_1x_b64x16_linearlr_bn_nowd_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/shufflenet_v1/shufflenet-v1-1x_16xb64_in1k.py
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/configs/shufflenet_v1/shufflenet_v1_1x_b64x16_linearlr_bn_nowd_imagenet.py
rename to openmmlab_test/mmclassification-0.24.1/configs/shufflenet_v1/shufflenet-v1-1x_16xb64_in1k.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/shufflenet_v1/shufflenet_v1_1x_b64x16_linearlr_bn_nowd_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/shufflenet_v1/shufflenet_v1_1x_b64x16_linearlr_bn_nowd_imagenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..031219794706f620ae3c3c0fb49c028f0c129150
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/shufflenet_v1/shufflenet_v1_1x_b64x16_linearlr_bn_nowd_imagenet.py
@@ -0,0 +1,6 @@
+_base_ = 'shufflenet-v1-1x_16xb64_in1k.py'
+
+_deprecation_ = dict(
+ expected='shufflenet-v1-1x_16xb64_in1k.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/shufflenet_v2/README.md b/openmmlab_test/mmclassification-0.24.1/configs/shufflenet_v2/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..78271543984c8886ef6c6dbd3ea1937f28788e8f
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/shufflenet_v2/README.md
@@ -0,0 +1,33 @@
+# ShuffleNet V2
+
+> [Shufflenet v2: Practical guidelines for efficient cnn architecture design](https://openaccess.thecvf.com/content_ECCV_2018/papers/Ningning_Light-weight_CNN_Architecture_ECCV_2018_paper.pdf)
+
+
+
+## Abstract
+
+Currently, the neural network architecture design is mostly guided by the *indirect* metric of computation complexity, i.e., FLOPs. However, the *direct* metric, e.g., speed, also depends on the other factors such as memory access cost and platform characterics. Thus, this work proposes to evaluate the direct metric on the target platform, beyond only considering FLOPs. Based on a series of controlled experiments, this work derives several practical *guidelines* for efficient network design. Accordingly, a new architecture is presented, called *ShuffleNet V2*. Comprehensive ablation experiments verify that our model is the state-of-the-art in terms of speed and accuracy tradeoff.
+
+
+
+
+
+## Results and models
+
+### ImageNet-1k
+
+| Model | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download |
+| :---------------: | :-------: | :------: | :-------: | :-------: | :-----------------------------------------------------------------------: | :-------------------------------------------------------------------------: |
+| ShuffleNetV2 1.0x | 2.28 | 0.149 | 69.55 | 88.92 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/shufflenet_v2/shufflenet-v2-1x_16xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/shufflenet_v2/shufflenet_v2_batch1024_imagenet_20200812-5bf4721e.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/shufflenet_v2/shufflenet_v2_batch1024_imagenet_20200804-8860eec9.log.json) |
+
+## Citation
+
+```
+@inproceedings{ma2018shufflenet,
+ title={Shufflenet v2: Practical guidelines for efficient cnn architecture design},
+ author={Ma, Ningning and Zhang, Xiangyu and Zheng, Hai-Tao and Sun, Jian},
+ booktitle={Proceedings of the European conference on computer vision (ECCV)},
+ pages={116--131},
+ year={2018}
+}
+```
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/shufflenet_v2/metafile.yml b/openmmlab_test/mmclassification-0.24.1/configs/shufflenet_v2/metafile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a06322dd6d4ca256e8499faad860c0abebde6798
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/shufflenet_v2/metafile.yml
@@ -0,0 +1,35 @@
+Collections:
+ - Name: Shufflenet V2
+ Metadata:
+ Training Data: ImageNet-1k
+ Training Techniques:
+ - SGD with Momentum
+ - Weight Decay
+ - No BN decay
+ Training Resources: 8x 1080 GPUs
+ Epochs: 300
+ Batch Size: 1024
+ Architecture:
+ - Shufflenet V2
+ Paper:
+ URL: https://openaccess.thecvf.com/content_ECCV_2018/papers/Ningning_Light-weight_CNN_Architecture_ECCV_2018_paper.pdf
+ Title: "ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design"
+ README: configs/shufflenet_v2/README.md
+ Code:
+ URL: https://github.com/open-mmlab/mmclassification/blob/v0.15.0/mmcls/models/backbones/shufflenet_v2.py#L134
+ Version: v0.15.0
+
+Models:
+ - Name: shufflenet-v2-1x_16xb64_in1k
+ Metadata:
+ FLOPs: 149000000
+ Parameters: 2280000
+ In Collection: Shufflenet V2
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 69.55
+ Top 5 Accuracy: 88.92
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/shufflenet_v2/shufflenet_v2_batch1024_imagenet_20200812-5bf4721e.pth
+ Config: configs/shufflenet_v2/shufflenet-v2-1x_16xb64_in1k.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/shufflenet_v2/shufflenet-v2-1x_16xb64_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/shufflenet_v2/shufflenet-v2-1x_16xb64_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..b43bd34d8d50bb64c740e6f3bd2552bdbe45326b
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/shufflenet_v2/shufflenet-v2-1x_16xb64_in1k.py
@@ -0,0 +1,8 @@
+_base_ = [
+ '../_base_/models/shufflenet_v2_1x.py',
+ '../_base_/datasets/imagenet_bs64_pil_resize.py',
+ '../_base_/schedules/imagenet_bs1024_linearlr_bn_nowd.py',
+ '../_base_/default_runtime.py'
+]
+
+fp16 = dict(loss_scale=512.)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/shufflenet_v2/shufflenet_v2_1x_b64x16_linearlr_bn_nowd_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/shufflenet_v2/shufflenet_v2_1x_b64x16_linearlr_bn_nowd_imagenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..c0938b0956ffe2b78e435d984a58839c20576932
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/shufflenet_v2/shufflenet_v2_1x_b64x16_linearlr_bn_nowd_imagenet.py
@@ -0,0 +1,6 @@
+_base_ = 'shufflenet-v2-1x_16xb64_in1k.py'
+
+_deprecation_ = dict(
+ expected='shufflenet-v2-1x_16xb64_in1k.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/README.md b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..86975ec89ba1f08fc69e0f96ac511f1c17b145da
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/README.md
@@ -0,0 +1,60 @@
+# Swin Transformer
+
+> [Swin Transformer: Hierarchical Vision Transformer using Shifted Windows](https://arxiv.org/pdf/2103.14030.pdf)
+
+
+
+## Abstract
+
+This paper presents a new vision Transformer, called Swin Transformer, that capably serves as a general-purpose backbone for computer vision. Challenges in adapting Transformer from language to vision arise from differences between the two domains, such as large variations in the scale of visual entities and the high resolution of pixels in images compared to words in text. To address these differences, we propose a hierarchical Transformer whose representation is computed with **S**hifted **win**dows. The shifted windowing scheme brings greater efficiency by limiting self-attention computation to non-overlapping local windows while also allowing for cross-window connection. This hierarchical architecture has the flexibility to model at various scales and has linear computational complexity with respect to image size. These qualities of Swin Transformer make it compatible with a broad range of vision tasks, including image classification (87.3 top-1 accuracy on ImageNet-1K) and dense prediction tasks such as object detection (58.7 box AP and 51.1 mask AP on COCO test-dev) and semantic segmentation (53.5 mIoU on ADE20K val). Its performance surpasses the previous state-of-the-art by a large margin of +2.7 box AP and +2.6 mask AP on COCO, and +3.2 mIoU on ADE20K, demonstrating the potential of Transformer-based models as vision backbones. The hierarchical design and the shifted window approach also prove beneficial for all-MLP architectures.
+
+
+
+
+
+## Results and models
+
+### ImageNet-21k
+
+The pre-trained models on ImageNet-21k are used to fine-tune, and therefore don't have evaluation results.
+
+| Model | resolution | Params(M) | Flops(G) | Download |
+| :----: | :--------: | :-------: | :------: | :---------------------------------------------------------------------------------------------------------------------: |
+| Swin-B | 224x224 | 86.74 | 15.14 | [model](https://download.openmmlab.com/mmclassification/v0/swin-transformer/convert/swin-base_3rdparty_in21k.pth) |
+| Swin-B | 384x384 | 86.88 | 44.49 | [model](https://download.openmmlab.com/mmclassification/v0/swin-transformer/convert/swin-base_3rdparty_in21k-384px.pth) |
+| Swin-L | 224x224 | 195.00 | 34.04 | [model](https://download.openmmlab.com/mmclassification/v0/swin-transformer/convert/swin-large_3rdparty_in21k.pth) |
+| Swin-L | 384x384 | 195.20 | 100.04 | [model](https://download.openmmlab.com/mmclassification/v0/swin-transformer/convert/swin-base_3rdparty_in21k-384px.pth) |
+
+### ImageNet-1k
+
+| Model | Pretrain | resolution | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download |
+| :------: | :----------: | :--------: | :-------: | :------: | :-------: | :-------: | :----------------------------------------------------------------: | :-------------------------------------------------------------------: |
+| Swin-T | From scratch | 224x224 | 28.29 | 4.36 | 81.18 | 95.61 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/swin_transformer/swin-tiny_16xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/swin-transformer/swin_tiny_224_b16x64_300e_imagenet_20210616_090925-66df6be6.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/swin-transformer/swin_tiny_224_b16x64_300e_imagenet_20210616_090925.log.json) |
+| Swin-S | From scratch | 224x224 | 49.61 | 8.52 | 83.02 | 96.29 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/swin_transformer/swin-small_16xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/swin-transformer/swin_small_224_b16x64_300e_imagenet_20210615_110219-7f9d988b.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/swin-transformer/swin_small_224_b16x64_300e_imagenet_20210615_110219.log.json) |
+| Swin-B | From scratch | 224x224 | 87.77 | 15.14 | 83.36 | 96.44 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/swin_transformer/swin_base_224_b16x64_300e_imagenet.py) | [model](https://download.openmmlab.com/mmclassification/v0/swin-transformer/swin_base_224_b16x64_300e_imagenet_20210616_190742-93230b0d.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/swin-transformer/swin_base_224_b16x64_300e_imagenet_20210616_190742.log.json) |
+| Swin-S\* | From scratch | 224x224 | 49.61 | 8.52 | 83.21 | 96.25 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/swin_transformer/swin-small_16xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/swin-transformer/convert/swin_small_patch4_window7_224-cc7a01c9.pth) |
+| Swin-B\* | From scratch | 224x224 | 87.77 | 15.14 | 83.42 | 96.44 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/swin_transformer/swin-base_16xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/swin-transformer/convert/swin_base_patch4_window7_224-4670dd19.pth) |
+| Swin-B\* | From scratch | 384x384 | 87.90 | 44.49 | 84.49 | 96.95 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/swin_transformer/swin-base_16xb64_in1k-384px.py) | [model](https://download.openmmlab.com/mmclassification/v0/swin-transformer/convert/swin_base_patch4_window12_384-02c598a4.pth) |
+| Swin-B\* | ImageNet-21k | 224x224 | 87.77 | 15.14 | 85.16 | 97.50 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/swin_transformer/swin-base_16xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/swin-transformer/convert/swin_base_patch4_window7_224_22kto1k-f967f799.pth) |
+| Swin-B\* | ImageNet-21k | 384x384 | 87.90 | 44.49 | 86.44 | 98.05 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/swin_transformer/swin-base_16xb64_in1k-384px.py) | [model](https://download.openmmlab.com/mmclassification/v0/swin-transformer/convert/swin_base_patch4_window12_384_22kto1k-d59b0d1d.pth) |
+| Swin-L\* | ImageNet-21k | 224x224 | 196.53 | 34.04 | 86.24 | 97.88 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/swin_transformer/swin-large_16xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/swin-transformer/convert/swin_large_patch4_window7_224_22kto1k-5f0996db.pth) |
+| Swin-L\* | ImageNet-21k | 384x384 | 196.74 | 100.04 | 87.25 | 98.25 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/swin_transformer/swin-large_16xb64_in1k-384px.py) | [model](https://download.openmmlab.com/mmclassification/v0/swin-transformer/convert/swin_large_patch4_window12_384_22kto1k-0a40944b.pth) |
+
+*Models with * are converted from the [official repo](https://github.com/microsoft/Swin-Transformer#main-results-on-imagenet-with-pretrained-models). The config files of these models are only for validation. We don't ensure these config files' training accuracy and welcome you to contribute your reproduction results.*
+
+### CUB-200-2011
+
+| Model | Pretrain | resolution | Params(M) | Flops(G) | Top-1 (%) | Config | Download |
+| :----: | :---------------------------------------------------: | :--------: | :-------: | :------: | :-------: | :-------------------------------------------------: | :----------------------------------------------------: |
+| Swin-L | [ImageNet-21k](https://download.openmmlab.com/mmclassification/v0/swin-transformer/convert/swin-base_3rdparty_in21k-384px.pth) | 384x384 | 195.51 | 100.04 | 91.87 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/swin_transformer/swin-large_8xb8_cub_384px.py) | [model](https://download.openmmlab.com/mmclassification/v0/swin-transformer/swin-large_8xb8_cub_384px_20220307-1bbaee6a.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/swin-transformer/swin-large_8xb8_cub_384px_20220307-1bbaee6a.log.json) |
+
+## Citation
+
+```
+@article{liu2021Swin,
+ title={Swin Transformer: Hierarchical Vision Transformer using Shifted Windows},
+ author={Liu, Ze and Lin, Yutong and Cao, Yue and Hu, Han and Wei, Yixuan and Zhang, Zheng and Lin, Stephen and Guo, Baining},
+ journal={arXiv preprint arXiv:2103.14030},
+ year={2021}
+}
+```
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/metafile.yml b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/metafile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b44c1ba809c496a66aeeff0dc5ee7b7ba619c467
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/metafile.yml
@@ -0,0 +1,201 @@
+Collections:
+ - Name: Swin-Transformer
+ Metadata:
+ Training Data: ImageNet-1k
+ Training Techniques:
+ - AdamW
+ - Weight Decay
+ Training Resources: 16x V100 GPUs
+ Epochs: 300
+ Batch Size: 1024
+ Architecture:
+ - Shift Window Multihead Self Attention
+ Paper:
+ URL: https://arxiv.org/pdf/2103.14030.pdf
+ Title: "Swin Transformer: Hierarchical Vision Transformer using Shifted Windows"
+ README: configs/swin_transformer/README.md
+ Code:
+ URL: https://github.com/open-mmlab/mmclassification/blob/v0.15.0/mmcls/models/backbones/swin_transformer.py#L176
+ Version: v0.15.0
+
+Models:
+ - Name: swin-tiny_16xb64_in1k
+ Metadata:
+ FLOPs: 4360000000
+ Parameters: 28290000
+ In Collection: Swin-Transformer
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 81.18
+ Top 5 Accuracy: 95.61
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/swin-transformer/swin_tiny_224_b16x64_300e_imagenet_20210616_090925-66df6be6.pth
+ Config: configs/swin_transformer/swin-tiny_16xb64_in1k.py
+ - Name: swin-small_16xb64_in1k
+ Metadata:
+ FLOPs: 8520000000
+ Parameters: 49610000
+ In Collection: Swin-Transformer
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 83.02
+ Top 5 Accuracy: 96.29
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/swin-transformer/swin_small_224_b16x64_300e_imagenet_20210615_110219-7f9d988b.pth
+ Config: configs/swin_transformer/swin-small_16xb64_in1k.py
+ - Name: swin-base_16xb64_in1k
+ Metadata:
+ FLOPs: 15140000000
+ Parameters: 87770000
+ In Collection: Swin-Transformer
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 83.36
+ Top 5 Accuracy: 96.44
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/swin-transformer/swin_base_224_b16x64_300e_imagenet_20210616_190742-93230b0d.pth
+ Config: configs/swin_transformer/swin-base_16xb64_in1k.py
+ - Name: swin-tiny_3rdparty_in1k
+ Metadata:
+ FLOPs: 4360000000
+ Parameters: 28290000
+ In Collection: Swin-Transformer
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 81.18
+ Top 5 Accuracy: 95.52
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/swin-transformer/convert/swin_tiny_patch4_window7_224-160bb0a5.pth
+ Converted From:
+ Weights: https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_tiny_patch4_window7_224.pth
+ Code: https://github.com/microsoft/Swin-Transformer/blob/777f6c66604bb5579086c4447efe3620344d95a9/models/swin_transformer.py#L458
+ Config: configs/swin_transformer/swin-tiny_16xb64_in1k.py
+ - Name: swin-small_3rdparty_in1k
+ Metadata:
+ FLOPs: 8520000000
+ Parameters: 49610000
+ In Collection: Swin-Transformer
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 83.21
+ Top 5 Accuracy: 96.25
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/swin-transformer/convert/swin_small_patch4_window7_224-cc7a01c9.pth
+ Converted From:
+ Weights: https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_small_patch4_window7_224.pth
+ Code: https://github.com/microsoft/Swin-Transformer/blob/777f6c66604bb5579086c4447efe3620344d95a9/models/swin_transformer.py#L458
+ Config: configs/swin_transformer/swin-small_16xb64_in1k.py
+ - Name: swin-base_3rdparty_in1k
+ Metadata:
+ FLOPs: 15140000000
+ Parameters: 87770000
+ In Collection: Swin-Transformer
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 83.42
+ Top 5 Accuracy: 96.44
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/swin-transformer/convert/swin_base_patch4_window7_224-4670dd19.pth
+ Converted From:
+ Weights: https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_base_patch4_window7_224.pth
+ Code: https://github.com/microsoft/Swin-Transformer/blob/777f6c66604bb5579086c4447efe3620344d95a9/models/swin_transformer.py#L458
+ Config: configs/swin_transformer/swin-base_16xb64_in1k.py
+ - Name: swin-base_3rdparty_in1k-384
+ Metadata:
+ FLOPs: 44490000000
+ Parameters: 87900000
+ In Collection: Swin-Transformer
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 84.49
+ Top 5 Accuracy: 96.95
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/swin-transformer/convert/swin_base_patch4_window12_384-02c598a4.pth
+ Converted From:
+ Weights: https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_base_patch4_window12_384.pth
+ Code: https://github.com/microsoft/Swin-Transformer/blob/777f6c66604bb5579086c4447efe3620344d95a9/models/swin_transformer.py#L458
+ Config: configs/swin_transformer/swin-base_16xb64_in1k-384px.py
+ - Name: swin-base_in21k-pre-3rdparty_in1k
+ Metadata:
+ FLOPs: 15140000000
+ Parameters: 87770000
+ In Collection: Swin-Transformer
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 85.16
+ Top 5 Accuracy: 97.50
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/swin-transformer/convert/swin_base_patch4_window7_224_22kto1k-f967f799.pth
+ Converted From:
+ Weights: https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_base_patch4_window7_224_22kto1k.pth
+ Code: https://github.com/microsoft/Swin-Transformer/blob/777f6c66604bb5579086c4447efe3620344d95a9/models/swin_transformer.py#L458
+ Config: configs/swin_transformer/swin-base_16xb64_in1k.py
+ - Name: swin-base_in21k-pre-3rdparty_in1k-384
+ Metadata:
+ FLOPs: 44490000000
+ Parameters: 87900000
+ In Collection: Swin-Transformer
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 86.44
+ Top 5 Accuracy: 98.05
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/swin-transformer/convert/swin_base_patch4_window12_384_22kto1k-d59b0d1d.pth
+ Converted From:
+ Weights: https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_base_patch4_window12_384_22kto1k.pth
+ Code: https://github.com/microsoft/Swin-Transformer/blob/777f6c66604bb5579086c4447efe3620344d95a9/models/swin_transformer.py#L458
+ Config: configs/swin_transformer/swin-base_16xb64_in1k-384px.py
+ - Name: swin-large_in21k-pre-3rdparty_in1k
+ Metadata:
+ FLOPs: 34040000000
+ Parameters: 196530000
+ In Collection: Swin-Transformer
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 86.24
+ Top 5 Accuracy: 97.88
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/swin-transformer/convert/swin_large_patch4_window7_224_22kto1k-5f0996db.pth
+ Converted From:
+ Weights: https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_large_patch4_window7_224_22kto1k.pth
+ Code: https://github.com/microsoft/Swin-Transformer/blob/777f6c66604bb5579086c4447efe3620344d95a9/models/swin_transformer.py#L458
+ Config: configs/swin_transformer/swin-large_16xb64_in1k.py
+ - Name: swin-large_in21k-pre-3rdparty_in1k-384
+ Metadata:
+ FLOPs: 100040000000
+ Parameters: 196740000
+ In Collection: Swin-Transformer
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 87.25
+ Top 5 Accuracy: 98.25
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/swin-transformer/convert/swin_large_patch4_window12_384_22kto1k-0a40944b.pth
+ Converted From:
+ Weights: https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_large_patch4_window12_384_22kto1k.pth
+ Code: https://github.com/microsoft/Swin-Transformer/blob/777f6c66604bb5579086c4447efe3620344d95a9/models/swin_transformer.py#L458
+ Config: configs/swin_transformer/swin-large_16xb64_in1k-384px.py
+ - Name: swin-large_8xb8_cub_384px
+ Metadata:
+ FLOPs: 100040000000
+ Parameters: 195510000
+ In Collection: Swin-Transformer
+ Results:
+ - Dataset: CUB-200-2011
+ Metrics:
+ Top 1 Accuracy: 91.87
+ Task: Image Classification
+ Pretrain: https://download.openmmlab.com/mmclassification/v0/swin-transformer/convert/swin-large_3rdparty_in21k-384px.pth
+ Weights: https://download.openmmlab.com/mmclassification/v0/swin-transformer/swin-large_8xb8_cub_384px_20220307-1bbaee6a.pth
+ Config: configs/swin_transformer/swin-large_8xb8_cub_384px.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin-base_16xb64_in1k-384px.py b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin-base_16xb64_in1k-384px.py
new file mode 100644
index 0000000000000000000000000000000000000000..711a0d6d218aa4726f096c0e7146acad40c6577f
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin-base_16xb64_in1k-384px.py
@@ -0,0 +1,7 @@
+# Only for evaluation
+_base_ = [
+ '../_base_/models/swin_transformer/base_384.py',
+ '../_base_/datasets/imagenet_bs64_swin_384.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py'
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin-base_16xb64_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin-base_16xb64_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..2a4548af0bf98c6a7db0fda4c659d9417b911c36
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin-base_16xb64_in1k.py
@@ -0,0 +1,6 @@
+_base_ = [
+ '../_base_/models/swin_transformer/base_224.py',
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py'
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin-large_16xb64_in1k-384px.py b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin-large_16xb64_in1k-384px.py
new file mode 100644
index 0000000000000000000000000000000000000000..a7f0ad2762f763b6be85ad0f0118ee6c9c725a0a
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin-large_16xb64_in1k-384px.py
@@ -0,0 +1,7 @@
+# Only for evaluation
+_base_ = [
+ '../_base_/models/swin_transformer/large_384.py',
+ '../_base_/datasets/imagenet_bs64_swin_384.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py'
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin-large_16xb64_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin-large_16xb64_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..4e875c59f380e6f3b8bdd06ae3d1ab81bb18a714
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin-large_16xb64_in1k.py
@@ -0,0 +1,7 @@
+# Only for evaluation
+_base_ = [
+ '../_base_/models/swin_transformer/large_224.py',
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py'
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin-large_8xb8_cub_384px.py b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin-large_8xb8_cub_384px.py
new file mode 100644
index 0000000000000000000000000000000000000000..d11371613d1ee258daea264b2cb46170197ecbe3
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin-large_8xb8_cub_384px.py
@@ -0,0 +1,37 @@
+_base_ = [
+ '../_base_/models/swin_transformer/large_384.py',
+ '../_base_/datasets/cub_bs8_384.py', '../_base_/schedules/cub_bs64.py',
+ '../_base_/default_runtime.py'
+]
+
+# model settings
+checkpoint = 'https://download.openmmlab.com/mmclassification/v0/swin-transformer/convert/swin-large_3rdparty_in21k-384px.pth' # noqa
+model = dict(
+ type='ImageClassifier',
+ backbone=dict(
+ init_cfg=dict(
+ type='Pretrained', checkpoint=checkpoint, prefix='backbone')),
+ head=dict(num_classes=200, ))
+
+paramwise_cfg = dict(
+ norm_decay_mult=0.0,
+ bias_decay_mult=0.0,
+ custom_keys={
+ '.absolute_pos_embed': dict(decay_mult=0.0),
+ '.relative_position_bias_table': dict(decay_mult=0.0)
+ })
+
+optimizer = dict(
+ _delete_=True,
+ type='AdamW',
+ lr=5e-6,
+ weight_decay=0.0005,
+ eps=1e-8,
+ betas=(0.9, 0.999),
+ paramwise_cfg=paramwise_cfg)
+optimizer_config = dict(grad_clip=dict(max_norm=5.0), _delete_=True)
+
+log_config = dict(interval=20) # log every 20 intervals
+
+checkpoint_config = dict(
+ interval=1, max_keep_ckpts=3) # save last three checkpoints
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin-small_16xb64_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin-small_16xb64_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..aa1fa21b0540f76aba6b3cc16de03cb69d5f4860
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin-small_16xb64_in1k.py
@@ -0,0 +1,6 @@
+_base_ = [
+ '../_base_/models/swin_transformer/small_224.py',
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py'
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin-tiny_16xb64_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin-tiny_16xb64_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..e1ed022a1b7633e06f01739971d96098a9e9e98e
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin-tiny_16xb64_in1k.py
@@ -0,0 +1,6 @@
+_base_ = [
+ '../_base_/models/swin_transformer/tiny_224.py',
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py'
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin_base_224_b16x64_300e_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin_base_224_b16x64_300e_imagenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..912c379b18fcc2232834a4c4e853f675368c4d60
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin_base_224_b16x64_300e_imagenet.py
@@ -0,0 +1,6 @@
+_base_ = 'swin-base_16xb64_in1k.py'
+
+_deprecation_ = dict(
+ expected='swin-base_16xb64_in1k.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin_base_384_evalonly_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin_base_384_evalonly_imagenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..9ed58889d7a52361bd104e7f76a1b119e1abcf91
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin_base_384_evalonly_imagenet.py
@@ -0,0 +1,6 @@
+_base_ = 'swin-base_16xb64_in1k-384px.py'
+
+_deprecation_ = dict(
+ expected='swin-base_16xb64_in1k-384px.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin_large_224_evalonly_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin_large_224_evalonly_imagenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..5ebb54a59e9ebdc1630f767389ed8764ea8cc58d
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin_large_224_evalonly_imagenet.py
@@ -0,0 +1,6 @@
+_base_ = 'swin-large_16xb64_in1k.py'
+
+_deprecation_ = dict(
+ expected='swin-large_16xb64_in1k.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin_large_384_evalonly_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin_large_384_evalonly_imagenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..9a59f5b649a44001157a42a5e618cf51b4899f56
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin_large_384_evalonly_imagenet.py
@@ -0,0 +1,6 @@
+_base_ = 'swin-large_16xb64_in1k-384px.py'
+
+_deprecation_ = dict(
+ expected='swin-large_16xb64_in1k-384px.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin_small_224_b16x64_300e_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin_small_224_b16x64_300e_imagenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..a747aa4debfaf0e366d8d319d037618b3d65bea9
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin_small_224_b16x64_300e_imagenet.py
@@ -0,0 +1,6 @@
+_base_ = 'swin-small_16xb64_in1k.py'
+
+_deprecation_ = dict(
+ expected='swin-small_16xb64_in1k.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin_tiny_224_b16x64_300e_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin_tiny_224_b16x64_300e_imagenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..2160eb91fc2596da711ab9c8d4a50cb8b6aba4a8
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer/swin_tiny_224_b16x64_300e_imagenet.py
@@ -0,0 +1,6 @@
+_base_ = 'swin-tiny_16xb64_in1k.py'
+
+_deprecation_ = dict(
+ expected='swin-tiny_16xb64_in1k.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/README.md b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..31d1aff5739540c3b81b61451162ad51a0446294
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/README.md
@@ -0,0 +1,58 @@
+# Swin Transformer V2
+
+> [Swin Transformer V2: Scaling Up Capacity and Resolution](https://arxiv.org/abs/2111.09883.pdf)
+
+
+
+## Abstract
+
+Large-scale NLP models have been shown to significantly improve the performance on language tasks with no signs of saturation. They also demonstrate amazing few-shot capabilities like that of human beings. This paper aims to explore large-scale models in computer vision. We tackle three major issues in training and application of large vision models, including training instability, resolution gaps between pre-training and fine-tuning, and hunger on labelled data. Three main techniques are proposed: 1) a residual-post-norm method combined with cosine attention to improve training stability; 2) A log-spaced continuous position bias method to effectively transfer models pre-trained using low-resolution images to downstream tasks with high-resolution inputs; 3) A self-supervised pre-training method, SimMIM, to reduce the needs of vast labeled images. Through these techniques, this paper successfully trained a 3 billion-parameter Swin Transformer V2 model, which is the largest dense vision model to date, and makes it capable of training with images of up to 1,536×1,536 resolution. It set new performance records on 4 representative vision tasks, including ImageNet-V2 image classification, COCO object detection, ADE20K semantic segmentation, and Kinetics-400 video action classification. Also note our training is much more efficient than that in Google's billion-level visual models, which consumes 40 times less labelled data and 40 times less training time.
+
+
+
+
+
+## Results and models
+
+### ImageNet-21k
+
+The pre-trained models on ImageNet-21k are used to fine-tune, and therefore don't have evaluation results.
+
+| Model | resolution | Params(M) | Flops(G) | Download |
+| :------: | :--------: | :-------: | :------: | :--------------------------------------------------------------------------------------------------------------------------------------: |
+| Swin-B\* | 192x192 | 87.92 | 8.51 | [model](https://download.openmmlab.com/mmclassification/v0/swin-v2/pretrain/swinv2-base-w12_3rdparty_in21k-192px_20220803-f7dc9763.pth) |
+| Swin-L\* | 192x192 | 196.74 | 19.04 | [model](https://download.openmmlab.com/mmclassification/v0/swin-v2/pretrain/swinv2-large-w12_3rdparty_in21k-192px_20220803-d9073fee.pth) |
+
+### ImageNet-1k
+
+| Model | Pretrain | resolution | window | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download |
+| :------: | :----------: | :--------: | :----: | :-------: | :------: | :-------: | :-------: | :-------------------------------------------------------------: | :----------------------------------------------------------------: |
+| Swin-T\* | From scratch | 256x256 | 8x8 | 28.35 | 4.35 | 81.76 | 95.87 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/swin_transformer_v2/swinv2-tiny-w8_16xb64_in1k-256px.py) | [model](https://download.openmmlab.com/mmclassification/v0/swin-v2/swinv2-tiny-w8_3rdparty_in1k-256px_20220803-e318968f.pth) |
+| Swin-T\* | From scratch | 256x256 | 16x16 | 28.35 | 4.4 | 82.81 | 96.23 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/swin_transformer_v2/swinv2-tiny-w16_16xb64_in1k-256px.py) | [model](https://download.openmmlab.com/mmclassification/v0/swin-v2/swinv2-tiny-w16_3rdparty_in1k-256px_20220803-9651cdd7.pth) |
+| Swin-S\* | From scratch | 256x256 | 8x8 | 49.73 | 8.45 | 83.74 | 96.6 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/swin_transformer_v2/swinv2-small-w8_16xb64_in1k-256px.py) | [model](https://download.openmmlab.com/mmclassification/v0/swin-v2/swinv2-small-w8_3rdparty_in1k-256px_20220803-b01a4332.pth) |
+| Swin-S\* | From scratch | 256x256 | 16x16 | 49.73 | 8.57 | 84.13 | 96.83 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/swin_transformer_v2/swinv2-small-w16_16xb64_in1k-256px.py) | [model](https://download.openmmlab.com/mmclassification/v0/swin-v2/swinv2-small-w16_3rdparty_in1k-256px_20220803-b707d206.pth) |
+| Swin-B\* | From scratch | 256x256 | 8x8 | 87.92 | 14.99 | 84.2 | 96.86 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/swin_transformer_v2/swinv2-base-w8_16xb64_in1k-256px.py) | [model](https://download.openmmlab.com/mmclassification/v0/swin-v2/swinv2-base-w8_3rdparty_in1k-256px_20220803-8ff28f2b.pth) |
+| Swin-B\* | From scratch | 256x256 | 16x16 | 87.92 | 15.14 | 84.6 | 97.05 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/swin_transformer_v2/swinv2-base-w16_16xb64_in1k-256px.py) | [model](https://download.openmmlab.com/mmclassification/v0/swin-v2/swinv2-base-w16_3rdparty_in1k-256px_20220803-5a1886b7.pth) |
+| Swin-B\* | ImageNet-21k | 256x256 | 16x16 | 87.92 | 15.14 | 86.17 | 97.88 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/swin_transformer_v2/swinv2-base-w16_in21k-pre_16xb64_in1k-256px.py) | [model](https://download.openmmlab.com/mmclassification/v0/swin-v2/swinv2-base-w16_in21k-pre_3rdparty_in1k-256px_20220803-8d7aa8ad.pth) |
+| Swin-B\* | ImageNet-21k | 384x384 | 24x24 | 87.92 | 34.07 | 87.14 | 98.23 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/swin_transformer_v2/swinv2-base-w24_in21k-pre_16xb64_in1k-384px.py) | [model](https://download.openmmlab.com/mmclassification/v0/swin-v2/swinv2-base-w24_in21k-pre_3rdparty_in1k-384px_20220803-44eb70f8.pth) |
+| Swin-L\* | ImageNet-21k | 256X256 | 16x16 | 196.75 | 33.86 | 86.93 | 98.06 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/swin_transformer_v2/swinv2-large-w16_in21k-pre_16xb64_in1k-256px.py) | [model](https://download.openmmlab.com/mmclassification/v0/swin-v2/swinv2-large-w16_in21k-pre_3rdparty_in1k-256px_20220803-c40cbed7.pth) |
+| Swin-L\* | ImageNet-21k | 384x384 | 24x24 | 196.75 | 76.2 | 87.59 | 98.27 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/swin_transformer_v2/swinv2-large-w24_in21k-pre_16xb64_in1k-384px.py) | [model](https://download.openmmlab.com/mmclassification/v0/swin-v2/swinv2-large-w24_in21k-pre_3rdparty_in1k-384px_20220803-3b36c165.pth) |
+
+*Models with * are converted from the [official repo](https://github.com/microsoft/Swin-Transformer#main-results-on-imagenet-with-pretrained-models). The config files of these models are only for validation. We don't ensure these config files' training accuracy and welcome you to contribute your reproduction results.*
+
+*ImageNet-21k pretrained models with input resolution of 256x256 and 384x384 both fine-tuned from the same pre-training model using a smaller input resolution of 192x192.*
+
+## Citation
+
+```
+@article{https://doi.org/10.48550/arxiv.2111.09883,
+ doi = {10.48550/ARXIV.2111.09883},
+ url = {https://arxiv.org/abs/2111.09883},
+ author = {Liu, Ze and Hu, Han and Lin, Yutong and Yao, Zhuliang and Xie, Zhenda and Wei, Yixuan and Ning, Jia and Cao, Yue and Zhang, Zheng and Dong, Li and Wei, Furu and Guo, Baining},
+ keywords = {Computer Vision and Pattern Recognition (cs.CV), FOS: Computer and information sciences, FOS: Computer and information sciences},
+ title = {Swin Transformer V2: Scaling Up Capacity and Resolution},
+ publisher = {arXiv},
+ year = {2021},
+ copyright = {Creative Commons Attribution 4.0 International}
+}
+```
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/metafile.yml b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/metafile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..cef839234c2da792359dc11e9d671671854d2c79
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/metafile.yml
@@ -0,0 +1,204 @@
+Collections:
+ - Name: Swin-Transformer-V2
+ Metadata:
+ Training Data: ImageNet-1k
+ Training Techniques:
+ - AdamW
+ - Weight Decay
+ Training Resources: 16x V100 GPUs
+ Epochs: 300
+ Batch Size: 1024
+ Architecture:
+ - Shift Window Multihead Self Attention
+ Paper:
+ URL: https://arxiv.org/abs/2111.09883.pdf
+ Title: "Swin Transformer V2: Scaling Up Capacity and Resolution"
+ README: configs/swin_transformer_v2/README.md
+
+Models:
+ - Name: swinv2-tiny-w8_3rdparty_in1k-256px
+ Metadata:
+ FLOPs: 4350000000
+ Parameters: 28350000
+ In Collection: Swin-Transformer-V2
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 81.76
+ Top 5 Accuracy: 95.87
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/swin-v2/swinv2-tiny-w8_3rdparty_in1k-256px_20220803-e318968f.pth
+ Config: configs/swin_transformer_v2/swinv2-tiny-w8_16xb64_in1k-256px.py
+ Converted From:
+ Weights: https://github.com/SwinTransformer/storage/releases/download/v2.0.0/swinv2_tiny_patch4_window8_256.pth
+ Code: https://github.com/microsoft/Swin-Transformer
+ - Name: swinv2-tiny-w16_3rdparty_in1k-256px
+ Metadata:
+ FLOPs: 4400000000
+ Parameters: 28350000
+ In Collection: Swin-Transformer-V2
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 82.81
+ Top 5 Accuracy: 96.23
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/swin-v2/swinv2-tiny-w16_3rdparty_in1k-256px_20220803-9651cdd7.pth
+ Config: configs/swin_transformer_v2/swinv2-tiny-w16_16xb64_in1k-256px.py
+ Converted From:
+ Weights: https://github.com/SwinTransformer/storage/releases/download/v2.0.0/swinv2_tiny_patch4_window16_256.pth
+ Code: https://github.com/microsoft/Swin-Transformer
+ - Name: swinv2-small-w8_3rdparty_in1k-256px
+ Metadata:
+ FLOPs: 8450000000
+ Parameters: 49730000
+ In Collection: Swin-Transformer-V2
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 83.74
+ Top 5 Accuracy: 96.6
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/swin-v2/swinv2-small-w8_3rdparty_in1k-256px_20220803-b01a4332.pth
+ Config: configs/swin_transformer_v2/swinv2-small-w8_16xb64_in1k-256px.py
+ Converted From:
+ Weights: https://github.com/SwinTransformer/storage/releases/download/v2.0.0/swinv2_small_patch4_window8_256.pth
+ Code: https://github.com/microsoft/Swin-Transformer
+ - Name: swinv2-small-w16_3rdparty_in1k-256px
+ Metadata:
+ FLOPs: 8570000000
+ Parameters: 49730000
+ In Collection: Swin-Transformer-V2
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 84.13
+ Top 5 Accuracy: 96.83
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/swin-v2/swinv2-small-w16_3rdparty_in1k-256px_20220803-b707d206.pth
+ Config: configs/swin_transformer_v2/swinv2-small-w16_16xb64_in1k-256px.py
+ Converted From:
+ Weights: https://github.com/SwinTransformer/storage/releases/download/v2.0.0/swinv2_small_patch4_window16_256.pth
+ Code: https://github.com/microsoft/Swin-Transformer
+ - Name: swinv2-base-w8_3rdparty_in1k-256px
+ Metadata:
+ FLOPs: 14990000000
+ Parameters: 87920000
+ In Collection: Swin-Transformer-V2
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 84.2
+ Top 5 Accuracy: 96.86
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/swin-v2/swinv2-base-w8_3rdparty_in1k-256px_20220803-8ff28f2b.pth
+ Config: configs/swin_transformer_v2/swinv2-base-w8_16xb64_in1k-256px.py
+ Converted From:
+ Weights: https://github.com/SwinTransformer/storage/releases/download/v2.0.0/swinv2_base_patch4_window8_256.pth
+ Code: https://github.com/microsoft/Swin-Transformer
+ - Name: swinv2-base-w16_3rdparty_in1k-256px
+ Metadata:
+ FLOPs: 15140000000
+ Parameters: 87920000
+ In Collection: Swin-Transformer-V2
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 84.6
+ Top 5 Accuracy: 97.05
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/swin-v2/swinv2-base-w16_3rdparty_in1k-256px_20220803-5a1886b7.pth
+ Config: configs/swin_transformer_v2/swinv2-base-w16_16xb64_in1k-256px.py
+ Converted From:
+ Weights: https://github.com/SwinTransformer/storage/releases/download/v2.0.0/swinv2_base_patch4_window16_256.pth
+ Code: https://github.com/microsoft/Swin-Transformer
+ - Name: swinv2-base-w16_in21k-pre_3rdparty_in1k-256px
+ Metadata:
+ Training Data: ImageNet-21k
+ FLOPs: 15140000000
+ Parameters: 87920000
+ In Collection: Swin-Transformer-V2
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 86.17
+ Top 5 Accuracy: 97.88
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/swin-v2/swinv2-base-w16_in21k-pre_3rdparty_in1k-256px_20220803-8d7aa8ad.pth
+ Config: configs/swin_transformer_v2/swinv2-base-w16_in21k-pre_16xb64_in1k-256px.py
+ Converted From:
+ Weights: https://github.com/SwinTransformer/storage/releases/download/v2.0.0/swinv2_base_patch4_window12to16_192to256_22kto1k_ft.pth
+ Code: https://github.com/microsoft/Swin-Transformer
+ - Name: swinv2-base-w24_in21k-pre_3rdparty_in1k-384px
+ Metadata:
+ Training Data: ImageNet-21k
+ FLOPs: 34070000000
+ Parameters: 87920000
+ In Collection: Swin-Transformer-V2
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 87.14
+ Top 5 Accuracy: 98.23
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/swin-v2/swinv2-base-w24_in21k-pre_3rdparty_in1k-384px_20220803-44eb70f8.pth
+ Config: configs/swin_transformer_v2/swinv2-base-w24_in21k-pre_16xb64_in1k-384px.py
+ Converted From:
+ Weights: https://github.com/SwinTransformer/storage/releases/download/v2.0.0/swinv2_base_patch4_window12to24_192to384_22kto1k_ft.pth
+ Code: https://github.com/microsoft/Swin-Transformer
+ - Name: swinv2-large-w16_in21k-pre_3rdparty_in1k-256px
+ Metadata:
+ Training Data: ImageNet-21k
+ FLOPs: 33860000000
+ Parameters: 196750000
+ In Collection: Swin-Transformer-V2
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 86.93
+ Top 5 Accuracy: 98.06
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/swin-v2/swinv2-large-w16_in21k-pre_3rdparty_in1k-256px_20220803-c40cbed7.pth
+ Config: configs/swin_transformer_v2/swinv2-large-w16_in21k-pre_16xb64_in1k-256px.py
+ Converted From:
+ Weights: https://github.com/SwinTransformer/storage/releases/download/v2.0.0/swinv2_large_patch4_window12to16_192to256_22kto1k_ft.pth
+ Code: https://github.com/microsoft/Swin-Transformer
+ - Name: swinv2-large-w24_in21k-pre_3rdparty_in1k-384px
+ Metadata:
+ Training Data: ImageNet-21k
+ FLOPs: 76200000000
+ Parameters: 196750000
+ In Collection: Swin-Transformer-V2
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 87.59
+ Top 5 Accuracy: 98.27
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/swin-v2/swinv2-large-w24_in21k-pre_3rdparty_in1k-384px_20220803-3b36c165.pth
+ Config: configs/swin_transformer_v2/swinv2-large-w24_in21k-pre_16xb64_in1k-384px.py
+ Converted From:
+ Weights: https://github.com/SwinTransformer/storage/releases/download/v2.0.0/swinv2_large_patch4_window12to24_192to384_22kto1k_ft.pth
+ Code: https://github.com/microsoft/Swin-Transformer
+ - Name: swinv2-base-w12_3rdparty_in21k-192px
+ Metadata:
+ Training Data: ImageNet-21k
+ FLOPs: 8510000000
+ Parameters: 87920000
+ In Collections: Swin-Transformer-V2
+ Results: null
+ Weights: https://download.openmmlab.com/mmclassification/v0/swin-v2/pretrain/swinv2-base-w12_3rdparty_in21k-192px_20220803-f7dc9763.pth
+ Converted From:
+ Weights: https://github.com/SwinTransformer/storage/releases/download/v2.0.0/swinv2_base_patch4_window12_192_22k.pth
+ Code: https://github.com/microsoft/Swin-Transformer
+ - Name: swinv2-large-w12_3rdparty_in21k-192px
+ Metadata:
+ Training Data: ImageNet-21k
+ FLOPs: 19040000000
+ Parameters: 196740000
+ In Collections: Swin-Transformer-V2
+ Results: null
+ Weights: https://download.openmmlab.com/mmclassification/v0/swin-v2/pretrain/swinv2-large-w12_3rdparty_in21k-192px_20220803-d9073fee.pth
+ Converted From:
+ Weights: https://github.com/SwinTransformer/storage/releases/download/v2.0.0/swinv2_large_patch4_window12_192_22k.pth
+ Code: https://github.com/microsoft/Swin-Transformer
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-base-w16_16xb64_in1k-256px.py b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-base-w16_16xb64_in1k-256px.py
new file mode 100644
index 0000000000000000000000000000000000000000..5f375ee1fc9b10885f8b9d9f4794b8530c1460b5
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-base-w16_16xb64_in1k-256px.py
@@ -0,0 +1,8 @@
+_base_ = [
+ '../_base_/models/swin_transformer_v2/base_256.py',
+ '../_base_/datasets/imagenet_bs64_swin_256.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py'
+]
+
+model = dict(backbone=dict(window_size=[16, 16, 16, 8]))
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-base-w16_in21k-pre_16xb64_in1k-256px.py b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-base-w16_in21k-pre_16xb64_in1k-256px.py
new file mode 100644
index 0000000000000000000000000000000000000000..0725f9e739a099551a4d5b5f007bcb83708be309
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-base-w16_in21k-pre_16xb64_in1k-256px.py
@@ -0,0 +1,13 @@
+_base_ = [
+ '../_base_/models/swin_transformer_v2/base_256.py',
+ '../_base_/datasets/imagenet_bs64_swin_256.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py'
+]
+
+model = dict(
+ type='ImageClassifier',
+ backbone=dict(
+ window_size=[16, 16, 16, 8],
+ drop_path_rate=0.2,
+ pretrained_window_sizes=[12, 12, 12, 6]))
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-base-w24_in21k-pre_16xb64_in1k-384px.py b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-base-w24_in21k-pre_16xb64_in1k-384px.py
new file mode 100644
index 0000000000000000000000000000000000000000..3dd4e5fd935a356d29e7790e91d4538c94711062
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-base-w24_in21k-pre_16xb64_in1k-384px.py
@@ -0,0 +1,14 @@
+_base_ = [
+ '../_base_/models/swin_transformer_v2/base_384.py',
+ '../_base_/datasets/imagenet_bs64_swin_384.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py'
+]
+
+model = dict(
+ type='ImageClassifier',
+ backbone=dict(
+ img_size=384,
+ window_size=[24, 24, 24, 12],
+ drop_path_rate=0.2,
+ pretrained_window_sizes=[12, 12, 12, 6]))
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-base-w8_16xb64_in1k-256px.py b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-base-w8_16xb64_in1k-256px.py
new file mode 100644
index 0000000000000000000000000000000000000000..23fc40701470f8e41252c274072896d1cd811f28
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-base-w8_16xb64_in1k-256px.py
@@ -0,0 +1,6 @@
+_base_ = [
+ '../_base_/models/swin_transformer_v2/base_256.py',
+ '../_base_/datasets/imagenet_bs64_swin_256.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py'
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-large-w16_in21k-pre_16xb64_in1k-256px.py b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-large-w16_in21k-pre_16xb64_in1k-256px.py
new file mode 100644
index 0000000000000000000000000000000000000000..62a2a29b843f197c15d8f53a7cbd1029be675fa8
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-large-w16_in21k-pre_16xb64_in1k-256px.py
@@ -0,0 +1,13 @@
+# Only for evaluation
+_base_ = [
+ '../_base_/models/swin_transformer_v2/large_256.py',
+ '../_base_/datasets/imagenet_bs64_swin_256.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py'
+]
+
+model = dict(
+ type='ImageClassifier',
+ backbone=dict(
+ window_size=[16, 16, 16, 8], pretrained_window_sizes=[12, 12, 12, 6]),
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-large-w24_in21k-pre_16xb64_in1k-384px.py b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-large-w24_in21k-pre_16xb64_in1k-384px.py
new file mode 100644
index 0000000000000000000000000000000000000000..d97d9b2b869c1e0c264910859b6f980387a7b6ab
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-large-w24_in21k-pre_16xb64_in1k-384px.py
@@ -0,0 +1,15 @@
+# Only for evaluation
+_base_ = [
+ '../_base_/models/swin_transformer_v2/large_384.py',
+ '../_base_/datasets/imagenet_bs64_swin_384.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py'
+]
+
+model = dict(
+ type='ImageClassifier',
+ backbone=dict(
+ img_size=384,
+ window_size=[24, 24, 24, 12],
+ pretrained_window_sizes=[12, 12, 12, 6]),
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-small-w16_16xb64_in1k-256px.py b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-small-w16_16xb64_in1k-256px.py
new file mode 100644
index 0000000000000000000000000000000000000000..f87265dd199c712a6442407db852b5d4b6aabd7d
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-small-w16_16xb64_in1k-256px.py
@@ -0,0 +1,8 @@
+_base_ = [
+ '../_base_/models/swin_transformer_v2/small_256.py',
+ '../_base_/datasets/imagenet_bs64_swin_256.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py'
+]
+
+model = dict(backbone=dict(window_size=[16, 16, 16, 8]))
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-small-w8_16xb64_in1k-256px.py b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-small-w8_16xb64_in1k-256px.py
new file mode 100644
index 0000000000000000000000000000000000000000..f1001f1b6e1978c3706ca6183f863c316b13ade4
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-small-w8_16xb64_in1k-256px.py
@@ -0,0 +1,6 @@
+_base_ = [
+ '../_base_/models/swin_transformer_v2/small_256.py',
+ '../_base_/datasets/imagenet_bs64_swin_256.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py'
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-tiny-w16_16xb64_in1k-256px.py b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-tiny-w16_16xb64_in1k-256px.py
new file mode 100644
index 0000000000000000000000000000000000000000..7e1f290f371e1b9084f4cd5291e1e638d0ad54e3
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-tiny-w16_16xb64_in1k-256px.py
@@ -0,0 +1,8 @@
+_base_ = [
+ '../_base_/models/swin_transformer_v2/tiny_256.py',
+ '../_base_/datasets/imagenet_bs64_swin_256.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py'
+]
+
+model = dict(backbone=dict(window_size=[16, 16, 16, 8]))
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-tiny-w8_16xb64_in1k-256px.py b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-tiny-w8_16xb64_in1k-256px.py
new file mode 100644
index 0000000000000000000000000000000000000000..2cdc9a25ae8a64758f8642c079e1ff7fbf0548c3
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/swin_transformer_v2/swinv2-tiny-w8_16xb64_in1k-256px.py
@@ -0,0 +1,6 @@
+_base_ = [
+ '../_base_/models/swin_transformer_v2/tiny_256.py',
+ '../_base_/datasets/imagenet_bs64_swin_256.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py'
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/t2t_vit/README.md b/openmmlab_test/mmclassification-0.24.1/configs/t2t_vit/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..1e3a082760efbcf7c5b2c719f38b117d67f8aff6
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/t2t_vit/README.md
@@ -0,0 +1,36 @@
+# Tokens-to-Token ViT
+
+> [Tokens-to-Token ViT: Training Vision Transformers from Scratch on ImageNet](https://arxiv.org/abs/2101.11986)
+
+
+
+## Abstract
+
+Transformers, which are popular for language modeling, have been explored for solving vision tasks recently, \\eg, the Vision Transformer (ViT) for image classification. The ViT model splits each image into a sequence of tokens with fixed length and then applies multiple Transformer layers to model their global relation for classification. However, ViT achieves inferior performance to CNNs when trained from scratch on a midsize dataset like ImageNet. We find it is because: 1) the simple tokenization of input images fails to model the important local structure such as edges and lines among neighboring pixels, leading to low training sample efficiency; 2) the redundant attention backbone design of ViT leads to limited feature richness for fixed computation budgets and limited training samples. To overcome such limitations, we propose a new Tokens-To-Token Vision Transformer (T2T-ViT), which incorporates 1) a layer-wise Tokens-to-Token (T2T) transformation to progressively structurize the image to tokens by recursively aggregating neighboring Tokens into one Token (Tokens-to-Token), such that local structure represented by surrounding tokens can be modeled and tokens length can be reduced; 2) an efficient backbone with a deep-narrow structure for vision transformer motivated by CNN architecture design after empirical study. Notably, T2T-ViT reduces the parameter count and MACs of vanilla ViT by half, while achieving more than 3.0% improvement when trained from scratch on ImageNet. It also outperforms ResNets and achieves comparable performance with MobileNets by directly training on ImageNet. For example, T2T-ViT with comparable size to ResNet50 (21.5M parameters) can achieve 83.3% top1 accuracy in image resolution 384×384 on ImageNet.
+
+
+
+
+
+## Results and models
+
+### ImageNet-1k
+
+| Model | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download |
+| :----------: | :-------: | :------: | :-------: | :-------: | :-------------------------------------------------------------------------: | :----------------------------------------------------------------------------: |
+| T2T-ViT_t-14 | 21.47 | 4.34 | 81.83 | 95.84 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/t2t_vit/t2t-vit-t-14_8xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/t2t-vit/t2t-vit-t-14_8xb64_in1k_20211220-f7378dd5.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/t2t-vit/t2t-vit-t-14_8xb64_in1k_20211220-f7378dd5.log.json) |
+| T2T-ViT_t-19 | 39.08 | 7.80 | 82.63 | 96.18 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/t2t_vit/t2t-vit-t-19_8xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/t2t-vit/t2t-vit-t-19_8xb64_in1k_20211214-7f5e3aaf.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/t2t-vit/t2t-vit-t-19_8xb64_in1k_20211214-7f5e3aaf.log.json) |
+| T2T-ViT_t-24 | 64.00 | 12.69 | 82.71 | 96.09 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/t2t_vit/t2t-vit-t-24_8xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/t2t-vit/t2t-vit-t-24_8xb64_in1k_20211214-b2a68ae3.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/t2t-vit/t2t-vit-t-24_8xb64_in1k_20211214-b2a68ae3.log.json) |
+
+*In consistent with the [official repo](https://github.com/yitu-opensource/T2T-ViT), we adopt the best checkpoints during training.*
+
+## Citation
+
+```
+@article{yuan2021tokens,
+ title={Tokens-to-token vit: Training vision transformers from scratch on imagenet},
+ author={Yuan, Li and Chen, Yunpeng and Wang, Tao and Yu, Weihao and Shi, Yujun and Tay, Francis EH and Feng, Jiashi and Yan, Shuicheng},
+ journal={arXiv preprint arXiv:2101.11986},
+ year={2021}
+}
+```
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/t2t_vit/metafile.yml b/openmmlab_test/mmclassification-0.24.1/configs/t2t_vit/metafile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f21254268ac7737481326e24107f7bf12405e8ec
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/t2t_vit/metafile.yml
@@ -0,0 +1,58 @@
+Collections:
+ - Name: Tokens-to-Token ViT
+ Metadata:
+ Training Data: ImageNet-1k
+ Architecture:
+ - Layer Normalization
+ - Scaled Dot-Product Attention
+ - Attention Dropout
+ - Dropout
+ - Tokens to Token
+ Paper:
+ URL: https://arxiv.org/abs/2101.11986
+ Title: "Tokens-to-Token ViT: Training Vision Transformers from Scratch on ImageNet"
+ README: configs/t2t_vit/README.md
+ Code:
+ URL: https://github.com/open-mmlab/mmclassification/blob/v0.17.0/mmcls/models/backbones/t2t_vit.py
+ Version: v0.17.0
+
+Models:
+ - Name: t2t-vit-t-14_8xb64_in1k
+ Metadata:
+ FLOPs: 4340000000
+ Parameters: 21470000
+ In Collection: Tokens-to-Token ViT
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 81.83
+ Top 5 Accuracy: 95.84
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/t2t-vit/t2t-vit-t-14_8xb64_in1k_20211220-f7378dd5.pth
+ Config: configs/t2t_vit/t2t-vit-t-14_8xb64_in1k.py
+ - Name: t2t-vit-t-19_8xb64_in1k
+ Metadata:
+ FLOPs: 7800000000
+ Parameters: 39080000
+ In Collection: Tokens-to-Token ViT
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 82.63
+ Top 5 Accuracy: 96.18
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/t2t-vit/t2t-vit-t-19_8xb64_in1k_20211214-7f5e3aaf.pth
+ Config: configs/t2t_vit/t2t-vit-t-19_8xb64_in1k.py
+ - Name: t2t-vit-t-24_8xb64_in1k
+ Metadata:
+ FLOPs: 12690000000
+ Parameters: 64000000
+ In Collection: Tokens-to-Token ViT
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 82.71
+ Top 5 Accuracy: 96.09
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/t2t-vit/t2t-vit-t-24_8xb64_in1k_20211214-b2a68ae3.pth
+ Config: configs/t2t_vit/t2t-vit-t-24_8xb64_in1k.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/t2t_vit/t2t-vit-t-14_8xb64_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/t2t_vit/t2t-vit-t-14_8xb64_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..a391df48ba3a3ae7036d9b52762e519cebcf3dd9
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/t2t_vit/t2t-vit-t-14_8xb64_in1k.py
@@ -0,0 +1,35 @@
+_base_ = [
+ '../_base_/models/t2t-vit-t-14.py',
+ '../_base_/datasets/imagenet_bs64_t2t_224.py',
+ '../_base_/default_runtime.py',
+]
+
+# optimizer
+paramwise_cfg = dict(
+ norm_decay_mult=0.0,
+ bias_decay_mult=0.0,
+ custom_keys={'cls_token': dict(decay_mult=0.0)},
+)
+optimizer = dict(
+ type='AdamW',
+ lr=5e-4,
+ weight_decay=0.05,
+ paramwise_cfg=paramwise_cfg,
+)
+optimizer_config = dict(grad_clip=None)
+
+# learning policy
+# FIXME: lr in the first 300 epochs conforms to the CosineAnnealing and
+# the lr in the last 10 epoch equals to min_lr
+lr_config = dict(
+ policy='CosineAnnealingCooldown',
+ min_lr=1e-5,
+ cool_down_time=10,
+ cool_down_ratio=0.1,
+ by_epoch=True,
+ warmup_by_epoch=True,
+ warmup='linear',
+ warmup_iters=10,
+ warmup_ratio=1e-6)
+custom_hooks = [dict(type='EMAHook', momentum=4e-5, priority='ABOVE_NORMAL')]
+runner = dict(type='EpochBasedRunner', max_epochs=310)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/t2t_vit/t2t-vit-t-19_8xb64_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/t2t_vit/t2t-vit-t-19_8xb64_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..e1157f8902f2cba0bbada6c8b0a68899ab10e617
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/t2t_vit/t2t-vit-t-19_8xb64_in1k.py
@@ -0,0 +1,35 @@
+_base_ = [
+ '../_base_/models/t2t-vit-t-19.py',
+ '../_base_/datasets/imagenet_bs64_t2t_224.py',
+ '../_base_/default_runtime.py',
+]
+
+# optimizer
+paramwise_cfg = dict(
+ norm_decay_mult=0.0,
+ bias_decay_mult=0.0,
+ custom_keys={'cls_token': dict(decay_mult=0.0)},
+)
+optimizer = dict(
+ type='AdamW',
+ lr=5e-4,
+ weight_decay=0.065,
+ paramwise_cfg=paramwise_cfg,
+)
+optimizer_config = dict(grad_clip=None)
+
+# learning policy
+# FIXME: lr in the first 300 epochs conforms to the CosineAnnealing and
+# the lr in the last 10 epoch equals to min_lr
+lr_config = dict(
+ policy='CosineAnnealingCooldown',
+ min_lr=1e-5,
+ cool_down_time=10,
+ cool_down_ratio=0.1,
+ by_epoch=True,
+ warmup_by_epoch=True,
+ warmup='linear',
+ warmup_iters=10,
+ warmup_ratio=1e-6)
+custom_hooks = [dict(type='EMAHook', momentum=4e-5, priority='ABOVE_NORMAL')]
+runner = dict(type='EpochBasedRunner', max_epochs=310)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/t2t_vit/t2t-vit-t-24_8xb64_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/t2t_vit/t2t-vit-t-24_8xb64_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..815f2f15bab1de2a1a33f201634307616f17c3e9
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/t2t_vit/t2t-vit-t-24_8xb64_in1k.py
@@ -0,0 +1,35 @@
+_base_ = [
+ '../_base_/models/t2t-vit-t-24.py',
+ '../_base_/datasets/imagenet_bs64_t2t_224.py',
+ '../_base_/default_runtime.py',
+]
+
+# optimizer
+paramwise_cfg = dict(
+ norm_decay_mult=0.0,
+ bias_decay_mult=0.0,
+ custom_keys={'cls_token': dict(decay_mult=0.0)},
+)
+optimizer = dict(
+ type='AdamW',
+ lr=5e-4,
+ weight_decay=0.065,
+ paramwise_cfg=paramwise_cfg,
+)
+optimizer_config = dict(grad_clip=None)
+
+# learning policy
+# FIXME: lr in the first 300 epochs conforms to the CosineAnnealing and
+# the lr in the last 10 epoch equals to min_lr
+lr_config = dict(
+ policy='CosineAnnealingCooldown',
+ min_lr=1e-5,
+ cool_down_time=10,
+ cool_down_ratio=0.1,
+ by_epoch=True,
+ warmup_by_epoch=True,
+ warmup='linear',
+ warmup_iters=10,
+ warmup_ratio=1e-6)
+custom_hooks = [dict(type='EMAHook', momentum=4e-5, priority='ABOVE_NORMAL')]
+runner = dict(type='EpochBasedRunner', max_epochs=310)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/tnt/README.md b/openmmlab_test/mmclassification-0.24.1/configs/tnt/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..948eef747ce3795039c4edf32434ec1a2aac2c49
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/tnt/README.md
@@ -0,0 +1,36 @@
+# TNT
+
+> [Transformer in Transformer](https://arxiv.org/abs/2103.00112)
+
+
+
+## Abstract
+
+Transformer is a new kind of neural architecture which encodes the input data as powerful features via the attention mechanism. Basically, the visual transformers first divide the input images into several local patches and then calculate both representations and their relationship. Since natural images are of high complexity with abundant detail and color information, the granularity of the patch dividing is not fine enough for excavating features of objects in different scales and locations. In this paper, we point out that the attention inside these local patches are also essential for building visual transformers with high performance and we explore a new architecture, namely, Transformer iN Transformer (TNT). Specifically, we regard the local patches (e.g., 16×16) as "visual sentences" and present to further divide them into smaller patches (e.g., 4×4) as "visual words". The attention of each word will be calculated with other words in the given visual sentence with negligible computational costs. Features of both words and sentences will be aggregated to enhance the representation ability. Experiments on several benchmarks demonstrate the effectiveness of the proposed TNT architecture, e.g., we achieve an 81.5% top-1 accuracy on the ImageNet, which is about 1.7% higher than that of the state-of-the-art visual transformer with similar computational cost.
+
+
+
+
+
+## Results and models
+
+### ImageNet-1k
+
+| Model | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download |
+| :---------: | :-------: | :------: | :-------: | :-------: | :--------------------------------------------------------------------------: | :----------------------------------------------------------------------------: |
+| TNT-small\* | 23.76 | 3.36 | 81.52 | 95.73 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/tnt/tnt-s-p16_16xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/tnt/tnt-small-p16_3rdparty_in1k_20210903-c56ee7df.pth) |
+
+*Models with * are converted from [timm](https://github.com/rwightman/pytorch-image-models/). The config files of these models are only for validation. We don't ensure these config files' training accuracy and welcome you to contribute your reproduction results.*
+
+## Citation
+
+```
+@misc{han2021transformer,
+ title={Transformer in Transformer},
+ author={Kai Han and An Xiao and Enhua Wu and Jianyuan Guo and Chunjing Xu and Yunhe Wang},
+ year={2021},
+ eprint={2103.00112},
+ archivePrefix={arXiv},
+ primaryClass={cs.CV}
+}
+```
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/tnt/metafile.yml b/openmmlab_test/mmclassification-0.24.1/configs/tnt/metafile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..67f3c7825fbb5037e753e9b65a25c6b691638d78
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/tnt/metafile.yml
@@ -0,0 +1,29 @@
+Collections:
+ - Name: Transformer in Transformer
+ Metadata:
+ Training Data: ImageNet-1k
+ Paper:
+ URL: https://arxiv.org/abs/2103.00112
+ Title: "Transformer in Transformer"
+ README: configs/tnt/README.md
+ Code:
+ URL: https://github.com/open-mmlab/mmclassification/blob/v0.15.0/mmcls/models/backbones/tnt.py#L203
+ Version: v0.15.0
+
+Models:
+ - Name: tnt-small-p16_3rdparty_in1k
+ Metadata:
+ FLOPs: 3360000000
+ Parameters: 23760000
+ In Collection: Transformer in Transformer
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 81.52
+ Top 5 Accuracy: 95.73
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/tnt/tnt-small-p16_3rdparty_in1k_20210903-c56ee7df.pth
+ Config: configs/tnt/tnt-s-p16_16xb64_in1k.py
+ Converted From:
+ Weights: https://github.com/contrastive/pytorch-image-models/releases/download/TNT/tnt_s_patch16_224.pth.tar
+ Code: https://github.com/contrastive/pytorch-image-models/blob/809271b0f3e5d9be4e11c0c5cec1dbba8b5e2c60/timm/models/tnt.py#L144
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/tnt/tnt-s-p16_16xb64_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/tnt/tnt-s-p16_16xb64_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..36693689d4149b8ab1b98e86339f87039a30b755
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/tnt/tnt-s-p16_16xb64_in1k.py
@@ -0,0 +1,39 @@
+# accuracy_top-1 : 81.52 accuracy_top-5 : 95.73
+_base_ = [
+ '../_base_/models/tnt_s_patch16_224.py',
+ '../_base_/datasets/imagenet_bs32_pil_resize.py',
+ '../_base_/default_runtime.py'
+]
+
+img_norm_cfg = dict(
+ mean=[127.5, 127.5, 127.5], std=[127.5, 127.5, 127.5], to_rgb=True)
+
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='Resize',
+ size=(248, -1),
+ interpolation='bicubic',
+ backend='pillow'),
+ dict(type='CenterCrop', crop_size=224),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img'])
+]
+
+dataset_type = 'ImageNet'
+data = dict(
+ samples_per_gpu=64, workers_per_gpu=4, test=dict(pipeline=test_pipeline))
+
+# optimizer
+optimizer = dict(type='AdamW', lr=1e-3, weight_decay=0.05)
+optimizer_config = dict(grad_clip=None)
+
+lr_config = dict(
+ policy='CosineAnnealing',
+ min_lr=0,
+ warmup_by_epoch=True,
+ warmup='linear',
+ warmup_iters=5,
+ warmup_ratio=1e-3)
+runner = dict(type='EpochBasedRunner', max_epochs=300)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/tnt/tnt_s_patch16_224_evalonly_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/tnt/tnt_s_patch16_224_evalonly_imagenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..3c054d4a643ad80c09ed0f6ea303fe7927eec51b
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/tnt/tnt_s_patch16_224_evalonly_imagenet.py
@@ -0,0 +1,6 @@
+_base_ = 'tnt-s-p16_16xb64_in1k.py'
+
+_deprecation_ = dict(
+ expected='tnt-s-p16_16xb64_in1k.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/twins/README.md b/openmmlab_test/mmclassification-0.24.1/configs/twins/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..87e72941f4a44519efd1232d0651dc9203986caa
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/twins/README.md
@@ -0,0 +1,39 @@
+# Twins
+
+> [Twins: Revisiting the Design of Spatial Attention in Vision Transformers](http://arxiv-export-lb.library.cornell.edu/abs/2104.13840)
+
+
+
+## Abstract
+
+Very recently, a variety of vision transformer architectures for dense prediction tasks have been proposed and they show that the design of spatial attention is critical to their success in these tasks. In this work, we revisit the design of the spatial attention and demonstrate that a carefully-devised yet simple spatial attention mechanism performs favourably against the state-of-the-art schemes. As a result, we propose two vision transformer architectures, namely, Twins-PCPVT and Twins-SVT. Our proposed architectures are highly-efficient and easy to implement, only involving matrix multiplications that are highly optimized in modern deep learning frameworks. More importantly, the proposed architectures achieve excellent performance on a wide range of visual tasks, including image level classification as well as dense detection and segmentation. The simplicity and strong performance suggest that our proposed architectures may serve as stronger backbones for many vision tasks. Our code is released at [this https URL](https://github.com/Meituan-AutoML/Twins).
+
+
+
+
+
+## Results and models
+
+### ImageNet-1k
+
+| Model | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download |
+| :-----------: | :-------: | :------: | :-------: | :-------: | :-------------------------------------------------------------------------: | :---------------------------------------------------------------------------: |
+| PCPVT-small\* | 24.11 | 3.67 | 81.14 | 95.69 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/twins/twins-pcpvt-small_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/twins/twins-pcpvt-small_3rdparty_8xb128_in1k_20220126-ef23c132.pth) |
+| PCPVT-base\* | 43.83 | 6.45 | 82.66 | 96.26 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/twins/twins-pcpvt-base_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/twins/twins-pcpvt-base_3rdparty_8xb128_in1k_20220126-f8c4b0d5.pth) |
+| PCPVT-large\* | 60.99 | 9.51 | 83.09 | 96.59 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/twins/twins-pcpvt-large_16xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/twins/twins-pcpvt-large_3rdparty_16xb64_in1k_20220126-c1ef8d80.pth) |
+| SVT-small\* | 24.06 | 2.82 | 81.77 | 95.57 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/twins/twins-svt-small_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/twins/twins-svt-small_3rdparty_8xb128_in1k_20220126-8fe5205b.pth) |
+| SVT-base\* | 56.07 | 8.35 | 83.13 | 96.29 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/twins/twins-svt-base_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/twins/twins-svt-base_3rdparty_8xb128_in1k_20220126-e31cc8e9.pth) |
+| SVT-large\* | 99.27 | 14.82 | 83.60 | 96.50 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/twins/twins-svt-large_16xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/twins/twins-svt-large_3rdparty_16xb64_in1k_20220126-4817645f.pth) |
+
+*Models with * are converted from [the official repo](https://github.com/Meituan-AutoML/Twins). The config files of these models are only for validation. We don't ensure these config files' training accuracy and welcome you to contribute your reproduction results. The validation accuracy is a little different from the official paper because of the PyTorch version. This result is get in PyTorch=1.9 while the official result is get in PyTorch=1.7*
+
+## Citation
+
+```
+@article{chu2021twins,
+ title={Twins: Revisiting spatial attention design in vision transformers},
+ author={Chu, Xiangxiang and Tian, Zhi and Wang, Yuqing and Zhang, Bo and Ren, Haibing and Wei, Xiaolin and Xia, Huaxia and Shen, Chunhua},
+ journal={arXiv preprint arXiv:2104.13840},
+ year={2021}altgvt
+}
+```
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/twins/metafile.yml b/openmmlab_test/mmclassification-0.24.1/configs/twins/metafile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f8a7d8198da2397464399e7f48cb09661494f51e
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/twins/metafile.yml
@@ -0,0 +1,114 @@
+Collections:
+ - Name: Twins
+ Metadata:
+ Training Data: ImageNet-1k
+ Architecture:
+ - Global Subsampled Attention
+ - Locally Grouped SelfAttention
+ - Conditional Position Encoding
+ - Pyramid Vision Transformer
+ Paper:
+ URL: http://arxiv-export-lb.library.cornell.edu/abs/2104.13840
+ Title: "Twins: Revisiting the Design of Spatial Attention in Vision Transformers"
+ README: configs/twins/README.md
+ Code:
+ URL: https://github.com/open-mmlab/mmclassification/blob/v0.20.1/mmcls/models/backbones/twins.py
+ Version: v0.20.1
+
+Models:
+ - Name: twins-pcpvt-small_3rdparty_8xb128_in1k
+ Metadata:
+ FLOPs: 3670000000 # 3.67G
+ Parameters: 24110000 # 24.11M
+ In Collection: Twins
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 81.14
+ Top 5 Accuracy: 95.69
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/twins/twins-pcpvt-small_3rdparty_8xb128_in1k_20220126-ef23c132.pth
+ Config: configs/twins/twins-pcpvt-small_8xb128_in1k.py
+ Converted From:
+ Weights: https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-vt3p-weights/twins_pcpvt_small-e70e7e7a.pth
+ Code: https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/twins.py
+ - Name: twins-pcpvt-base_3rdparty_8xb128_in1k
+ Metadata:
+ FLOPs: 6450000000 # 6.45G
+ Parameters: 43830000 # 43.83M
+ In Collection: Twins
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 82.66
+ Top 5 Accuracy: 96.26
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/twins/twins-pcpvt-base_3rdparty_8xb128_in1k_20220126-f8c4b0d5.pth
+ Config: configs/twins/twins-pcpvt-base_8xb128_in1k.py
+ Converted From:
+ Weights: https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-vt3p-weights/twins_pcpvt_small-e70e7e7a.pth
+ Code: https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/twins.py
+ - Name: twins-pcpvt-large_3rdparty_16xb64_in1k
+ Metadata:
+ FLOPs: 9510000000 # 9.51G
+ Parameters: 60990000 # 60.99M
+ In Collection: Twins
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 83.09
+ Top 5 Accuracy: 96.59
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/twins/twins-pcpvt-large_3rdparty_16xb64_in1k_20220126-c1ef8d80.pth
+ Config: configs/twins/twins-pcpvt-large_16xb64_in1k.py
+ Converted From:
+ Weights: https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-vt3p-weights/twins_pcpvt_small-e70e7e7a.pth
+ Code: https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/twins.py
+ - Name: twins-svt-small_3rdparty_8xb128_in1k
+ Metadata:
+ FLOPs: 2820000000 # 2.82G
+ Parameters: 24060000 # 24.06M
+ In Collection: Twins
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 81.77
+ Top 5 Accuracy: 95.57
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/twins/twins-svt-small_3rdparty_8xb128_in1k_20220126-8fe5205b.pth
+ Config: configs/twins/twins-svt-small_8xb128_in1k.py
+ Converted From:
+ Weights: https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-vt3p-weights/twins_pcpvt_small-e70e7e7a.pth
+ Code: https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/twins.py
+ - Name: twins-svt-base_8xb128_3rdparty_in1k
+ Metadata:
+ FLOPs: 8350000000 # 8.35G
+ Parameters: 56070000 # 56.07M
+ In Collection: Twins
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 83.13
+ Top 5 Accuracy: 96.29
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/twins/twins-svt-base_3rdparty_8xb128_in1k_20220126-e31cc8e9.pth
+ Config: configs/twins/twins-svt-base_8xb128_in1k.py
+ Converted From:
+ Weights: https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-vt3p-weights/twins_pcpvt_small-e70e7e7a.pth
+ Code: https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/twins.py
+ - Name: twins-svt-large_3rdparty_16xb64_in1k
+ Metadata:
+ FLOPs: 14820000000 # 14.82G
+ Parameters: 99270000 # 99.27M
+ In Collection: Twins
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 83.60
+ Top 5 Accuracy: 96.50
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/twins/twins-svt-large_3rdparty_16xb64_in1k_20220126-4817645f.pth
+ Config: configs/twins/twins-svt-large_16xb64_in1k.py
+ Converted From:
+ Weights: https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-vt3p-weights/twins_pcpvt_small-e70e7e7a.pth
+ Code: https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/twins.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/twins/twins-pcpvt-base_8xb128_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/twins/twins-pcpvt-base_8xb128_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..8ea9adc30625f968cb2970811ef320c82eb8fdf5
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/twins/twins-pcpvt-base_8xb128_in1k.py
@@ -0,0 +1,33 @@
+_base_ = [
+ '../_base_/models/twins_pcpvt_base.py',
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py'
+]
+
+data = dict(samples_per_gpu=128)
+
+paramwise_cfg = dict(_delete=True, norm_decay_mult=0.0, bias_decay_mult=0.0)
+
+# for batch in each gpu is 128, 8 gpu
+# lr = 5e-4 * 128 * 8 / 512 = 0.001
+optimizer = dict(
+ type='AdamW',
+ lr=5e-4 * 128 * 8 / 512,
+ weight_decay=0.05,
+ eps=1e-8,
+ betas=(0.9, 0.999),
+ paramwise_cfg=paramwise_cfg)
+optimizer_config = dict(_delete_=True, grad_clip=dict(max_norm=5.0))
+
+# learning policy
+lr_config = dict(
+ policy='CosineAnnealing',
+ by_epoch=True,
+ min_lr_ratio=1e-2,
+ warmup='linear',
+ warmup_ratio=1e-3,
+ warmup_iters=5,
+ warmup_by_epoch=True)
+
+evaluation = dict(interval=1, metric='accuracy')
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/twins/twins-pcpvt-large_16xb64_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/twins/twins-pcpvt-large_16xb64_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..e9c9a35e873b08bad614a8ed34bfb49a2c001aef
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/twins/twins-pcpvt-large_16xb64_in1k.py
@@ -0,0 +1,5 @@
+_base_ = ['twins-pcpvt-base_8xb128_in1k.py']
+
+model = dict(backbone=dict(arch='large'), head=dict(in_channels=512))
+
+data = dict(samples_per_gpu=64)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/twins/twins-pcpvt-small_8xb128_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/twins/twins-pcpvt-small_8xb128_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..cb8bdc38c78e34402dd325289c81a7470a90df96
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/twins/twins-pcpvt-small_8xb128_in1k.py
@@ -0,0 +1,3 @@
+_base_ = ['twins-pcpvt-base_8xb128_in1k.py']
+
+model = dict(backbone=dict(arch='small'), head=dict(in_channels=512))
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/twins/twins-svt-base_8xb128_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/twins/twins-svt-base_8xb128_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..e2db2301844aa2e241cf80c55c09451c0c162916
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/twins/twins-svt-base_8xb128_in1k.py
@@ -0,0 +1,33 @@
+_base_ = [
+ '../_base_/models/twins_svt_base.py',
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py'
+]
+
+data = dict(samples_per_gpu=128)
+
+paramwise_cfg = dict(_delete=True, norm_decay_mult=0.0, bias_decay_mult=0.0)
+
+# for batch in each gpu is 128, 8 gpu
+# lr = 5e-4 * 128 * 8 / 512 = 0.001
+optimizer = dict(
+ type='AdamW',
+ lr=5e-4 * 128 * 8 / 512,
+ weight_decay=0.05,
+ eps=1e-8,
+ betas=(0.9, 0.999),
+ paramwise_cfg=paramwise_cfg)
+optimizer_config = dict(_delete_=True, grad_clip=dict(max_norm=5.0))
+
+# learning policy
+lr_config = dict(
+ policy='CosineAnnealing',
+ by_epoch=True,
+ min_lr_ratio=1e-2,
+ warmup='linear',
+ warmup_ratio=1e-3,
+ warmup_iters=5,
+ warmup_by_epoch=True)
+
+evaluation = dict(interval=1, metric='accuracy')
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/twins/twins-svt-large_16xb64_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/twins/twins-svt-large_16xb64_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..9288a706781025b030206faa1530e1968be775b7
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/twins/twins-svt-large_16xb64_in1k.py
@@ -0,0 +1,5 @@
+_base_ = ['twins-svt-base_8xb128_in1k.py']
+
+data = dict(samples_per_gpu=64)
+
+model = dict(backbone=dict(arch='large'), head=dict(in_channels=1024))
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/twins/twins-svt-small_8xb128_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/twins/twins-svt-small_8xb128_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..b92f1d3f3441aceb37a2f680067659d13157677b
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/twins/twins-svt-small_8xb128_in1k.py
@@ -0,0 +1,3 @@
+_base_ = ['twins-svt-base_8xb128_in1k.py']
+
+model = dict(backbone=dict(arch='small'), head=dict(in_channels=512))
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/van/README.md b/openmmlab_test/mmclassification-0.24.1/configs/van/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..a84cf3299327b97e24db95eab10fbdbb15cb1c32
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/van/README.md
@@ -0,0 +1,50 @@
+# Visual Attention Network
+
+> [Visual Attention Network](https://arxiv.org/pdf/2202.09741v2.pdf)
+
+
+
+## Abstract
+
+While originally designed for natural language processing (NLP) tasks, the self-attention mechanism has recently taken various computer vision areas by storm. However, the 2D nature of images brings three challenges for applying self-attention in computer vision. (1) Treating images as 1D sequences neglects their 2D structures. (2) The quadratic complexity is too expensive for high-resolution images. (3) It only captures spatial adaptability but ignores channel adaptability. In this paper, we propose a novel large kernel attention (LKA) module to enable self-adaptive and long-range correlations in self-attention while avoiding the above issues. We further introduce a novel neural network based on LKA, namely Visual Attention Network (VAN). While extremely simple and efficient, VAN outperforms the state-of-the-art vision transformers and convolutional neural networks with a large margin in extensive experiments, including image classification, object detection, semantic segmentation, instance segmentation, etc.
+
+
+
+
+
+## Results and models
+
+### ImageNet-1k
+
+| Model | Pretrain | resolution | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download |
+| :------: | :----------: | :--------: | :-------: | :------: | :-------: | :-------: | :----------------------------------------------------------------: | :-------------------------------------------------------------------: |
+| VAN-B0\* | From scratch | 224x224 | 4.11 | 0.88 | 75.41 | 93.02 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/van/van-b0_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/van/van-tiny_8xb128_in1k_20220501-385941af.pth) |
+| VAN-B1\* | From scratch | 224x224 | 13.86 | 2.52 | 81.01 | 95.63 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/van/van-b1_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/van/van-small_8xb128_in1k_20220501-17bc91aa.pth) |
+| VAN-B2\* | From scratch | 224x224 | 26.58 | 5.03 | 82.80 | 96.21 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/van/van-b2_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/van/van-base_8xb128_in1k_20220501-6a4cc31b.pth) |
+| VAN-B3\* | From scratch | 224x224 | 44.77 | 8.99 | 83.86 | 96.73 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/van/van-b3_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/van/van-large_8xb128_in1k_20220501-f212ba21.pth) |
+| VAN-B4\* | From scratch | 224x224 | 60.28 | 12.22 | 84.13 | 96.86 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/van/van-b4_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/van/van-b4_3rdparty_in1k_20220909-f4665b92.pth) |
+
+\*Models with * are converted from [the official repo](https://github.com/Visual-Attention-Network/VAN-Classification). The config files of these models are only for validation. We don't ensure these config files' training accuracy and welcome you to contribute your reproduction results.
+
+### Pre-trained Models
+
+The pre-trained models on ImageNet-21k are used to fine-tune on the downstream tasks.
+
+| Model | Pretrain | resolution | Params(M) | Flops(G) | Download |
+| :------: | :----------: | :--------: | :-------: | :------: | :---------------------------------------------------------------------------------------------------------: |
+| VAN-B4\* | ImageNet-21k | 224x224 | 60.28 | 12.22 | [model](https://download.openmmlab.com/mmclassification/v0/van/van-b4_3rdparty_in21k_20220909-db926b18.pth) |
+| VAN-B5\* | ImageNet-21k | 224x224 | 89.97 | 17.21 | [model](https://download.openmmlab.com/mmclassification/v0/van/van-b5_3rdparty_in21k_20220909-18e904e3.pth) |
+| VAN-B6\* | ImageNet-21k | 224x224 | 283.9 | 55.28 | [model](https://download.openmmlab.com/mmclassification/v0/van/van-b6_3rdparty_in21k_20220909-96c2cb3a.pth) |
+
+\*Models with * are converted from [the official repo](https://github.com/Visual-Attention-Network/VAN-Classification).
+
+## Citation
+
+```
+@article{guo2022visual,
+ title={Visual Attention Network},
+ author={Guo, Meng-Hao and Lu, Cheng-Ze and Liu, Zheng-Ning and Cheng, Ming-Ming and Hu, Shi-Min},
+ journal={arXiv preprint arXiv:2202.09741},
+ year={2022}
+}
+```
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/van/metafile.yml b/openmmlab_test/mmclassification-0.24.1/configs/van/metafile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c32df84abfd2fc197b620d36a3303e3b4a6ad4e7
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/van/metafile.yml
@@ -0,0 +1,84 @@
+Collections:
+ - Name: Visual-Attention-Network
+ Metadata:
+ Training Data: ImageNet-1k
+ Training Techniques:
+ - AdamW
+ - Weight Decay
+ Architecture:
+ - Visual Attention Network
+ - LKA
+ Paper:
+ URL: https://arxiv.org/pdf/2202.09741v2.pdf
+ Title: "Visual Attention Network"
+ README: configs/van/README.md
+ Code:
+ URL: https://github.com/open-mmlab/mmclassification/blob/v0.23.0/mmcls/models/backbones/van.py
+ Version: v0.23.0
+
+Models:
+ - Name: van-b0_3rdparty_in1k
+ Metadata:
+ FLOPs: 880000000 # 0.88G
+ Parameters: 4110000 # 4.11M
+ In Collection: Visual-Attention-Network
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 75.41
+ Top 5 Accuracy: 93.02
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/van/van-tiny_8xb128_in1k_20220501-385941af.pth
+ Config: configs/van/van-b0_8xb128_in1k.py
+ - Name: van-b1_3rdparty_in1k
+ Metadata:
+ FLOPs: 2520000000 # 2.52G
+ Parameters: 13860000 # 13.86M
+ In Collection: Visual-Attention-Network
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 81.01
+ Top 5 Accuracy: 95.63
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/van/van-small_8xb128_in1k_20220501-17bc91aa.pth
+ Config: configs/van/van-b1_8xb128_in1k.py
+ - Name: van-b2_3rdparty_in1k
+ Metadata:
+ FLOPs: 5030000000 # 5.03G
+ Parameters: 26580000 # 26.58M
+ In Collection: Visual-Attention-Network
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 82.80
+ Top 5 Accuracy: 96.21
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/van/van-base_8xb128_in1k_20220501-6a4cc31b.pth
+ Config: configs/van/van-b2_8xb128_in1k.py
+ - Name: van-b3_3rdparty_in1k
+ Metadata:
+ FLOPs: 8990000000 # 8.99G
+ Parameters: 44770000 # 44.77M
+ In Collection: Visual-Attention-Network
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 83.86
+ Top 5 Accuracy: 96.73
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/van/van-large_8xb128_in1k_20220501-f212ba21.pth
+ Config: configs/van/van-b3_8xb128_in1k.py
+ - Name: van-b4_3rdparty_in1k
+ Metadata:
+ FLOPs: 12220000000 # 12.22G
+ Parameters: 60280000 # 60.28M
+ In Collection: Visual-Attention-Network
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 84.13
+ Top 5 Accuracy: 96.86
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/van/van-b4_3rdparty_in1k_20220909-f4665b92.pth
+ Config: configs/van/van-b4_8xb128_in1k.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/van/van-b0_8xb128_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/van/van-b0_8xb128_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..1acb7af38eb8c06476541749a00e493d97eebb68
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/van/van-b0_8xb128_in1k.py
@@ -0,0 +1,61 @@
+_base_ = [
+ '../_base_/models/van/van_b0.py',
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py'
+]
+
+# Note that the mean and variance used here are different from other configs
+img_norm_cfg = dict(
+ mean=[127.5, 127.5, 127.5], std=[127.5, 127.5, 127.5], to_rgb=True)
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='RandomResizedCrop',
+ size=224,
+ backend='pillow',
+ interpolation='bicubic'),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(
+ type='RandAugment',
+ policies={{_base_.rand_increasing_policies}},
+ num_policies=2,
+ total_level=10,
+ magnitude_level=9,
+ magnitude_std=0.5,
+ hparams=dict(
+ pad_val=[round(x) for x in img_norm_cfg['mean'][::-1]],
+ interpolation='bicubic')),
+ dict(type='ColorJitter', brightness=0.4, contrast=0.4, saturation=0.4),
+ dict(
+ type='RandomErasing',
+ erase_prob=0.25,
+ mode='rand',
+ min_area_ratio=0.02,
+ max_area_ratio=1 / 3,
+ fill_color=img_norm_cfg['mean'][::-1],
+ fill_std=img_norm_cfg['std'][::-1]),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='Collect', keys=['img', 'gt_label'])
+]
+
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='Resize',
+ size=(248, -1),
+ backend='pillow',
+ interpolation='bicubic'),
+ dict(type='CenterCrop', crop_size=224),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img'])
+]
+
+data = dict(
+ samples_per_gpu=128,
+ train=dict(pipeline=train_pipeline),
+ val=dict(pipeline=test_pipeline),
+ test=dict(pipeline=test_pipeline))
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/van/van-b1_8xb128_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/van/van-b1_8xb128_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..64483db867d8ffe59d422d1e99a266fa01650973
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/van/van-b1_8xb128_in1k.py
@@ -0,0 +1,61 @@
+_base_ = [
+ '../_base_/models/van/van_b1.py',
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py'
+]
+
+# Note that the mean and variance used here are different from other configs
+img_norm_cfg = dict(
+ mean=[127.5, 127.5, 127.5], std=[127.5, 127.5, 127.5], to_rgb=True)
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='RandomResizedCrop',
+ size=224,
+ backend='pillow',
+ interpolation='bicubic'),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(
+ type='RandAugment',
+ policies={{_base_.rand_increasing_policies}},
+ num_policies=2,
+ total_level=10,
+ magnitude_level=9,
+ magnitude_std=0.5,
+ hparams=dict(
+ pad_val=[round(x) for x in img_norm_cfg['mean'][::-1]],
+ interpolation='bicubic')),
+ dict(type='ColorJitter', brightness=0.4, contrast=0.4, saturation=0.4),
+ dict(
+ type='RandomErasing',
+ erase_prob=0.25,
+ mode='rand',
+ min_area_ratio=0.02,
+ max_area_ratio=1 / 3,
+ fill_color=img_norm_cfg['mean'][::-1],
+ fill_std=img_norm_cfg['std'][::-1]),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='Collect', keys=['img', 'gt_label'])
+]
+
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='Resize',
+ size=(248, -1),
+ backend='pillow',
+ interpolation='bicubic'),
+ dict(type='CenterCrop', crop_size=224),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img'])
+]
+
+data = dict(
+ samples_per_gpu=128,
+ train=dict(pipeline=train_pipeline),
+ val=dict(pipeline=test_pipeline),
+ test=dict(pipeline=test_pipeline))
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/van/van-b2_8xb128_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/van/van-b2_8xb128_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..88493dc2e0fc2e72e8752ecaaacb99ed8c1dddb7
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/van/van-b2_8xb128_in1k.py
@@ -0,0 +1,61 @@
+_base_ = [
+ '../_base_/models/van/van_b2.py',
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py'
+]
+
+# Note that the mean and variance used here are different from other configs
+img_norm_cfg = dict(
+ mean=[127.5, 127.5, 127.5], std=[127.5, 127.5, 127.5], to_rgb=True)
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='RandomResizedCrop',
+ size=224,
+ backend='pillow',
+ interpolation='bicubic'),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(
+ type='RandAugment',
+ policies={{_base_.rand_increasing_policies}},
+ num_policies=2,
+ total_level=10,
+ magnitude_level=9,
+ magnitude_std=0.5,
+ hparams=dict(
+ pad_val=[round(x) for x in img_norm_cfg['mean'][::-1]],
+ interpolation='bicubic')),
+ dict(type='ColorJitter', brightness=0.4, contrast=0.4, saturation=0.4),
+ dict(
+ type='RandomErasing',
+ erase_prob=0.25,
+ mode='rand',
+ min_area_ratio=0.02,
+ max_area_ratio=1 / 3,
+ fill_color=img_norm_cfg['mean'][::-1],
+ fill_std=img_norm_cfg['std'][::-1]),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='Collect', keys=['img', 'gt_label'])
+]
+
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='Resize',
+ size=(248, -1),
+ backend='pillow',
+ interpolation='bicubic'),
+ dict(type='CenterCrop', crop_size=224),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img'])
+]
+
+data = dict(
+ samples_per_gpu=128,
+ train=dict(pipeline=train_pipeline),
+ val=dict(pipeline=test_pipeline),
+ test=dict(pipeline=test_pipeline))
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/van/van-b3_8xb128_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/van/van-b3_8xb128_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..6b415f656fba450c12b9a272783b0b8555506b57
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/van/van-b3_8xb128_in1k.py
@@ -0,0 +1,61 @@
+_base_ = [
+ '../_base_/models/van/van_b3.py',
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py'
+]
+
+# Note that the mean and variance used here are different from other configs
+img_norm_cfg = dict(
+ mean=[127.5, 127.5, 127.5], std=[127.5, 127.5, 127.5], to_rgb=True)
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='RandomResizedCrop',
+ size=224,
+ backend='pillow',
+ interpolation='bicubic'),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(
+ type='RandAugment',
+ policies={{_base_.rand_increasing_policies}},
+ num_policies=2,
+ total_level=10,
+ magnitude_level=9,
+ magnitude_std=0.5,
+ hparams=dict(
+ pad_val=[round(x) for x in img_norm_cfg['mean'][::-1]],
+ interpolation='bicubic')),
+ dict(type='ColorJitter', brightness=0.4, contrast=0.4, saturation=0.4),
+ dict(
+ type='RandomErasing',
+ erase_prob=0.25,
+ mode='rand',
+ min_area_ratio=0.02,
+ max_area_ratio=1 / 3,
+ fill_color=img_norm_cfg['mean'][::-1],
+ fill_std=img_norm_cfg['std'][::-1]),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='Collect', keys=['img', 'gt_label'])
+]
+
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='Resize',
+ size=(248, -1),
+ backend='pillow',
+ interpolation='bicubic'),
+ dict(type='CenterCrop', crop_size=224),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img'])
+]
+
+data = dict(
+ samples_per_gpu=128,
+ train=dict(pipeline=train_pipeline),
+ val=dict(pipeline=test_pipeline),
+ test=dict(pipeline=test_pipeline))
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/van/van-b4_8xb128_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/van/van-b4_8xb128_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..ba8914f820927fe24cfd33d1d3ddb31adc60466f
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/van/van-b4_8xb128_in1k.py
@@ -0,0 +1,61 @@
+_base_ = [
+ '../_base_/models/van/van_b4.py',
+ '../_base_/datasets/imagenet_bs64_swin_224.py',
+ '../_base_/schedules/imagenet_bs1024_adamw_swin.py',
+ '../_base_/default_runtime.py'
+]
+
+# Note that the mean and variance used here are different from other configs
+img_norm_cfg = dict(
+ mean=[127.5, 127.5, 127.5], std=[127.5, 127.5, 127.5], to_rgb=True)
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='RandomResizedCrop',
+ size=224,
+ backend='pillow',
+ interpolation='bicubic'),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(
+ type='RandAugment',
+ policies={{_base_.rand_increasing_policies}},
+ num_policies=2,
+ total_level=10,
+ magnitude_level=9,
+ magnitude_std=0.5,
+ hparams=dict(
+ pad_val=[round(x) for x in img_norm_cfg['mean'][::-1]],
+ interpolation='bicubic')),
+ dict(type='ColorJitter', brightness=0.4, contrast=0.4, saturation=0.4),
+ dict(
+ type='RandomErasing',
+ erase_prob=0.25,
+ mode='rand',
+ min_area_ratio=0.02,
+ max_area_ratio=1 / 3,
+ fill_color=img_norm_cfg['mean'][::-1],
+ fill_std=img_norm_cfg['std'][::-1]),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='Collect', keys=['img', 'gt_label'])
+]
+
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='Resize',
+ size=(248, -1),
+ backend='pillow',
+ interpolation='bicubic'),
+ dict(type='CenterCrop', crop_size=224),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img'])
+]
+
+data = dict(
+ samples_per_gpu=128,
+ train=dict(pipeline=train_pipeline),
+ val=dict(pipeline=test_pipeline),
+ test=dict(pipeline=test_pipeline))
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/van/van-base_8xb128_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/van/van-base_8xb128_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..e331980db2df49b343c409dad699368df14dbe1b
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/van/van-base_8xb128_in1k.py
@@ -0,0 +1,6 @@
+_base_ = ['./van-b2_8xb128_in1k.py']
+
+_deprecation_ = dict(
+ expected='van-b2_8xb128_in1k.p',
+ reference='https://github.com/open-mmlab/mmclassification/pull/1017',
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/van/van-large_8xb128_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/van/van-large_8xb128_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..84f8c7eddd06f7853fdf8a21ace92ec20e6554c3
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/van/van-large_8xb128_in1k.py
@@ -0,0 +1,6 @@
+_base_ = ['./van-b3_8xb128_in1k.py']
+
+_deprecation_ = dict(
+ expected='van-b3_8xb128_in1k.p',
+ reference='https://github.com/open-mmlab/mmclassification/pull/1017',
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/van/van-small_8xb128_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/van/van-small_8xb128_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..75d3220b47c2200881a8bf17c11a97e478238e1c
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/van/van-small_8xb128_in1k.py
@@ -0,0 +1,6 @@
+_base_ = ['./van-b1_8xb128_in1k.py']
+
+_deprecation_ = dict(
+ expected='van-b1_8xb128_in1k.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/1017',
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/van/van-tiny_8xb128_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/van/van-tiny_8xb128_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f83e77c6ba30054dfd44497698cbff3e6c234f5
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/van/van-tiny_8xb128_in1k.py
@@ -0,0 +1,6 @@
+_base_ = ['./van-b0_8xb128_in1k.py']
+
+_deprecation_ = dict(
+ expected='van-b0_8xb128_in1k.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/1017',
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/vgg/README.md b/openmmlab_test/mmclassification-0.24.1/configs/vgg/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..454489ff3101bb274616316cac10397d99cd3fed
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/vgg/README.md
@@ -0,0 +1,39 @@
+# VGG
+
+> [Very Deep Convolutional Networks for Large-Scale Image Recognition](https://arxiv.org/abs/1409.1556)
+
+
+
+## Abstract
+
+In this work we investigate the effect of the convolutional network depth on its accuracy in the large-scale image recognition setting. Our main contribution is a thorough evaluation of networks of increasing depth using an architecture with very small (3x3) convolution filters, which shows that a significant improvement on the prior-art configurations can be achieved by pushing the depth to 16-19 weight layers. These findings were the basis of our ImageNet Challenge 2014 submission, where our team secured the first and the second places in the localisation and classification tracks respectively. We also show that our representations generalise well to other datasets, where they achieve state-of-the-art results. We have made our two best-performing ConvNet models publicly available to facilitate further research on the use of deep visual representations in computer vision.
+
+
+
+
+
+## Results and models
+
+### ImageNet-1k
+
+| Model | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download |
+| :-------: | :-------: | :------: | :-------: | :-------: | :---------------------------------------------------------------------------: | :-----------------------------------------------------------------------------: |
+| VGG-11 | 132.86 | 7.63 | 68.75 | 88.87 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/vgg/vgg11_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/vgg/vgg11_batch256_imagenet_20210208-4271cd6c.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/vgg/vgg11_batch256_imagenet_20210208-4271cd6c.log.json) |
+| VGG-13 | 133.05 | 11.34 | 70.02 | 89.46 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/vgg/vgg13_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/vgg/vgg13_batch256_imagenet_20210208-4d1d6080.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/vgg/vgg13_batch256_imagenet_20210208-4d1d6080.log.json) |
+| VGG-16 | 138.36 | 15.5 | 71.62 | 90.49 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/vgg/vgg16_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/vgg/vgg16_batch256_imagenet_20210208-db26f1a5.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/vgg/vgg16_batch256_imagenet_20210208-db26f1a5.log.json) |
+| VGG-19 | 143.67 | 19.67 | 72.41 | 90.80 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/vgg/vgg19_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/vgg/vgg19_batch256_imagenet_20210208-e6920e4a.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/vgg/vgg19_batch256_imagenet_20210208-e6920e4a.log.json) |
+| VGG-11-BN | 132.87 | 7.64 | 70.67 | 90.16 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/vgg/vgg11bn_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/vgg/vgg11_bn_batch256_imagenet_20210207-f244902c.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/vgg/vgg11_bn_batch256_imagenet_20210207-f244902c.log.json) |
+| VGG-13-BN | 133.05 | 11.36 | 72.12 | 90.66 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/vgg/vgg13bn_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/vgg/vgg13_bn_batch256_imagenet_20210207-1a8b7864.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/vgg/vgg13_bn_batch256_imagenet_20210207-1a8b7864.log.json) |
+| VGG-16-BN | 138.37 | 15.53 | 73.74 | 91.66 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/vgg/vgg16_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/vgg/vgg16_bn_batch256_imagenet_20210208-7e55cd29.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/vgg/vgg16_bn_batch256_imagenet_20210208-7e55cd29.log.json) |
+| VGG-19-BN | 143.68 | 19.7 | 74.68 | 92.27 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/vgg/vgg19bn_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/vgg/vgg19_bn_batch256_imagenet_20210208-da620c4f.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/vgg/vgg19_bn_batch256_imagenet_20210208-da620c4f.log.json) |
+
+## Citation
+
+```
+@article{simonyan2014very,
+ title={Very deep convolutional networks for large-scale image recognition},
+ author={Simonyan, Karen and Zisserman, Andrew},
+ journal={arXiv preprint arXiv:1409.1556},
+ year={2014}
+}
+```
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/vgg/metafile.yml b/openmmlab_test/mmclassification-0.24.1/configs/vgg/metafile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4410c950db54414f406820a331369d7e2aadefba
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/vgg/metafile.yml
@@ -0,0 +1,125 @@
+Collections:
+ - Name: VGG
+ Metadata:
+ Training Data: ImageNet-1k
+ Training Techniques:
+ - SGD with Momentum
+ - Weight Decay
+ Training Resources: 8x Xp GPUs
+ Epochs: 100
+ Batch Size: 256
+ Architecture:
+ - VGG
+ Paper:
+ URL: https://arxiv.org/abs/1409.1556
+ Title: "Very Deep Convolutional Networks for Large-Scale Image"
+ README: configs/vgg/README.md
+ Code:
+ URL: https://github.com/open-mmlab/mmclassification/blob/v0.15.0/mmcls/models/backbones/vgg.py#L39
+ Version: v0.15.0
+
+Models:
+ - Name: vgg11_8xb32_in1k
+ Metadata:
+ FLOPs: 7630000000
+ Parameters: 132860000
+ In Collection: VGG
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 68.75
+ Top 5 Accuracy: 88.87
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/vgg/vgg11_batch256_imagenet_20210208-4271cd6c.pth
+ Config: configs/vgg/vgg11_8xb32_in1k.py
+ - Name: vgg13_8xb32_in1k
+ Metadata:
+ FLOPs: 11340000000
+ Parameters: 133050000
+ In Collection: VGG
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 70.02
+ Top 5 Accuracy: 89.46
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/vgg/vgg13_batch256_imagenet_20210208-4d1d6080.pth
+ Config: configs/vgg/vgg13_8xb32_in1k.py
+ - Name: vgg16_8xb32_in1k
+ Metadata:
+ FLOPs: 15500000000
+ Parameters: 138360000
+ In Collection: VGG
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 71.62
+ Top 5 Accuracy: 90.49
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/vgg/vgg16_batch256_imagenet_20210208-db26f1a5.pth
+ Config: configs/vgg/vgg16_8xb32_in1k.py
+ - Name: vgg19_8xb32_in1k
+ Metadata:
+ FLOPs: 19670000000
+ Parameters: 143670000
+ In Collection: VGG
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 72.41
+ Top 5 Accuracy: 90.8
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/vgg/vgg19_batch256_imagenet_20210208-e6920e4a.pth
+ Config: configs/vgg/vgg19_8xb32_in1k.py
+ - Name: vgg11bn_8xb32_in1k
+ Metadata:
+ FLOPs: 7640000000
+ Parameters: 132870000
+ In Collection: VGG
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 70.67
+ Top 5 Accuracy: 90.16
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/vgg/vgg11_bn_batch256_imagenet_20210207-f244902c.pth
+ Config: configs/vgg/vgg11bn_8xb32_in1k.py
+ - Name: vgg13bn_8xb32_in1k
+ Metadata:
+ FLOPs: 11360000000
+ Parameters: 133050000
+ In Collection: VGG
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 72.12
+ Top 5 Accuracy: 90.66
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/vgg/vgg13_bn_batch256_imagenet_20210207-1a8b7864.pth
+ Config: configs/vgg/vgg13bn_8xb32_in1k.py
+ - Name: vgg16bn_8xb32_in1k
+ Metadata:
+ FLOPs: 15530000000
+ Parameters: 138370000
+ In Collection: VGG
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 73.74
+ Top 5 Accuracy: 91.66
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/vgg/vgg16_bn_batch256_imagenet_20210208-7e55cd29.pth
+ Config: configs/vgg/vgg16bn_8xb32_in1k.py
+ - Name: vgg19bn_8xb32_in1k
+ Metadata:
+ FLOPs: 19700000000
+ Parameters: 143680000
+ In Collection: VGG
+ Results:
+ - Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 74.68
+ Top 5 Accuracy: 92.27
+ Task: Image Classification
+ Weights: https://download.openmmlab.com/mmclassification/v0/vgg/vgg19_bn_batch256_imagenet_20210208-da620c4f.pth
+ Config: configs/vgg/vgg19bn_8xb32_in1k.py
diff --git a/openmmlab_test/mmclassification-speed-benchmark/configs/vgg/vgg11_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg11_8xb32_in1k.py
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/configs/vgg/vgg11_b32x8_imagenet.py
rename to openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg11_8xb32_in1k.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg11_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg11_b32x8_imagenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..b15396be55f8e19ee576fce669bebe77271aa70f
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg11_b32x8_imagenet.py
@@ -0,0 +1,6 @@
+_base_ = 'vgg11_8xb32_in1k.py'
+
+_deprecation_ = dict(
+ expected='vgg11_8xb32_in1k.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/configs/vgg/vgg11bn_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg11bn_8xb32_in1k.py
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/configs/vgg/vgg11bn_b32x8_imagenet.py
rename to openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg11bn_8xb32_in1k.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg11bn_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg11bn_b32x8_imagenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..350c9befebff5bd2065bf95e278af9c868dde931
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg11bn_b32x8_imagenet.py
@@ -0,0 +1,6 @@
+_base_ = 'vgg11bn_8xb32_in1k.py'
+
+_deprecation_ = dict(
+ expected='vgg11bn_8xb32_in1k.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/configs/vgg/vgg13_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg13_8xb32_in1k.py
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/configs/vgg/vgg13_b32x8_imagenet.py
rename to openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg13_8xb32_in1k.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg13_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg13_b32x8_imagenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..6198ca2ca17e20a82884f9cd0653e0fdc89d8396
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg13_b32x8_imagenet.py
@@ -0,0 +1,6 @@
+_base_ = 'vgg13_8xb32_in1k.py'
+
+_deprecation_ = dict(
+ expected='vgg13_8xb32_in1k.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/configs/vgg/vgg13bn_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg13bn_8xb32_in1k.py
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/configs/vgg/vgg13bn_b32x8_imagenet.py
rename to openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg13bn_8xb32_in1k.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg13bn_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg13bn_b32x8_imagenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..0a715d7fb8139e779bde52738641ebad61d9d03f
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg13bn_b32x8_imagenet.py
@@ -0,0 +1,6 @@
+_base_ = 'vgg13bn_8xb32_in1k.py'
+
+_deprecation_ = dict(
+ expected='vgg13bn_8xb32_in1k.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/configs/vgg/vgg16_b16x8_voc.py b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg16_8xb16_voc.py
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/configs/vgg/vgg16_b16x8_voc.py
rename to openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg16_8xb16_voc.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg16_8xb32_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg16_8xb32_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..a477db37494182c896b7b49ccf10d767b5b63887
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg16_8xb32_in1k.py
@@ -0,0 +1,7 @@
+_base_ = [
+ '../_base_/models/vgg16bn.py',
+ '../_base_/datasets/imagenet_bs32_pil_resize.py',
+ '../_base_/schedules/imagenet_bs256.py', '../_base_/default_runtime.py'
+]
+optimizer = dict(lr=0.01)
+fp16 = dict(loss_scale=512.)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg16_b16x8_voc.py b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg16_b16x8_voc.py
new file mode 100644
index 0000000000000000000000000000000000000000..06225e722058790e928b5879eb4023611dc91f70
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg16_b16x8_voc.py
@@ -0,0 +1,6 @@
+_base_ = 'vgg16_8xb16_voc.py'
+
+_deprecation_ = dict(
+ expected='vgg16_8xb16_voc.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg16_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg16_b32x8_imagenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..2fefb94977ff1446f7a53bf3855f6f9679d3b6fa
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg16_b32x8_imagenet.py
@@ -0,0 +1,6 @@
+_base_ = 'vgg16_8xb32_in1k.py'
+
+_deprecation_ = dict(
+ expected='vgg16_8xb32_in1k.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/configs/vgg/vgg16bn_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg16bn_8xb32_in1k.py
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/configs/vgg/vgg16bn_b32x8_imagenet.py
rename to openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg16bn_8xb32_in1k.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg16bn_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg16bn_b32x8_imagenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..cb21917f578f8b6ea14ab8a75e73792279e93615
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg16bn_b32x8_imagenet.py
@@ -0,0 +1,6 @@
+_base_ = 'vgg16bn_8xb32_in1k.py'
+
+_deprecation_ = dict(
+ expected='vgg16bn_8xb32_in1k.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/configs/vgg/vgg19_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg19_8xb32_in1k.py
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/configs/vgg/vgg19_b32x8_imagenet.py
rename to openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg19_8xb32_in1k.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg19_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg19_b32x8_imagenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..e8b8b25a1a2140947206b73c1b5a4625e8c922a3
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg19_b32x8_imagenet.py
@@ -0,0 +1,6 @@
+_base_ = 'vgg19_8xb32_in1k.py'
+
+_deprecation_ = dict(
+ expected='vgg19_8xb32_in1k.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/configs/vgg/vgg19bn_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg19bn_8xb32_in1k.py
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/configs/vgg/vgg19bn_b32x8_imagenet.py
rename to openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg19bn_8xb32_in1k.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg19bn_b32x8_imagenet.py b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg19bn_b32x8_imagenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..f615496c2ce1f214ed4b1ede16fef53670dd2305
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/vgg/vgg19bn_b32x8_imagenet.py
@@ -0,0 +1,6 @@
+_base_ = 'vgg19bn_8xb32_in1k.py'
+
+_deprecation_ = dict(
+ expected='vgg19bn_8xb32_in1k.py',
+ reference='https://github.com/open-mmlab/mmclassification/pull/508',
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/README.md b/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..c35c242ef92d444fdaf95a059400bca31a737b27
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/README.md
@@ -0,0 +1,57 @@
+# Vision Transformer
+
+> [An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale](https://arxiv.org/pdf/2010.11929.pdf)
+
+
+
+## Abstract
+
+While the Transformer architecture has become the de-facto standard for natural language processing tasks, its applications to computer vision remain limited. In vision, attention is either applied in conjunction with convolutional networks, or used to replace certain components of convolutional networks while keeping their overall structure in place. We show that this reliance on CNNs is not necessary and a pure transformer applied directly to sequences of image patches can perform very well on image classification tasks. When pre-trained on large amounts of data and transferred to multiple mid-sized or small image recognition benchmarks (ImageNet, CIFAR-100, VTAB, etc.), Vision Transformer (ViT) attains excellent results compared to state-of-the-art convolutional networks while requiring substantially fewer computational resources to train.
+
+
+
+
+
+## Results and models
+
+The training step of Vision Transformers is divided into two steps. The first
+step is training the model on a large dataset, like ImageNet-21k, and get the
+pre-trained model. And the second step is training the model on the target
+dataset, like ImageNet-1k, and get the fine-tuned model. Here, we provide both
+pre-trained models and fine-tuned models.
+
+### ImageNet-21k
+
+The pre-trained models on ImageNet-21k are used to fine-tune, and therefore don't have evaluation results.
+
+| Model | resolution | Params(M) | Flops(G) | Download |
+| :-------: | :--------: | :-------: | :------: | :--------------------------------------------------------------------------------------------------------------------------------------: |
+| ViT-B16\* | 224x224 | 86.86 | 33.03 | [model](https://download.openmmlab.com/mmclassification/v0/vit/pretrain/vit-base-p16_3rdparty_pt-64xb64_in1k-224_20210928-02284250.pth) |
+| ViT-B32\* | 224x224 | 88.30 | 8.56 | [model](https://download.openmmlab.com/mmclassification/v0/vit/pretrain/vit-base-p32_3rdparty_pt-64xb64_in1k-224_20210928-eee25dd4.pth) |
+| ViT-L16\* | 224x224 | 304.72 | 116.68 | [model](https://download.openmmlab.com/mmclassification/v0/vit/pretrain/vit-large-p16_3rdparty_pt-64xb64_in1k-224_20210928-0001f9a1.pth) |
+
+*Models with * are converted from the [official repo](https://github.com/google-research/vision_transformer#available-vit-models).*
+
+### ImageNet-1k
+
+| Model | Pretrain | resolution | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download |
+| :-----------: | :----------: | :--------: | :-------: | :------: | :-------: | :-------: | :--------------------------------------------------------------: | :----------------------------------------------------------------: |
+| ViT-B16\* | ImageNet-21k | 384x384 | 86.86 | 33.03 | 85.43 | 97.77 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/vision_transformer/vit-base-p16_ft-64xb64_in1k-384.py) | [model](https://download.openmmlab.com/mmclassification/v0/vit/finetune/vit-base-p16_in21k-pre-3rdparty_ft-64xb64_in1k-384_20210928-98e8652b.pth) |
+| ViT-B32\* | ImageNet-21k | 384x384 | 88.30 | 8.56 | 84.01 | 97.08 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/vision_transformer/vit-base-p32_ft-64xb64_in1k-384.py) | [model](https://download.openmmlab.com/mmclassification/v0/vit/finetune/vit-base-p32_in21k-pre-3rdparty_ft-64xb64_in1k-384_20210928-9cea8599.pth) |
+| ViT-L16\* | ImageNet-21k | 384x384 | 304.72 | 116.68 | 85.63 | 97.63 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/vision_transformer/vit-large-p16_ft-64xb64_in1k-384.py) | [model](https://download.openmmlab.com/mmclassification/v0/vit/finetune/vit-large-p16_in21k-pre-3rdparty_ft-64xb64_in1k-384_20210928-b20ba619.pth) |
+| ViT-B16 (IPU) | ImageNet-21k | 224x224 | 86.86 | 33.03 | 81.22 | 95.56 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/vision_transformer/vit-base-p16_ft-4xb544-ipu_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/vit/vit-base-p16_ft-4xb544-ipu_in1k_20220603-c215811a.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/vit/vit-base-p16_ft-4xb544-ipu_in1k.log) |
+
+*Models with * are converted from the [official repo](https://github.com/google-research/vision_transformer#available-vit-models). The config files of these models are only for validation. We don't ensure these config files' training accuracy and welcome you to contribute your reproduction results.*
+
+## Citation
+
+```
+@inproceedings{
+ dosovitskiy2021an,
+ title={An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale},
+ author={Alexey Dosovitskiy and Lucas Beyer and Alexander Kolesnikov and Dirk Weissenborn and Xiaohua Zhai and Thomas Unterthiner and Mostafa Dehghani and Matthias Minderer and Georg Heigold and Sylvain Gelly and Jakob Uszkoreit and Neil Houlsby},
+ booktitle={International Conference on Learning Representations},
+ year={2021},
+ url={https://openreview.net/forum?id=YicbFdNTTy}
+}
+```
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/metafile.yml b/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/metafile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9ac804698b2126ef33179d9d520e6c7e936b0dff
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/metafile.yml
@@ -0,0 +1,79 @@
+Collections:
+ - Name: Vision Transformer
+ Metadata:
+ Architecture:
+ - Attention Dropout
+ - Convolution
+ - Dense Connections
+ - Dropout
+ - GELU
+ - Layer Normalization
+ - Multi-Head Attention
+ - Scaled Dot-Product Attention
+ - Tanh Activation
+ Paper:
+ URL: https://arxiv.org/pdf/2010.11929.pdf
+ Title: 'An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale'
+ README: configs/vision_transformer/README.md
+ Code:
+ URL: https://github.com/open-mmlab/mmclassification/blob/v0.17.0/mmcls/models/backbones/vision_transformer.py
+ Version: v0.17.0
+
+Models:
+ - Name: vit-base-p16_in21k-pre-3rdparty_ft-64xb64_in1k-384
+ In Collection: Vision Transformer
+ Metadata:
+ FLOPs: 33030000000
+ Parameters: 86860000
+ Training Data:
+ - ImageNet-21k
+ - ImageNet-1k
+ Results:
+ - Dataset: ImageNet-1k
+ Task: Image Classification
+ Metrics:
+ Top 1 Accuracy: 85.43
+ Top 5 Accuracy: 97.77
+ Weights: https://download.openmmlab.com/mmclassification/v0/vit/finetune/vit-base-p16_in21k-pre-3rdparty_ft-64xb64_in1k-384_20210928-98e8652b.pth
+ Converted From:
+ Weights: https://console.cloud.google.com/storage/browser/_details/vit_models/augreg/B_16-i21k-300ep-lr_0.001-aug_medium1-wd_0.1-do_0.0-sd_0.0--imagenet2012-steps_20k-lr_0.03-res_384.npz
+ Code: https://github.com/google-research/vision_transformer/blob/88a52f8892c80c10de99194990a517b4d80485fd/vit_jax/models.py#L208
+ Config: configs/vision_transformer/vit-base-p16_ft-64xb64_in1k-384.py
+ - Name: vit-base-p32_in21k-pre-3rdparty_ft-64xb64_in1k-384
+ In Collection: Vision Transformer
+ Metadata:
+ FLOPs: 8560000000
+ Parameters: 88300000
+ Training Data:
+ - ImageNet-21k
+ - ImageNet-1k
+ Results:
+ - Dataset: ImageNet-1k
+ Task: Image Classification
+ Metrics:
+ Top 1 Accuracy: 84.01
+ Top 5 Accuracy: 97.08
+ Weights: https://download.openmmlab.com/mmclassification/v0/vit/finetune/vit-base-p32_in21k-pre-3rdparty_ft-64xb64_in1k-384_20210928-9cea8599.pth
+ Converted From:
+ Weights: https://console.cloud.google.com/storage/browser/_details/vit_models/augreg/B_32-i21k-300ep-lr_0.001-aug_light1-wd_0.1-do_0.0-sd_0.0--imagenet2012-steps_20k-lr_0.01-res_384.npz
+ Code: https://github.com/google-research/vision_transformer/blob/88a52f8892c80c10de99194990a517b4d80485fd/vit_jax/models.py#L208
+ Config: configs/vision_transformer/vit-base-p32_ft-64xb64_in1k-384.py
+ - Name: vit-large-p16_in21k-pre-3rdparty_ft-64xb64_in1k-384
+ In Collection: Vision Transformer
+ Metadata:
+ FLOPs: 116680000000
+ Parameters: 304720000
+ Training Data:
+ - ImageNet-21k
+ - ImageNet-1k
+ Results:
+ - Dataset: ImageNet-1k
+ Task: Image Classification
+ Metrics:
+ Top 1 Accuracy: 85.63
+ Top 5 Accuracy: 97.63
+ Weights: https://download.openmmlab.com/mmclassification/v0/vit/finetune/vit-large-p16_in21k-pre-3rdparty_ft-64xb64_in1k-384_20210928-b20ba619.pth
+ Converted From:
+ Weights: https://console.cloud.google.com/storage/browser/_details/vit_models/augreg/L_16-i21k-300ep-lr_0.001-aug_strong1-wd_0.1-do_0.0-sd_0.0--imagenet2012-steps_20k-lr_0.01-res_384.npz
+ Code: https://github.com/google-research/vision_transformer/blob/88a52f8892c80c10de99194990a517b4d80485fd/vit_jax/models.py#L208
+ Config: configs/vision_transformer/vit-large-p16_ft-64xb64_in1k-384.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-base-p16_ft-4xb544-ipu_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-base-p16_ft-4xb544-ipu_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..097d8d6b97c316b2371e891bec4618c7de9a9fb7
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-base-p16_ft-4xb544-ipu_in1k.py
@@ -0,0 +1,115 @@
+_base_ = [
+ '../_base_/models/vit-base-p16.py',
+ '../_base_/datasets/imagenet_bs64_pil_resize_autoaug.py',
+ '../_base_/default_runtime.py'
+]
+
+# specific to vit pretrain
+paramwise_cfg = dict(custom_keys={
+ '.cls_token': dict(decay_mult=0.0),
+ '.pos_embed': dict(decay_mult=0.0)
+})
+
+pretrained = 'https://download.openmmlab.com/mmclassification/v0/vit/pretrain/vit-base-p16_3rdparty_pt-64xb64_in1k-224_20210928-02284250.pth' # noqa
+
+model = dict(
+ head=dict(
+ loss=dict(type='CrossEntropyLoss', loss_weight=1.0, _delete_=True), ),
+ backbone=dict(
+ img_size=224,
+ init_cfg=dict(
+ type='Pretrained',
+ checkpoint=pretrained,
+ _delete_=True,
+ prefix='backbone')))
+
+img_norm_cfg = dict(
+ mean=[127.5, 127.5, 127.5], std=[127.5, 127.5, 127.5], to_rgb=True)
+
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='RandomResizedCrop', size=224, backend='pillow'),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='ToHalf', keys=['img']),
+ dict(type='Collect', keys=['img', 'gt_label'])
+]
+
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='Resize', size=(224, -1), backend='pillow'),
+ dict(type='CenterCrop', crop_size=224),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToHalf', keys=['img']),
+ dict(type='Collect', keys=['img'])
+]
+
+# change batch size
+data = dict(
+ samples_per_gpu=17,
+ workers_per_gpu=16,
+ drop_last=True,
+ train=dict(pipeline=train_pipeline),
+ train_dataloader=dict(mode='async'),
+ val=dict(pipeline=test_pipeline, ),
+ val_dataloader=dict(samples_per_gpu=4, workers_per_gpu=1),
+ test=dict(pipeline=test_pipeline),
+ test_dataloader=dict(samples_per_gpu=4, workers_per_gpu=1))
+
+# remove clip-norm
+optimizer_config = dict()
+
+# optimizer
+optimizer = dict(
+ type='SGD',
+ lr=0.08,
+ weight_decay=1e-5,
+ momentum=0.9,
+ paramwise_cfg=paramwise_cfg,
+)
+
+# learning policy
+lr_config = dict(
+ policy='CosineAnnealing',
+ min_lr=0,
+ warmup='linear',
+ warmup_iters=800,
+ warmup_ratio=0.02,
+)
+
+# ipu cfg
+# model partition config
+ipu_model_cfg = dict(
+ train_split_edges=[
+ dict(layer_to_call='backbone.patch_embed', ipu_id=0),
+ dict(layer_to_call='backbone.layers.3', ipu_id=1),
+ dict(layer_to_call='backbone.layers.6', ipu_id=2),
+ dict(layer_to_call='backbone.layers.9', ipu_id=3)
+ ],
+ train_ckpt_nodes=['backbone.layers.{}'.format(i) for i in range(12)])
+
+# device config
+options_cfg = dict(
+ randomSeed=42,
+ partialsType='half',
+ train_cfg=dict(
+ executionStrategy='SameAsIpu',
+ Training=dict(gradientAccumulation=32),
+ availableMemoryProportion=[0.3, 0.3, 0.3, 0.3],
+ ),
+ eval_cfg=dict(deviceIterations=1, ),
+)
+
+# add model partition config and device config to runner
+runner = dict(
+ type='IterBasedRunner',
+ ipu_model_cfg=ipu_model_cfg,
+ options_cfg=options_cfg,
+ max_iters=5000)
+
+checkpoint_config = dict(interval=1000)
+
+fp16 = dict(loss_scale=256.0, velocity_accum_type='half', accum_type='half')
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-base-p16_ft-64xb64_in1k-384.py b/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-base-p16_ft-64xb64_in1k-384.py
new file mode 100644
index 0000000000000000000000000000000000000000..cb42d0d813acd58f1ee473422f596c41a45296b7
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-base-p16_ft-64xb64_in1k-384.py
@@ -0,0 +1,36 @@
+_base_ = [
+ '../_base_/models/vit-base-p16.py',
+ '../_base_/datasets/imagenet_bs64_pil_resize_autoaug.py',
+ '../_base_/schedules/imagenet_bs4096_AdamW.py',
+ '../_base_/default_runtime.py'
+]
+
+model = dict(backbone=dict(img_size=384))
+
+img_norm_cfg = dict(
+ mean=[127.5, 127.5, 127.5], std=[127.5, 127.5, 127.5], to_rgb=True)
+
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='RandomResizedCrop', size=384, backend='pillow'),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='Collect', keys=['img', 'gt_label'])
+]
+
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='Resize', size=(384, -1), backend='pillow'),
+ dict(type='CenterCrop', crop_size=384),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img'])
+]
+
+data = dict(
+ train=dict(pipeline=train_pipeline),
+ val=dict(pipeline=test_pipeline),
+ test=dict(pipeline=test_pipeline),
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-base-p16_pt-64xb64_in1k-224.py b/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-base-p16_pt-64xb64_in1k-224.py
new file mode 100644
index 0000000000000000000000000000000000000000..79c323b1efb61f6e9947fe326802c1ca8532d6ad
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-base-p16_pt-64xb64_in1k-224.py
@@ -0,0 +1,12 @@
+_base_ = [
+ '../_base_/models/vit-base-p16.py',
+ '../_base_/datasets/imagenet_bs64_pil_resize_autoaug.py',
+ '../_base_/schedules/imagenet_bs4096_AdamW.py',
+ '../_base_/default_runtime.py'
+]
+
+model = dict(
+ head=dict(hidden_dim=3072),
+ train_cfg=dict(
+ augments=dict(type='BatchMixup', alpha=0.2, num_classes=1000,
+ prob=1.)))
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-base-p32_ft-64xb64_in1k-384.py b/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-base-p32_ft-64xb64_in1k-384.py
new file mode 100644
index 0000000000000000000000000000000000000000..0386fef1fdad57197c62e4e039c361b81638e80d
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-base-p32_ft-64xb64_in1k-384.py
@@ -0,0 +1,36 @@
+_base_ = [
+ '../_base_/models/vit-base-p32.py',
+ '../_base_/datasets/imagenet_bs64_pil_resize_autoaug.py',
+ '../_base_/schedules/imagenet_bs4096_AdamW.py',
+ '../_base_/default_runtime.py'
+]
+
+model = dict(backbone=dict(img_size=384))
+
+img_norm_cfg = dict(
+ mean=[127.5, 127.5, 127.5], std=[127.5, 127.5, 127.5], to_rgb=True)
+
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='RandomResizedCrop', size=384, backend='pillow'),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='Collect', keys=['img', 'gt_label'])
+]
+
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='Resize', size=(384, -1), backend='pillow'),
+ dict(type='CenterCrop', crop_size=384),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img'])
+]
+
+data = dict(
+ train=dict(pipeline=train_pipeline),
+ val=dict(pipeline=test_pipeline),
+ test=dict(pipeline=test_pipeline),
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-base-p32_pt-64xb64_in1k-224.py b/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-base-p32_pt-64xb64_in1k-224.py
new file mode 100644
index 0000000000000000000000000000000000000000..a477e2119e229075de50ee4e4b3b222bb0ce0881
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-base-p32_pt-64xb64_in1k-224.py
@@ -0,0 +1,12 @@
+_base_ = [
+ '../_base_/models/vit-base-p32.py',
+ '../_base_/datasets/imagenet_bs64_pil_resize_autoaug.py',
+ '../_base_/schedules/imagenet_bs4096_AdamW.py',
+ '../_base_/default_runtime.py'
+]
+
+model = dict(
+ head=dict(hidden_dim=3072),
+ train_cfg=dict(
+ augments=dict(type='BatchMixup', alpha=0.2, num_classes=1000,
+ prob=1.)))
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-large-p16_ft-64xb64_in1k-384.py b/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-large-p16_ft-64xb64_in1k-384.py
new file mode 100644
index 0000000000000000000000000000000000000000..5be99188bfe0fbbce2de927c9d9c55ed74131d2f
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-large-p16_ft-64xb64_in1k-384.py
@@ -0,0 +1,36 @@
+_base_ = [
+ '../_base_/models/vit-large-p16.py',
+ '../_base_/datasets/imagenet_bs64_pil_resize_autoaug.py',
+ '../_base_/schedules/imagenet_bs4096_AdamW.py',
+ '../_base_/default_runtime.py'
+]
+
+model = dict(backbone=dict(img_size=384))
+
+img_norm_cfg = dict(
+ mean=[127.5, 127.5, 127.5], std=[127.5, 127.5, 127.5], to_rgb=True)
+
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='RandomResizedCrop', size=384, backend='pillow'),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='Collect', keys=['img', 'gt_label'])
+]
+
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='Resize', size=(384, -1), backend='pillow'),
+ dict(type='CenterCrop', crop_size=384),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img'])
+]
+
+data = dict(
+ train=dict(pipeline=train_pipeline),
+ val=dict(pipeline=test_pipeline),
+ test=dict(pipeline=test_pipeline),
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-large-p16_pt-64xb64_in1k-224.py b/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-large-p16_pt-64xb64_in1k-224.py
new file mode 100644
index 0000000000000000000000000000000000000000..5cf7a7d30c0b467f32f0e7c2cdc7a4138f46997a
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-large-p16_pt-64xb64_in1k-224.py
@@ -0,0 +1,12 @@
+_base_ = [
+ '../_base_/models/vit-large-p16.py',
+ '../_base_/datasets/imagenet_bs64_pil_resize_autoaug.py',
+ '../_base_/schedules/imagenet_bs4096_AdamW.py',
+ '../_base_/default_runtime.py'
+]
+
+model = dict(
+ head=dict(hidden_dim=3072),
+ train_cfg=dict(
+ augments=dict(type='BatchMixup', alpha=0.2, num_classes=1000,
+ prob=1.)))
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-large-p32_ft-64xb64_in1k-384.py b/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-large-p32_ft-64xb64_in1k-384.py
new file mode 100644
index 0000000000000000000000000000000000000000..60506b02416ac25366544424995c78e270d272b6
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-large-p32_ft-64xb64_in1k-384.py
@@ -0,0 +1,37 @@
+# Refer to pytorch-image-models
+_base_ = [
+ '../_base_/models/vit-large-p32.py',
+ '../_base_/datasets/imagenet_bs64_pil_resize_autoaug.py',
+ '../_base_/schedules/imagenet_bs4096_AdamW.py',
+ '../_base_/default_runtime.py'
+]
+
+model = dict(backbone=dict(img_size=384))
+
+img_norm_cfg = dict(
+ mean=[127.5, 127.5, 127.5], std=[127.5, 127.5, 127.5], to_rgb=True)
+
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='RandomResizedCrop', size=384, backend='pillow'),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='Collect', keys=['img', 'gt_label'])
+]
+
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='Resize', size=(384, -1), backend='pillow'),
+ dict(type='CenterCrop', crop_size=384),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img'])
+]
+
+data = dict(
+ train=dict(pipeline=train_pipeline),
+ val=dict(pipeline=test_pipeline),
+ test=dict(pipeline=test_pipeline),
+)
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-large-p32_pt-64xb64_in1k-224.py b/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-large-p32_pt-64xb64_in1k-224.py
new file mode 100644
index 0000000000000000000000000000000000000000..773ade874ad48326cd3e68d53df8d8d5e1e44739
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/vision_transformer/vit-large-p32_pt-64xb64_in1k-224.py
@@ -0,0 +1,12 @@
+_base_ = [
+ '../_base_/models/vit-large-p32.py',
+ '../_base_/datasets/imagenet_bs64_pil_resize_autoaug.py',
+ '../_base_/schedules/imagenet_bs4096_AdamW.py',
+ '../_base_/default_runtime.py'
+]
+
+model = dict(
+ head=dict(hidden_dim=3072),
+ train_cfg=dict(
+ augments=dict(type='BatchMixup', alpha=0.2, num_classes=1000,
+ prob=1.)))
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/wrn/README.md b/openmmlab_test/mmclassification-0.24.1/configs/wrn/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..b036caafe0a87c9ecd3c14480212fde5d016c85c
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/wrn/README.md
@@ -0,0 +1,35 @@
+# Wide-ResNet
+
+> [Wide Residual Networks](https://arxiv.org/abs/1605.07146)
+
+
+
+## Abstract
+
+Deep residual networks were shown to be able to scale up to thousands of layers and still have improving performance. However, each fraction of a percent of improved accuracy costs nearly doubling the number of layers, and so training very deep residual networks has a problem of diminishing feature reuse, which makes these networks very slow to train. To tackle these problems, in this paper we conduct a detailed experimental study on the architecture of ResNet blocks, based on which we propose a novel architecture where we decrease depth and increase width of residual networks. We call the resulting network structures wide residual networks (WRNs) and show that these are far superior over their commonly used thin and very deep counterparts. For example, we demonstrate that even a simple 16-layer-deep wide residual network outperforms in accuracy and efficiency all previous deep residual networks, including thousand-layer-deep networks, achieving new state-of-the-art results on CIFAR, SVHN, COCO, and significant improvements on ImageNet.
+
+
+
+
+
+## Results and models
+
+### ImageNet-1k
+
+| Model | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download |
+| :-------------: | :-------: | :------: | :-------: | :-------: | :------------------------------------------------------------------------: | :--------------------------------------------------------------------------: |
+| WRN-50\* | 68.88 | 11.44 | 78.48 | 94.08 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/wrn/wide-resnet50_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/wrn/wide-resnet50_3rdparty_8xb32_in1k_20220304-66678344.pth) |
+| WRN-101\* | 126.89 | 22.81 | 78.84 | 94.28 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/wrn/wide-resnet101_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/wrn/wide-resnet101_3rdparty_8xb32_in1k_20220304-8d5f9d61.pth) |
+| WRN-50 (timm)\* | 68.88 | 11.44 | 81.45 | 95.53 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/wrn/wide-resnet50_timm_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/wrn/wide-resnet50_3rdparty-timm_8xb32_in1k_20220304-83ae4399.pth) |
+
+*Models with * are converted from the [TorchVision](https://github.com/pytorch/vision/blob/main/torchvision/models/resnet.py) and [TIMM](https://github.com/rwightman/pytorch-image-models/blob/master). The config files of these models are only for inference. We don't ensure these config files' training accuracy and welcome you to contribute your reproduction results.*
+
+## Citation
+
+```bibtex
+@INPROCEEDINGS{Zagoruyko2016WRN,
+ author = {Sergey Zagoruyko and Nikos Komodakis},
+ title = {Wide Residual Networks},
+ booktitle = {BMVC},
+ year = {2016}}
+```
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/wrn/metafile.yml b/openmmlab_test/mmclassification-0.24.1/configs/wrn/metafile.yml
new file mode 100644
index 0000000000000000000000000000000000000000..cc37eefd23a941661f2a940bd593aa7802144196
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/wrn/metafile.yml
@@ -0,0 +1,77 @@
+Collections:
+ - Name: Wide-ResNet
+ Metadata:
+ Training Data: ImageNet-1k
+ Training Techniques:
+ - SGD with Momentum
+ - Weight Decay
+ Training Resources: 8x V100 GPUs
+ Epochs: 100
+ Batch Size: 256
+ Architecture:
+ - 1x1 Convolution
+ - Batch Normalization
+ - Convolution
+ - Global Average Pooling
+ - Max Pooling
+ - ReLU
+ - Residual Connection
+ - Softmax
+ - Wide Residual Block
+ Paper:
+ URL: https://arxiv.org/abs/1605.07146
+ Title: "Wide Residual Networks"
+ README: configs/wrn/README.md
+ Code:
+ URL: https://github.com/open-mmlab/mmclassification/blob/v0.20.1/mmcls/models/backbones/resnet.py#L383
+ Version: v0.20.1
+
+Models:
+ - Name: wide-resnet50_3rdparty_8xb32_in1k
+ Metadata:
+ FLOPs: 11440000000 # 11.44G
+ Parameters: 68880000 # 68.88M
+ In Collection: Wide-ResNet
+ Results:
+ - Task: Image Classification
+ Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 78.48
+ Top 5 Accuracy: 94.08
+ Weights: https://download.openmmlab.com/mmclassification/v0/wrn/wide-resnet50_3rdparty_8xb32_in1k_20220304-66678344.pth
+ Config: configs/wrn/wide-resnet50_8xb32_in1k.py
+ Converted From:
+ Weights: https://download.pytorch.org/models/wide_resnet50_2-95faca4d.pth
+ Code: https://github.com/pytorch/vision/blob/main/torchvision/models/resnet.py
+ - Name: wide-resnet101_3rdparty_8xb32_in1k
+ Metadata:
+ FLOPs: 22810000000 # 22.81G
+ Parameters: 126890000 # 126.89M
+ In Collection: Wide-ResNet
+ Results:
+ - Task: Image Classification
+ Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 78.84
+ Top 5 Accuracy: 94.28
+ Weights: https://download.openmmlab.com/mmclassification/v0/wrn/wide-resnet101_3rdparty_8xb32_in1k_20220304-8d5f9d61.pth
+ Config: configs/wrn/wide-resnet101_8xb32_in1k.py
+ Converted From:
+ Weights: https://download.pytorch.org/models/wide_resnet101_2-32ee1156.pth
+ Code: https://github.com/pytorch/vision/blob/main/torchvision/models/resnet.py
+ - Name: wide-resnet50_3rdparty-timm_8xb32_in1k
+ Metadata:
+ FLOPs: 11440000000 # 11.44G
+ Parameters: 68880000 # 68.88M
+ In Collection: Wide-ResNet
+ Results:
+ - Task: Image Classification
+ Dataset: ImageNet-1k
+ Metrics:
+ Top 1 Accuracy: 81.45
+ Top 5 Accuracy: 95.53
+ Weights: https://download.openmmlab.com/mmclassification/v0/wrn/wide-resnet50_3rdparty-timm_8xb32_in1k_20220304-83ae4399.pth
+ Config: configs/wrn/wide-resnet50_timm_8xb32_in1k.py
+ Converted From:
+ Weights: https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/wide_resnet50_racm-8234f177.pth
+ Code: https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/resnet.py
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/wrn/wide-resnet101_8xb32_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/wrn/wide-resnet101_8xb32_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..d1bf5e5e5fac3655bd27f64f4c5c5a1316403a3b
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/wrn/wide-resnet101_8xb32_in1k.py
@@ -0,0 +1,7 @@
+_base_ = [
+ '../_base_/models/wide-resnet50.py',
+ '../_base_/datasets/imagenet_bs32_pil_resize.py',
+ '../_base_/schedules/imagenet_bs256.py', '../_base_/default_runtime.py'
+]
+
+model = dict(backbone=dict(depth=101))
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/wrn/wide-resnet50_8xb32_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/wrn/wide-resnet50_8xb32_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..edf6a0518ac73f4eaa54f261ecbfce8acf0f2035
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/wrn/wide-resnet50_8xb32_in1k.py
@@ -0,0 +1,5 @@
+_base_ = [
+ '../_base_/models/wide-resnet50.py',
+ '../_base_/datasets/imagenet_bs32_pil_resize.py',
+ '../_base_/schedules/imagenet_bs256.py', '../_base_/default_runtime.py'
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/configs/wrn/wide-resnet50_timm_8xb32_in1k.py b/openmmlab_test/mmclassification-0.24.1/configs/wrn/wide-resnet50_timm_8xb32_in1k.py
new file mode 100644
index 0000000000000000000000000000000000000000..8dca8f37319f8d60df0e42123b2ebe16a3f7d9d8
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/configs/wrn/wide-resnet50_timm_8xb32_in1k.py
@@ -0,0 +1,5 @@
+_base_ = [
+ '../_base_/models/wide-resnet50.py',
+ '../_base_/datasets/imagenet_bs32_pil_bicubic.py',
+ '../_base_/schedules/imagenet_bs256.py', '../_base_/default_runtime.py'
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/demo/bird.JPEG b/openmmlab_test/mmclassification-0.24.1/demo/bird.JPEG
new file mode 100644
index 0000000000000000000000000000000000000000..9c132a099e87d1c3c1a76dfd9201b03801301eab
Binary files /dev/null and b/openmmlab_test/mmclassification-0.24.1/demo/bird.JPEG differ
diff --git a/openmmlab_test/mmclassification-0.24.1/demo/cat-dog.png b/openmmlab_test/mmclassification-0.24.1/demo/cat-dog.png
new file mode 100644
index 0000000000000000000000000000000000000000..2ddd0fdb2e6c9269a9739d525a8feae05af2ee5f
Binary files /dev/null and b/openmmlab_test/mmclassification-0.24.1/demo/cat-dog.png differ
diff --git a/openmmlab_test/mmclassification-speed-benchmark/demo/demo.JPEG b/openmmlab_test/mmclassification-0.24.1/demo/demo.JPEG
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/demo/demo.JPEG
rename to openmmlab_test/mmclassification-0.24.1/demo/demo.JPEG
diff --git a/openmmlab_test/mmclassification-0.24.1/demo/dog.jpg b/openmmlab_test/mmclassification-0.24.1/demo/dog.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c68fb054ad2dd2e5968a866c3140849c84b5484b
Binary files /dev/null and b/openmmlab_test/mmclassification-0.24.1/demo/dog.jpg differ
diff --git a/openmmlab_test/mmclassification-0.24.1/demo/image_demo.py b/openmmlab_test/mmclassification-0.24.1/demo/image_demo.py
new file mode 100644
index 0000000000000000000000000000000000000000..8539ef48fe14e160bad53bdb241db5612cf4216f
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/demo/image_demo.py
@@ -0,0 +1,33 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from argparse import ArgumentParser
+
+import mmcv
+
+from mmcls.apis import inference_model, init_model, show_result_pyplot
+
+
+def main():
+ parser = ArgumentParser()
+ parser.add_argument('img', help='Image file')
+ parser.add_argument('config', help='Config file')
+ parser.add_argument('checkpoint', help='Checkpoint file')
+ parser.add_argument(
+ '--show',
+ action='store_true',
+ help='Whether to show the predict results by matplotlib.')
+ parser.add_argument(
+ '--device', default='cuda:0', help='Device used for inference')
+ args = parser.parse_args()
+
+ # build the model from a config file and a checkpoint file
+ model = init_model(args.config, args.checkpoint, device=args.device)
+ # test a single image
+ result = inference_model(model, args.img)
+ # show the results
+ print(mmcv.dump(result, file_format='json', indent=4))
+ if args.show:
+ show_result_pyplot(model, args.img, result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/openmmlab_test/mmclassification-0.24.1/demo/ipu_train_example.sh b/openmmlab_test/mmclassification-0.24.1/demo/ipu_train_example.sh
new file mode 100644
index 0000000000000000000000000000000000000000..94c8456d97897a717166d83fb4a494a8a61bfceb
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/demo/ipu_train_example.sh
@@ -0,0 +1,9 @@
+
+
+# get SOTA accuracy 81.2 for 224 input ViT fine-tuning, reference is below:
+# https://github.com/google-research/vision_transformer#available-vit-models
+# cfg: vit-base-p16_ft-4xb544_in1k-224_ipu train model in fp16 precision
+# 8 epoch, 2176 batch size, 16 IPUs, 4 replicas, model Tput = 5600 images, training time 0.6 hour roughly
+cfg_name=vit-base-p16_ft-4xb544_in1k-224_ipu
+python3 tools/train.py configs/vision_transformer/${cfg_name}.py --ipu-replicas 4 --no-validate &&
+python3 tools/test.py configs/vision_transformer/${cfg_name}.py work_dirs/${cfg_name}/latest.pth --metrics accuracy --device ipu
diff --git a/openmmlab_test/mmclassification-0.24.1/docker/Dockerfile b/openmmlab_test/mmclassification-0.24.1/docker/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..fc36510f8e710424c72d37f9f760e13fba2861a2
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docker/Dockerfile
@@ -0,0 +1,23 @@
+ARG PYTORCH="1.8.1"
+ARG CUDA="10.2"
+ARG CUDNN="7"
+
+FROM pytorch/pytorch:${PYTORCH}-cuda${CUDA}-cudnn${CUDNN}-devel
+
+ENV TORCH_CUDA_ARCH_LIST="6.0 6.1 7.0+PTX"
+ENV TORCH_NVCC_FLAGS="-Xfatbin -compress-all"
+ENV CMAKE_PREFIX_PATH="(dirname(which conda))/../"
+
+RUN apt-get update && apt-get install -y ffmpeg libsm6 libxext6 git ninja-build libglib2.0-0 libsm6 libxrender-dev libxext6 \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
+
+# Install MMCV
+RUN pip install openmim
+RUN mim install mmcv-full
+
+# Install MMClassification
+RUN conda clean --all
+RUN git clone https://github.com/open-mmlab/mmclassification.git
+WORKDIR ./mmclassification
+RUN pip install --no-cache-dir -e .
diff --git a/openmmlab_test/mmclassification-0.24.1/docker/serve/Dockerfile b/openmmlab_test/mmclassification-0.24.1/docker/serve/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..3056e9053e9c05e8235b34d226496603e8572d83
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docker/serve/Dockerfile
@@ -0,0 +1,49 @@
+ARG PYTORCH="1.8.1"
+ARG CUDA="10.2"
+ARG CUDNN="7"
+FROM pytorch/pytorch:${PYTORCH}-cuda${CUDA}-cudnn${CUDNN}-devel
+
+ARG MMCV="1.7.0"
+ARG MMCLS="0.24.1"
+
+ENV PYTHONUNBUFFERED TRUE
+
+RUN apt-get update && \
+ DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
+ ca-certificates \
+ g++ \
+ openjdk-11-jre-headless \
+ # MMDet Requirements
+ ffmpeg libsm6 libxext6 git ninja-build libglib2.0-0 libsm6 libxrender-dev libxext6 \
+ && rm -rf /var/lib/apt/lists/*
+
+ENV PATH="/opt/conda/bin:$PATH"
+RUN export FORCE_CUDA=1
+
+# TORCHSEVER
+RUN pip install torchserve torch-model-archiver
+
+# MMLAB
+ARG PYTORCH
+ARG CUDA
+RUN ["/bin/bash", "-c", "pip install mmcv-full==${MMCV} -f https://download.openmmlab.com/mmcv/dist/cu${CUDA//./}/torch${PYTORCH}/index.html"]
+RUN pip install mmcls==${MMCLS}
+
+RUN useradd -m model-server \
+ && mkdir -p /home/model-server/tmp
+
+COPY entrypoint.sh /usr/local/bin/entrypoint.sh
+
+RUN chmod +x /usr/local/bin/entrypoint.sh \
+ && chown -R model-server /home/model-server
+
+COPY config.properties /home/model-server/config.properties
+RUN mkdir /home/model-server/model-store && chown -R model-server /home/model-server/model-store
+
+EXPOSE 8080 8081 8082
+
+USER model-server
+WORKDIR /home/model-server
+ENV TEMP=/home/model-server/tmp
+ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
+CMD ["serve"]
diff --git a/openmmlab_test/mmclassification-speed-benchmark/docker/serve/config.properties b/openmmlab_test/mmclassification-0.24.1/docker/serve/config.properties
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/docker/serve/config.properties
rename to openmmlab_test/mmclassification-0.24.1/docker/serve/config.properties
diff --git a/openmmlab_test/mmclassification-speed-benchmark/docker/serve/entrypoint.sh b/openmmlab_test/mmclassification-0.24.1/docker/serve/entrypoint.sh
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/docker/serve/entrypoint.sh
rename to openmmlab_test/mmclassification-0.24.1/docker/serve/entrypoint.sh
diff --git a/openmmlab_test/mmclassification-speed-benchmark/docs/Makefile b/openmmlab_test/mmclassification-0.24.1/docs/en/Makefile
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/docs/Makefile
rename to openmmlab_test/mmclassification-0.24.1/docs/en/Makefile
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/_static/css/readthedocs.css b/openmmlab_test/mmclassification-0.24.1/docs/en/_static/css/readthedocs.css
new file mode 100644
index 0000000000000000000000000000000000000000..577a67a88fa6693c9256d9d971a1ffe3eb6460e7
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/_static/css/readthedocs.css
@@ -0,0 +1,27 @@
+.header-logo {
+ background-image: url("../image/mmcls-logo.png");
+ background-size: 204px 40px;
+ height: 40px;
+ width: 204px;
+}
+
+pre {
+ white-space: pre;
+}
+
+article.pytorch-article section code {
+ padding: .2em .4em;
+ background-color: #f3f4f7;
+ border-radius: 5px;
+}
+
+/* Disable the change in tables */
+article.pytorch-article section table code {
+ padding: unset;
+ background-color: unset;
+ border-radius: unset;
+}
+
+table.autosummary td {
+ width: 50%
+}
diff --git a/openmmlab_test/mmclassification-speed-benchmark/resources/mmcls-logo.png b/openmmlab_test/mmclassification-0.24.1/docs/en/_static/image/mmcls-logo.png
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/resources/mmcls-logo.png
rename to openmmlab_test/mmclassification-0.24.1/docs/en/_static/image/mmcls-logo.png
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/_static/image/tools/analysis/analyze_log.jpg b/openmmlab_test/mmclassification-0.24.1/docs/en/_static/image/tools/analysis/analyze_log.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8eb1a27d6464d255b84b23a7460a5f622f51712f
Binary files /dev/null and b/openmmlab_test/mmclassification-0.24.1/docs/en/_static/image/tools/analysis/analyze_log.jpg differ
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/_static/image/tools/visualization/lr_schedule1.png b/openmmlab_test/mmclassification-0.24.1/docs/en/_static/image/tools/visualization/lr_schedule1.png
new file mode 100644
index 0000000000000000000000000000000000000000..31fca35bb525280af6f83b755aef3f2495f07ed2
Binary files /dev/null and b/openmmlab_test/mmclassification-0.24.1/docs/en/_static/image/tools/visualization/lr_schedule1.png differ
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/_static/image/tools/visualization/lr_schedule2.png b/openmmlab_test/mmclassification-0.24.1/docs/en/_static/image/tools/visualization/lr_schedule2.png
new file mode 100644
index 0000000000000000000000000000000000000000..8c6231db8db2a60c3be70d0e4388f5565bcd915b
Binary files /dev/null and b/openmmlab_test/mmclassification-0.24.1/docs/en/_static/image/tools/visualization/lr_schedule2.png differ
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/_static/js/custom.js b/openmmlab_test/mmclassification-0.24.1/docs/en/_static/js/custom.js
new file mode 100644
index 0000000000000000000000000000000000000000..44a4057dc20cd44442c2d7a0869e864bf30f4e46
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/_static/js/custom.js
@@ -0,0 +1 @@
+var collapsedSections = ['Model zoo'];
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/_templates/classtemplate.rst b/openmmlab_test/mmclassification-0.24.1/docs/en/_templates/classtemplate.rst
new file mode 100644
index 0000000000000000000000000000000000000000..4f74842394ec9807fb1ae2d8f05a8a57e9a2e24c
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/_templates/classtemplate.rst
@@ -0,0 +1,14 @@
+.. role:: hidden
+ :class: hidden-section
+.. currentmodule:: {{ module }}
+
+
+{{ name | underline}}
+
+.. autoclass:: {{ name }}
+ :members:
+
+
+..
+ autogenerated from source/_templates/classtemplate.rst
+ note it does not have :inherited-members:
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/api/apis.rst b/openmmlab_test/mmclassification-0.24.1/docs/en/api/apis.rst
new file mode 100644
index 0000000000000000000000000000000000000000..67e05b9349f8151df694382a16b566cda5dd3b81
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/api/apis.rst
@@ -0,0 +1,45 @@
+.. role:: hidden
+ :class: hidden-section
+
+mmcls.apis
+===================================
+
+These are some high-level APIs for classification tasks.
+
+.. contents:: mmcls.apis
+ :depth: 2
+ :local:
+ :backlinks: top
+
+.. currentmodule:: mmcls.apis
+
+Train
+------------------
+
+.. autosummary::
+ :toctree: generated
+ :nosignatures:
+
+ init_random_seed
+ set_random_seed
+ train_model
+
+Test
+------------------
+.. autosummary::
+ :toctree: generated
+ :nosignatures:
+
+ single_gpu_test
+ multi_gpu_test
+
+Inference
+------------------
+
+.. autosummary::
+ :toctree: generated
+ :nosignatures:
+
+ init_model
+ inference_model
+ show_result_pyplot
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/api/core.rst b/openmmlab_test/mmclassification-0.24.1/docs/en/api/core.rst
new file mode 100644
index 0000000000000000000000000000000000000000..83e1dbf42f0343a78986c68889b8965ddee45307
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/api/core.rst
@@ -0,0 +1,62 @@
+.. role:: hidden
+ :class: hidden-section
+
+mmcls.core
+===================================
+
+This package includes some runtime components. These components are useful in
+classification tasks but not supported by MMCV yet.
+
+.. note::
+
+ Some components may be moved to MMCV in the future.
+
+.. contents:: mmcls.core
+ :depth: 2
+ :local:
+ :backlinks: top
+
+.. currentmodule:: mmcls.core
+
+Evaluation
+------------------
+
+Evaluation metrics calculation functions
+
+.. autosummary::
+ :toctree: generated
+ :nosignatures:
+
+ precision
+ recall
+ f1_score
+ precision_recall_f1
+ average_precision
+ mAP
+ support
+ average_performance
+ calculate_confusion_matrix
+
+Hook
+------------------
+
+.. autosummary::
+ :toctree: generated
+ :nosignatures:
+ :template: classtemplate.rst
+
+ ClassNumCheckHook
+ PreciseBNHook
+ CosineAnnealingCooldownLrUpdaterHook
+ MMClsWandbHook
+
+
+Optimizers
+------------------
+
+.. autosummary::
+ :toctree: generated
+ :nosignatures:
+ :template: classtemplate.rst
+
+ Lamb
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/api/datasets.rst b/openmmlab_test/mmclassification-0.24.1/docs/en/api/datasets.rst
new file mode 100644
index 0000000000000000000000000000000000000000..640ce1ad7d2bf05986e0499c300b96e215f84076
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/api/datasets.rst
@@ -0,0 +1,61 @@
+.. role:: hidden
+ :class: hidden-section
+
+mmcls.datasets
+===================================
+
+The ``datasets`` package contains several usual datasets for image classification tasks and some dataset wrappers.
+
+.. currentmodule:: mmcls.datasets
+
+Custom Dataset
+--------------
+
+.. autoclass:: CustomDataset
+
+ImageNet
+--------
+
+.. autoclass:: ImageNet
+
+.. autoclass:: ImageNet21k
+
+CIFAR
+-----
+
+.. autoclass:: CIFAR10
+
+.. autoclass:: CIFAR100
+
+MNIST
+-----
+
+.. autoclass:: MNIST
+
+.. autoclass:: FashionMNIST
+
+VOC
+---
+
+.. autoclass:: VOC
+
+StanfordCars Cars
+-----------------
+
+.. autoclass:: StanfordCars
+
+Base classes
+------------
+
+.. autoclass:: BaseDataset
+
+.. autoclass:: MultiLabelDataset
+
+Dataset Wrappers
+----------------
+
+.. autoclass:: ConcatDataset
+
+.. autoclass:: RepeatDataset
+
+.. autoclass:: ClassBalancedDataset
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/api/models.rst b/openmmlab_test/mmclassification-0.24.1/docs/en/api/models.rst
new file mode 100644
index 0000000000000000000000000000000000000000..0c317916d37079a617cbf59136b4788ad3733434
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/api/models.rst
@@ -0,0 +1,141 @@
+.. role:: hidden
+ :class: hidden-section
+
+mmcls.models
+===================================
+
+The ``models`` package contains several sub-packages for addressing the different components of a model.
+
+- :ref:`classifiers`: The top-level module which defines the whole process of a classification model.
+- :ref:`backbones`: Usually a feature extraction network, e.g., ResNet, MobileNet.
+- :ref:`necks`: The component between backbones and heads, e.g., GlobalAveragePooling.
+- :ref:`heads`: The component for specific tasks. In MMClassification, we provides heads for classification.
+- :ref:`losses`: Loss functions.
+
+.. currentmodule:: mmcls.models
+
+.. autosummary::
+ :toctree: generated
+ :nosignatures:
+
+ build_classifier
+ build_backbone
+ build_neck
+ build_head
+ build_loss
+
+.. _classifiers:
+
+Classifier
+------------------
+
+.. autosummary::
+ :toctree: generated
+ :nosignatures:
+ :template: classtemplate.rst
+
+ BaseClassifier
+ ImageClassifier
+
+.. _backbones:
+
+Backbones
+------------------
+
+.. autosummary::
+ :toctree: generated
+ :nosignatures:
+ :template: classtemplate.rst
+
+ AlexNet
+ CSPDarkNet
+ CSPNet
+ CSPResNeXt
+ CSPResNet
+ Conformer
+ ConvMixer
+ ConvNeXt
+ DenseNet
+ DistilledVisionTransformer
+ EfficientNet
+ HRNet
+ LeNet5
+ MlpMixer
+ MobileNetV2
+ MobileNetV3
+ PCPVT
+ PoolFormer
+ RegNet
+ RepMLPNet
+ RepVGG
+ Res2Net
+ ResNeSt
+ ResNeXt
+ ResNet
+ ResNetV1c
+ ResNetV1d
+ ResNet_CIFAR
+ SEResNeXt
+ SEResNet
+ SVT
+ ShuffleNetV1
+ ShuffleNetV2
+ SwinTransformer
+ T2T_ViT
+ TIMMBackbone
+ TNT
+ VAN
+ VGG
+ VisionTransformer
+ EfficientFormer
+ HorNet
+
+.. _necks:
+
+Necks
+------------------
+
+.. autosummary::
+ :toctree: generated
+ :nosignatures:
+ :template: classtemplate.rst
+
+ GlobalAveragePooling
+ GeneralizedMeanPooling
+ HRFuseScales
+
+.. _heads:
+
+Heads
+------------------
+
+.. autosummary::
+ :toctree: generated
+ :nosignatures:
+ :template: classtemplate.rst
+
+ ClsHead
+ LinearClsHead
+ StackedLinearClsHead
+ MultiLabelClsHead
+ MultiLabelLinearClsHead
+ VisionTransformerClsHead
+ DeiTClsHead
+ ConformerHead
+
+.. _losses:
+
+Losses
+------------------
+
+.. autosummary::
+ :toctree: generated
+ :nosignatures:
+ :template: classtemplate.rst
+
+ Accuracy
+ AsymmetricLoss
+ CrossEntropyLoss
+ LabelSmoothLoss
+ FocalLoss
+ SeesawLoss
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/api/models.utils.augment.rst b/openmmlab_test/mmclassification-0.24.1/docs/en/api/models.utils.augment.rst
new file mode 100644
index 0000000000000000000000000000000000000000..54442f7130afd375163924315ba2be82b57719c2
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/api/models.utils.augment.rst
@@ -0,0 +1,35 @@
+.. role:: hidden
+ :class: hidden-section
+
+Batch Augmentation
+===================================
+
+Batch augmentation is the augmentation which involve multiple samples, such as Mixup and CutMix.
+
+In MMClassification, these batch augmentation is used as a part of :ref:`classifiers`. A typical usage is as below:
+
+.. code-block:: python
+
+ model = dict(
+ backbone = ...,
+ neck = ...,
+ head = ...,
+ train_cfg=dict(augments=[
+ dict(type='BatchMixup', alpha=0.8, prob=0.5, num_classes=num_classes),
+ dict(type='BatchCutMix', alpha=1.0, prob=0.5, num_classes=num_classes),
+ ]))
+ )
+
+.. currentmodule:: mmcls.models.utils.augment
+
+Mixup
+-----
+.. autoclass:: BatchMixupLayer
+
+CutMix
+------
+.. autoclass:: BatchCutMixLayer
+
+ResizeMix
+---------
+.. autoclass:: BatchResizeMixLayer
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/api/models.utils.rst b/openmmlab_test/mmclassification-0.24.1/docs/en/api/models.utils.rst
new file mode 100644
index 0000000000000000000000000000000000000000..c9687a72149df8ddc4a835e7b9a2ab938659b99f
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/api/models.utils.rst
@@ -0,0 +1,50 @@
+.. role:: hidden
+ :class: hidden-section
+
+mmcls.models.utils
+===================================
+
+This package includes some helper functions and common components used in various networks.
+
+.. contents:: mmcls.models.utils
+ :depth: 2
+ :local:
+ :backlinks: top
+
+.. currentmodule:: mmcls.models.utils
+
+Common Components
+------------------
+
+.. autosummary::
+ :toctree: generated
+ :nosignatures:
+ :template: classtemplate.rst
+
+ InvertedResidual
+ SELayer
+ ShiftWindowMSA
+ MultiheadAttention
+ ConditionalPositionEncoding
+
+Helper Functions
+------------------
+
+channel_shuffle
+^^^^^^^^^^^^^^^
+.. autofunction:: channel_shuffle
+
+make_divisible
+^^^^^^^^^^^^^^
+.. autofunction:: make_divisible
+
+to_ntuple
+^^^^^^^^^^^^^^
+.. autofunction:: to_ntuple
+.. autofunction:: to_2tuple
+.. autofunction:: to_3tuple
+.. autofunction:: to_4tuple
+
+is_tracing
+^^^^^^^^^^^^^^
+.. autofunction:: is_tracing
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/api/transforms.rst b/openmmlab_test/mmclassification-0.24.1/docs/en/api/transforms.rst
new file mode 100644
index 0000000000000000000000000000000000000000..4a39f082fa48c6d0fedfcdac8fe18b18223d50a7
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/api/transforms.rst
@@ -0,0 +1,171 @@
+.. role:: hidden
+ :class: hidden-section
+
+Data Transformations
+***********************************
+
+In MMClassification, the data preparation and the dataset is decomposed. The
+datasets only define how to get samples' basic information from the file
+system. These basic information includes the ground-truth label and raw images
+data / the paths of images.
+
+To prepare the inputs data, we need to do some transformations on these basic
+information. These transformations includes loading, preprocessing and
+formatting. And a series of data transformations makes up a data pipeline.
+Therefore, you can find the a ``pipeline`` argument in the configs of dataset,
+for example:
+
+.. code:: python
+
+ img_norm_cfg = dict(
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
+ train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='RandomResizedCrop', size=224),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='Collect', keys=['img', 'gt_label'])
+ ]
+ test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='Resize', size=256),
+ dict(type='CenterCrop', crop_size=224),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img'])
+ ]
+
+ data = dict(
+ train=dict(..., pipeline=train_pipeline),
+ val=dict(..., pipeline=test_pipeline),
+ test=dict(..., pipeline=test_pipeline),
+ )
+
+Every item of a pipeline list is one of the following data transformations class. And if you want to add a custom data transformation class, the tutorial :doc:`Custom Data Pipelines ` will help you.
+
+.. contents:: mmcls.datasets.pipelines
+ :depth: 2
+ :local:
+ :backlinks: top
+
+.. currentmodule:: mmcls.datasets.pipelines
+
+Loading
+=======
+
+LoadImageFromFile
+---------------------
+.. autoclass:: LoadImageFromFile
+
+Preprocessing and Augmentation
+==============================
+
+CenterCrop
+---------------------
+.. autoclass:: CenterCrop
+
+Lighting
+---------------------
+.. autoclass:: Lighting
+
+Normalize
+---------------------
+.. autoclass:: Normalize
+
+Pad
+---------------------
+.. autoclass:: Pad
+
+Resize
+---------------------
+.. autoclass:: Resize
+
+RandomCrop
+---------------------
+.. autoclass:: RandomCrop
+
+RandomErasing
+---------------------
+.. autoclass:: RandomErasing
+
+RandomFlip
+---------------------
+.. autoclass:: RandomFlip
+
+RandomGrayscale
+---------------------
+.. autoclass:: RandomGrayscale
+
+RandomResizedCrop
+---------------------
+.. autoclass:: RandomResizedCrop
+
+ColorJitter
+---------------------
+.. autoclass:: ColorJitter
+
+
+Composed Augmentation
+---------------------
+Composed augmentation is a kind of methods which compose a series of data
+augmentation transformations, such as ``AutoAugment`` and ``RandAugment``.
+
+.. autoclass:: AutoAugment
+
+.. autoclass:: RandAugment
+
+In composed augmentation, we need to specify several data transformations or
+several groups of data transformations (The ``policies`` argument) as the
+random sampling space. These data transformations are chosen from the below
+table. In addition, we provide some preset policies in `this folder`_.
+
+.. _this folder: https://github.com/open-mmlab/mmclassification/tree/master/configs/_base_/datasets/pipelines
+
+.. autosummary::
+ :toctree: generated
+ :nosignatures:
+ :template: classtemplate.rst
+
+ AutoContrast
+ Brightness
+ ColorTransform
+ Contrast
+ Cutout
+ Equalize
+ Invert
+ Posterize
+ Rotate
+ Sharpness
+ Shear
+ Solarize
+ SolarizeAdd
+ Translate
+
+Formatting
+==========
+
+Collect
+---------------------
+.. autoclass:: Collect
+
+ImageToTensor
+---------------------
+.. autoclass:: ImageToTensor
+
+ToNumpy
+---------------------
+.. autoclass:: ToNumpy
+
+ToPIL
+---------------------
+.. autoclass:: ToPIL
+
+ToTensor
+---------------------
+.. autoclass:: ToTensor
+
+Transpose
+---------------------
+.. autoclass:: Transpose
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/api/utils.rst b/openmmlab_test/mmclassification-0.24.1/docs/en/api/utils.rst
new file mode 100644
index 0000000000000000000000000000000000000000..206fc82c087bfb4b69c658f62bd36f72a225194e
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/api/utils.rst
@@ -0,0 +1,23 @@
+.. role:: hidden
+ :class: hidden-section
+
+mmcls.utils
+===================================
+
+These are some useful help function in the ``utils`` package.
+
+.. contents:: mmcls.utils
+ :depth: 1
+ :local:
+ :backlinks: top
+
+.. currentmodule:: mmcls.utils
+
+.. autosummary::
+ :toctree: generated
+ :nosignatures:
+
+ collect_env
+ get_root_logger
+ load_json_log
+ setup_multi_processes
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/changelog.md b/openmmlab_test/mmclassification-0.24.1/docs/en/changelog.md
new file mode 100644
index 0000000000000000000000000000000000000000..c044f4baf5f003ebdcd80c53128911a43e9214cc
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/changelog.md
@@ -0,0 +1,718 @@
+# Changelog
+
+## v0.24.1(31/10/2022)
+
+### New Features
+
+- Support mmcls with NPU backend. ([#1072](https://github.com/open-mmlab/mmclassification/pull/1072))
+
+### Bug Fixes
+
+- Fix performance issue in convnext DDP train. ([#1098](https://github.com/open-mmlab/mmclassification/pull/1098))
+
+## v0.24.0(30/9/2022)
+
+### Highlights
+
+- Support HorNet, EfficientFormerm, SwinTransformer V2 and MViT backbones.
+- Support Standford Cars dataset.
+
+### New Features
+
+- Support HorNet Backbone. ([#1013](https://github.com/open-mmlab/mmclassification/pull/1013))
+- Support EfficientFormer. ([#954](https://github.com/open-mmlab/mmclassification/pull/954))
+- Support Stanford Cars dataset. ([#893](https://github.com/open-mmlab/mmclassification/pull/893))
+- Support CSRA head. ([#881](https://github.com/open-mmlab/mmclassification/pull/881))
+- Support Swin Transform V2. ([#799](https://github.com/open-mmlab/mmclassification/pull/799))
+- Support MViT and add checkpoints. ([#924](https://github.com/open-mmlab/mmclassification/pull/924))
+
+### Improvements
+
+- \[Improve\] replace loop of progressbar in api/test. ([#878](https://github.com/open-mmlab/mmclassification/pull/878))
+- \[Enhance\] RepVGG for YOLOX-PAI. ([#1025](https://github.com/open-mmlab/mmclassification/pull/1025))
+- \[Enhancement\] Update VAN. ([#1017](https://github.com/open-mmlab/mmclassification/pull/1017))
+- \[Refactor\] Re-write `get_sinusoid_encoding` from third-party implementation. ([#965](https://github.com/open-mmlab/mmclassification/pull/965))
+- \[Improve\] Upgrade onnxsim to v0.4.0. ([#915](https://github.com/open-mmlab/mmclassification/pull/915))
+- \[Improve\] Fixed typo in `RepVGG`. ([#985](https://github.com/open-mmlab/mmclassification/pull/985))
+- \[Improve\] Using `train_step` instead of `forward` in PreciseBNHook ([#964](https://github.com/open-mmlab/mmclassification/pull/964))
+- \[Improve\] Use `forward_dummy` to calculate FLOPS. ([#953](https://github.com/open-mmlab/mmclassification/pull/953))
+
+### Bug Fixes
+
+- Fix warning with `torch.meshgrid`. ([#860](https://github.com/open-mmlab/mmclassification/pull/860))
+- Add matplotlib minimum version requriments. ([#909](https://github.com/open-mmlab/mmclassification/pull/909))
+- val loader should not drop last by default. ([#857](https://github.com/open-mmlab/mmclassification/pull/857))
+- Fix config.device bug in toturial. ([#1059](https://github.com/open-mmlab/mmclassification/pull/1059))
+- Fix attenstion clamp max params ([#1034](https://github.com/open-mmlab/mmclassification/pull/1034))
+- Fix device mismatch in Swin-v2. ([#976](https://github.com/open-mmlab/mmclassification/pull/976))
+- Fix the output position of Swin-Transformer. ([#947](https://github.com/open-mmlab/mmclassification/pull/947))
+
+### Docs Update
+
+- Fix typo in config.md. ([#827](https://github.com/open-mmlab/mmclassification/pull/827))
+- Add version for torchvision to avoide error. ([#903](https://github.com/open-mmlab/mmclassification/pull/903))
+- Fixed typo for `--out-dir` option of analyze_results.py. ([#898](https://github.com/open-mmlab/mmclassification/pull/898))
+- Refine the docstring of RegNet ([#935](https://github.com/open-mmlab/mmclassification/pull/935))
+
+## v0.23.2(28/7/2022)
+
+### New Features
+
+- Support MPS device. ([#894](https://github.com/open-mmlab/mmclassification/pull/894))
+
+### Bug Fixes
+
+- Fix a bug in Albu which caused crashing. ([#918](https://github.com/open-mmlab/mmclassification/pull/918))
+
+## v0.23.1(2/6/2022)
+
+### New Features
+
+- Dedicated MMClsWandbHook for MMClassification (Weights and Biases Integration) ([#764](https://github.com/open-mmlab/mmclassification/pull/764))
+
+### Improvements
+
+- Use mdformat instead of markdownlint to format markdown. ([#844](https://github.com/open-mmlab/mmclassification/pull/844))
+
+### Bug Fixes
+
+- Fix wrong `--local_rank`.
+
+### Docs Update
+
+- Update install tutorials. ([#854](https://github.com/open-mmlab/mmclassification/pull/854))
+- Fix wrong link in README. ([#835](https://github.com/open-mmlab/mmclassification/pull/835))
+
+## v0.23.0(1/5/2022)
+
+### New Features
+
+- Support DenseNet. ([#750](https://github.com/open-mmlab/mmclassification/pull/750))
+- Support VAN. ([#739](https://github.com/open-mmlab/mmclassification/pull/739))
+
+### Improvements
+
+- Support training on IPU and add fine-tuning configs of ViT. ([#723](https://github.com/open-mmlab/mmclassification/pull/723))
+
+### Docs Update
+
+- New style API reference, and easier to use! Welcome [view it](https://mmclassification.readthedocs.io/en/master/api/models.html). ([#774](https://github.com/open-mmlab/mmclassification/pull/774))
+
+## v0.22.1(15/4/2022)
+
+### New Features
+
+- \[Feature\] Support resize relative position embedding in `SwinTransformer`. ([#749](https://github.com/open-mmlab/mmclassification/pull/749))
+- \[Feature\] Add PoolFormer backbone and checkpoints. ([#746](https://github.com/open-mmlab/mmclassification/pull/746))
+
+### Improvements
+
+- \[Enhance\] Improve CPE performance by reduce memory copy. ([#762](https://github.com/open-mmlab/mmclassification/pull/762))
+- \[Enhance\] Add extra dataloader settings in configs. ([#752](https://github.com/open-mmlab/mmclassification/pull/752))
+
+## v0.22.0(30/3/2022)
+
+### Highlights
+
+- Support a series of CSP Network, such as CSP-ResNet, CSP-ResNeXt and CSP-DarkNet.
+- A new `CustomDataset` class to help you build dataset of yourself!
+- Support ConvMixer, RepMLP and new dataset - CUB dataset.
+
+### New Features
+
+- \[Feature\] Add CSPNet and backbone and checkpoints ([#735](https://github.com/open-mmlab/mmclassification/pull/735))
+- \[Feature\] Add `CustomDataset`. ([#738](https://github.com/open-mmlab/mmclassification/pull/738))
+- \[Feature\] Add diff seeds to diff ranks. ([#744](https://github.com/open-mmlab/mmclassification/pull/744))
+- \[Feature\] Support ConvMixer. ([#716](https://github.com/open-mmlab/mmclassification/pull/716))
+- \[Feature\] Our `dist_train` & `dist_test` tools support distributed training on multiple machines. ([#734](https://github.com/open-mmlab/mmclassification/pull/734))
+- \[Feature\] Add RepMLP backbone and checkpoints. ([#709](https://github.com/open-mmlab/mmclassification/pull/709))
+- \[Feature\] Support CUB dataset. ([#703](https://github.com/open-mmlab/mmclassification/pull/703))
+- \[Feature\] Support ResizeMix. ([#676](https://github.com/open-mmlab/mmclassification/pull/676))
+
+### Improvements
+
+- \[Enhance\] Use `--a-b` instead of `--a_b` in arguments. ([#754](https://github.com/open-mmlab/mmclassification/pull/754))
+- \[Enhance\] Add `get_cat_ids` and `get_gt_labels` to KFoldDataset. ([#721](https://github.com/open-mmlab/mmclassification/pull/721))
+- \[Enhance\] Set torch seed in `worker_init_fn`. ([#733](https://github.com/open-mmlab/mmclassification/pull/733))
+
+### Bug Fixes
+
+- \[Fix\] Fix the discontiguous output feature map of ConvNeXt. ([#743](https://github.com/open-mmlab/mmclassification/pull/743))
+
+### Docs Update
+
+- \[Docs\] Add brief installation steps in README for copy&paste. ([#755](https://github.com/open-mmlab/mmclassification/pull/755))
+- \[Docs\] fix logo url link from mmocr to mmcls. ([#732](https://github.com/open-mmlab/mmclassification/pull/732))
+
+## v0.21.0(04/03/2022)
+
+### Highlights
+
+- Support ResNetV1c and Wide-ResNet, and provide pre-trained models.
+- Support dynamic input shape for ViT-based algorithms. Now our ViT, DeiT, Swin-Transformer and T2T-ViT support forwarding with any input shape.
+- Reproduce training results of DeiT. And our DeiT-T and DeiT-S have higher accuracy comparing with the official weights.
+
+### New Features
+
+- Add ResNetV1c. ([#692](https://github.com/open-mmlab/mmclassification/pull/692))
+- Support Wide-ResNet. ([#715](https://github.com/open-mmlab/mmclassification/pull/715))
+- Support gem pooling ([#677](https://github.com/open-mmlab/mmclassification/pull/677))
+
+### Improvements
+
+- Reproduce training results of DeiT. ([#711](https://github.com/open-mmlab/mmclassification/pull/711))
+- Add ConvNeXt pretrain models on ImageNet-1k. ([#707](https://github.com/open-mmlab/mmclassification/pull/707))
+- Support dynamic input shape for ViT-based algorithms. ([#706](https://github.com/open-mmlab/mmclassification/pull/706))
+- Add `evaluate` function for ConcatDataset. ([#650](https://github.com/open-mmlab/mmclassification/pull/650))
+- Enhance vis-pipeline tool. ([#604](https://github.com/open-mmlab/mmclassification/pull/604))
+- Return code 1 if scripts runs failed. ([#694](https://github.com/open-mmlab/mmclassification/pull/694))
+- Use PyTorch official `one_hot` to implement `convert_to_one_hot`. ([#696](https://github.com/open-mmlab/mmclassification/pull/696))
+- Add a new pre-commit-hook to automatically add a copyright. ([#710](https://github.com/open-mmlab/mmclassification/pull/710))
+- Add deprecation message for deploy tools. ([#697](https://github.com/open-mmlab/mmclassification/pull/697))
+- Upgrade isort pre-commit hooks. ([#687](https://github.com/open-mmlab/mmclassification/pull/687))
+- Use `--gpu-id` instead of `--gpu-ids` in non-distributed multi-gpu training/testing. ([#688](https://github.com/open-mmlab/mmclassification/pull/688))
+- Remove deprecation. ([#633](https://github.com/open-mmlab/mmclassification/pull/633))
+
+### Bug Fixes
+
+- Fix Conformer forward with irregular input size. ([#686](https://github.com/open-mmlab/mmclassification/pull/686))
+- Add `dist.barrier` to fix a bug in directory checking. ([#666](https://github.com/open-mmlab/mmclassification/pull/666))
+
+## v0.20.1(07/02/2022)
+
+### Bug Fixes
+
+- Fix the MMCV dependency version.
+
+## v0.20.0(30/01/2022)
+
+### Highlights
+
+- Support K-fold cross-validation. The tutorial will be released later.
+- Support HRNet, ConvNeXt, Twins and EfficientNet.
+- Support model conversion from PyTorch to Core-ML by a tool.
+
+### New Features
+
+- Support K-fold cross-validation. ([#563](https://github.com/open-mmlab/mmclassification/pull/563))
+- Support HRNet and add pre-trained models. ([#660](https://github.com/open-mmlab/mmclassification/pull/660))
+- Support ConvNeXt and add pre-trained models. ([#670](https://github.com/open-mmlab/mmclassification/pull/670))
+- Support Twins and add pre-trained models. ([#642](https://github.com/open-mmlab/mmclassification/pull/642))
+- Support EfficientNet and add pre-trained models.([#649](https://github.com/open-mmlab/mmclassification/pull/649))
+- Support `features_only` option in `TIMMBackbone`. ([#668](https://github.com/open-mmlab/mmclassification/pull/668))
+- Add conversion script from pytorch to Core-ML model. ([#597](https://github.com/open-mmlab/mmclassification/pull/597))
+
+### Improvements
+
+- New-style CPU training and inference. ([#674](https://github.com/open-mmlab/mmclassification/pull/674))
+- Add setup multi-processing both in train and test. ([#671](https://github.com/open-mmlab/mmclassification/pull/671))
+- Rewrite channel split operation in ShufflenetV2. ([#632](https://github.com/open-mmlab/mmclassification/pull/632))
+- Deprecate the support for "python setup.py test". ([#646](https://github.com/open-mmlab/mmclassification/pull/646))
+- Support single-label, softmax, custom eps by asymmetric loss. ([#609](https://github.com/open-mmlab/mmclassification/pull/609))
+- Save class names in best checkpoint created by evaluation hook. ([#641](https://github.com/open-mmlab/mmclassification/pull/641))
+
+### Bug Fixes
+
+- Fix potential unexcepted behaviors if `metric_options` is not specified in multi-label evaluation. ([#647](https://github.com/open-mmlab/mmclassification/pull/647))
+- Fix API changes in `pytorch-grad-cam>=1.3.7`. ([#656](https://github.com/open-mmlab/mmclassification/pull/656))
+- Fix bug which breaks `cal_train_time` in `analyze_logs.py`. ([#662](https://github.com/open-mmlab/mmclassification/pull/662))
+
+### Docs Update
+
+- Update README in configs according to OpenMMLab standard. ([#672](https://github.com/open-mmlab/mmclassification/pull/672))
+- Update installation guide and README. ([#624](https://github.com/open-mmlab/mmclassification/pull/624))
+
+## v0.19.0(31/12/2021)
+
+### Highlights
+
+- The feature extraction function has been enhanced. See [#593](https://github.com/open-mmlab/mmclassification/pull/593) for more details.
+- Provide the high-acc ResNet-50 training settings from [*ResNet strikes back*](https://arxiv.org/abs/2110.00476).
+- Reproduce the training accuracy of T2T-ViT & RegNetX, and provide self-training checkpoints.
+- Support DeiT & Conformer backbone and checkpoints.
+- Provide a CAM visualization tool based on [pytorch-grad-cam](https://github.com/jacobgil/pytorch-grad-cam), and detailed [user guide](https://mmclassification.readthedocs.io/en/latest/tools/visualization.html#class-activation-map-visualization)!
+
+### New Features
+
+- Support Precise BN. ([#401](https://github.com/open-mmlab/mmclassification/pull/401))
+- Add CAM visualization tool. ([#577](https://github.com/open-mmlab/mmclassification/pull/577))
+- Repeated Aug and Sampler Registry. ([#588](https://github.com/open-mmlab/mmclassification/pull/588))
+- Add DeiT backbone and checkpoints. ([#576](https://github.com/open-mmlab/mmclassification/pull/576))
+- Support LAMB optimizer. ([#591](https://github.com/open-mmlab/mmclassification/pull/591))
+- Implement the conformer backbone. ([#494](https://github.com/open-mmlab/mmclassification/pull/494))
+- Add the frozen function for Swin Transformer model. ([#574](https://github.com/open-mmlab/mmclassification/pull/574))
+- Support using checkpoint in Swin Transformer to save memory. ([#557](https://github.com/open-mmlab/mmclassification/pull/557))
+
+### Improvements
+
+- \[Reproduction\] Reproduce RegNetX training accuracy. ([#587](https://github.com/open-mmlab/mmclassification/pull/587))
+- \[Reproduction\] Reproduce training results of T2T-ViT. ([#610](https://github.com/open-mmlab/mmclassification/pull/610))
+- \[Enhance\] Provide high-acc training settings of ResNet. ([#572](https://github.com/open-mmlab/mmclassification/pull/572))
+- \[Enhance\] Set a random seed when the user does not set a seed. ([#554](https://github.com/open-mmlab/mmclassification/pull/554))
+- \[Enhance\] Added `NumClassCheckHook` and unit tests. ([#559](https://github.com/open-mmlab/mmclassification/pull/559))
+- \[Enhance\] Enhance feature extraction function. ([#593](https://github.com/open-mmlab/mmclassification/pull/593))
+- \[Enhance\] Improve efficiency of precision, recall, f1_score and support. ([#595](https://github.com/open-mmlab/mmclassification/pull/595))
+- \[Enhance\] Improve accuracy calculation performance. ([#592](https://github.com/open-mmlab/mmclassification/pull/592))
+- \[Refactor\] Refactor `analysis_log.py`. ([#529](https://github.com/open-mmlab/mmclassification/pull/529))
+- \[Refactor\] Use new API of matplotlib to handle blocking input in visualization. ([#568](https://github.com/open-mmlab/mmclassification/pull/568))
+- \[CI\] Cancel previous runs that are not completed. ([#583](https://github.com/open-mmlab/mmclassification/pull/583))
+- \[CI\] Skip build CI if only configs or docs modification. ([#575](https://github.com/open-mmlab/mmclassification/pull/575))
+
+### Bug Fixes
+
+- Fix test sampler bug. ([#611](https://github.com/open-mmlab/mmclassification/pull/611))
+- Try to create a symbolic link, otherwise copy. ([#580](https://github.com/open-mmlab/mmclassification/pull/580))
+- Fix a bug for multiple output in swin transformer. ([#571](https://github.com/open-mmlab/mmclassification/pull/571))
+
+### Docs Update
+
+- Update mmcv, torch, cuda version in Dockerfile and docs. ([#594](https://github.com/open-mmlab/mmclassification/pull/594))
+- Add analysis&misc docs. ([#525](https://github.com/open-mmlab/mmclassification/pull/525))
+- Fix docs build dependency. ([#584](https://github.com/open-mmlab/mmclassification/pull/584))
+
+## v0.18.0(30/11/2021)
+
+### Highlights
+
+- Support MLP-Mixer backbone and provide pre-trained checkpoints.
+- Add a tool to visualize the learning rate curve of the training phase. Welcome to use with the [tutorial](https://mmclassification.readthedocs.io/en/latest/tools/visualization.html#learning-rate-schedule-visualization)!
+
+### New Features
+
+- Add MLP Mixer Backbone. ([#528](https://github.com/open-mmlab/mmclassification/pull/528), [#539](https://github.com/open-mmlab/mmclassification/pull/539))
+- Support positive weights in BCE. ([#516](https://github.com/open-mmlab/mmclassification/pull/516))
+- Add a tool to visualize learning rate in each iterations. ([#498](https://github.com/open-mmlab/mmclassification/pull/498))
+
+### Improvements
+
+- Use CircleCI to do unit tests. ([#567](https://github.com/open-mmlab/mmclassification/pull/567))
+- Focal loss for single label tasks. ([#548](https://github.com/open-mmlab/mmclassification/pull/548))
+- Remove useless `import_modules_from_string`. ([#544](https://github.com/open-mmlab/mmclassification/pull/544))
+- Rename config files according to the config name standard. ([#508](https://github.com/open-mmlab/mmclassification/pull/508))
+- Use `reset_classifier` to remove head of timm backbones. ([#534](https://github.com/open-mmlab/mmclassification/pull/534))
+- Support passing arguments to loss from head. ([#523](https://github.com/open-mmlab/mmclassification/pull/523))
+- Refactor `Resize` transform and add `Pad` transform. ([#506](https://github.com/open-mmlab/mmclassification/pull/506))
+- Update mmcv dependency version. ([#509](https://github.com/open-mmlab/mmclassification/pull/509))
+
+### Bug Fixes
+
+- Fix bug when using `ClassBalancedDataset`. ([#555](https://github.com/open-mmlab/mmclassification/pull/555))
+- Fix a bug when using iter-based runner with 'val' workflow. ([#542](https://github.com/open-mmlab/mmclassification/pull/542))
+- Fix interpolation method checking in `Resize`. ([#547](https://github.com/open-mmlab/mmclassification/pull/547))
+- Fix a bug when load checkpoints in mulit-GPUs environment. ([#527](https://github.com/open-mmlab/mmclassification/pull/527))
+- Fix an error on indexing scalar metrics in `analyze_result.py`. ([#518](https://github.com/open-mmlab/mmclassification/pull/518))
+- Fix wrong condition judgment in `analyze_logs.py` and prevent empty curve. ([#510](https://github.com/open-mmlab/mmclassification/pull/510))
+
+### Docs Update
+
+- Fix vit config and model broken links. ([#564](https://github.com/open-mmlab/mmclassification/pull/564))
+- Add abstract and image for every paper. ([#546](https://github.com/open-mmlab/mmclassification/pull/546))
+- Add mmflow and mim in banner and readme. ([#543](https://github.com/open-mmlab/mmclassification/pull/543))
+- Add schedule and runtime tutorial docs. ([#499](https://github.com/open-mmlab/mmclassification/pull/499))
+- Add the top-5 acc in ResNet-CIFAR README. ([#531](https://github.com/open-mmlab/mmclassification/pull/531))
+- Fix TOC of `visualization.md` and add example images. ([#513](https://github.com/open-mmlab/mmclassification/pull/513))
+- Use docs link of other projects and add MMCV docs. ([#511](https://github.com/open-mmlab/mmclassification/pull/511))
+
+## v0.17.0(29/10/2021)
+
+### Highlights
+
+- Support Tokens-to-Token ViT backbone and Res2Net backbone. Welcome to use!
+- Support ImageNet21k dataset.
+- Add a pipeline visualization tool. Try it with the [tutorials](https://mmclassification.readthedocs.io/en/latest/tools/visualization.html#pipeline-visualization)!
+
+### New Features
+
+- Add Tokens-to-Token ViT backbone and converted checkpoints. ([#467](https://github.com/open-mmlab/mmclassification/pull/467))
+- Add Res2Net backbone and converted weights. ([#465](https://github.com/open-mmlab/mmclassification/pull/465))
+- Support ImageNet21k dataset. ([#461](https://github.com/open-mmlab/mmclassification/pull/461))
+- Support seesaw loss. ([#500](https://github.com/open-mmlab/mmclassification/pull/500))
+- Add a pipeline visualization tool. ([#406](https://github.com/open-mmlab/mmclassification/pull/406))
+- Add a tool to find broken files. ([#482](https://github.com/open-mmlab/mmclassification/pull/482))
+- Add a tool to test TorchServe. ([#468](https://github.com/open-mmlab/mmclassification/pull/468))
+
+### Improvements
+
+- Refator Vision Transformer. ([#395](https://github.com/open-mmlab/mmclassification/pull/395))
+- Use context manager to reuse matplotlib figures. ([#432](https://github.com/open-mmlab/mmclassification/pull/432))
+
+### Bug Fixes
+
+- Remove `DistSamplerSeedHook` if use `IterBasedRunner`. ([#501](https://github.com/open-mmlab/mmclassification/pull/501))
+- Set the priority of `EvalHook` to "LOW" to avoid a bug when using `IterBasedRunner`. ([#488](https://github.com/open-mmlab/mmclassification/pull/488))
+- Fix a wrong parameter of `get_root_logger` in `apis/train.py`. ([#486](https://github.com/open-mmlab/mmclassification/pull/486))
+- Fix version check in dataset builder. ([#474](https://github.com/open-mmlab/mmclassification/pull/474))
+
+### Docs Update
+
+- Add English Colab tutorials and update Chinese Colab tutorials. ([#483](https://github.com/open-mmlab/mmclassification/pull/483), [#497](https://github.com/open-mmlab/mmclassification/pull/497))
+- Add tutuorial for config files. ([#487](https://github.com/open-mmlab/mmclassification/pull/487))
+- Add model-pages in Model Zoo. ([#480](https://github.com/open-mmlab/mmclassification/pull/480))
+- Add code-spell pre-commit hook and fix a large mount of typos. ([#470](https://github.com/open-mmlab/mmclassification/pull/470))
+
+## v0.16.0(30/9/2021)
+
+### Highlights
+
+- We have improved compatibility with downstream repositories like MMDetection and MMSegmentation. We will add some examples about how to use our backbones in MMDetection.
+- Add RepVGG backbone and checkpoints. Welcome to use it!
+- Add timm backbones wrapper, now you can simply use backbones of pytorch-image-models in MMClassification!
+
+### New Features
+
+- Add RepVGG backbone and checkpoints. ([#414](https://github.com/open-mmlab/mmclassification/pull/414))
+- Add timm backbones wrapper. ([#427](https://github.com/open-mmlab/mmclassification/pull/427))
+
+### Improvements
+
+- Fix TnT compatibility and verbose warning. ([#436](https://github.com/open-mmlab/mmclassification/pull/436))
+- Support setting `--out-items` in `tools/test.py`. ([#437](https://github.com/open-mmlab/mmclassification/pull/437))
+- Add datetime info and saving model using torch\<1.6 format. ([#439](https://github.com/open-mmlab/mmclassification/pull/439))
+- Improve downstream repositories compatibility. ([#421](https://github.com/open-mmlab/mmclassification/pull/421))
+- Rename the option `--options` to `--cfg-options` in some tools. ([#425](https://github.com/open-mmlab/mmclassification/pull/425))
+- Add PyTorch 1.9 and Python 3.9 build workflow, and remove some CI. ([#422](https://github.com/open-mmlab/mmclassification/pull/422))
+
+### Bug Fixes
+
+- Fix format error in `test.py` when metric returns `np.ndarray`. ([#441](https://github.com/open-mmlab/mmclassification/pull/441))
+- Fix `publish_model` bug if no parent of `out_file`. ([#463](https://github.com/open-mmlab/mmclassification/pull/463))
+- Fix num_classes bug in pytorch2onnx.py. ([#458](https://github.com/open-mmlab/mmclassification/pull/458))
+- Fix missing runtime requirement `packaging`. ([#459](https://github.com/open-mmlab/mmclassification/pull/459))
+- Fix saving simplified model bug in ONNX export tool. ([#438](https://github.com/open-mmlab/mmclassification/pull/438))
+
+### Docs Update
+
+- Update `getting_started.md` and `install.md`. And rewrite `finetune.md`. ([#466](https://github.com/open-mmlab/mmclassification/pull/466))
+- Use PyTorch style docs theme. ([#457](https://github.com/open-mmlab/mmclassification/pull/457))
+- Update metafile and Readme. ([#435](https://github.com/open-mmlab/mmclassification/pull/435))
+- Add `CITATION.cff`. ([#428](https://github.com/open-mmlab/mmclassification/pull/428))
+
+## v0.15.0(31/8/2021)
+
+### Highlights
+
+- Support `hparams` argument in `AutoAugment` and `RandAugment` to provide hyperparameters for sub-policies.
+- Support custom squeeze channels in `SELayer`.
+- Support classwise weight in losses.
+
+### New Features
+
+- Add `hparams` argument in `AutoAugment` and `RandAugment` and some other improvement. ([#398](https://github.com/open-mmlab/mmclassification/pull/398))
+- Support classwise weight in losses. ([#388](https://github.com/open-mmlab/mmclassification/pull/388))
+- Enhance `SELayer` to support custom squeeze channels. ([#417](https://github.com/open-mmlab/mmclassification/pull/417))
+
+### Code Refactor
+
+- Better result visualization. ([#419](https://github.com/open-mmlab/mmclassification/pull/419))
+- Use `post_process` function to handle pred result processing. ([#390](https://github.com/open-mmlab/mmclassification/pull/390))
+- Update `digit_version` function. ([#402](https://github.com/open-mmlab/mmclassification/pull/402))
+- Avoid albumentations to install both opencv and opencv-headless. ([#397](https://github.com/open-mmlab/mmclassification/pull/397))
+- Avoid unnecessary listdir when building ImageNet. ([#396](https://github.com/open-mmlab/mmclassification/pull/396))
+- Use dynamic mmcv download link in TorchServe dockerfile. ([#387](https://github.com/open-mmlab/mmclassification/pull/387))
+
+### Docs Improvement
+
+- Add readme of some algorithms and update meta yml. ([#418](https://github.com/open-mmlab/mmclassification/pull/418))
+- Add Copyright information. ([#413](https://github.com/open-mmlab/mmclassification/pull/413))
+- Fix typo 'metirc'. ([#411](https://github.com/open-mmlab/mmclassification/pull/411))
+- Update QQ group QR code. ([#393](https://github.com/open-mmlab/mmclassification/pull/393))
+- Add PR template and modify issue template. ([#380](https://github.com/open-mmlab/mmclassification/pull/380))
+
+## v0.14.0(4/8/2021)
+
+### Highlights
+
+- Add transformer-in-transformer backbone and pretrain checkpoints, refers to [the paper](https://arxiv.org/abs/2103.00112).
+- Add Chinese colab tutorial.
+- Provide dockerfile to build mmcls dev docker image.
+
+### New Features
+
+- Add transformer in transformer backbone and pretrain checkpoints. ([#339](https://github.com/open-mmlab/mmclassification/pull/339))
+- Support mim, welcome to use mim to manage your mmcls project. ([#376](https://github.com/open-mmlab/mmclassification/pull/376))
+- Add Dockerfile. ([#365](https://github.com/open-mmlab/mmclassification/pull/365))
+- Add ResNeSt configs. ([#332](https://github.com/open-mmlab/mmclassification/pull/332))
+
+### Improvements
+
+- Use the `presistent_works` option if available, to accelerate training. ([#349](https://github.com/open-mmlab/mmclassification/pull/349))
+- Add Chinese ipynb tutorial. ([#306](https://github.com/open-mmlab/mmclassification/pull/306))
+- Refactor unit tests. ([#321](https://github.com/open-mmlab/mmclassification/pull/321))
+- Support to test mmdet inference with mmcls backbone. ([#343](https://github.com/open-mmlab/mmclassification/pull/343))
+- Use zero as default value of `thrs` in metrics. ([#341](https://github.com/open-mmlab/mmclassification/pull/341))
+
+### Bug Fixes
+
+- Fix ImageNet dataset annotation file parse bug. ([#370](https://github.com/open-mmlab/mmclassification/pull/370))
+- Fix docstring typo and init bug in ShuffleNetV1. ([#374](https://github.com/open-mmlab/mmclassification/pull/374))
+- Use local ATTENTION registry to avoid conflict with other repositories. ([#376](https://github.com/open-mmlab/mmclassification/pull/375))
+- Fix swin transformer config bug. ([#355](https://github.com/open-mmlab/mmclassification/pull/355))
+- Fix `patch_cfg` argument bug in SwinTransformer. ([#368](https://github.com/open-mmlab/mmclassification/pull/368))
+- Fix duplicate `init_weights` call in ViT init function. ([#373](https://github.com/open-mmlab/mmclassification/pull/373))
+- Fix broken `_base_` link in a resnet config. ([#361](https://github.com/open-mmlab/mmclassification/pull/361))
+- Fix vgg-19 model link missing. ([#363](https://github.com/open-mmlab/mmclassification/pull/363))
+
+## v0.13.0(3/7/2021)
+
+- Support Swin-Transformer backbone and add training configs for Swin-Transformer on ImageNet.
+
+### New Features
+
+- Support Swin-Transformer backbone and add training configs for Swin-Transformer on ImageNet. (#271)
+- Add pretained model of RegNetX. (#269)
+- Support adding custom hooks in config file. (#305)
+- Improve and add Chinese translation of `CONTRIBUTING.md` and all tools tutorials. (#320)
+- Dump config before training. (#282)
+- Add torchscript and torchserve deployment tools. (#279, #284)
+
+### Improvements
+
+- Improve test tools and add some new tools. (#322)
+- Correct MobilenetV3 backbone structure and add pretained models. (#291)
+- Refactor `PatchEmbed` and `HybridEmbed` as independent components. (#330)
+- Refactor mixup and cutmix as `Augments` to support more functions. (#278)
+- Refactor weights initialization method. (#270, #318, #319)
+- Refactor `LabelSmoothLoss` to support multiple calculation formulas. (#285)
+
+### Bug Fixes
+
+- Fix bug for CPU training. (#286)
+- Fix missing test data when `num_imgs` can not be evenly divided by `num_gpus`. (#299)
+- Fix build compatible with pytorch v1.3-1.5. (#301)
+- Fix `magnitude_std` bug in `RandAugment`. (#309)
+- Fix bug when `samples_per_gpu` is 1. (#311)
+
+## v0.12.0(3/6/2021)
+
+- Finish adding Chinese tutorials and build Chinese documentation on readthedocs.
+- Update ResNeXt checkpoints and ResNet checkpoints on CIFAR.
+
+### New Features
+
+- Improve and add Chinese translation of `data_pipeline.md` and `new_modules.md`. (#265)
+- Build Chinese translation on readthedocs. (#267)
+- Add an argument efficientnet_style to `RandomResizedCrop` and `CenterCrop`. (#268)
+
+### Improvements
+
+- Only allow directory operation when rank==0 when testing. (#258)
+- Fix typo in `base_head`. (#274)
+- Update ResNeXt checkpoints. (#283)
+
+### Bug Fixes
+
+- Add attribute `data.test` in MNIST configs. (#264)
+- Download CIFAR/MNIST dataset only on rank 0. (#273)
+- Fix MMCV version compatibility. (#276)
+- Fix CIFAR color channels bug and update checkpoints in model zoo. (#280)
+
+## v0.11.1(21/5/2021)
+
+- Refine `new_dataset.md` and add Chinese translation of `finture.md`, `new_dataset.md`.
+
+### New Features
+
+- Add `dim` argument for `GlobalAveragePooling`. (#236)
+- Add random noise to `RandAugment` magnitude. (#240)
+- Refine `new_dataset.md` and add Chinese translation of `finture.md`, `new_dataset.md`. (#243)
+
+### Improvements
+
+- Refactor arguments passing for Heads. (#239)
+- Allow more flexible `magnitude_range` in `RandAugment`. (#249)
+- Inherits MMCV registry so that in the future OpenMMLab repos like MMDet and MMSeg could directly use the backbones supported in MMCls. (#252)
+
+### Bug Fixes
+
+- Fix typo in `analyze_results.py`. (#237)
+- Fix typo in unittests. (#238)
+- Check if specified tmpdir exists when testing to avoid deleting existing data. (#242 & #258)
+- Add missing config files in `MANIFEST.in`. (#250 & #255)
+- Use temporary directory under shared directory to collect results to avoid unavailability of temporary directory for multi-node testing. (#251)
+
+## v0.11.0(1/5/2021)
+
+- Support cutmix trick.
+- Support random augmentation.
+- Add `tools/deployment/test.py` as a ONNX runtime test tool.
+- Support ViT backbone and add training configs for ViT on ImageNet.
+- Add Chinese `README.md` and some Chinese tutorials.
+
+### New Features
+
+- Support cutmix trick. (#198)
+- Add `simplify` option in `pytorch2onnx.py`. (#200)
+- Support random augmentation. (#201)
+- Add config and checkpoint for training ResNet on CIFAR-100. (#208)
+- Add `tools/deployment/test.py` as a ONNX runtime test tool. (#212)
+- Support ViT backbone and add training configs for ViT on ImageNet. (#214)
+- Add finetuning configs for ViT on ImageNet. (#217)
+- Add `device` option to support training on CPU. (#219)
+- Add Chinese `README.md` and some Chinese tutorials. (#221)
+- Add `metafile.yml` in configs to support interaction with paper with code(PWC) and MMCLI. (#225)
+- Upload configs and converted checkpoints for ViT fintuning on ImageNet. (#230)
+
+### Improvements
+
+- Fix `LabelSmoothLoss` so that label smoothing and mixup could be enabled at the same time. (#203)
+- Add `cal_acc` option in `ClsHead`. (#206)
+- Check `CLASSES` in checkpoint to avoid unexpected key error. (#207)
+- Check mmcv version when importing mmcls to ensure compatibility. (#209)
+- Update `CONTRIBUTING.md` to align with that in MMCV. (#210)
+- Change tags to html comments in configs README.md. (#226)
+- Clean codes in ViT backbone. (#227)
+- Reformat `pytorch2onnx.md` tutorial. (#229)
+- Update `setup.py` to support MMCLI. (#232)
+
+### Bug Fixes
+
+- Fix missing `cutmix_prob` in ViT configs. (#220)
+- Fix backend for resize in ResNeXt configs. (#222)
+
+## v0.10.0(1/4/2021)
+
+- Support AutoAugmentation
+- Add tutorials for installation and usage.
+
+### New Features
+
+- Add `Rotate` pipeline for data augmentation. (#167)
+- Add `Invert` pipeline for data augmentation. (#168)
+- Add `Color` pipeline for data augmentation. (#171)
+- Add `Solarize` and `Posterize` pipeline for data augmentation. (#172)
+- Support fp16 training. (#178)
+- Add tutorials for installation and basic usage of MMClassification.(#176)
+- Support `AutoAugmentation`, `AutoContrast`, `Equalize`, `Contrast`, `Brightness` and `Sharpness` pipelines for data augmentation. (#179)
+
+### Improvements
+
+- Support dynamic shape export to onnx. (#175)
+- Release training configs and update model zoo for fp16 (#184)
+- Use MMCV's EvalHook in MMClassification (#182)
+
+### Bug Fixes
+
+- Fix wrong naming in vgg config (#181)
+
+## v0.9.0(1/3/2021)
+
+- Implement mixup trick.
+- Add a new tool to create TensorRT engine from ONNX, run inference and verify outputs in Python.
+
+### New Features
+
+- Implement mixup and provide configs of training ResNet50 using mixup. (#160)
+- Add `Shear` pipeline for data augmentation. (#163)
+- Add `Translate` pipeline for data augmentation. (#165)
+- Add `tools/onnx2tensorrt.py` as a tool to create TensorRT engine from ONNX, run inference and verify outputs in Python. (#153)
+
+### Improvements
+
+- Add `--eval-options` in `tools/test.py` to support eval options override, matching the behavior of other open-mmlab projects. (#158)
+- Support showing and saving painted results in `mmcls.apis.test` and `tools/test.py`, matching the behavior of other open-mmlab projects. (#162)
+
+### Bug Fixes
+
+- Fix configs for VGG, replace checkpoints converted from other repos with the ones trained by ourselves and upload the missing logs in the model zoo. (#161)
+
+## v0.8.0(31/1/2021)
+
+- Support multi-label task.
+- Support more flexible metrics settings.
+- Fix bugs.
+
+### New Features
+
+- Add evaluation metrics: mAP, CP, CR, CF1, OP, OR, OF1 for multi-label task. (#123)
+- Add BCE loss for multi-label task. (#130)
+- Add focal loss for multi-label task. (#131)
+- Support PASCAL VOC 2007 dataset for multi-label task. (#134)
+- Add asymmetric loss for multi-label task. (#132)
+- Add analyze_results.py to select images for success/fail demonstration. (#142)
+- Support new metric that calculates the total number of occurrences of each label. (#143)
+- Support class-wise evaluation results. (#143)
+- Add thresholds in eval_metrics. (#146)
+- Add heads and a baseline config for multilabel task. (#145)
+
+### Improvements
+
+- Remove the models with 0 checkpoint and ignore the repeated papers when counting papers to gain more accurate model statistics. (#135)
+- Add tags in README.md. (#137)
+- Fix optional issues in docstring. (#138)
+- Update stat.py to classify papers. (#139)
+- Fix mismatched columns in README.md. (#150)
+- Fix test.py to support more evaluation metrics. (#155)
+
+### Bug Fixes
+
+- Fix bug in VGG weight_init. (#140)
+- Fix bug in 2 ResNet configs in which outdated heads were used. (#147)
+- Fix bug of misordered height and width in `RandomCrop` and `RandomResizedCrop`. (#151)
+- Fix missing `meta_keys` in `Collect`. (#149 & #152)
+
+## v0.7.0(31/12/2020)
+
+- Add more evaluation metrics.
+- Fix bugs.
+
+### New Features
+
+- Remove installation of MMCV from requirements. (#90)
+- Add 3 evaluation metrics: precision, recall and F-1 score. (#93)
+- Allow config override during testing and inference with `--options`. (#91 & #96)
+
+### Improvements
+
+- Use `build_runner` to make runners more flexible. (#54)
+- Support to get category ids in `BaseDataset`. (#72)
+- Allow `CLASSES` override during `BaseDateset` initialization. (#85)
+- Allow input image as ndarray during inference. (#87)
+- Optimize MNIST config. (#98)
+- Add config links in model zoo documentation. (#99)
+- Use functions from MMCV to collect environment. (#103)
+- Refactor config files so that they are now categorized by methods. (#116)
+- Add README in config directory. (#117)
+- Add model statistics. (#119)
+- Refactor documentation in consistency with other MM repositories. (#126)
+
+### Bug Fixes
+
+- Add missing `CLASSES` argument to dataset wrappers. (#66)
+- Fix slurm evaluation error during training. (#69)
+- Resolve error caused by shape in `Accuracy`. (#104)
+- Fix bug caused by extremely insufficient data in distributed sampler.(#108)
+- Fix bug in `gpu_ids` in distributed training. (#107)
+- Fix bug caused by extremely insufficient data in collect results during testing (#114)
+
+## v0.6.0(11/10/2020)
+
+- Support new method: ResNeSt and VGG.
+- Support new dataset: CIFAR10.
+- Provide new tools to do model inference, model conversion from pytorch to onnx.
+
+### New Features
+
+- Add model inference. (#16)
+- Add pytorch2onnx. (#20)
+- Add PIL backend for transform `Resize`. (#21)
+- Add ResNeSt. (#25)
+- Add VGG and its pretained models. (#27)
+- Add CIFAR10 configs and models. (#38)
+- Add albumentations transforms. (#45)
+- Visualize results on image demo. (#58)
+
+### Improvements
+
+- Replace urlretrieve with urlopen in dataset.utils. (#13)
+- Resize image according to its short edge. (#22)
+- Update ShuffleNet config. (#31)
+- Update pre-trained models for shufflenet_v2, shufflenet_v1, se-resnet50, se-resnet101. (#33)
+
+### Bug Fixes
+
+- Fix init_weights in `shufflenet_v2.py`. (#29)
+- Fix the parameter `size` in test_pipeline. (#30)
+- Fix the parameter in cosine lr schedule. (#32)
+- Fix the convert tools for mobilenet_v2. (#34)
+- Fix crash in CenterCrop transform when image is greyscale (#40)
+- Fix outdated configs. (#53)
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/compatibility.md b/openmmlab_test/mmclassification-0.24.1/docs/en/compatibility.md
new file mode 100644
index 0000000000000000000000000000000000000000..1affb8e7d1e33448e5eab414f6847a67a9f8ebe5
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/compatibility.md
@@ -0,0 +1,8 @@
+# Compatibility of MMClassification 0.x
+
+## MMClassification 0.20.1
+
+### MMCV compatibility
+
+In Twins backbone, we use the `PatchEmbed` module of MMCV, and this module is added after MMCV 1.4.2.
+Therefore, we need to update the mmcv version to 1.4.2.
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/conf.py b/openmmlab_test/mmclassification-0.24.1/docs/en/conf.py
new file mode 100644
index 0000000000000000000000000000000000000000..301696b38f6bb0f8db07cfb40c1080611507dc16
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/conf.py
@@ -0,0 +1,238 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# This file only contains a selection of the most common options. For a full
+# list see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+import os
+import subprocess
+import sys
+
+import pytorch_sphinx_theme
+from sphinx.builders.html import StandaloneHTMLBuilder
+
+sys.path.insert(0, os.path.abspath('../../'))
+
+# -- Project information -----------------------------------------------------
+
+project = 'MMClassification'
+copyright = '2020, OpenMMLab'
+author = 'MMClassification Authors'
+
+# The full version, including alpha/beta/rc tags
+version_file = '../../mmcls/version.py'
+
+
+def get_version():
+ with open(version_file, 'r') as f:
+ exec(compile(f.read(), version_file, 'exec'))
+ return locals()['__version__']
+
+
+release = get_version()
+
+# -- General configuration ---------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+ 'sphinx.ext.autodoc',
+ 'sphinx.ext.autosummary',
+ 'sphinx.ext.intersphinx',
+ 'sphinx.ext.napoleon',
+ 'sphinx.ext.viewcode',
+ 'myst_parser',
+ 'sphinx_copybutton',
+]
+
+autodoc_mock_imports = ['mmcv._ext', 'matplotlib']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+#
+source_suffix = {
+ '.rst': 'restructuredtext',
+ '.md': 'markdown',
+}
+
+language = 'en'
+
+# The master toctree document.
+master_doc = 'index'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'pytorch_sphinx_theme'
+html_theme_path = [pytorch_sphinx_theme.get_html_theme_path()]
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#
+html_theme_options = {
+ 'logo_url':
+ 'https://mmclassification.readthedocs.io/en/latest/',
+ 'menu': [
+ {
+ 'name': 'GitHub',
+ 'url': 'https://github.com/open-mmlab/mmclassification'
+ },
+ {
+ 'name':
+ 'Colab Tutorials',
+ 'children': [
+ {
+ 'name':
+ 'Train and inference with shell commands',
+ 'url':
+ 'https://colab.research.google.com/github/'
+ 'open-mmlab/mmclassification/blob/master/docs/en/'
+ 'tutorials/MMClassification_tools.ipynb',
+ },
+ {
+ 'name':
+ 'Train and inference with Python APIs',
+ 'url':
+ 'https://colab.research.google.com/github/'
+ 'open-mmlab/mmclassification/blob/master/docs/en/'
+ 'tutorials/MMClassification_python.ipynb',
+ },
+ ]
+ },
+ ],
+ # Specify the language of shared menu
+ 'menu_lang':
+ 'en'
+}
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+html_css_files = ['css/readthedocs.css']
+html_js_files = ['js/custom.js']
+
+# -- Options for HTMLHelp output ---------------------------------------------
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'mmclsdoc'
+
+# -- Options for LaTeX output ------------------------------------------------
+
+latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ #
+ # 'papersize': 'letterpaper',
+
+ # The font size ('10pt', '11pt' or '12pt').
+ #
+ # 'pointsize': '10pt',
+
+ # Additional stuff for the LaTeX preamble.
+ #
+ # 'preamble': '',
+ 'preamble':
+ r'''
+\hypersetup{unicode=true}
+\usepackage{CJKutf8}
+\DeclareUnicodeCharacter{00A0}{\nobreakspace}
+\DeclareUnicodeCharacter{2203}{\ensuremath{\exists}}
+\DeclareUnicodeCharacter{2200}{\ensuremath{\forall}}
+\DeclareUnicodeCharacter{2286}{\ensuremath{\subseteq}}
+\DeclareUnicodeCharacter{2713}{x}
+\DeclareUnicodeCharacter{27FA}{\ensuremath{\Longleftrightarrow}}
+\DeclareUnicodeCharacter{221A}{\ensuremath{\sqrt{}}}
+\DeclareUnicodeCharacter{221B}{\ensuremath{\sqrt[3]{}}}
+\DeclareUnicodeCharacter{2295}{\ensuremath{\oplus}}
+\DeclareUnicodeCharacter{2297}{\ensuremath{\otimes}}
+\begin{CJK}{UTF8}{gbsn}
+\AtEndDocument{\end{CJK}}
+''',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+latex_documents = [
+ (master_doc, 'mmcls.tex', 'MMClassification Documentation', author,
+ 'manual'),
+]
+
+# -- Options for manual page output ------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [(master_doc, 'mmcls', 'MMClassification Documentation', [author],
+ 1)]
+
+# -- Options for Texinfo output ----------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ (master_doc, 'mmcls', 'MMClassification Documentation', author, 'mmcls',
+ 'OpenMMLab image classification toolbox and benchmark.', 'Miscellaneous'),
+]
+
+# -- Options for Epub output -------------------------------------------------
+
+# Bibliographic Dublin Core info.
+epub_title = project
+
+# The unique identifier of the text. This can be a ISBN number
+# or the project homepage.
+#
+# epub_identifier = ''
+
+# A unique identification for the text.
+#
+# epub_uid = ''
+
+# A list of files that should not be packed into the epub file.
+epub_exclude_files = ['search.html']
+
+# set priority when building html
+StandaloneHTMLBuilder.supported_image_types = [
+ 'image/svg+xml', 'image/gif', 'image/png', 'image/jpeg'
+]
+
+# -- Extension configuration -------------------------------------------------
+# Ignore >>> when copying code
+copybutton_prompt_text = r'>>> |\.\.\. '
+copybutton_prompt_is_regexp = True
+# Auto-generated header anchors
+myst_heading_anchors = 3
+# Configuration for intersphinx
+intersphinx_mapping = {
+ 'python': ('https://docs.python.org/3', None),
+ 'numpy': ('https://numpy.org/doc/stable', None),
+ 'torch': ('https://pytorch.org/docs/stable/', None),
+ 'mmcv': ('https://mmcv.readthedocs.io/en/master/', None),
+}
+
+
+def builder_inited_handler(app):
+ subprocess.run(['./stat.py'])
+
+
+def setup(app):
+ app.connect('builder-inited', builder_inited_handler)
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/device/npu.md b/openmmlab_test/mmclassification-0.24.1/docs/en/device/npu.md
new file mode 100644
index 0000000000000000000000000000000000000000..281c8f41e00b9d72832b1dd1c50660fcddab5fda
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/device/npu.md
@@ -0,0 +1,34 @@
+# NPU (HUAWEI Ascend)
+
+## Usage
+
+Please install MMCV with NPU device support according to {external+mmcv:doc}`the tutorial `.
+
+Here we use 8 NPUs on your computer to train the model with the following command:
+
+```shell
+bash tools/dist_train.sh configs/cspnet/resnet50_8xb32_in1k.py 8 --device npu
+```
+
+Also, you can use only one NPU to trian the model with the following command:
+
+```shell
+python tools/train.py configs/cspnet/resnet50_8xb32_in1k.py --device npu
+```
+
+## Verified Models
+
+| Model | Top-1 (%) | Top-5 (%) | Config | Download |
+| :--------------------------------------------------------: | :-------: | :-------: | :-----------------------------------------------------------: | :-------------------------------------------------------------: |
+| [CSPResNeXt50](../papers/cspnet.md) | 77.10 | 93.55 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/cspnet/cspresnext50_8xb32_in1k.py) | [model](<>) \| [log](https://download.openmmlab.com/mmclassification/v0/device/npu/cspresnext50_8xb32_in1k.log.json) |
+| [DenseNet121](../papers/densenet.md) | 72.62 | 91.04 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/densenet/densenet121_4xb256_in1k.py) | [model](<>) \| [log](https://download.openmmlab.com/mmclassification/v0/device/npu/densenet121_4xb256_in1k.log.json) |
+| [EfficientNet-B4(AA + AdvProp)](../papers/efficientnet.md) | 75.55 | 92.86 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientnet/efficientnet-b4_8xb32-01norm_in1k.py) | [model](<>) \| [log](https://download.openmmlab.com/mmclassification/v0/device/npu/efficientnet-b4_8xb32-01norm_in1k.log.json) |
+| [HRNet-W18](../papers/hrnet.md) | 77.01 | 93.46 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/hrnet/hrnet-w18_4xb32_in1k.py) | [model](<>) \| [log](https://download.openmmlab.com/mmclassification/v0/device/npu/hrnet-w18_4xb32_in1k.log.json) |
+| [ResNetV1D-152](../papers/resnet.md) | 77.11 | 94.54 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnet/resnetv1d152_8xb32_in1k.py) | [model](<>) \| [log](<>) |
+| [ResNet-50](../papers/resnet.md) | 76.40 | - | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnet/resnet50_8xb32_in1k.py) | [model](<>) \| [log](<>) |
+| [ResNetXt-32x4d-50](../papers/resnext.md) | 77.55 | 93.75 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnext/resnext50-32x4d_8xb32_in1k.py) | [model](<>) \| [log](https://download.openmmlab.com/mmclassification/v0/device/npu/resnext50-32x4d_8xb32_in1k.log.json) |
+| [SE-ResNet-50](../papers/seresnet.md) | 77.64 | 93.76 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/seresnet/seresnet50_8xb32_in1k.py) | [model](<>) \| [log](https://download.openmmlab.com/mmclassification/v0/device/npu/seresnet50_8xb32_in1k.log.json) |
+| [VGG-11](../papers/vgg.md) | 68.92 | 88.83 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/vgg/vgg11_8xb32_in1k.py) | [model](<>) \| [log](https://download.openmmlab.com/mmclassification/v0/device/npu/vgg11_8xb32_in1k.log.json) |
+| [ShuffleNetV2 1.0x](../papers/shufflenet_v2.md) | 69.53 | 88.82 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/shufflenet_v2/shufflenet-v2-1x_16xb64_in1k.py) | [model](<>) \| [log](https://download.openmmlab.com/mmclassification/v0/device/npu/shufflenet-v2-1x_16xb64_in1k.json) |
+
+**All above models are provided by Huawei Ascend group.**
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/docutils.conf b/openmmlab_test/mmclassification-0.24.1/docs/en/docutils.conf
new file mode 100644
index 0000000000000000000000000000000000000000..0c00c84688701117f231fd0c8ec295fb747b7d8f
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/docutils.conf
@@ -0,0 +1,2 @@
+[html writers]
+table_style: colwidths-auto
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/faq.md b/openmmlab_test/mmclassification-0.24.1/docs/en/faq.md
new file mode 100644
index 0000000000000000000000000000000000000000..81f32c5f558246ea305640c1deda4d8c02ccf643
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/faq.md
@@ -0,0 +1,83 @@
+# Frequently Asked Questions
+
+We list some common troubles faced by many users and their corresponding
+solutions here. Feel free to enrich the list if you find any frequent issues
+and have ways to help others to solve them. If the contents here do not cover
+your issue, please create an issue using the
+[provided templates](https://github.com/open-mmlab/mmclassification/issues/new/choose)
+and make sure you fill in all required information in the template.
+
+## Installation
+
+- Compatibility issue between MMCV and MMClassification; "AssertionError:
+ MMCV==xxx is used but incompatible. Please install mmcv>=xxx, \<=xxx."
+
+ Compatible MMClassification and MMCV versions are shown as below. Please
+ choose the correct version of MMCV to avoid installation issues.
+
+ | MMClassification version | MMCV version |
+ | :----------------------: | :--------------------: |
+ | dev | mmcv>=1.7.0, \<1.9.0 |
+ | 0.24.1 (master) | mmcv>=1.4.2, \<1.9.0 |
+ | 0.23.2 | mmcv>=1.4.2, \<1.7.0 |
+ | 0.22.1 | mmcv>=1.4.2, \<1.6.0 |
+ | 0.21.0 | mmcv>=1.4.2, \<=1.5.0 |
+ | 0.20.1 | mmcv>=1.4.2, \<=1.5.0 |
+ | 0.19.0 | mmcv>=1.3.16, \<=1.5.0 |
+ | 0.18.0 | mmcv>=1.3.16, \<=1.5.0 |
+ | 0.17.0 | mmcv>=1.3.8, \<=1.5.0 |
+ | 0.16.0 | mmcv>=1.3.8, \<=1.5.0 |
+ | 0.15.0 | mmcv>=1.3.8, \<=1.5.0 |
+ | 0.15.0 | mmcv>=1.3.8, \<=1.5.0 |
+ | 0.14.0 | mmcv>=1.3.8, \<=1.5.0 |
+ | 0.13.0 | mmcv>=1.3.8, \<=1.5.0 |
+ | 0.12.0 | mmcv>=1.3.1, \<=1.5.0 |
+ | 0.11.1 | mmcv>=1.3.1, \<=1.5.0 |
+ | 0.11.0 | mmcv>=1.3.0 |
+ | 0.10.0 | mmcv>=1.3.0 |
+ | 0.9.0 | mmcv>=1.1.4 |
+ | 0.8.0 | mmcv>=1.1.4 |
+ | 0.7.0 | mmcv>=1.1.4 |
+ | 0.6.0 | mmcv>=1.1.4 |
+
+ ```{note}
+ Since the `dev` branch is under frequent development, the MMCV
+ version dependency may be inaccurate. If you encounter problems when using
+ the `dev` branch, please try to update MMCV to the latest version.
+ ```
+
+- Using Albumentations
+
+ If you would like to use `albumentations`, we suggest using `pip install -r requirements/albu.txt` or
+ `pip install -U albumentations --no-binary qudida,albumentations`.
+
+ If you simply use `pip install albumentations>=0.3.2`, it will install `opencv-python-headless` simultaneously
+ (even though you have already installed `opencv-python`). Please refer to the
+ [official documentation](https://albumentations.ai/docs/getting_started/installation/#note-on-opencv-dependencies)
+ for details.
+
+## Coding
+
+- Do I need to reinstall mmcls after some code modifications?
+
+ If you follow [the best practice](install.md) and install mmcls from source,
+ any local modifications made to the code will take effect without
+ reinstallation.
+
+- How to develop with multiple MMClassification versions?
+
+ Generally speaking, we recommend to use different virtual environments to
+ manage MMClassification in different working directories. However, you
+ can also use the same environment to develop MMClassification in different
+ folders, like mmcls-0.21, mmcls-0.23. When you run the train or test shell script,
+ it will adopt the mmcls package in the current folder. And when you run other Python
+ script, you can also add `` PYTHONPATH=`pwd` `` at the beginning of your command
+ to use the package in the current folder.
+
+ Conversely, to use the default MMClassification installed in the environment
+ rather than the one you are working with, you can remove the following line
+ in those shell scripts:
+
+ ```shell
+ PYTHONPATH="$(dirname $0)/..":$PYTHONPATH
+ ```
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/getting_started.md b/openmmlab_test/mmclassification-0.24.1/docs/en/getting_started.md
new file mode 100644
index 0000000000000000000000000000000000000000..4e8a9fcc09156ca0a8405b9bcc5891b4ab769117
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/getting_started.md
@@ -0,0 +1,275 @@
+# Getting Started
+
+This page provides basic tutorials about the usage of MMClassification.
+
+## Prepare datasets
+
+It is recommended to symlink the dataset root to `$MMCLASSIFICATION/data`.
+If your folder structure is different, you may need to change the corresponding paths in config files.
+
+```
+mmclassification
+├── mmcls
+├── tools
+├── configs
+├── docs
+├── data
+│ ├── imagenet
+│ │ ├── meta
+│ │ ├── train
+│ │ ├── val
+│ ├── cifar
+│ │ ├── cifar-10-batches-py
+│ ├── mnist
+│ │ ├── train-images-idx3-ubyte
+│ │ ├── train-labels-idx1-ubyte
+│ │ ├── t10k-images-idx3-ubyte
+│ │ ├── t10k-labels-idx1-ubyte
+
+```
+
+For ImageNet, it has multiple versions, but the most commonly used one is [ILSVRC 2012](http://www.image-net.org/challenges/LSVRC/2012/). It can be accessed with the following steps.
+
+1. Register an account and login to the [download page](http://www.image-net.org/download-images).
+2. Find download links for ILSVRC2012 and download the following two files
+ - ILSVRC2012_img_train.tar (~138GB)
+ - ILSVRC2012_img_val.tar (~6.3GB)
+3. Untar the downloaded files
+4. Download meta data using this [script](https://github.com/BVLC/caffe/blob/master/data/ilsvrc12/get_ilsvrc_aux.sh)
+
+For MNIST, CIFAR10 and CIFAR100, the datasets will be downloaded and unzipped automatically if they are not found.
+
+For using custom datasets, please refer to [Tutorial 3: Customize Dataset](tutorials/new_dataset.md).
+
+## Inference with pretrained models
+
+We provide scripts to inference a single image, inference a dataset and test a dataset (e.g., ImageNet).
+
+### Inference a single image
+
+```shell
+python demo/image_demo.py ${IMAGE_FILE} ${CONFIG_FILE} ${CHECKPOINT_FILE}
+
+# Example
+python demo/image_demo.py demo/demo.JPEG configs/resnet/resnet50_8xb32_in1k.py \
+ https://download.openmmlab.com/mmclassification/v0/resnet/resnet50_8xb32_in1k_20210831-ea4938fc.pth
+```
+
+### Inference and test a dataset
+
+- single GPU
+- CPU
+- single node multiple GPU
+- multiple node
+
+You can use the following commands to infer a dataset.
+
+```shell
+# single-gpu
+python tools/test.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--metrics ${METRICS}] [--out ${RESULT_FILE}]
+
+# CPU: disable GPUs and run single-gpu testing script
+export CUDA_VISIBLE_DEVICES=-1
+python tools/test.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--metrics ${METRICS}] [--out ${RESULT_FILE}]
+
+# multi-gpu
+./tools/dist_test.sh ${CONFIG_FILE} ${CHECKPOINT_FILE} ${GPU_NUM} [--metrics ${METRICS}] [--out ${RESULT_FILE}]
+
+# multi-node in slurm environment
+python tools/test.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--metrics ${METRICS}] [--out ${RESULT_FILE}] --launcher slurm
+```
+
+Optional arguments:
+
+- `RESULT_FILE`: Filename of the output results. If not specified, the results will not be saved to a file. Support formats include json, yaml and pickle.
+- `METRICS`:Items to be evaluated on the results, like accuracy, precision, recall, etc.
+
+Examples:
+
+Infer ResNet-50 on ImageNet validation set to get predicted labels and their corresponding predicted scores.
+
+```shell
+python tools/test.py configs/resnet/resnet50_8xb16_cifar10.py \
+ https://download.openmmlab.com/mmclassification/v0/resnet/resnet50_b16x8_cifar10_20210528-f54bfad9.pth \
+ --out result.pkl
+```
+
+## Train a model
+
+MMClassification implements distributed training and non-distributed training,
+which uses `MMDistributedDataParallel` and `MMDataParallel` respectively.
+
+All outputs (log files and checkpoints) will be saved to the working directory,
+which is specified by `work_dir` in the config file.
+
+By default we evaluate the model on the validation set after each epoch, you can change the evaluation interval by adding the interval argument in the training config.
+
+```python
+evaluation = dict(interval=12) # Evaluate the model per 12 epochs.
+```
+
+### Train with a single GPU
+
+```shell
+python tools/train.py ${CONFIG_FILE} [optional arguments]
+```
+
+If you want to specify the working directory in the command, you can add an argument `--work_dir ${YOUR_WORK_DIR}`.
+
+### Train with CPU
+
+The process of training on the CPU is consistent with single GPU training. We just need to disable GPUs before the training process.
+
+```shell
+export CUDA_VISIBLE_DEVICES=-1
+```
+
+And then run the script [above](#train-with-a-single-gpu).
+
+```{warning}
+The process of training on the CPU is consistent with single GPU training. We just need to disable GPUs before the training process.
+```
+
+### Train with multiple GPUs in single machine
+
+```shell
+./tools/dist_train.sh ${CONFIG_FILE} ${GPU_NUM} [optional arguments]
+```
+
+Optional arguments are:
+
+- `--no-validate` (**not suggested**): By default, the codebase will perform evaluation at every k (default value is 1) epochs during the training. To disable this behavior, use `--no-validate`.
+- `--work-dir ${WORK_DIR}`: Override the working directory specified in the config file.
+- `--resume-from ${CHECKPOINT_FILE}`: Resume from a previous checkpoint file.
+
+Difference between `resume-from` and `load-from`:
+`resume-from` loads both the model weights and optimizer status, and the epoch is also inherited from the specified checkpoint. It is usually used for resuming the training process that is interrupted accidentally.
+`load-from` only loads the model weights and the training epoch starts from 0. It is usually used for finetuning.
+
+### Train with multiple machines
+
+If you launch with multiple machines simply connected with ethernet, you can simply run following commands:
+
+On the first machine:
+
+```shell
+NNODES=2 NODE_RANK=0 PORT=$MASTER_PORT MASTER_ADDR=$MASTER_ADDR sh tools/dist_train.sh $CONFIG $GPUS
+```
+
+On the second machine:
+
+```shell
+NNODES=2 NODE_RANK=1 PORT=$MASTER_PORT MASTER_ADDR=$MASTER_ADDR sh tools/dist_train.sh $CONFIG $GPUS
+```
+
+Usually it is slow if you do not have high speed networking like InfiniBand.
+
+If you run MMClassification on a cluster managed with [slurm](https://slurm.schedmd.com/), you can use the script `slurm_train.sh`. (This script also supports single machine training.)
+
+```shell
+[GPUS=${GPUS}] ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} ${CONFIG_FILE} ${WORK_DIR}
+```
+
+You can check [slurm_train.sh](https://github.com/open-mmlab/mmclassification/blob/master/tools/slurm_train.sh) for full arguments and environment variables.
+
+If you have just multiple machines connected with ethernet, you can refer to
+PyTorch [launch utility](https://pytorch.org/docs/stable/distributed_deprecated.html#launch-utility).
+Usually it is slow if you do not have high speed networking like InfiniBand.
+
+### Launch multiple jobs on a single machine
+
+If you launch multiple jobs on a single machine, e.g., 2 jobs of 4-GPU training on a machine with 8 GPUs,
+you need to specify different ports (29500 by default) for each job to avoid communication conflict.
+
+If you use `dist_train.sh` to launch training jobs, you can set the port in commands.
+
+```shell
+CUDA_VISIBLE_DEVICES=0,1,2,3 PORT=29500 ./tools/dist_train.sh ${CONFIG_FILE} 4
+CUDA_VISIBLE_DEVICES=4,5,6,7 PORT=29501 ./tools/dist_train.sh ${CONFIG_FILE} 4
+```
+
+If you use launch training jobs with Slurm, you need to modify the config files (usually the 6th line from the bottom in config files) to set different communication ports.
+
+In `config1.py`,
+
+```python
+dist_params = dict(backend='nccl', port=29500)
+```
+
+In `config2.py`,
+
+```python
+dist_params = dict(backend='nccl', port=29501)
+```
+
+Then you can launch two jobs with `config1.py` ang `config2.py`.
+
+```shell
+CUDA_VISIBLE_DEVICES=0,1,2,3 GPUS=4 ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config1.py ${WORK_DIR}
+CUDA_VISIBLE_DEVICES=4,5,6,7 GPUS=4 ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config2.py ${WORK_DIR}
+```
+
+### Train with IPU
+
+The process of training on the IPU is consistent with single GPU training. We just need to have IPU machine and environment
+and add an extra argument `--ipu-replicas ${IPU_NUM}`
+
+## Useful tools
+
+We provide lots of useful tools under `tools/` directory.
+
+### Get the FLOPs and params (experimental)
+
+We provide a script adapted from [flops-counter.pytorch](https://github.com/sovrasov/flops-counter.pytorch) to compute the FLOPs and params of a given model.
+
+```shell
+python tools/analysis_tools/get_flops.py ${CONFIG_FILE} [--shape ${INPUT_SHAPE}]
+```
+
+You will get the result like this.
+
+```
+==============================
+Input shape: (3, 224, 224)
+Flops: 4.12 GFLOPs
+Params: 25.56 M
+==============================
+```
+
+```{warning}
+This tool is still experimental and we do not guarantee that the number is correct. You may well use the result for simple comparisons, but double check it before you adopt it in technical reports or papers.
+- FLOPs are related to the input shape while parameters are not. The default input shape is (1, 3, 224, 224).
+- Some operators are not counted into FLOPs like GN and custom operators. Refer to [`mmcv.cnn.get_model_complexity_info()`](https://github.com/open-mmlab/mmcv/blob/master/mmcv/cnn/utils/flops_counter.py) for details.
+```
+
+### Publish a model
+
+Before you publish a model, you may want to
+
+1. Convert model weights to CPU tensors.
+2. Delete the optimizer states.
+3. Compute the hash of the checkpoint file and append the hash id to the filename.
+
+```shell
+python tools/convert_models/publish_model.py ${INPUT_FILENAME} ${OUTPUT_FILENAME}
+```
+
+E.g.,
+
+```shell
+python tools/convert_models/publish_model.py work_dirs/resnet50/latest.pth imagenet_resnet50.pth
+```
+
+The final output filename will be `imagenet_resnet50_{date}-{hash id}.pth`.
+
+## Tutorials
+
+Currently, we provide five tutorials for users.
+
+- [learn about config](tutorials/config.md)
+- [finetune models](tutorials/finetune.md)
+- [add new dataset](tutorials/new_dataset.md)
+- [design data pipeline](tutorials/data_pipeline.md)
+- [add new modules](tutorials/new_modules.md)
+- [customize schedule](tutorials/schedule.md)
+- [customize runtime settings](tutorials/runtime.md).
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/index.rst b/openmmlab_test/mmclassification-0.24.1/docs/en/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..d0a15b1d1c5ca2ca4839db870b51acb7d1bef2f1
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/index.rst
@@ -0,0 +1,99 @@
+Welcome to MMClassification's documentation!
+============================================
+
+You can switch between Chinese and English documentation in the lower-left corner of the layout.
+
+您可以在页面左下角切换中英文文档。
+
+.. toctree::
+ :maxdepth: 1
+ :caption: Get Started
+
+ install.md
+ getting_started.md
+
+
+.. toctree::
+ :maxdepth: 1
+ :caption: Tutorials
+
+ tutorials/config.md
+ tutorials/finetune.md
+ tutorials/new_dataset.md
+ tutorials/data_pipeline.md
+ tutorials/new_modules.md
+ tutorials/schedule.md
+ tutorials/runtime.md
+
+
+.. toctree::
+ :maxdepth: 1
+ :caption: Model zoo
+ :glob:
+
+ modelzoo_statistics.md
+ model_zoo.md
+ papers/*
+
+
+.. toctree::
+ :maxdepth: 1
+ :caption: Useful Tools and Scripts
+
+ tools/pytorch2onnx.md
+ tools/onnx2tensorrt.md
+ tools/pytorch2torchscript.md
+ tools/model_serving.md
+ tools/visualization.md
+ tools/analysis.md
+ tools/miscellaneous.md
+
+
+.. toctree::
+ :maxdepth: 1
+ :caption: Community
+
+ community/CONTRIBUTING.md
+
+
+.. toctree::
+ :maxdepth: 1
+ :caption: API Reference
+
+ mmcls.apis
+ mmcls.core
+ mmcls.models
+ mmcls.models.utils
+ mmcls.datasets
+ Data Transformations
+ Batch Augmentation
+ mmcls.utils
+
+
+.. toctree::
+ :maxdepth: 1
+ :caption: Notes
+
+ changelog.md
+ compatibility.md
+ faq.md
+
+
+.. toctree::
+ :maxdepth: 1
+ :caption: Device Support
+
+ device/npu.md
+
+.. toctree::
+ :caption: Language Switch
+
+ English
+ 简体中文
+
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`search`
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/install.md b/openmmlab_test/mmclassification-0.24.1/docs/en/install.md
new file mode 100644
index 0000000000000000000000000000000000000000..bde1a815af29b5e4de0042bb5a11329ca513c195
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/install.md
@@ -0,0 +1,219 @@
+# Prerequisites
+
+In this section we demonstrate how to prepare an environment with PyTorch.
+
+MMClassification works on Linux, Windows and macOS. It requires Python 3.6+, CUDA 9.2+ and PyTorch 1.5+.
+
+```{note}
+If you are experienced with PyTorch and have already installed it, just skip this part and jump to the [next section](#installation). Otherwise, you can follow these steps for the preparation.
+```
+
+**Step 1.** Download and install Miniconda from the [official website](https://docs.conda.io/en/latest/miniconda.html).
+
+**Step 2.** Create a conda environment and activate it.
+
+```shell
+conda create --name openmmlab python=3.8 -y
+conda activate openmmlab
+```
+
+**Step 3.** Install PyTorch following [official instructions](https://pytorch.org/get-started/locally/), e.g.
+
+On GPU platforms:
+
+```shell
+conda install pytorch torchvision -c pytorch
+```
+
+```{warning}
+This command will automatically install the latest version PyTorch and cudatoolkit, please check whether they matches your environment.
+```
+
+On CPU platforms:
+
+```shell
+conda install pytorch torchvision cpuonly -c pytorch
+```
+
+# Installation
+
+We recommend that users follow our best practices to install MMClassification. However, the whole process is highly customizable. See [Customize Installation](#customize-installation) section for more information.
+
+## Best Practices
+
+**Step 0.** Install [MMCV](https://github.com/open-mmlab/mmcv) using [MIM](https://github.com/open-mmlab/mim).
+
+```shell
+pip install -U openmim
+mim install mmcv-full
+```
+
+**Step 1.** Install MMClassification.
+
+According to your needs, we support two install modes:
+
+- [Install from source (Recommended)](#install-from-source): You want to develop your own image classification task or new features based on MMClassification framework. For example, you want to add new dataset or new models. And you can use all tools we provided.
+- [Install as a Python package](#install-as-a-python-package): You just want to call MMClassification's APIs or import MMClassification's modules in your project.
+
+### Install from source
+
+In this case, install mmcls from source:
+
+```shell
+git clone https://github.com/open-mmlab/mmclassification.git
+cd mmclassification
+pip install -v -e .
+# "-v" means verbose, or more output
+# "-e" means installing a project in editable mode,
+# thus any local modifications made to the code will take effect without reinstallation.
+```
+
+Optionally, if you want to contribute to MMClassification or experience experimental functions, please checkout to the dev branch:
+
+```shell
+git checkout dev
+```
+
+### Install as a Python package
+
+Just install with pip.
+
+```shell
+pip install mmcls
+```
+
+## Verify the installation
+
+To verify whether MMClassification is installed correctly, we provide some sample codes to run an inference demo.
+
+**Step 1.** We need to download config and checkpoint files.
+
+```shell
+mim download mmcls --config resnet50_8xb32_in1k --dest .
+```
+
+**Step 2.** Verify the inference demo.
+
+Option (a). If you install mmcls from source, just run the following command:
+
+```shell
+python demo/image_demo.py demo/demo.JPEG resnet50_8xb32_in1k.py resnet50_8xb32_in1k_20210831-ea4938fc.pth --device cpu
+```
+
+You will see the output result dict including `pred_label`, `pred_score` and `pred_class` in your terminal.
+And if you have graphical interface (instead of remote terminal etc.), you can enable `--show` option to show
+the demo image with these predictions in a window.
+
+Option (b). If you install mmcls as a python package, open you python interpreter and copy&paste the following codes.
+
+```python
+from mmcls.apis import init_model, inference_model
+
+config_file = 'resnet50_8xb32_in1k.py'
+checkpoint_file = 'resnet50_8xb32_in1k_20210831-ea4938fc.pth'
+model = init_model(config_file, checkpoint_file, device='cpu') # or device='cuda:0'
+inference_model(model, 'demo/demo.JPEG')
+```
+
+You will see a dict printed, including the predicted label, score and category name.
+
+## Customize Installation
+
+### CUDA versions
+
+When installing PyTorch, you need to specify the version of CUDA. If you are
+not clear on which to choose, follow our recommendations:
+
+- For Ampere-based NVIDIA GPUs, such as GeForce 30 series and NVIDIA A100, CUDA 11 is a must.
+- For older NVIDIA GPUs, CUDA 11 is backward compatible, but CUDA 10.2 offers better compatibility and is more lightweight.
+
+Please make sure the GPU driver satisfies the minimum version requirements. See [this table](https://docs.nvidia.com/cuda/cuda-toolkit-release-notes/index.html#cuda-major-component-versions__table-cuda-toolkit-driver-versions) for more information.
+
+```{note}
+Installing CUDA runtime libraries is enough if you follow our best practices,
+because no CUDA code will be compiled locally. However if you hope to compile
+MMCV from source or develop other CUDA operators, you need to install the
+complete CUDA toolkit from NVIDIA's [website](https://developer.nvidia.com/cuda-downloads),
+and its version should match the CUDA version of PyTorch. i.e., the specified
+version of cudatoolkit in `conda install` command.
+```
+
+### Install MMCV without MIM
+
+MMCV contains C++ and CUDA extensions, thus depending on PyTorch in a complex
+way. MIM solves such dependencies automatically and makes the installation
+easier. However, it is not a must.
+
+To install MMCV with pip instead of MIM, please follow
+[MMCV installation guides](https://mmcv.readthedocs.io/en/latest/get_started/installation.html).
+This requires manually specifying a find-url based on PyTorch version and its CUDA version.
+
+For example, the following command install mmcv-full built for PyTorch 1.10.x and CUDA 11.3.
+
+```shell
+pip install mmcv-full -f https://download.openmmlab.com/mmcv/dist/cu113/torch1.10/index.html
+```
+
+### Install on CPU-only platforms
+
+MMClassification can be built for CPU only environment. In CPU mode you can
+train (requires MMCV version >= 1.4.4), test or inference a model.
+
+Some functionalities are gone in this mode, usually GPU-compiled ops. But don't
+worry, almost all models in MMClassification don't depends on these ops.
+
+### Install on Google Colab
+
+[Google Colab](https://research.google.com/) usually has PyTorch installed,
+thus we only need to install MMCV and MMClassification with the following
+commands.
+
+**Step 1.** Install [MMCV](https://github.com/open-mmlab/mmcv) using [MIM](https://github.com/open-mmlab/mim).
+
+```shell
+!pip3 install openmim
+!mim install mmcv-full
+```
+
+**Step 2.** Install MMClassification from the source.
+
+```shell
+!git clone https://github.com/open-mmlab/mmclassification.git
+%cd mmclassification
+!pip install -e .
+```
+
+**Step 3.** Verification.
+
+```python
+import mmcls
+print(mmcls.__version__)
+# Example output: 0.23.0 or newer
+```
+
+```{note}
+Within Jupyter, the exclamation mark `!` is used to call external executables and `%cd` is a [magic command](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-cd) to change the current working directory of Python.
+```
+
+### Using MMClassification with Docker
+
+We provide a [Dockerfile](https://github.com/open-mmlab/mmclassification/blob/master/docker/Dockerfile)
+to build an image. Ensure that your [docker version](https://docs.docker.com/engine/install/) >=19.03.
+
+```shell
+# build an image with PyTorch 1.8.1, CUDA 10.2
+# If you prefer other versions, just modified the Dockerfile
+docker build -t mmclassification docker/
+```
+
+Run it with
+
+```shell
+docker run --gpus all --shm-size=8g -it -v {DATA_DIR}:/mmclassification/data mmclassification
+```
+
+## Trouble shooting
+
+If you have some issues during the installation, please first view the [FAQ](faq.md) page.
+You may [open an issue](https://github.com/open-mmlab/mmclassification/issues/new/choose)
+on GitHub if no solution is found.
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/model_zoo.md b/openmmlab_test/mmclassification-0.24.1/docs/en/model_zoo.md
new file mode 100644
index 0000000000000000000000000000000000000000..46b42a97e68998f575d043f4eda1ae5ed7433ecc
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/model_zoo.md
@@ -0,0 +1,162 @@
+# Model Zoo
+
+## ImageNet
+
+ImageNet has multiple versions, but the most commonly used one is [ILSVRC 2012](http://www.image-net.org/challenges/LSVRC/2012/).
+The ResNet family models below are trained by standard data augmentations, i.e., RandomResizedCrop, RandomHorizontalFlip and Normalize.
+
+| Model | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download |
+| :--------------------------------: | :-------------------------------: | :-----------------------------: | :-------: | :-------: | :---------------------------------------: | :-----------------------------------------: |
+| VGG-11 | 132.86 | 7.63 | 68.75 | 88.87 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/vgg/vgg11_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/vgg/vgg11_batch256_imagenet_20210208-4271cd6c.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/vgg/vgg11_batch256_imagenet_20210208-4271cd6c.log.json) |
+| VGG-13 | 133.05 | 11.34 | 70.02 | 89.46 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/vgg/vgg13_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/vgg/vgg13_batch256_imagenet_20210208-4d1d6080.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/vgg/vgg13_batch256_imagenet_20210208-4d1d6080.log.json) |
+| VGG-16 | 138.36 | 15.5 | 71.62 | 90.49 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/vgg/vgg16_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/vgg/vgg16_batch256_imagenet_20210208-db26f1a5.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/vgg/vgg16_batch256_imagenet_20210208-db26f1a5.log.json) |
+| VGG-19 | 143.67 | 19.67 | 72.41 | 90.80 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/vgg/vgg19_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/vgg/vgg19_batch256_imagenet_20210208-e6920e4a.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/vgg/vgg19_batch256_imagenet_20210208-e6920e4a.log.json) |
+| VGG-11-BN | 132.87 | 7.64 | 70.75 | 90.12 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/vgg/vgg11bn_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/vgg/vgg11_bn_batch256_imagenet_20210207-f244902c.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/vgg/vgg11_bn_batch256_imagenet_20210207-f244902c.log.json) |
+| VGG-13-BN | 133.05 | 11.36 | 72.15 | 90.71 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/vgg/vgg13bn_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/vgg/vgg13_bn_batch256_imagenet_20210207-1a8b7864.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/vgg/vgg13_bn_batch256_imagenet_20210207-1a8b7864.log.json) |
+| VGG-16-BN | 138.37 | 15.53 | 73.72 | 91.68 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/vgg/vgg16_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/vgg/vgg16_bn_batch256_imagenet_20210208-7e55cd29.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/vgg/vgg16_bn_batch256_imagenet_20210208-7e55cd29.log.json) |
+| VGG-19-BN | 143.68 | 19.7 | 74.70 | 92.24 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/vgg/vgg19bn_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/vgg/vgg19_bn_batch256_imagenet_20210208-da620c4f.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/vgg/vgg19_bn_batch256_imagenet_20210208-da620c4f.log.json) |
+| RepVGG-A0\* | 9.11(train) \| 8.31 (deploy) | 1.52 (train) \| 1.36 (deploy) | 72.41 | 90.50 | [config (train)](https://github.com/open-mmlab/mmclassification/blob/master/configs/repvgg/repvgg-A0_4xb64-coslr-120e_in1k.py) \| [config (deploy)](https://github.com/open-mmlab/mmclassification/blob/master/configs/repvgg/deploy/repvgg-A0_deploy_4xb64-coslr-120e_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/repvgg/repvgg-A0_3rdparty_4xb64-coslr-120e_in1k_20210909-883ab98c.pth) |
+| RepVGG-A1\* | 14.09 (train) \| 12.79 (deploy) | 2.64 (train) \| 2.37 (deploy) | 74.47 | 91.85 | [config (train)](https://github.com/open-mmlab/mmclassification/blob/master/configs/repvgg/repvgg-A1_4xb64-coslr-120e_in1k.py) \| [config (deploy)](https://github.com/open-mmlab/mmclassification/blob/master/configs/repvgg/deploy/repvgg-A1_deploy_4xb64-coslr-120e_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/repvgg/repvgg-A1_3rdparty_4xb64-coslr-120e_in1k_20210909-24003a24.pth) |
+| RepVGG-A2\* | 28.21 (train) \| 25.5 (deploy) | 5.7 (train) \| 5.12 (deploy) | 76.48 | 93.01 | [config (train)](https://github.com/open-mmlab/mmclassification/blob/master/configs/repvgg/repvgg-A2_4xb64-coslr-120e_in1k.py) \| [config (deploy)](https://github.com/open-mmlab/mmclassification/blob/master/configs/repvgg/deploy/repvgg-A2_deploy_4xb64-coslr-120e_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/repvgg/repvgg-A2_3rdparty_4xb64-coslr-120e_in1k_20210909-97d7695a.pth) |
+| RepVGG-B0\* | 15.82 (train) \| 14.34 (deploy) | 3.42 (train) \| 3.06 (deploy) | 75.14 | 92.42 | [config (train)](https://github.com/open-mmlab/mmclassification/blob/master/configs/repvgg/repvgg-B0_4xb64-coslr-120e_in1k.py) \| [config (deploy)](https://github.com/open-mmlab/mmclassification/blob/master/configs/repvgg/deploy/repvgg-B0_deploy_4xb64-coslr-120e_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/repvgg/repvgg-B0_3rdparty_4xb64-coslr-120e_in1k_20210909-446375f4.pth) |
+| RepVGG-B1\* | 57.42 (train) \| 51.83 (deploy) | 13.16 (train) \| 11.82 (deploy) | 78.37 | 94.11 | [config (train)](https://github.com/open-mmlab/mmclassification/blob/master/configs/repvgg/repvgg-B1_4xb64-coslr-120e_in1k.py) \| [config (deploy)](https://github.com/open-mmlab/mmclassification/blob/master/configs/repvgg/deploy/repvgg-B1_deploy_4xb64-coslr-120e_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/repvgg/repvgg-B1_3rdparty_4xb64-coslr-120e_in1k_20210909-750cdf67.pth) |
+| RepVGG-B1g2\* | 45.78 (train) \| 41.36 (deploy) | 9.82 (train) \| 8.82 (deploy) | 77.79 | 93.88 | [config (train)](https://github.com/open-mmlab/mmclassification/blob/master/configs/repvgg/repvgg-B1g2_4xb64-coslr-120e_in1k.py) \| [config (deploy)](https://github.com/open-mmlab/mmclassification/blob/master/configs/repvgg/deploy/repvgg-B1g2_deploy_4xb64-coslr-120e_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/repvgg/repvgg-B1g2_3rdparty_4xb64-coslr-120e_in1k_20210909-344f6422.pth) |
+| RepVGG-B1g4\* | 39.97 (train) \| 36.13 (deploy) | 8.15 (train) \| 7.32 (deploy) | 77.58 | 93.84 | [config (train)](https://github.com/open-mmlab/mmclassification/blob/master/configs/repvgg/repvgg-B1g4_4xb64-coslr-120e_in1k.py) \| [config (deploy)](https://github.com/open-mmlab/mmclassification/blob/master/configs/repvgg/deploy/repvgg-B1g4_deploy_4xb64-coslr-120e_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/repvgg/repvgg-B1g4_3rdparty_4xb64-coslr-120e_in1k_20210909-d4c1a642.pth) |
+| RepVGG-B2\* | 89.02 (train) \| 80.32 (deploy) | 20.46 (train) \| 18.39 (deploy) | 78.78 | 94.42 | [config (train)](https://github.com/open-mmlab/mmclassification/blob/master/configs/repvgg/repvgg-B2_4xb64-coslr-120e_in1k.py) \| [config (deploy)](https://github.com/open-mmlab/mmclassification/blob/master/configs/repvgg/deploy/repvgg-B2_deploy_4xb64-coslr-120e_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/repvgg/repvgg-B2_3rdparty_4xb64-coslr-120e_in1k_20210909-bd6b937c.pth) |
+| RepVGG-B2g4\* | 61.76 (train) \| 55.78 (deploy) | 12.63 (train) \| 11.34 (deploy) | 79.38 | 94.68 | [config (train)](https://github.com/open-mmlab/mmclassification/blob/master/configs/repvgg/repvgg-B2g4_4xb64-autoaug-lbs-mixup-coslr-200e_in1k.py) \| [config (deploy)](https://github.com/open-mmlab/mmclassification/blob/master/configs/repvgg/deploy/repvgg-B2g4_deploy_4xb64-autoaug-lbs-mixup-coslr-200e_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/repvgg/repvgg-B2g4_3rdparty_4xb64-autoaug-lbs-mixup-coslr-200e_in1k_20210909-7b7955f0.pth) |
+| RepVGG-B3\* | 123.09 (train) \| 110.96 (deploy) | 29.17 (train) \| 26.22 (deploy) | 80.52 | 95.26 | [config (train)](https://github.com/open-mmlab/mmclassification/blob/master/configs/repvgg/repvgg-B3_4xb64-autoaug-lbs-mixup-coslr-200e_in1k.py) \| [config (deploy)](https://github.com/open-mmlab/mmclassification/blob/master/configs/repvgg/deploy/repvgg-B3_deploy_4xb64-autoaug-lbs-mixup-coslr-200e_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/repvgg/repvgg-B3_3rdparty_4xb64-autoaug-lbs-mixup-coslr-200e_in1k_20210909-dda968bf.pth) |
+| RepVGG-B3g4\* | 83.83 (train) \| 75.63 (deploy) | 17.9 (train) \| 16.08 (deploy) | 80.22 | 95.10 | [config (train)](https://github.com/open-mmlab/mmclassification/blob/master/configs/repvgg/repvgg-B3g4_4xb64-autoaug-lbs-mixup-coslr-200e_in1k.py) \| [config (deploy)](https://github.com/open-mmlab/mmclassification/blob/master/configs/repvgg/deploy/repvgg-B3g4_deploy_4xb64-autoaug-lbs-mixup-coslr-200e_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/repvgg/repvgg-B3g4_3rdparty_4xb64-autoaug-lbs-mixup-coslr-200e_in1k_20210909-4e54846a.pth) |
+| RepVGG-D2se\* | 133.33 (train) \| 120.39 (deploy) | 36.56 (train) \| 32.85 (deploy) | 81.81 | 95.94 | [config (train)](https://github.com/open-mmlab/mmclassification/blob/master/configs/repvgg/repvgg-D2se_4xb64-autoaug-lbs-mixup-coslr-200e_in1k.py) \| [config (deploy)](https://github.com/open-mmlab/mmclassification/blob/master/configs/repvgg/deploy/repvgg-D2se_deploy_4xb64-autoaug-lbs-mixup-coslr-200e_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/repvgg/repvgg-D2se_3rdparty_4xb64-autoaug-lbs-mixup-coslr-200e_in1k_20210909-cf3139b7.pth) |
+| ResNet-18 | 11.69 | 1.82 | 70.07 | 89.44 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnet/resnet18_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/resnet/resnet18_batch256_imagenet_20200708-34ab8f90.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/resnet/resnet18_batch256_imagenet_20200708-34ab8f90.log.json) |
+| ResNet-34 | 21.8 | 3.68 | 73.85 | 91.53 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnet/resnet34_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/resnet/resnet34_batch256_imagenet_20200708-32ffb4f7.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/resnet/resnet34_batch256_imagenet_20200708-32ffb4f7.log.json) |
+| ResNet-50 (rsb-a1) | 25.56 | 4.12 | 80.12 | 94.78 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnet/resnet50_8xb256-rsb-a1-600e_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/resnet/resnet50_8xb256-rsb-a1-600e_in1k_20211228-20e21305.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/resnet/resnet50_8xb256-rsb-a1-600e_in1k_20211228-20e21305.log.json) |
+| ResNet-101 | 44.55 | 7.85 | 78.18 | 94.03 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnet/resnet101_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/resnet/resnet101_batch256_imagenet_20200708-753f3608.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/resnet/resnet101_batch256_imagenet_20200708-753f3608.log.json) |
+| ResNet-152 | 60.19 | 11.58 | 78.63 | 94.16 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnet/resnet152_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/resnet/resnet152_batch256_imagenet_20200708-ec25b1f9.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/resnet/resnet152_batch256_imagenet_20200708-ec25b1f9.log.json) |
+| Res2Net-50-14w-8s\* | 25.06 | 4.22 | 78.14 | 93.85 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/res2net/res2net50-w14-s8_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/res2net/res2net50-w14-s8_3rdparty_8xb32_in1k_20210927-bc967bf1.pth) |
+| Res2Net-50-26w-8s\* | 48.40 | 8.39 | 79.20 | 94.36 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/res2net/res2net50-w26-s8_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/res2net/res2net50-w26-s8_3rdparty_8xb32_in1k_20210927-f547a94b.pth) |
+| Res2Net-101-26w-4s\* | 45.21 | 8.12 | 79.19 | 94.44 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/res2net/res2net101-w26-s4_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/res2net/res2net101-w26-s4_3rdparty_8xb32_in1k_20210927-870b6c36.pth) |
+| ResNeSt-50\* | 27.48 | 5.41 | 81.13 | 95.59 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnest/resnest50_32xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/resnest/resnest50_imagenet_converted-1ebf0afe.pth) |
+| ResNeSt-101\* | 48.28 | 10.27 | 82.32 | 96.24 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnest/resnest101_32xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/resnest/resnest101_imagenet_converted-032caa52.pth) |
+| ResNeSt-200\* | 70.2 | 17.53 | 82.41 | 96.22 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnest/resnest200_64xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/resnest/resnest200_imagenet_converted-581a60f2.pth) |
+| ResNeSt-269\* | 110.93 | 22.58 | 82.70 | 96.28 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnest/resnest269_64xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/resnest/resnest269_imagenet_converted-59930960.pth) |
+| ResNetV1D-50 | 25.58 | 4.36 | 77.54 | 93.57 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnet/resnetv1d50_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/resnet/resnetv1d50_b32x8_imagenet_20210531-db14775a.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/resnet/resnetv1d50_b32x8_imagenet_20210531-db14775a.log.json) |
+| ResNetV1D-101 | 44.57 | 8.09 | 78.93 | 94.48 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnet/resnetv1d101_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/resnet/resnetv1d101_b32x8_imagenet_20210531-6e13bcd3.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/resnet/resnetv1d101_b32x8_imagenet_20210531-6e13bcd3.log.json) |
+| ResNetV1D-152 | 60.21 | 11.82 | 79.41 | 94.7 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnet/resnetv1d152_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/resnet/resnetv1d152_b32x8_imagenet_20210531-278cf22a.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/resnet/resnetv1d152_b32x8_imagenet_20210531-278cf22a.log.json) |
+| ResNeXt-32x4d-50 | 25.03 | 4.27 | 77.90 | 93.66 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnext/resnext50-32x4d_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/resnext/resnext50_32x4d_b32x8_imagenet_20210429-56066e27.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/resnext/resnext50_32x4d_b32x8_imagenet_20210429-56066e27.log.json) |
+| ResNeXt-32x4d-101 | 44.18 | 8.03 | 78.71 | 94.12 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnext/resnext101-32x4d_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/resnext/resnext101_32x4d_b32x8_imagenet_20210506-e0fa3dd5.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/resnext/resnext101_32x4d_b32x8_imagenet_20210506-e0fa3dd5.log.json) |
+| ResNeXt-32x8d-101 | 88.79 | 16.5 | 79.23 | 94.58 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnext/resnext101-32x8d_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/resnext/resnext101_32x8d_b32x8_imagenet_20210506-23a247d5.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/resnext/resnext101_32x8d_b32x8_imagenet_20210506-23a247d5.log.json) |
+| ResNeXt-32x4d-152 | 59.95 | 11.8 | 78.93 | 94.41 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnext/resnext152-32x4d_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/resnext/resnext152_32x4d_b32x8_imagenet_20210524-927787be.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/resnext/resnext152_32x4d_b32x8_imagenet_20210524-927787be.log.json) |
+| SE-ResNet-50 | 28.09 | 4.13 | 77.74 | 93.84 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/seresnet/seresnet50_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/se-resnet/se-resnet50_batch256_imagenet_20200804-ae206104.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/se-resnet/se-resnet50_batch256_imagenet_20200708-657b3c36.log.json) |
+| SE-ResNet-101 | 49.33 | 7.86 | 78.26 | 94.07 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/seresnet/seresnet101_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/se-resnet/se-resnet101_batch256_imagenet_20200804-ba5b51d4.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/se-resnet/se-resnet101_batch256_imagenet_20200708-038a4d04.log.json) |
+| RegNetX-400MF | 5.16 | 0.41 | 72.56 | 90.78 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/regnet/regnetx-400mf_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/regnet/regnetx-400mf_8xb128_in1k_20211213-89bfc226.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/regnet/regnetx-400mf_8xb128_in1k_20211208_143316.log.json) |
+| RegNetX-800MF | 7.26 | 0.81 | 74.76 | 92.32 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/regnet/regnetx-800mf_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/regnet/regnetx-800mf_8xb128_in1k_20211213-222b0f11.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/regnet/regnetx-800mf_8xb128_in1k_20211207_143037.log.json) |
+| RegNetX-1.6GF | 9.19 | 1.63 | 76.84 | 93.31 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/regnet/regnetx-1.6gf_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/regnet/regnetx-1.6gf_8xb128_in1k_20211213-d1b89758.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/regnet/regnetx-1.6gf_8xb128_in1k_20211208_143018.log.json) |
+| RegNetX-3.2GF | 15.3 | 3.21 | 78.09 | 94.08 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/regnet/regnetx-3.2gf_8xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/regnet/regnetx-3.2gf_8xb64_in1k_20211213-1fdd82ae.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/regnet/regnetx-3.2gf_8xb64_in1k_20211208_142720.log.json) |
+| RegNetX-4.0GF | 22.12 | 4.0 | 78.60 | 94.17 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/regnet/regnetx-4.0gf_8xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/regnet/regnetx-4.0gf_8xb64_in1k_20211213-efed675c.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/regnet/regnetx-4.0gf_8xb64_in1k_20211207_150431.log.json) |
+| RegNetX-6.4GF | 26.21 | 6.51 | 79.38 | 94.65 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/regnet/regnetx-6.4gf_8xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/regnet/regnetx-6.4gf_8xb64_in1k_20211215-5c6089da.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/regnet/regnetx-6.4gf_8xb64_in1k_20211213_172748.log.json) |
+| RegNetX-8.0GF | 39.57 | 8.03 | 79.12 | 94.51 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/regnet/regnetx-8.0gf_8xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/regnet/regnetx-8.0gf_8xb64_in1k_20211213-9a9fcc76.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/regnet/regnetx-8.0gf_8xb64_in1k_20211208_103250.log.json) |
+| RegNetX-12GF | 46.11 | 12.15 | 79.67 | 95.03 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/regnet/regnetx-12gf_8xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/regnet/regnetx-12gf_8xb64_in1k_20211213-5df8c2f8.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/regnet/regnetx-12gf_8xb64_in1k_20211208_143713.log.json) |
+| ShuffleNetV1 1.0x (group=3) | 1.87 | 0.146 | 68.13 | 87.81 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/shufflenet_v1/shufflenet-v1-1x_16xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/shufflenet_v1/shufflenet_v1_batch1024_imagenet_20200804-5d6cec73.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/shufflenet_v1/shufflenet_v1_batch1024_imagenet_20200804-5d6cec73.log.json) |
+| ShuffleNetV2 1.0x | 2.28 | 0.149 | 69.55 | 88.92 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/shufflenet_v2/shufflenet-v2-1x_16xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/shufflenet_v2/shufflenet_v2_batch1024_imagenet_20200812-5bf4721e.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/shufflenet_v2/shufflenet_v2_batch1024_imagenet_20200804-8860eec9.log.json) |
+| MobileNet V2 | 3.5 | 0.319 | 71.86 | 90.42 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/mobilenet_v2/mobilenet-v2_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobilenet_v2/mobilenet_v2_batch256_imagenet_20200708-3b2dc3af.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobilenet_v2/mobilenet_v2_batch256_imagenet_20200708-3b2dc3af.log.json) |
+| ViT-B/16\* | 86.86 | 33.03 | 85.43 | 97.77 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/vision_transformer/vit-base-p16_ft-64xb64_in1k-384.py) | [model](https://download.openmmlab.com/mmclassification/v0/vit/finetune/vit-base-p16_in21k-pre-3rdparty_ft-64xb64_in1k-384_20210928-98e8652b.pth) |
+| ViT-B/32\* | 88.3 | 8.56 | 84.01 | 97.08 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/vision_transformer/vit-base-p32_ft-64xb64_in1k-384.py) | [model](https://download.openmmlab.com/mmclassification/v0/vit/finetune/vit-base-p32_in21k-pre-3rdparty_ft-64xb64_in1k-384_20210928-9cea8599.pth) |
+| ViT-L/16\* | 304.72 | 116.68 | 85.63 | 97.63 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/vision_transformer/vit-large-p16_ft-64xb64_in1k-384.py) | [model](https://download.openmmlab.com/mmclassification/v0/vit/finetune/vit-large-p16_in21k-pre-3rdparty_ft-64xb64_in1k-384_20210928-b20ba619.pth) |
+| Swin-Transformer tiny | 28.29 | 4.36 | 81.18 | 95.61 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/swin_transformer/swin-tiny_16xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/swin-transformer/swin_tiny_224_b16x64_300e_imagenet_20210616_090925-66df6be6.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/swin-transformer/swin_tiny_224_b16x64_300e_imagenet_20210616_090925.log.json) |
+| Swin-Transformer small | 49.61 | 8.52 | 83.02 | 96.29 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/swin_transformer/swin-small_16xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/swin-transformer/swin_small_224_b16x64_300e_imagenet_20210615_110219-7f9d988b.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/swin-transformer/swin_small_224_b16x64_300e_imagenet_20210615_110219.log.json) |
+| Swin-Transformer base | 87.77 | 15.14 | 83.36 | 96.44 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/swin_transformer/swin_base_224_b16x64_300e_imagenet.py) | [model](https://download.openmmlab.com/mmclassification/v0/swin-transformer/swin_base_224_b16x64_300e_imagenet_20210616_190742-93230b0d.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/swin-transformer/swin_base_224_b16x64_300e_imagenet_20210616_190742.log.json) |
+| Transformer in Transformer small\* | 23.76 | 3.36 | 81.52 | 95.73 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/tnt/tnt-s-p16_16xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/tnt/tnt-small-p16_3rdparty_in1k_20210903-c56ee7df.pth) |
+| T2T-ViT_t-14 | 21.47 | 4.34 | 81.83 | 95.84 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/t2t_vit/t2t-vit-t-14_8xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/t2t-vit/t2t-vit-t-14_8xb64_in1k_20211220-f7378dd5.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/t2t-vit/t2t-vit-t-14_8xb64_in1k_20211220-f7378dd5.log.json) |
+| T2T-ViT_t-19 | 39.08 | 7.80 | 82.63 | 96.18 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/t2t_vit/t2t-vit-t-19_8xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/t2t-vit/t2t-vit-t-19_8xb64_in1k_20211214-7f5e3aaf.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/t2t-vit/t2t-vit-t-19_8xb64_in1k_20211214-7f5e3aaf.log.json) |
+| T2T-ViT_t-24 | 64.00 | 12.69 | 82.71 | 96.09 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/t2t_vit/t2t-vit-t-24_8xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/t2t-vit/t2t-vit-t-24_8xb64_in1k_20211214-b2a68ae3.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/t2t-vit/t2t-vit-t-24_8xb64_in1k_20211214-b2a68ae3.log.json) |
+| Mixer-B/16\* | 59.88 | 12.61 | 76.68 | 92.25 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/mlp_mixer/mlp-mixer-base-p16_64xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mlp-mixer/mixer-base-p16_3rdparty_64xb64_in1k_20211124-1377e3e0.pth) |
+| Mixer-L/16\* | 208.2 | 44.57 | 72.34 | 88.02 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/mlp_mixer/mlp-mixer-large-p16_64xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mlp-mixer/mixer-large-p16_3rdparty_64xb64_in1k_20211124-5a2519d2.pth) |
+| DeiT-tiny | 5.72 | 1.08 | 74.50 | 92.24 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/deit/deit-tiny_pt-4xb256_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/deit/deit-tiny_pt-4xb256_in1k_20220218-13b382a0.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/deit/deit-tiny_pt-4xb256_in1k_20220218-13b382a0.log.json) |
+| DeiT-tiny distilled\* | 5.72 | 1.08 | 74.51 | 91.90 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/deit/deit-tiny-distilled_pt-4xb256_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/deit/deit-tiny-distilled_3rdparty_pt-4xb256_in1k_20211216-c429839a.pth) |
+| DeiT-small | 22.05 | 4.24 | 80.69 | 95.06 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/deit/deit-small_pt-4xb256_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/deit/deit-small_pt-4xb256_in1k_20220218-9425b9bb.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/deit/deit-small_pt-4xb256_in1k_20220218-9425b9bb.log.json) |
+| DeiT-small distilled\* | 22.05 | 4.24 | 81.17 | 95.40 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/deit/deit-small-distilled_pt-4xb256_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/deit/deit-small-distilled_3rdparty_pt-4xb256_in1k_20211216-4de1d725.pth) |
+| DeiT-base | 86.57 | 16.86 | 81.76 | 95.81 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/deit/deit-base_pt-16xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/deit/deit-base_pt-16xb64_in1k_20220216-db63c16c.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/deit/deit-base_pt-16xb64_in1k_20220216-db63c16c.log.json) |
+| DeiT-base distilled\* | 86.57 | 16.86 | 83.33 | 96.49 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/deit/deit-base-distilled_pt-16xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/deit/deit-base-distilled_3rdparty_pt-16xb64_in1k_20211216-42891296.pth) |
+| DeiT-base 384px\* | 86.86 | 49.37 | 83.04 | 96.31 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/deit/deit-base_ft-16xb32_in1k-384px.py) | [model](https://download.openmmlab.com/mmclassification/v0/deit/deit-base_3rdparty_ft-16xb32_in1k-384px_20211124-822d02f2.pth) |
+| DeiT-base distilled 384px\* | 86.86 | 49.37 | 85.55 | 97.35 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/deit/deit-base-distilled_ft-16xb32_in1k-384px.py) | [model](https://download.openmmlab.com/mmclassification/v0/deit/deit-base-distilled_3rdparty_ft-16xb32_in1k-384px_20211216-e48d6000.pth) |
+| Conformer-tiny-p16\* | 23.52 | 4.90 | 81.31 | 95.60 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/conformer/conformer-tiny-p16_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/conformer/conformer-tiny-p16_3rdparty_8xb128_in1k_20211206-f6860372.pth) |
+| Conformer-small-p32\* | 38.85 | 7.09 | 81.96 | 96.02 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/conformer/conformer-small-p32_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/conformer/conformer-small-p32_8xb128_in1k_20211206-947a0816.pth) |
+| Conformer-small-p16\* | 37.67 | 10.31 | 83.32 | 96.46 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/conformer/conformer-small-p16_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/conformer/conformer-small-p16_3rdparty_8xb128_in1k_20211206-3065dcf5.pth) |
+| Conformer-base-p16\* | 83.29 | 22.89 | 83.82 | 96.59 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/conformer/conformer-base-p16_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/conformer/conformer-base-p16_3rdparty_8xb128_in1k_20211206-bfdf8637.pth) |
+| PCPVT-small\* | 24.11 | 3.67 | 81.14 | 95.69 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/twins/twins-pcpvt-small_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/twins/twins-pcpvt-small_3rdparty_8xb128_in1k_20220126-ef23c132.pth) |
+| PCPVT-base\* | 43.83 | 6.45 | 82.66 | 96.26 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/twins/twins-pcpvt-base_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/twins/twins-pcpvt-base_3rdparty_8xb128_in1k_20220126-f8c4b0d5.pth) |
+| PCPVT-large\* | 60.99 | 9.51 | 83.09 | 96.59 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/twins/twins-pcpvt-large_16xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/twins/twins-pcpvt-large_3rdparty_16xb64_in1k_20220126-c1ef8d80.pth) |
+| SVT-small\* | 24.06 | 2.82 | 81.77 | 95.57 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/twins/twins-svt-small_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/twins/twins-svt-small_3rdparty_8xb128_in1k_20220126-8fe5205b.pth) |
+| SVT-base\* | 56.07 | 8.35 | 83.13 | 96.29 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/twins/twins-svt-base_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/twins/twins-svt-base_3rdparty_8xb128_in1k_20220126-e31cc8e9.pth) |
+| SVT-large\* | 99.27 | 14.82 | 83.60 | 96.50 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/twins/twins-svt-large_16xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/twins/twins-svt-large_3rdparty_16xb64_in1k_20220126-4817645f.pth) |
+| EfficientNet-B0\* | 5.29 | 0.02 | 76.74 | 93.17 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientnet/efficientnet-b0_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b0_3rdparty_8xb32_in1k_20220119-a7e2a0b1.pth) |
+| EfficientNet-B0 (AA)\* | 5.29 | 0.02 | 77.26 | 93.41 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientnet/efficientnet-b0_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b0_3rdparty_8xb32-aa_in1k_20220119-8d939117.pth) |
+| EfficientNet-B0 (AA + AdvProp)\* | 5.29 | 0.02 | 77.53 | 93.61 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientnet/efficientnet-b0_8xb32-01norm_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b0_3rdparty_8xb32-aa-advprop_in1k_20220119-26434485.pth) |
+| EfficientNet-B1\* | 7.79 | 0.03 | 78.68 | 94.28 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientnet/efficientnet-b1_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b1_3rdparty_8xb32_in1k_20220119-002556d9.pth) |
+| EfficientNet-B1 (AA)\* | 7.79 | 0.03 | 79.20 | 94.42 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientnet/efficientnet-b1_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b1_3rdparty_8xb32-aa_in1k_20220119-619d8ae3.pth) |
+| EfficientNet-B1 (AA + AdvProp)\* | 7.79 | 0.03 | 79.52 | 94.43 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientnet/efficientnet-b1_8xb32-01norm_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b1_3rdparty_8xb32-aa-advprop_in1k_20220119-5715267d.pth) |
+| EfficientNet-B2\* | 9.11 | 0.03 | 79.64 | 94.80 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientnet/efficientnet-b2_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b2_3rdparty_8xb32_in1k_20220119-ea374a30.pth) |
+| EfficientNet-B2 (AA)\* | 9.11 | 0.03 | 80.21 | 94.96 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientnet/efficientnet-b2_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b2_3rdparty_8xb32-aa_in1k_20220119-dd61e80b.pth) |
+| EfficientNet-B2 (AA + AdvProp)\* | 9.11 | 0.03 | 80.45 | 95.07 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientnet/efficientnet-b2_8xb32-01norm_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b2_3rdparty_8xb32-aa-advprop_in1k_20220119-1655338a.pth) |
+| EfficientNet-B3\* | 12.23 | 0.06 | 81.01 | 95.34 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientnet/efficientnet-b3_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b3_3rdparty_8xb32_in1k_20220119-4b4d7487.pth) |
+| EfficientNet-B3 (AA)\* | 12.23 | 0.06 | 81.58 | 95.67 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientnet/efficientnet-b3_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b3_3rdparty_8xb32-aa_in1k_20220119-5b4887a0.pth) |
+| EfficientNet-B3 (AA + AdvProp)\* | 12.23 | 0.06 | 81.81 | 95.69 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientnet/efficientnet-b3_8xb32-01norm_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b3_3rdparty_8xb32-aa-advprop_in1k_20220119-53b41118.pth) |
+| EfficientNet-B4\* | 19.34 | 0.12 | 82.57 | 96.09 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientnet/efficientnet-b4_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b4_3rdparty_8xb32_in1k_20220119-81fd4077.pth) |
+| EfficientNet-B4 (AA)\* | 19.34 | 0.12 | 82.95 | 96.26 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientnet/efficientnet-b4_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b4_3rdparty_8xb32-aa_in1k_20220119-45b8bd2b.pth) |
+| EfficientNet-B4 (AA + AdvProp)\* | 19.34 | 0.12 | 83.25 | 96.44 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientnet/efficientnet-b4_8xb32-01norm_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b4_3rdparty_8xb32-aa-advprop_in1k_20220119-38c2238c.pth) |
+| EfficientNet-B5\* | 30.39 | 0.24 | 83.18 | 96.47 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientnet/efficientnet-b5_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b5_3rdparty_8xb32_in1k_20220119-e9814430.pth) |
+| EfficientNet-B5 (AA)\* | 30.39 | 0.24 | 83.82 | 96.76 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientnet/efficientnet-b5_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b5_3rdparty_8xb32-aa_in1k_20220119-2cab8b78.pth) |
+| EfficientNet-B5 (AA + AdvProp)\* | 30.39 | 0.24 | 84.21 | 96.98 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientnet/efficientnet-b5_8xb32-01norm_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b5_3rdparty_8xb32-aa-advprop_in1k_20220119-f57a895a.pth) |
+| EfficientNet-B6 (AA)\* | 43.04 | 0.41 | 84.05 | 96.82 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientnet/efficientnet-b6_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b6_3rdparty_8xb32-aa_in1k_20220119-45b03310.pth) |
+| EfficientNet-B6 (AA + AdvProp)\* | 43.04 | 0.41 | 84.74 | 97.14 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientnet/efficientnet-b6_8xb32-01norm_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b6_3rdparty_8xb32-aa-advprop_in1k_20220119-bfe3485e.pth) |
+| EfficientNet-B7 (AA)\* | 66.35 | 0.72 | 84.38 | 96.88 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientnet/efficientnet-b7_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b7_3rdparty_8xb32-aa_in1k_20220119-bf03951c.pth) |
+| EfficientNet-B7 (AA + AdvProp)\* | 66.35 | 0.72 | 85.14 | 97.23 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientnet/efficientnet-b7_8xb32-01norm_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b7_3rdparty_8xb32-aa-advprop_in1k_20220119-c6dbff10.pth) |
+| EfficientNet-B8 (AA + AdvProp)\* | 87.41 | 1.09 | 85.38 | 97.28 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientnet/efficientnet-b8_8xb32-01norm_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientnet/efficientnet-b8_3rdparty_8xb32-aa-advprop_in1k_20220119-297ce1b7.pth) |
+| ConvNeXt-T\* | 28.59 | 4.46 | 82.05 | 95.86 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/convnext/convnext-tiny_32xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/convnext/convnext-tiny_3rdparty_32xb128_in1k_20220124-18abde00.pth) |
+| ConvNeXt-S\* | 50.22 | 8.69 | 83.13 | 96.44 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/convnext/convnext-small_32xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/convnext/convnext-small_3rdparty_32xb128_in1k_20220124-d39b5192.pth) |
+| ConvNeXt-B\* | 88.59 | 15.36 | 83.85 | 96.74 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/convnext/convnext-base_32xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/convnext/convnext-base_3rdparty_32xb128_in1k_20220124-d0915162.pth) |
+| ConvNeXt-B\* | 88.59 | 15.36 | 85.81 | 97.86 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/convnext/convnext-base_32xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/convnext/convnext-base_in21k-pre-3rdparty_32xb128_in1k_20220124-eb2d6ada.pth) |
+| ConvNeXt-L\* | 197.77 | 34.37 | 84.30 | 96.89 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/convnext/convnext-large_64xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/convnext/convnext-large_3rdparty_64xb64_in1k_20220124-f8a0ded0.pth) |
+| ConvNeXt-L\* | 197.77 | 34.37 | 86.61 | 98.04 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/convnext/convnext-large_64xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/convnext/convnext-large_in21k-pre-3rdparty_64xb64_in1k_20220124-2412403d.pth) |
+| ConvNeXt-XL\* | 350.20 | 60.93 | 86.97 | 98.20 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/convnext/convnext-xlarge_64xb64_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/convnext/convnext-xlarge_in21k-pre-3rdparty_64xb64_in1k_20220124-76b6863d.pth) |
+| HRNet-W18\* | 21.30 | 4.33 | 76.75 | 93.44 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/hrnet/hrnet-w18_4xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w18_3rdparty_8xb32_in1k_20220120-0c10b180.pth) |
+| HRNet-W30\* | 37.71 | 8.17 | 78.19 | 94.22 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/hrnet/hrnet-w30_4xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w30_3rdparty_8xb32_in1k_20220120-8aa3832f.pth) |
+| HRNet-W32\* | 41.23 | 8.99 | 78.44 | 94.19 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/hrnet/hrnet-w32_4xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w32_3rdparty_8xb32_in1k_20220120-c394f1ab.pth) |
+| HRNet-W40\* | 57.55 | 12.77 | 78.94 | 94.47 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/hrnet/hrnet-w40_4xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w40_3rdparty_8xb32_in1k_20220120-9a2dbfc5.pth) |
+| HRNet-W44\* | 67.06 | 14.96 | 78.88 | 94.37 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/hrnet/hrnet-w44_4xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w44_3rdparty_8xb32_in1k_20220120-35d07f73.pth) |
+| HRNet-W48\* | 77.47 | 17.36 | 79.32 | 94.52 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/hrnet/hrnet-w48_4xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w48_3rdparty_8xb32_in1k_20220120-e555ef50.pth) |
+| HRNet-W64\* | 128.06 | 29.00 | 79.46 | 94.65 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/hrnet/hrnet-w64_4xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w64_3rdparty_8xb32_in1k_20220120-19126642.pth) |
+| HRNet-W18 (ssld)\* | 21.30 | 4.33 | 81.06 | 95.70 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/hrnet/hrnet-w18_4xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w18_3rdparty_8xb32-ssld_in1k_20220120-455f69ea.pth) |
+| HRNet-W48 (ssld)\* | 77.47 | 17.36 | 83.63 | 96.79 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/hrnet/hrnet-w48_4xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/hrnet/hrnet-w48_3rdparty_8xb32-ssld_in1k_20220120-d0459c38.pth) |
+| WRN-50\* | 68.88 | 11.44 | 81.45 | 95.53 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/wrn/wide-resnet50_timm_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/wrn/wide-resnet50_3rdparty-timm_8xb32_in1k_20220304-83ae4399.pth) |
+| WRN-101\* | 126.89 | 22.81 | 78.84 | 94.28 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/wrn/wide-resnet101_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/wrn/wide-resnet101_3rdparty_8xb32_in1k_20220304-8d5f9d61.pth) |
+| CSPDarkNet50\* | 27.64 | 5.04 | 80.05 | 95.07 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/cspnet/cspdarknet50_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/cspnet/cspdarknet50_3rdparty_8xb32_in1k_20220329-bd275287.pth) |
+| CSPResNet50\* | 21.62 | 3.48 | 79.55 | 94.68 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/cspnet/cspresnet50_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/cspnet/cspresnet50_3rdparty_8xb32_in1k_20220329-dd6dddfb.pth) |
+| CSPResNeXt50\* | 20.57 | 3.11 | 79.96 | 94.96 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/cspnet/cspresnext50_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/cspnet/cspresnext50_3rdparty_8xb32_in1k_20220329-2cc84d21.pth) |
+| DenseNet121\* | 7.98 | 2.88 | 74.96 | 92.21 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/densenet/densenet121_4xb256_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/densenet/densenet121_4xb256_in1k_20220426-07450f99.pth) |
+| DenseNet169\* | 14.15 | 3.42 | 76.08 | 93.11 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/densenet/densenet169_4xb256_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/densenet/densenet169_4xb256_in1k_20220426-a2889902.pth) |
+| DenseNet201\* | 20.01 | 4.37 | 77.32 | 93.64 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/densenet/densenet201_4xb256_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/densenet/densenet201_4xb256_in1k_20220426-05cae4ef.pth) |
+| DenseNet161\* | 28.68 | 7.82 | 77.61 | 93.83 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/densenet/densenet161_4xb256_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/densenet/densenet161_4xb256_in1k_20220426-ee6a80a9.pth) |
+| VAN-T\* | 4.11 | 0.88 | 75.41 | 93.02 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/van/van-tiny_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/van/van-tiny_8xb128_in1k_20220501-385941af.pth) |
+| VAN-S\* | 13.86 | 2.52 | 81.01 | 95.63 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/van/van-small_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/van/van-small_8xb128_in1k_20220501-17bc91aa.pth) |
+| VAN-B\* | 26.58 | 5.03 | 82.80 | 96.21 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/van/van-base_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/van/van-base_8xb128_in1k_20220501-6a4cc31b.pth) |
+| VAN-L\* | 44.77 | 8.99 | 83.86 | 96.73 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/van/van-large_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/van/van-large_8xb128_in1k_20220501-f212ba21.pth) |
+| MViTv2-tiny\* | 24.17 | 4.70 | 82.33 | 96.15 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/mvit/mvitv2-tiny_8xb256_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mvit/mvitv2-tiny_3rdparty_in1k_20220722-db7beeef.pth) |
+| MViTv2-small\* | 34.87 | 7.00 | 83.63 | 96.51 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/mvit/mvitv2-small_8xb256_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mvit/mvitv2-small_3rdparty_in1k_20220722-986bd741.pth) |
+| MViTv2-base\* | 51.47 | 10.20 | 84.34 | 96.86 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/mvit/mvitv2-base_8xb256_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mvit/mvitv2-base_3rdparty_in1k_20220722-9c4f0a17.pth) |
+| MViTv2-large\* | 217.99 | 42.10 | 85.25 | 97.14 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/mvit/mvitv2-large_8xb256_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mvit/mvitv2-large_3rdparty_in1k_20220722-2b57b983.pth) |
+| EfficientFormer-l1\* | 12.19 | 1.30 | 80.46 | 94.99 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientformer/efficientformer-l1_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientformer/efficientformer-l1_3rdparty_in1k_20220803-d66e61df.pth) |
+| EfficientFormer-l3\* | 31.41 | 3.93 | 82.45 | 96.18 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientformer/efficientformer-l3_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientformer/efficientformer-l3_3rdparty_in1k_20220803-dde1c8c5.pth) |
+| EfficientFormer-l7\* | 82.23 | 10.16 | 83.40 | 96.60 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientformer/efficientformer-l7_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/efficientformer/efficientformer-l7_3rdparty_in1k_20220803-41a552bb.pth) |
+
+*Models with * are converted from other repos, others are trained by ourselves.*
+
+## CIFAR10
+
+| Model | Params(M) | Flops(G) | Top-1 (%) | Config | Download |
+| :--------------: | :-------: | :------: | :-------: | :----: | :------------------------------------------------------------------------------------------------------------: |
+| ResNet-18-b16x8 | 11.17 | 0.56 | 94.82 | | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnet/resnet18_8xb16_cifar10.py) |
+| ResNet-34-b16x8 | 21.28 | 1.16 | 95.34 | | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnet/resnet34_8xb16_cifar10.py) |
+| ResNet-50-b16x8 | 23.52 | 1.31 | 95.55 | | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnet/resnet50_8xb16_cifar10.py) |
+| ResNet-101-b16x8 | 42.51 | 2.52 | 95.58 | | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnet/resnet101_8xb16_cifar10.py) |
+| ResNet-152-b16x8 | 58.16 | 3.74 | 95.76 | | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnet/resnet152_8xb16_cifar10.py) |
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/stat.py b/openmmlab_test/mmclassification-0.24.1/docs/en/stat.py
new file mode 100644
index 0000000000000000000000000000000000000000..8f1e5b2d52262dd16d8ed1515b21683827467b62
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/stat.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python
+import functools as func
+import glob
+import os
+import re
+from pathlib import Path
+
+import numpy as np
+
+MMCLS_ROOT = Path(__file__).absolute().parents[1]
+url_prefix = 'https://github.com/open-mmlab/mmclassification/blob/master/'
+
+papers_root = Path('papers')
+papers_root.mkdir(exist_ok=True)
+files = [Path(f) for f in sorted(glob.glob('../../configs/*/README.md'))]
+
+stats = []
+titles = []
+num_ckpts = 0
+num_configs = 0
+
+for f in files:
+ with open(f, 'r') as content_file:
+ content = content_file.read()
+
+ # Extract checkpoints
+ ckpts = set(x.lower().strip()
+ for x in re.findall(r'\[model\]\((https?.*)\)', content))
+ if len(ckpts) == 0:
+ continue
+ num_ckpts += len(ckpts)
+
+ # Extract paper title
+ match_res = list(re.finditer(r'> \[(.*)\]\((.*)\)', content))
+ if len(match_res) > 0:
+ title, paperlink = match_res[0].groups()
+ else:
+ title = content.split('\n')[0].replace('# ', '').strip()
+ paperlink = None
+ titles.append(title)
+
+ # Replace paper link to a button
+ if paperlink is not None:
+ start = match_res[0].start()
+ end = match_res[0].end()
+ # link_button = f'{title}'
+ link_button = f'[{title}]({paperlink})'
+ content = content[:start] + link_button + content[end:]
+
+ # Extract paper type
+ _papertype = [x for x in re.findall(r'\[([A-Z]+)\]', content)]
+ assert len(_papertype) > 0
+ papertype = _papertype[0]
+ paper = set([(papertype, title)])
+
+ # Write a copy of README
+ copy = papers_root / (f.parent.name + '.md')
+ if copy.exists():
+ os.remove(copy)
+
+ def replace_link(matchobj):
+ # Replace relative link to GitHub link.
+ name = matchobj.group(1)
+ link = matchobj.group(2)
+ if not link.startswith('http') and (f.parent / link).exists():
+ rel_link = (f.parent / link).absolute().relative_to(MMCLS_ROOT)
+ link = url_prefix + str(rel_link)
+ return f'[{name}]({link})'
+
+ content = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', replace_link, content)
+
+ with open(copy, 'w') as copy_file:
+ copy_file.write(content)
+
+ statsmsg = f"""
+\t* [{papertype}] [{title}]({copy}) ({len(ckpts)} ckpts)
+"""
+ stats.append(dict(paper=paper, ckpts=ckpts, statsmsg=statsmsg, copy=copy))
+
+allpapers = func.reduce(lambda a, b: a.union(b),
+ [stat['paper'] for stat in stats])
+msglist = '\n'.join(stat['statsmsg'] for stat in stats)
+
+papertypes, papercounts = np.unique([t for t, _ in allpapers],
+ return_counts=True)
+countstr = '\n'.join(
+ [f' - {t}: {c}' for t, c in zip(papertypes, papercounts)])
+
+modelzoo = f"""
+# Model Zoo Summary
+
+* Number of papers: {len(set(titles))}
+{countstr}
+
+* Number of checkpoints: {num_ckpts}
+{msglist}
+"""
+
+with open('modelzoo_statistics.md', 'w') as f:
+ f.write(modelzoo)
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/tools/analysis.md b/openmmlab_test/mmclassification-0.24.1/docs/en/tools/analysis.md
new file mode 100644
index 0000000000000000000000000000000000000000..0e583b04afbb119ee8f4f36554c7ded2da266e38
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/tools/analysis.md
@@ -0,0 +1,211 @@
+# Analysis
+
+
+
+- [Log Analysis](#log-analysis)
+ - [Plot Curves](#plot-curves)
+ - [Calculate Training Time](#calculate-training-time)
+- [Result Analysis](#result-analysis)
+ - [Evaluate Results](#evaluate-results)
+ - [View Typical Results](#view-typical-results)
+- [Model Complexity](#model-complexity)
+- [FAQs](#faqs)
+
+
+
+## Log Analysis
+
+### Plot Curves
+
+`tools/analysis_tools/analyze_logs.py` plots curves of given keys according to the log files.
+
+
+
+```shell
+python tools/analysis_tools/analyze_logs.py plot_curve \
+ ${JSON_LOGS} \
+ [--keys ${KEYS}] \
+ [--title ${TITLE}] \
+ [--legend ${LEGEND}] \
+ [--backend ${BACKEND}] \
+ [--style ${STYLE}] \
+ [--out ${OUT_FILE}] \
+ [--window-size ${WINDOW_SIZE}]
+```
+
+**Description of all arguments**:
+
+- `json_logs` : The paths of the log files, separate multiple files by spaces.
+- `--keys` : The fields of the logs to analyze, separate multiple keys by spaces. Defaults to 'loss'.
+- `--title` : The title of the figure. Defaults to use the filename.
+- `--legend` : The names of legend, the number of which must be equal to `len(${JSON_LOGS}) * len(${KEYS})`. Defaults to use `"${JSON_LOG}-${KEYS}"`.
+- `--backend` : The backend of matplotlib. Defaults to auto selected by matplotlib.
+- `--style` : The style of the figure. Default to `whitegrid`.
+- `--out` : The path of the output picture. If not set, the figure won't be saved.
+- `--window-size`: The shape of the display window. The format should be `'W*H'`. Defaults to `'12*7'`.
+
+```{note}
+The `--style` option depends on `seaborn` package, please install it before setting it.
+```
+
+Examples:
+
+- Plot the loss curve in training.
+
+ ```shell
+ python tools/analysis_tools/analyze_logs.py plot_curve your_log_json --keys loss --legend loss
+ ```
+
+- Plot the top-1 accuracy and top-5 accuracy curves, and save the figure to results.jpg.
+
+ ```shell
+ python tools/analysis_tools/analyze_logs.py plot_curve your_log_json --keys accuracy_top-1 accuracy_top-5 --legend top1 top5 --out results.jpg
+ ```
+
+- Compare the top-1 accuracy of two log files in the same figure.
+
+ ```shell
+ python tools/analysis_tools/analyze_logs.py plot_curve log1.json log2.json --keys accuracy_top-1 --legend exp1 exp2
+ ```
+
+```{note}
+The tool will automatically select to find keys in training logs or validation logs according to the keys.
+Therefore, if you add a custom evaluation metric, please also add the key to `TEST_METRICS` in this tool.
+```
+
+### Calculate Training Time
+
+`tools/analysis_tools/analyze_logs.py` can also calculate the training time according to the log files.
+
+```shell
+python tools/analysis_tools/analyze_logs.py cal_train_time \
+ ${JSON_LOGS}
+ [--include-outliers]
+```
+
+**Description of all arguments**:
+
+- `json_logs` : The paths of the log files, separate multiple files by spaces.
+- `--include-outliers` : If set, include the first iteration in each epoch (Sometimes the time of first iterations is longer).
+
+Example:
+
+```shell
+python tools/analysis_tools/analyze_logs.py cal_train_time work_dirs/some_exp/20200422_153324.log.json
+```
+
+The output is expected to be like the below.
+
+```text
+-----Analyze train time of work_dirs/some_exp/20200422_153324.log.json-----
+slowest epoch 68, average time is 0.3818
+fastest epoch 1, average time is 0.3694
+time std over epochs is 0.0020
+average iter time: 0.3777 s/iter
+```
+
+## Result Analysis
+
+With the `--out` argument in `tools/test.py`, we can save the inference results of all samples as a file.
+And with this result file, we can do further analysis.
+
+### Evaluate Results
+
+`tools/analysis_tools/eval_metric.py` can evaluate metrics again.
+
+```shell
+python tools/analysis_tools/eval_metric.py \
+ ${CONFIG} \
+ ${RESULT} \
+ [--metrics ${METRICS}] \
+ [--cfg-options ${CFG_OPTIONS}] \
+ [--metric-options ${METRIC_OPTIONS}]
+```
+
+Description of all arguments:
+
+- `config` : The path of the model config file.
+- `result`: The Output result file in json/pickle format from `tools/test.py`.
+- `--metrics` : Evaluation metrics, the acceptable values depend on the dataset.
+- `--cfg-options`: If specified, the key-value pair config will be merged into the config file, for more details please refer to [Tutorial 1: Learn about Configs](../tutorials/config.md)
+- `--metric-options`: If specified, the key-value pair arguments will be passed to the `metric_options` argument of dataset's `evaluate` function.
+
+```{note}
+In `tools/test.py`, we support using `--out-items` option to select which kind of results will be saved. Please ensure the result file includes "class_scores" to use this tool.
+```
+
+**Examples**:
+
+```shell
+python tools/analysis_tools/eval_metric.py configs/t2t_vit/t2t-vit-t-14_8xb64_in1k.py your_result.pkl --metrics accuracy --metric-options "topk=(1,5)"
+```
+
+### View Typical Results
+
+`tools/analysis_tools/analyze_results.py` can save the images with the highest scores in successful or failed prediction.
+
+```shell
+python tools/analysis_tools/analyze_results.py \
+ ${CONFIG} \
+ ${RESULT} \
+ [--out-dir ${OUT_DIR}] \
+ [--topk ${TOPK}] \
+ [--cfg-options ${CFG_OPTIONS}]
+```
+
+**Description of all arguments**:
+
+- `config` : The path of the model config file.
+- `result`: Output result file in json/pickle format from `tools/test.py`.
+- `--out-dir`: Directory to store output files.
+- `--topk`: The number of images in successful or failed prediction with the highest `topk` scores to save. If not specified, it will be set to 20.
+- `--cfg-options`: If specified, the key-value pair config will be merged into the config file, for more details please refer to [Tutorial 1: Learn about Configs](../tutorials/config.md)
+
+```{note}
+In `tools/test.py`, we support using `--out-items` option to select which kind of results will be saved. Please ensure the result file includes "pred_score", "pred_label" and "pred_class" to use this tool.
+```
+
+**Examples**:
+
+```shell
+python tools/analysis_tools/analyze_results.py \
+ configs/resnet/resnet50_b32x8_imagenet.py \
+ result.pkl \
+ --out-dir results \
+ --topk 50
+```
+
+## Model Complexity
+
+### Get the FLOPs and params (experimental)
+
+We provide a script adapted from [flops-counter.pytorch](https://github.com/sovrasov/flops-counter.pytorch) to compute the FLOPs and params of a given model.
+
+```shell
+python tools/analysis_tools/get_flops.py ${CONFIG_FILE} [--shape ${INPUT_SHAPE}]
+```
+
+Description of all arguments:
+
+- `config` : The path of the model config file.
+- `--shape`: Input size, support single value or double value parameter, such as `--shape 256` or `--shape 224 256`. If not set, default to be `224 224`.
+
+You will get a result like this.
+
+```text
+==============================
+Input shape: (3, 224, 224)
+Flops: 4.12 GFLOPs
+Params: 25.56 M
+==============================
+```
+
+```{warning}
+This tool is still experimental and we do not guarantee that the number is correct. You may well use the result for simple comparisons, but double-check it before you adopt it in technical reports or papers.
+- FLOPs are related to the input shape while parameters are not. The default input shape is (1, 3, 224, 224).
+- Some operators are not counted into FLOPs like GN and custom operators. Refer to [`mmcv.cnn.get_model_complexity_info()`](https://github.com/open-mmlab/mmcv/blob/master/mmcv/cnn/utils/flops_counter.py) for details.
+```
+
+## FAQs
+
+- None
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/tools/miscellaneous.md b/openmmlab_test/mmclassification-0.24.1/docs/en/tools/miscellaneous.md
new file mode 100644
index 0000000000000000000000000000000000000000..4e66d91a5a262c10aeab8ca277f2aaa0e408bd81
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/tools/miscellaneous.md
@@ -0,0 +1,59 @@
+# Miscellaneous
+
+
+
+- [Print the entire config](#print-the-entire-config)
+- [Verify Dataset](#verify-dataset)
+- [FAQs](#faqs)
+
+
+
+## Print the entire config
+
+`tools/misc/print_config.py` prints the whole config verbatim, expanding all its imports.
+
+```shell
+python tools/misc/print_config.py ${CONFIG} [--cfg-options ${CFG_OPTIONS}]
+```
+
+Description of all arguments:
+
+- `config` : The path of the model config file.
+- `--cfg-options`: If specified, the key-value pair config will be merged into the config file, for more details please refer to [Tutorial 1: Learn about Configs](../tutorials/config.md)
+
+**Examples**:
+
+```shell
+python tools/misc/print_config.py configs/t2t_vit/t2t-vit-t-14_8xb64_in1k.py
+```
+
+## Verify Dataset
+
+`tools/misc/verify_dataset.py` can verify dataset, check whether there are broken pictures in the given dataset.
+
+```shell
+python tools/print_config.py \
+ ${CONFIG} \
+ [--out-path ${OUT-PATH}] \
+ [--phase ${PHASE}] \
+ [--num-process ${NUM-PROCESS}]
+ [--cfg-options ${CFG_OPTIONS}]
+```
+
+**Description of all arguments**:
+
+- `config` : The path of the model config file.
+- `--out-path` : The path to save the verification result, if not set, defaults to 'brokenfiles.log'.
+- `--phase` : Phase of dataset to verify, accept "train" "test" and "val", if not set, defaults to "train".
+- `--num-process` : number of process to use, if not set, defaults to 1.
+- `--cfg-options`: If specified, the key-value pair config will be merged into the config file, for more details please refer to [Tutorial 1: Learn about Configs](../tutorials/config.md)
+
+**Examples**:
+
+```shell
+python tools/misc/verify_dataset.py configs/t2t_vit/t2t-vit-t-14_8xb64_in1k.py --out-path broken_imgs.log --phase val --num-process 8
+```
+
+## FAQs
+
+- None
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/tools/model_serving.md b/openmmlab_test/mmclassification-0.24.1/docs/en/tools/model_serving.md
new file mode 100644
index 0000000000000000000000000000000000000000..d633a0f32d436c65933d9b46b189286b8b3a076b
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/tools/model_serving.md
@@ -0,0 +1,87 @@
+# Model Serving
+
+In order to serve an `MMClassification` model with [`TorchServe`](https://pytorch.org/serve/), you can follow the steps:
+
+## 1. Convert model from MMClassification to TorchServe
+
+```shell
+python tools/deployment/mmcls2torchserve.py ${CONFIG_FILE} ${CHECKPOINT_FILE} \
+--output-folder ${MODEL_STORE} \
+--model-name ${MODEL_NAME}
+```
+
+```{note}
+${MODEL_STORE} needs to be an absolute path to a folder.
+```
+
+Example:
+
+```shell
+python tools/deployment/mmcls2torchserve.py \
+ configs/resnet/resnet18_8xb32_in1k.py \
+ checkpoints/resnet18_8xb32_in1k_20210831-fbbb1da6.pth \
+ --output-folder ./checkpoints \
+ --model-name resnet18_in1k
+```
+
+## 2. Build `mmcls-serve` docker image
+
+```shell
+docker build -t mmcls-serve:latest docker/serve/
+```
+
+## 3. Run `mmcls-serve`
+
+Check the official docs for [running TorchServe with docker](https://github.com/pytorch/serve/blob/master/docker/README.md#running-torchserve-in-a-production-docker-environment).
+
+In order to run in GPU, you need to install [nvidia-docker](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html). You can omit the `--gpus` argument in order to run in GPU.
+
+Example:
+
+```shell
+docker run --rm \
+--cpus 8 \
+--gpus device=0 \
+-p8080:8080 -p8081:8081 -p8082:8082 \
+--mount type=bind,source=`realpath ./checkpoints`,target=/home/model-server/model-store \
+mmcls-serve:latest
+```
+
+```{note}
+`realpath ./checkpoints` points to the absolute path of "./checkpoints", and you can replace it with the absolute path where you store torchserve models.
+```
+
+[Read the docs](https://github.com/pytorch/serve/blob/master/docs/rest_api.md) about the Inference (8080), Management (8081) and Metrics (8082) APis
+
+## 4. Test deployment
+
+```shell
+curl http://127.0.0.1:8080/predictions/${MODEL_NAME} -T demo/demo.JPEG
+```
+
+You should obtain a response similar to:
+
+```json
+{
+ "pred_label": 58,
+ "pred_score": 0.38102269172668457,
+ "pred_class": "water snake"
+}
+```
+
+And you can use `test_torchserver.py` to compare result of TorchServe and PyTorch, and visualize them.
+
+```shell
+python tools/deployment/test_torchserver.py ${IMAGE_FILE} ${CONFIG_FILE} ${CHECKPOINT_FILE} ${MODEL_NAME}
+[--inference-addr ${INFERENCE_ADDR}] [--device ${DEVICE}]
+```
+
+Example:
+
+```shell
+python tools/deployment/test_torchserver.py \
+ demo/demo.JPEG \
+ configs/resnet/resnet18_8xb32_in1k.py \
+ checkpoints/resnet18_8xb32_in1k_20210831-fbbb1da6.pth \
+ resnet18_in1k
+```
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/tools/onnx2tensorrt.md b/openmmlab_test/mmclassification-0.24.1/docs/en/tools/onnx2tensorrt.md
new file mode 100644
index 0000000000000000000000000000000000000000..ea0f148460a55eda698b24194bd84df83503c95b
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/tools/onnx2tensorrt.md
@@ -0,0 +1,80 @@
+# ONNX to TensorRT (Experimental)
+
+
+
+- [ONNX to TensorRT (Experimental)](#onnx-to-tensorrt-experimental)
+ - [How to convert models from ONNX to TensorRT](#how-to-convert-models-from-onnx-to-tensorrt)
+ - [Prerequisite](#prerequisite)
+ - [Usage](#usage)
+ - [List of supported models convertible to TensorRT](#list-of-supported-models-convertible-to-tensorrt)
+ - [Reminders](#reminders)
+ - [FAQs](#faqs)
+
+
+
+## How to convert models from ONNX to TensorRT
+
+### Prerequisite
+
+1. Please refer to [install.md](https://mmclassification.readthedocs.io/en/latest/install.html#install-mmclassification) for installation of MMClassification from source.
+2. Use our tool [pytorch2onnx.md](./pytorch2onnx.md) to convert the model from PyTorch to ONNX.
+
+### Usage
+
+```bash
+python tools/deployment/onnx2tensorrt.py \
+ ${MODEL} \
+ --trt-file ${TRT_FILE} \
+ --shape ${IMAGE_SHAPE} \
+ --max-batch-size ${MAX_BATCH_SIZE} \
+ --workspace-size ${WORKSPACE_SIZE} \
+ --fp16 \
+ --show \
+ --verify \
+```
+
+Description of all arguments:
+
+- `model` : The path of an ONNX model file.
+- `--trt-file`: The Path of output TensorRT engine file. If not specified, it will be set to `tmp.trt`.
+- `--shape`: The height and width of model input. If not specified, it will be set to `224 224`.
+- `--max-batch-size`: The max batch size of TensorRT model, should not be less than 1.
+- `--fp16`: Enable fp16 mode.
+- `--workspace-size` : The required GPU workspace size in GiB to build TensorRT engine. If not specified, it will be set to `1` GiB.
+- `--show`: Determines whether to show the outputs of the model. If not specified, it will be set to `False`.
+- `--verify`: Determines whether to verify the correctness of models between ONNXRuntime and TensorRT. If not specified, it will be set to `False`.
+
+Example:
+
+```bash
+python tools/deployment/onnx2tensorrt.py \
+ checkpoints/resnet/resnet18_b16x8_cifar10.onnx \
+ --trt-file checkpoints/resnet/resnet18_b16x8_cifar10.trt \
+ --shape 224 224 \
+ --show \
+ --verify \
+```
+
+## List of supported models convertible to TensorRT
+
+The table below lists the models that are guaranteed to be convertible to TensorRT.
+
+| Model | Config | Status |
+| :----------: | :-----------------------------------------------------: | :----: |
+| MobileNetV2 | `configs/mobilenet_v2/mobilenet-v2_8xb32_in1k.py` | Y |
+| ResNet | `configs/resnet/resnet18_8xb16_cifar10.py` | Y |
+| ResNeXt | `configs/resnext/resnext50-32x4d_8xb32_in1k.py` | Y |
+| ShuffleNetV1 | `configs/shufflenet_v1/shufflenet-v1-1x_16xb64_in1k.py` | Y |
+| ShuffleNetV2 | `configs/shufflenet_v2/shufflenet-v2-1x_16xb64_in1k.py` | Y |
+
+Notes:
+
+- *All models above are tested with Pytorch==1.6.0 and TensorRT-7.2.1.6.Ubuntu-16.04.x86_64-gnu.cuda-10.2.cudnn8.0*
+
+## Reminders
+
+- If you meet any problem with the listed models above, please create an issue and it would be taken care of soon. For models not included in the list, we may not provide much help here due to the limited resources. Please try to dig a little deeper and debug by yourself.
+
+## FAQs
+
+- None
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/tools/pytorch2onnx.md b/openmmlab_test/mmclassification-0.24.1/docs/en/tools/pytorch2onnx.md
new file mode 100644
index 0000000000000000000000000000000000000000..7352d453bb7ececdbe57300737583b6891c31201
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/tools/pytorch2onnx.md
@@ -0,0 +1,204 @@
+# Pytorch to ONNX (Experimental)
+
+
+
+- [Pytorch to ONNX (Experimental)](#pytorch-to-onnx-experimental)
+ - [How to convert models from Pytorch to ONNX](#how-to-convert-models-from-pytorch-to-onnx)
+ - [Prerequisite](#prerequisite)
+ - [Usage](#usage)
+ - [Description of all arguments:](#description-of-all-arguments)
+ - [How to evaluate ONNX models with ONNX Runtime](#how-to-evaluate-onnx-models-with-onnx-runtime)
+ - [Prerequisite](#prerequisite-1)
+ - [Usage](#usage-1)
+ - [Description of all arguments](#description-of-all-arguments-1)
+ - [Results and Models](#results-and-models)
+ - [List of supported models exportable to ONNX](#list-of-supported-models-exportable-to-onnx)
+ - [Reminders](#reminders)
+ - [FAQs](#faqs)
+
+
+
+## How to convert models from Pytorch to ONNX
+
+### Prerequisite
+
+1. Please refer to [install](https://mmclassification.readthedocs.io/en/latest/install.html#install-mmclassification) for installation of MMClassification.
+2. Install onnx and onnxruntime
+
+```shell
+pip install onnx onnxruntime==1.5.1
+```
+
+### Usage
+
+```bash
+python tools/deployment/pytorch2onnx.py \
+ ${CONFIG_FILE} \
+ --checkpoint ${CHECKPOINT_FILE} \
+ --output-file ${OUTPUT_FILE} \
+ --shape ${IMAGE_SHAPE} \
+ --opset-version ${OPSET_VERSION} \
+ --dynamic-export \
+ --show \
+ --simplify \
+ --verify \
+```
+
+### Description of all arguments:
+
+- `config` : The path of a model config file.
+- `--checkpoint` : The path of a model checkpoint file.
+- `--output-file`: The path of output ONNX model. If not specified, it will be set to `tmp.onnx`.
+- `--shape`: The height and width of input tensor to the model. If not specified, it will be set to `224 224`.
+- `--opset-version` : The opset version of ONNX. If not specified, it will be set to `11`.
+- `--dynamic-export` : Determines whether to export ONNX with dynamic input shape and output shapes. If not specified, it will be set to `False`.
+- `--show`: Determines whether to print the architecture of the exported model. If not specified, it will be set to `False`.
+- `--simplify`: Determines whether to simplify the exported ONNX model. If not specified, it will be set to `False`.
+- `--verify`: Determines whether to verify the correctness of an exported model. If not specified, it will be set to `False`.
+
+Example:
+
+```bash
+python tools/deployment/pytorch2onnx.py \
+ configs/resnet/resnet18_8xb16_cifar10.py \
+ --checkpoint checkpoints/resnet/resnet18_8xb16_cifar10.pth \
+ --output-file checkpoints/resnet/resnet18_8xb16_cifar10.onnx \
+ --dynamic-export \
+ --show \
+ --simplify \
+ --verify \
+```
+
+## How to evaluate ONNX models with ONNX Runtime
+
+We prepare a tool `tools/deployment/test.py` to evaluate ONNX models with ONNXRuntime or TensorRT.
+
+### Prerequisite
+
+- Install onnx and onnxruntime-gpu
+
+ ```shell
+ pip install onnx onnxruntime-gpu
+ ```
+
+### Usage
+
+```bash
+python tools/deployment/test.py \
+ ${CONFIG_FILE} \
+ ${ONNX_FILE} \
+ --backend ${BACKEND} \
+ --out ${OUTPUT_FILE} \
+ --metrics ${EVALUATION_METRICS} \
+ --metric-options ${EVALUATION_OPTIONS} \
+ --show
+ --show-dir ${SHOW_DIRECTORY} \
+ --cfg-options ${CFG_OPTIONS} \
+```
+
+### Description of all arguments
+
+- `config`: The path of a model config file.
+- `model`: The path of a ONNX model file.
+- `--backend`: Backend for input model to run and should be `onnxruntime` or `tensorrt`.
+- `--out`: The path of output result file in pickle format.
+- `--metrics`: Evaluation metrics, which depends on the dataset, e.g., "accuracy", "precision", "recall", "f1_score", "support" for single label dataset, and "mAP", "CP", "CR", "CF1", "OP", "OR", "OF1" for multi-label dataset.
+- `--show`: Determines whether to show classifier outputs. If not specified, it will be set to `False`.
+- `--show-dir`: Directory where painted images will be saved
+- `--metrics-options`: Custom options for evaluation, the key-value pair in `xxx=yyy` format will be kwargs for `dataset.evaluate()` function
+- `--cfg-options`: Override some settings in the used config file, the key-value pair in `xxx=yyy` format will be merged into config file.
+
+### Results and Models
+
+This part selects ImageNet for onnxruntime verification. ImageNet has multiple versions, but the most commonly used one is [ILSVRC 2012](http://www.image-net.org/challenges/LSVRC/2012/).
+
+
+
+
Model
+
Config
+
Metric
+
PyTorch
+
ONNXRuntime
+
TensorRT-fp32
+
TensorRT-fp16
+
+
+
ResNet
+
resnet50_8xb32_in1k.py
+
Top 1 / 5
+
76.55 / 93.15
+
76.49 / 93.22
+
76.49 / 93.22
+
76.50 / 93.20
+
+
+
ResNeXt
+
resnext50-32x4d_8xb32_in1k.py
+
Top 1 / 5
+
77.90 / 93.66
+
77.90 / 93.66
+
77.90 / 93.66
+
77.89 / 93.65
+
+
+
SE-ResNet
+
seresnet50_8xb32_in1k.py
+
Top 1 / 5
+
77.74 / 93.84
+
77.74 / 93.84
+
77.74 / 93.84
+
77.74 / 93.85
+
+
+
ShuffleNetV1
+
shufflenet-v1-1x_16xb64_in1k.py
+
Top 1 / 5
+
68.13 / 87.81
+
68.13 / 87.81
+
68.13 / 87.81
+
68.10 / 87.80
+
+
+
ShuffleNetV2
+
shufflenet-v2-1x_16xb64_in1k.py
+
Top 1 / 5
+
69.55 / 88.92
+
69.55 / 88.92
+
69.55 / 88.92
+
69.55 / 88.92
+
+
+
MobileNetV2
+
mobilenet-v2_8xb32_in1k.py
+
Top 1 / 5
+
71.86 / 90.42
+
71.86 / 90.42
+
71.86 / 90.42
+
71.88 / 90.40
+
+
+
+## List of supported models exportable to ONNX
+
+The table below lists the models that are guaranteed to be exportable to ONNX and runnable in ONNX Runtime.
+
+| Model | Config | Batch Inference | Dynamic Shape | Note |
+| :----------: | :-------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------: | :-----------: | ---- |
+| MobileNetV2 | [mobilenet-v2_8xb32_in1k.py](https://github.com/open-mmlab/mmclassification/tree/master/configs/mobilenet_v2/mobilenet-v2_8xb32_in1k.py) | Y | Y | |
+| ResNet | [resnet18_8xb16_cifar10.py](https://github.com/open-mmlab/mmclassification/tree/master/configs/resnet/resnet18_8xb16_cifar10.py) | Y | Y | |
+| ResNeXt | [resnext50-32x4d_8xb32_in1k.py](https://github.com/open-mmlab/mmclassification/tree/master/configs/resnext/resnext50-32x4d_8xb32_in1k.py) | Y | Y | |
+| SE-ResNet | [seresnet50_8xb32_in1k.py](https://github.com/open-mmlab/mmclassification/tree/master/configs/seresnet/seresnet50_8xb32_in1k.py) | Y | Y | |
+| ShuffleNetV1 | [shufflenet-v1-1x_16xb64_in1k.py](https://github.com/open-mmlab/mmclassification/tree/master/configs/shufflenet_v1/shufflenet-v1-1x_16xb64_in1k.py) | Y | Y | |
+| ShuffleNetV2 | [shufflenet-v2-1x_16xb64_in1k.py](https://github.com/open-mmlab/mmclassification/tree/master/configs/shufflenet_v2/shufflenet-v2-1x_16xb64_in1k.py) | Y | Y | |
+
+Notes:
+
+- *All models above are tested with Pytorch==1.6.0*
+
+## Reminders
+
+- If you meet any problem with the listed models above, please create an issue and it would be taken care of soon. For models not included in the list, please try to dig a little deeper and debug a little bit more and hopefully solve them by yourself.
+
+## FAQs
+
+- None
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/tools/pytorch2torchscript.md b/openmmlab_test/mmclassification-0.24.1/docs/en/tools/pytorch2torchscript.md
new file mode 100644
index 0000000000000000000000000000000000000000..8b01cd02dda3f3aa35880d9e45996cf54917dedc
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/tools/pytorch2torchscript.md
@@ -0,0 +1,56 @@
+# Pytorch to TorchScript (Experimental)
+
+
+
+- [Pytorch to TorchScript (Experimental)](#pytorch-to-torchscript-experimental)
+ - [How to convert models from Pytorch to TorchScript](#how-to-convert-models-from-pytorch-to-torchscript)
+ - [Usage](#usage)
+ - [Description of all arguments](#description-of-all-arguments)
+ - [Reminders](#reminders)
+ - [FAQs](#faqs)
+
+
+
+## How to convert models from Pytorch to TorchScript
+
+### Usage
+
+```bash
+python tools/deployment/pytorch2torchscript.py \
+ ${CONFIG_FILE} \
+ --checkpoint ${CHECKPOINT_FILE} \
+ --output-file ${OUTPUT_FILE} \
+ --shape ${IMAGE_SHAPE} \
+ --verify \
+```
+
+### Description of all arguments
+
+- `config` : The path of a model config file.
+- `--checkpoint` : The path of a model checkpoint file.
+- `--output-file`: The path of output TorchScript model. If not specified, it will be set to `tmp.pt`.
+- `--shape`: The height and width of input tensor to the model. If not specified, it will be set to `224 224`.
+- `--verify`: Determines whether to verify the correctness of an exported model. If not specified, it will be set to `False`.
+
+Example:
+
+```bash
+python tools/deployment/pytorch2onnx.py \
+ configs/resnet/resnet18_8xb16_cifar10.py \
+ --checkpoint checkpoints/resnet/resnet18_8xb16_cifar10.pth \
+ --output-file checkpoints/resnet/resnet18_8xb16_cifar10.pt \
+ --verify \
+```
+
+Notes:
+
+- *All models are tested with Pytorch==1.8.1*
+
+## Reminders
+
+- For torch.jit.is_tracing() is only supported after v1.6. For users with pytorch v1.3-v1.5, we suggest early returning tensors manually.
+- If you meet any problem with the models in this repo, please create an issue and it would be taken care of soon.
+
+## FAQs
+
+- None
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/tools/visualization.md b/openmmlab_test/mmclassification-0.24.1/docs/en/tools/visualization.md
new file mode 100644
index 0000000000000000000000000000000000000000..01282453f8994631c049d37d3022d81edb8b0729
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/tools/visualization.md
@@ -0,0 +1,302 @@
+# Visualization
+
+
+
+- [Pipeline Visualization](#pipeline-visualization)
+- [Learning Rate Schedule Visualization](#learning-rate-schedule-visualization)
+- [Class Activation Map Visualization](#class-activation-map-visualization)
+- [FAQs](#faqs)
+
+
+
+## Pipeline Visualization
+
+```bash
+python tools/visualizations/vis_pipeline.py \
+ ${CONFIG_FILE} \
+ [--output-dir ${OUTPUT_DIR}] \
+ [--phase ${DATASET_PHASE}] \
+ [--number ${BUNBER_IMAGES_DISPLAY}] \
+ [--skip-type ${SKIP_TRANSFORM_TYPE}] \
+ [--mode ${DISPLAY_MODE}] \
+ [--show] \
+ [--adaptive] \
+ [--min-edge-length ${MIN_EDGE_LENGTH}] \
+ [--max-edge-length ${MAX_EDGE_LENGTH}] \
+ [--bgr2rgb] \
+ [--window-size ${WINDOW_SIZE}] \
+ [--cfg-options ${CFG_OPTIONS}]
+```
+
+**Description of all arguments**:
+
+- `config` : The path of a model config file.
+- `--output-dir`: The output path for visualized images. If not specified, it will be set to `''`, which means not to save.
+- `--phase`: Phase of visualizing dataset,must be one of `[train, val, test]`. If not specified, it will be set to `train`.
+- `--number`: The number of samples to visualized. If not specified, display all images in the dataset.
+- `--skip-type`: The pipelines to be skipped. If not specified, it will be set to `['ToTensor', 'Normalize', 'ImageToTensor', 'Collect']`.
+- `--mode`: The display mode, can be one of `[original, pipeline, concat]`. If not specified, it will be set to `concat`.
+- `--show`: If set, display pictures in pop-up windows.
+- `--adaptive`: If set, adaptively resize images for better visualization.
+- `--min-edge-length`: The minimum edge length, used when `--adaptive` is set. When any side of the picture is smaller than `${MIN_EDGE_LENGTH}`, the picture will be enlarged while keeping the aspect ratio unchanged, and the short side will be aligned to `${MIN_EDGE_LENGTH}`. If not specified, it will be set to 200.
+- `--max-edge-length`: The maximum edge length, used when `--adaptive` is set. When any side of the picture is larger than `${MAX_EDGE_LENGTH}`, the picture will be reduced while keeping the aspect ratio unchanged, and the long side will be aligned to `${MAX_EDGE_LENGTH}`. If not specified, it will be set to 1000.
+- `--bgr2rgb`: If set, flip the color channel order of images.
+- `--window-size`: The shape of the display window. If not specified, it will be set to `12*7`. If used, it must be in the format `'W*H'`.
+- `--cfg-options` : Modifications to the configuration file, refer to [Tutorial 1: Learn about Configs](https://mmclassification.readthedocs.io/en/latest/tutorials/config.html).
+
+```{note}
+
+1. If the `--mode` is not specified, it will be set to `concat` as default, get the pictures stitched together by original pictures and transformed pictures; if the `--mode` is set to `original`, get the original pictures; if the `--mode` is set to `transformed`, get the transformed pictures; if the `--mode` is set to `pipeline`, get all the intermediate images through the pipeline.
+
+2. When `--adaptive` option is set, images that are too large or too small will be automatically adjusted, you can use `--min-edge-length` and `--max-edge-length` to set the adjust size.
+```
+
+**Examples**:
+
+1. In **'original'** mode, visualize 100 original pictures in the `CIFAR100` validation set, then display and save them in the `./tmp` folder:
+
+```shell
+python ./tools/visualizations/vis_pipeline.py configs/resnet/resnet50_8xb16_cifar100.py --phase val --output-dir tmp --mode original --number 100 --show --adaptive --bgr2rgb
+```
+
+
+
+2. In **'transformed'** mode, visualize all the transformed pictures of the `ImageNet` training set and display them in pop-up windows:
+
+```shell
+python ./tools/visualizations/vis_pipeline.py ./configs/resnet/resnet50_8xb32_in1k.py --show --mode transformed
+```
+
+
+
+3. In **'concat'** mode, visualize 10 pairs of origin and transformed images for comparison in the `ImageNet` train set and save them in the `./tmp` folder:
+
+```shell
+python ./tools/visualizations/vis_pipeline.py configs/swin_transformer/swin_base_224_b16x64_300e_imagenet.py --phase train --output-dir tmp --number 10 --adaptive
+```
+
+
+
+4. In **'pipeline'** mode, visualize all the intermediate pictures in the `ImageNet` train set through the pipeline:
+
+```shell
+python ./tools/visualizations/vis_pipeline.py configs/swin_transformer/swin_base_224_b16x64_300e_imagenet.py --phase train --adaptive --mode pipeline --show
+```
+
+
+
+## Learning Rate Schedule Visualization
+
+```bash
+python tools/visualizations/vis_lr.py \
+ ${CONFIG_FILE} \
+ --dataset-size ${DATASET_SIZE} \
+ --ngpus ${NUM_GPUs}
+ --save-path ${SAVE_PATH} \
+ --title ${TITLE} \
+ --style ${STYLE} \
+ --window-size ${WINDOW_SIZE}
+ --cfg-options
+```
+
+**Description of all arguments**:
+
+- `config` : The path of a model config file.
+- `dataset-size` : The size of the datasets. If set,`build_dataset` will be skipped and `${DATASET_SIZE}` will be used as the size. Default to use the function `build_dataset`.
+- `ngpus` : The number of GPUs used in training, default to be 1.
+- `save-path` : The learning rate curve plot save path, default not to save.
+- `title` : Title of figure. If not set, default to be config file name.
+- `style` : Style of plt. If not set, default to be `whitegrid`.
+- `window-size`: The shape of the display window. If not specified, it will be set to `12*7`. If used, it must be in the format `'W*H'`.
+- `cfg-options` : Modifications to the configuration file, refer to [Tutorial 1: Learn about Configs](https://mmclassification.readthedocs.io/en/latest/tutorials/config.html).
+
+```{note}
+Loading annotations maybe consume much time, you can directly specify the size of the dataset with `dataset-size` to save time.
+```
+
+**Examples**:
+
+```bash
+python tools/visualizations/vis_lr.py configs/resnet/resnet50_b16x8_cifar100.py
+```
+
+
+
+When using ImageNet, directly specify the size of ImageNet, as below:
+
+```bash
+python tools/visualizations/vis_lr.py configs/repvgg/repvgg-B3g4_4xb64-autoaug-lbs-mixup-coslr-200e_in1k.py --dataset-size 1281167 --ngpus 4 --save-path ./repvgg-B3g4_4xb64-lr.jpg
+```
+
+
+
+## Class Activation Map Visualization
+
+MMClassification provides `tools\visualizations\vis_cam.py` tool to visualize class activation map. Please use `pip install "grad-cam>=1.3.6"` command to install [pytorch-grad-cam](https://github.com/jacobgil/pytorch-grad-cam).
+
+The supported methods are as follows:
+
+| Method | What it does |
+| ------------ | ---------------------------------------------------------------------------------------------------------------------------- |
+| GradCAM | Weight the 2D activations by the average gradient |
+| GradCAM++ | Like GradCAM but uses second order gradients |
+| XGradCAM | Like GradCAM but scale the gradients by the normalized activations |
+| EigenCAM | Takes the first principle component of the 2D Activations (no class discrimination, but seems to give great results) |
+| EigenGradCAM | Like EigenCAM but with class discrimination: First principle component of Activations\*Grad. Looks like GradCAM, but cleaner |
+| LayerCAM | Spatially weight the activations by positive gradients. Works better especially in lower layers |
+
+**Command**:
+
+```bash
+python tools/visualizations/vis_cam.py \
+ ${IMG} \
+ ${CONFIG_FILE} \
+ ${CHECKPOINT} \
+ [--target-layers ${TARGET-LAYERS}] \
+ [--preview-model] \
+ [--method ${METHOD}] \
+ [--target-category ${TARGET-CATEGORY}] \
+ [--save-path ${SAVE_PATH}] \
+ [--vit-like] \
+ [--num-extra-tokens ${NUM-EXTRA-TOKENS}]
+ [--aug_smooth] \
+ [--eigen_smooth] \
+ [--device ${DEVICE}] \
+ [--cfg-options ${CFG-OPTIONS}]
+```
+
+**Description of all arguments**:
+
+- `img` : The target picture path.
+- `config` : The path of the model config file.
+- `checkpoint` : The path of the checkpoint.
+- `--target-layers` : The target layers to get activation maps, one or more network layers can be specified. If not set, use the norm layer of the last block.
+- `--preview-model` : Whether to print all network layer names in the model.
+- `--method` : Visualization method, supports `GradCAM`, `GradCAM++`, `XGradCAM`, `EigenCAM`, `EigenGradCAM`, `LayerCAM`, which is case insensitive. Defaults to `GradCAM`.
+- `--target-category` : Target category, if not set, use the category detected by the given model.
+- `--save-path` : The path to save the CAM visualization image. If not set, the CAM image will not be saved.
+- `--vit-like` : Whether the network is ViT-like network.
+- `--num-extra-tokens` : The number of extra tokens in ViT-like backbones. If not set, use num_extra_tokens the backbone.
+- `--aug_smooth` : Whether to use TTA(Test Time Augment) to get CAM.
+- `--eigen_smooth` : Whether to use the principal component to reduce noise.
+- `--device` : The computing device used. Default to 'cpu'.
+- `--cfg-options` : Modifications to the configuration file, refer to [Tutorial 1: Learn about Configs](https://mmclassification.readthedocs.io/en/latest/tutorials/config.html).
+
+```{note}
+The argument `--preview-model` can view all network layers names in the given model. It will be helpful if you know nothing about the model layers when setting `--target-layers`.
+```
+
+**Examples(CNN)**:
+
+Here are some examples of `target-layers` in ResNet-50, which can be any module or layer:
+
+- `'backbone.layer4'` means the output of the forth ResLayer.
+- `'backbone.layer4.2'` means the output of the third BottleNeck block in the forth ResLayer.
+- `'backbone.layer4.2.conv1'` means the output of the `conv1` layer in above BottleNeck block.
+
+```{note}
+For `ModuleList` or `Sequential`, you can also use the index to specify which sub-module is the target layer.
+
+For example, the `backbone.layer4[-1]` is the same as `backbone.layer4.2` since `layer4` is a `Sequential` with three sub-modules.
+```
+
+1. Use different methods to visualize CAM for `ResNet50`, the `target-category` is the predicted result by the given checkpoint, using the default `target-layers`.
+
+ ```shell
+ python tools/visualizations/vis_cam.py \
+ demo/bird.JPEG \
+ configs/resnet/resnet50_8xb32_in1k.py \
+ https://download.openmmlab.com/mmclassification/v0/resnet/resnet50_batch256_imagenet_20200708-cfb998bf.pth \
+ --method GradCAM
+ # GradCAM++, XGradCAM, EigenCAM, EigenGradCAM, LayerCAM
+ ```
+
+ | Image | GradCAM | GradCAM++ | EigenGradCAM | LayerCAM |
+ | ------------------------------------ | --------------------------------------- | ----------------------------------------- | -------------------------------------------- | ---------------------------------------- |
+ |
|
|
|
|
|
+
+2. Use different `target-category` to get CAM from the same picture. In `ImageNet` dataset, the category 238 is 'Greater Swiss Mountain dog', the category 281 is 'tabby, tabby cat'.
+
+ ```shell
+ python tools/visualizations/vis_cam.py \
+ demo/cat-dog.png configs/resnet/resnet50_8xb32_in1k.py \
+ https://download.openmmlab.com/mmclassification/v0/resnet/resnet50_batch256_imagenet_20200708-cfb998bf.pth \
+ --target-layers 'backbone.layer4.2' \
+ --method GradCAM \
+ --target-category 238
+ # --target-category 281
+ ```
+
+ | Category | Image | GradCAM | XGradCAM | LayerCAM |
+ | -------- | ---------------------------------------------- | ------------------------------------------------ | ------------------------------------------------- | ------------------------------------------------- |
+ | Dog |
|
+
+**Examples(Transformer)**:
+
+Here are some examples:
+
+- `'backbone.norm3'` for Swin-Transformer;
+- `'backbone.layers[-1].ln1'` for ViT;
+
+For ViT-like networks, such as ViT, T2T-ViT and Swin-Transformer, the features are flattened. And for drawing the CAM, we need to specify the `--vit-like` argument to reshape the features into square feature maps.
+
+Besides the flattened features, some ViT-like networks also add extra tokens like the class token in ViT and T2T-ViT, and the distillation token in DeiT. In these networks, the final classification is done on the tokens computed in the last attention block, and therefore, the classification score will not be affected by other features and the gradient of the classification score with respect to them, will be zero. Therefore, you shouldn't use the output of the last attention block as the target layer in these networks.
+
+To exclude these extra tokens, we need know the number of extra tokens. Almost all transformer-based backbones in MMClassification have the `num_extra_tokens` attribute. If you want to use this tool in a new or third-party network that don't have the `num_extra_tokens` attribute, please specify it the `--num-extra-tokens` argument.
+
+1. Visualize CAM for `Swin Transformer`, using default `target-layers`:
+
+ ```shell
+ python tools/visualizations/vis_cam.py \
+ demo/bird.JPEG \
+ configs/swin_transformer/swin-tiny_16xb64_in1k.py \
+ https://download.openmmlab.com/mmclassification/v0/swin-transformer/swin_tiny_224_b16x64_300e_imagenet_20210616_090925-66df6be6.pth \
+ --vit-like
+ ```
+
+2. Visualize CAM for `Vision Transformer(ViT)`:
+
+ ```shell
+ python tools/visualizations/vis_cam.py \
+ demo/bird.JPEG \
+ configs/vision_transformer/vit-base-p16_ft-64xb64_in1k-384.py \
+ https://download.openmmlab.com/mmclassification/v0/vit/finetune/vit-base-p16_in21k-pre-3rdparty_ft-64xb64_in1k-384_20210928-98e8652b.pth \
+ --vit-like \
+ --target-layers 'backbone.layers[-1].ln1'
+ ```
+
+3. Visualize CAM for `T2T-ViT`:
+
+ ```shell
+ python tools/visualizations/vis_cam.py \
+ demo/bird.JPEG \
+ configs/t2t_vit/t2t-vit-t-14_8xb64_in1k.py \
+ https://download.openmmlab.com/mmclassification/v0/t2t-vit/t2t-vit-t-14_3rdparty_8xb64_in1k_20210928-b7c09b62.pth \
+ --vit-like \
+ --target-layers 'backbone.encoder[-1].ln1'
+ ```
+
+| Image | ResNet50 | ViT | Swin | T2T-ViT |
+| --------------------------------------- | ------------------------------------------ | -------------------------------------- | --------------------------------------- | ------------------------------------------ |
+|
|
|
|
|
|
+
+## FAQs
+
+- None
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/MMClassification_python.ipynb b/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/MMClassification_python.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..e0466665ebc9d08054b2d1d608b618e75fe7a5bf
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/MMClassification_python.ipynb
@@ -0,0 +1,2040 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "XjQxmm04iTx4"
+ },
+ "source": [
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "UdMfIsMpiODD"
+ },
+ "source": [
+ "# MMClassification Python API tutorial on Colab\n",
+ "\n",
+ "In this tutorial, we will introduce the following content:\n",
+ "\n",
+ "* How to install MMCls\n",
+ "* Inference a model with Python API\n",
+ "* Fine-tune a model with Python API"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "iOl0X9UEiRvE"
+ },
+ "source": [
+ "## Install MMClassification\n",
+ "\n",
+ "Before using MMClassification, we need to prepare the environment with the following steps:\n",
+ "\n",
+ "1. Install Python, CUDA, C/C++ compiler and git\n",
+ "2. Install PyTorch (CUDA version)\n",
+ "3. Install mmcv\n",
+ "4. Clone mmcls source code from GitHub and install it\n",
+ "\n",
+ "Because this tutorial is on Google Colab, and the basic environment has been completed, we can skip the first two steps."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "_i7cjqS_LtoP"
+ },
+ "source": [
+ "### Check environment"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "c6MbAw10iUJI",
+ "outputId": "dd37cdf5-7bcf-4a03-f5b5-4b17c3ca16de"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "/content\n"
+ ]
+ }
+ ],
+ "source": [
+ "%cd /content"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "4IyFL3MaiYRu",
+ "outputId": "5008efdf-0356-4d93-ba9d-e51787036213"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "/content\n"
+ ]
+ }
+ ],
+ "source": [
+ "!pwd"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "DMw7QwvpiiUO",
+ "outputId": "33fa5eb8-d083-4a1f-d094-ab0f59e2818e"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "nvcc: NVIDIA (R) Cuda compiler driver\n",
+ "Copyright (c) 2005-2020 NVIDIA Corporation\n",
+ "Built on Mon_Oct_12_20:09:46_PDT_2020\n",
+ "Cuda compilation tools, release 11.1, V11.1.105\n",
+ "Build cuda_11.1.TC455_06.29190527_0\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Check nvcc version\n",
+ "!nvcc -V"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "4VIBU7Fain4D",
+ "outputId": "ec20652d-ca24-4b82-b407-e90354d728f8"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0\n",
+ "Copyright (C) 2017 Free Software Foundation, Inc.\n",
+ "This is free software; see the source for copying conditions. There is NO\n",
+ "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n",
+ "\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Check GCC version\n",
+ "!gcc --version"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "24lDLCqFisZ9",
+ "outputId": "30ec9a1c-cdb3-436c-cdc8-f2a22afe254f"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "1.9.0+cu111\n",
+ "True\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Check PyTorch installation\n",
+ "import torch, torchvision\n",
+ "print(torch.__version__)\n",
+ "print(torch.cuda.is_available())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "R2aZNLUwizBs"
+ },
+ "source": [
+ "### Install MMCV\n",
+ "\n",
+ "MMCV is the basic package of all OpenMMLab packages. We have pre-built wheels on Linux, so we can download and install them directly.\n",
+ "\n",
+ "Please pay attention to PyTorch and CUDA versions to match the wheel.\n",
+ "\n",
+ "In the above steps, we have checked the version of PyTorch and CUDA, and they are 1.9.0 and 11.1 respectively, so we need to choose the corresponding wheel.\n",
+ "\n",
+ "In addition, we can also install the full version of mmcv (mmcv-full). It includes full features and various CUDA ops out of the box, but needs a longer time to build."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "nla40LrLi7oo",
+ "outputId": "162bf14d-0d3e-4540-e85e-a46084a786b1"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Looking in links: https://download.openmmlab.com/mmcv/dist/cu111/torch1.9.0/index.html\n",
+ "Collecting mmcv\n",
+ " Downloading mmcv-1.3.15.tar.gz (352 kB)\n",
+ "\u001b[K |████████████████████████████████| 352 kB 5.2 MB/s \n",
+ "\u001b[?25hCollecting addict\n",
+ " Downloading addict-2.4.0-py3-none-any.whl (3.8 kB)\n",
+ "Requirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from mmcv) (1.19.5)\n",
+ "Requirement already satisfied: packaging in /usr/local/lib/python3.7/dist-packages (from mmcv) (21.0)\n",
+ "Requirement already satisfied: Pillow in /usr/local/lib/python3.7/dist-packages (from mmcv) (7.1.2)\n",
+ "Requirement already satisfied: pyyaml in /usr/local/lib/python3.7/dist-packages (from mmcv) (3.13)\n",
+ "Collecting yapf\n",
+ " Downloading yapf-0.31.0-py2.py3-none-any.whl (185 kB)\n",
+ "\u001b[K |████████████████████████████████| 185 kB 49.9 MB/s \n",
+ "\u001b[?25hRequirement already satisfied: pyparsing>=2.0.2 in /usr/local/lib/python3.7/dist-packages (from packaging->mmcv) (2.4.7)\n",
+ "Building wheels for collected packages: mmcv\n",
+ " Building wheel for mmcv (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
+ " Created wheel for mmcv: filename=mmcv-1.3.15-py2.py3-none-any.whl size=509835 sha256=793fe3796421336ca7a7740a1397a54016ba71ce95fd80cb80a116644adb4070\n",
+ " Stored in directory: /root/.cache/pip/wheels/b2/f4/4e/8f6d2dd2bef6b7eb8c89aa0e5d61acd7bff60aaf3d4d4b29b0\n",
+ "Successfully built mmcv\n",
+ "Installing collected packages: yapf, addict, mmcv\n",
+ "Successfully installed addict-2.4.0 mmcv-1.3.15 yapf-0.31.0\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Install mmcv\n",
+ "!pip install mmcv -f https://download.openmmlab.com/mmcv/dist/cu111/torch1.9.0/index.html\n",
+ "# !pip install mmcv-full -f https://download.openmmlab.com/mmcv/dist/cu110/torch1.9.0/index.html"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "GDTUrYvXjlRb"
+ },
+ "source": [
+ "### Clone and install MMClassification\n",
+ "\n",
+ "Next, we clone the latest mmcls repository from GitHub and install it."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "Bwme6tWHjl5s",
+ "outputId": "eae20624-4695-4cd9-c3e5-9c59596d150a"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Cloning into 'mmclassification'...\n",
+ "remote: Enumerating objects: 4152, done.\u001b[K\n",
+ "remote: Counting objects: 100% (994/994), done.\u001b[K\n",
+ "remote: Compressing objects: 100% (576/576), done.\u001b[K\n",
+ "remote: Total 4152 (delta 476), reused 765 (delta 401), pack-reused 3158\u001b[K\n",
+ "Receiving objects: 100% (4152/4152), 8.20 MiB | 21.00 MiB/s, done.\n",
+ "Resolving deltas: 100% (2524/2524), done.\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Clone mmcls repository\n",
+ "!git clone https://github.com/open-mmlab/mmclassification.git\n",
+ "%cd mmclassification/\n",
+ "\n",
+ "# Install MMClassification from source\n",
+ "!pip install -e . "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "hFg_oSG4j3zB",
+ "outputId": "05a91f9b-d41c-4ae7-d4fe-c30a30d3f639"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "0.16.0\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Check MMClassification installation\n",
+ "import mmcls\n",
+ "print(mmcls.__version__)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "4Mi3g6yzj96L"
+ },
+ "source": [
+ "## Inference a model with Python API\n",
+ "\n",
+ "MMClassification provides many pre-trained models, and you can check them by the link of [model zoo](https://mmclassification.readthedocs.io/en/latest/model_zoo.html). Almost all models can reproduce the results in original papers or reach higher metrics. And we can use these models directly.\n",
+ "\n",
+ "To use the pre-trained model, we need to do the following steps:\n",
+ "\n",
+ "- Prepare the model\n",
+ " - Prepare the config file\n",
+ " - Prepare the checkpoint file\n",
+ "- Build the model\n",
+ "- Inference with the model"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "nDQchz8CkJaT",
+ "outputId": "9805bd7d-cc2a-4269-b43d-257412f1df93"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "--2021-10-21 03:52:36-- https://www.dropbox.com/s/k5fsqi6qha09l1v/banana.png?dl=0\n",
+ "Resolving www.dropbox.com (www.dropbox.com)... 162.125.3.18, 2620:100:601b:18::a27d:812\n",
+ "Connecting to www.dropbox.com (www.dropbox.com)|162.125.3.18|:443... connected.\n",
+ "HTTP request sent, awaiting response... 301 Moved Permanently\n",
+ "Location: /s/raw/k5fsqi6qha09l1v/banana.png [following]\n",
+ "--2021-10-21 03:52:36-- https://www.dropbox.com/s/raw/k5fsqi6qha09l1v/banana.png\n",
+ "Reusing existing connection to www.dropbox.com:443.\n",
+ "HTTP request sent, awaiting response... 302 Found\n",
+ "Location: https://uc10f85c3c33c4b5233bac4d074e.dl.dropboxusercontent.com/cd/0/inline/BYYklQk6LNPXNm7o5xE_fxE2GA9reePyNajQgoe9roPlSrtsJd4WN6RVww7zrtNZWFq8iZv349MNQJlm7vVaqRBxTcd0ufxkqbcJYJvOrORpxOPV7mHmhMjKYUncez8YNqELGwDd-aeZqLGKBC8spSnx/file# [following]\n",
+ "--2021-10-21 03:52:36-- https://uc10f85c3c33c4b5233bac4d074e.dl.dropboxusercontent.com/cd/0/inline/BYYklQk6LNPXNm7o5xE_fxE2GA9reePyNajQgoe9roPlSrtsJd4WN6RVww7zrtNZWFq8iZv349MNQJlm7vVaqRBxTcd0ufxkqbcJYJvOrORpxOPV7mHmhMjKYUncez8YNqELGwDd-aeZqLGKBC8spSnx/file\n",
+ "Resolving uc10f85c3c33c4b5233bac4d074e.dl.dropboxusercontent.com (uc10f85c3c33c4b5233bac4d074e.dl.dropboxusercontent.com)... 162.125.3.15, 2620:100:601b:15::a27d:80f\n",
+ "Connecting to uc10f85c3c33c4b5233bac4d074e.dl.dropboxusercontent.com (uc10f85c3c33c4b5233bac4d074e.dl.dropboxusercontent.com)|162.125.3.15|:443... connected.\n",
+ "HTTP request sent, awaiting response... 200 OK\n",
+ "Length: 297299 (290K) [image/png]\n",
+ "Saving to: ‘demo/banana.png’\n",
+ "\n",
+ "demo/banana.png 100%[===================>] 290.33K --.-KB/s in 0.08s \n",
+ "\n",
+ "2021-10-21 03:52:36 (3.47 MB/s) - ‘demo/banana.png’ saved [297299/297299]\n",
+ "\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Get the demo image\n",
+ "!wget https://www.dropbox.com/s/k5fsqi6qha09l1v/banana.png?dl=0 -O demo/banana.png"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 420
+ },
+ "id": "o2eiitWnkQq_",
+ "outputId": "192b3ebb-202b-4d6e-e178-561223024318"
+ },
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 20,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "from PIL import Image\n",
+ "Image.open('demo/banana.png')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "sRfAui8EkTDX"
+ },
+ "source": [
+ "### Prepare the config file and checkpoint file\n",
+ "\n",
+ "We configure a model with a config file and save weights with a checkpoint file.\n",
+ "\n",
+ "On GitHub, you can find all these pre-trained models in the config folder of MMClassification. For example, you can find the config files and checkpoints of Mobilenet V2 in [this link](https://github.com/open-mmlab/mmclassification/tree/master/configs/mobilenet_v2).\n",
+ "\n",
+ "We have integrated many config files for various models in the MMClassification repository. As for the checkpoint, we can download it in advance, or just pass an URL to API, and MMClassification will download it before load weights."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "VvRoZpBGkgpC",
+ "outputId": "68282782-015e-4f5c-cef2-79be3bf6a9b7"
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "configs/mobilenet_v2/mobilenet-v2_8xb32_in1k.py\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Confirm the config file exists\n",
+ "!ls configs/mobilenet_v2/mobilenet-v2_8xb32_in1k.py\n",
+ "\n",
+ "# Specify the path of the config file and checkpoint file.\n",
+ "config_file = 'configs/mobilenet_v2/mobilenet-v2_8xb32_in1k.py'\n",
+ "checkpoint_file = 'https://download.openmmlab.com/mmclassification/v0/mobilenet_v2/mobilenet_v2_batch256_imagenet_20200708-3b2dc3af.pth'"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "eiYdsHoIkpD1"
+ },
+ "source": [
+ "### Inference the model\n",
+ "\n",
+ "MMClassification provides high-level Python API to inference models.\n",
+ "\n",
+ "At first, we build the MobilenetV2 model and load the checkpoint."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 323,
+ "referenced_widgets": [
+ "badf240bbb7d442fbd214e837edbffe2",
+ "520112917e0f4844995d418c5041d23a",
+ "9f3f6b72b4d14e2a96b9185331c8081b",
+ "a275bef3584b49ab9b680b528420d461",
+ "c4b2c6914a05497b8d2b691bd6dda6da",
+ "863d2a8cc4074f2e890ba6aea7c54384",
+ "be55ab36267d4dcab1d83dfaa8540270",
+ "31475aa888da4c8d844ba99a0b3397f5",
+ "e310c50e610248dd897fbbf5dd09dd7a",
+ "8a8ab7c27e404459951cffe7a32b8faa",
+ "e1a3dce90c1a4804a9ef0c687a9c0703"
+ ]
+ },
+ "id": "KwJWlR2QkpiV",
+ "outputId": "982b365e-d3be-4e3d-dee7-c507a8020292"
+ },
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "/usr/local/lib/python3.7/dist-packages/mmcv/cnn/bricks/transformer.py:28: UserWarning: Fail to import ``MultiScaleDeformableAttention`` from ``mmcv.ops.multi_scale_deform_attn``, You should install ``mmcv-full`` if you need this module. \n",
+ " warnings.warn('Fail to import ``MultiScaleDeformableAttention`` from '\n",
+ "/usr/lib/python3.7/importlib/_bootstrap.py:219: RuntimeWarning: numpy.ufunc size changed, may indicate binary incompatibility. Expected 192 from C header, got 216 from PyObject\n",
+ " return f(*args, **kwds)\n",
+ "/usr/lib/python3.7/importlib/_bootstrap.py:219: RuntimeWarning: numpy.ufunc size changed, may indicate binary incompatibility. Expected 192 from C header, got 216 from PyObject\n",
+ " return f(*args, **kwds)\n",
+ "/usr/lib/python3.7/importlib/_bootstrap.py:219: RuntimeWarning: numpy.ufunc size changed, may indicate binary incompatibility. Expected 192 from C header, got 216 from PyObject\n",
+ " return f(*args, **kwds)\n",
+ "/usr/local/lib/python3.7/dist-packages/yaml/constructor.py:126: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3,and in 3.9 it will stop working\n",
+ " if not isinstance(key, collections.Hashable):\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Use load_from_http loader\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Downloading: \"https://download.openmmlab.com/mmclassification/v0/mobilenet_v2/mobilenet_v2_batch256_imagenet_20200708-3b2dc3af.pth\" to /root/.cache/torch/hub/checkpoints/mobilenet_v2_batch256_imagenet_20200708-3b2dc3af.pth\n"
+ ]
+ },
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "badf240bbb7d442fbd214e837edbffe2",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ " 0%| | 0.00/13.5M [00:00, ?B/s]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "/content/mmclassification/mmcls/apis/inference.py:44: UserWarning: Class names are not saved in the checkpoint's meta data, use imagenet by default.\n",
+ " warnings.warn('Class names are not saved in the checkpoint\\'s '\n"
+ ]
+ }
+ ],
+ "source": [
+ "import mmcv\n",
+ "from mmcls.apis import inference_model, init_model, show_result_pyplot\n",
+ "\n",
+ "# Specify the device, if you cannot use GPU, you can also use CPU \n",
+ "# by specifying `device='cpu'`.\n",
+ "device = 'cuda:0'\n",
+ "# device = 'cpu'\n",
+ "\n",
+ "# Build the model according to the config file and load the checkpoint.\n",
+ "model = init_model(config_file, checkpoint_file, device=device)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "GiSACYFgkvNE",
+ "outputId": "252ae93d-a4fd-4581-f98e-6dadfde6c078"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(mmcls.models.classifiers.image.ImageClassifier,\n",
+ " mmcls.models.classifiers.base.BaseClassifier,\n",
+ " mmcv.runner.base_module.BaseModule,\n",
+ " torch.nn.modules.module.Module,\n",
+ " object)"
+ ]
+ },
+ "execution_count": 23,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# The model's inheritance relationship\n",
+ "model.__class__.__mro__"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "FyjY7hP9k0_D",
+ "outputId": "6cc4f9aa-5d25-46ae-ff21-4f24e68760c9"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'pred_class': 'banana', 'pred_label': 954, 'pred_score': 0.9999284744262695}"
+ ]
+ },
+ "execution_count": 24,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# The inference result in a single image\n",
+ "img = 'demo/banana.png'\n",
+ "img_array = mmcv.imread(img)\n",
+ "result = inference_model(model, img_array)\n",
+ "result"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 427
+ },
+ "id": "ndwdD8eUk96g",
+ "outputId": "5cf3639c-a857-4e92-dc09-21ea0ec474f9"
+ },
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ "
"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "%matplotlib inline\n",
+ "# Validate the fine-tuned model\n",
+ "\n",
+ "img = mmcv.imread('data/cats_dogs_dataset/training_set/training_set/cats/cat.1.jpg')\n",
+ "\n",
+ "model.cfg = cfg\n",
+ "result = inference_model(model, img)\n",
+ "\n",
+ "show_result_pyplot(model, img, result)"
+ ]
+ }
+ ],
+ "metadata": {
+ "accelerator": "GPU",
+ "colab": {
+ "collapsed_sections": [],
+ "name": "MMClassification_python.ipynb",
+ "provenance": [],
+ "toc_visible": true
+ },
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.11"
+ },
+ "widgets": {
+ "application/vnd.jupyter.widget-state+json": {
+ "31475aa888da4c8d844ba99a0b3397f5": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "1.5.0",
+ "model_name": "ProgressStyleModel",
+ "state": {
+ "_model_module": "@jupyter-widgets/controls",
+ "_model_module_version": "1.5.0",
+ "_model_name": "ProgressStyleModel",
+ "_view_count": null,
+ "_view_module": "@jupyter-widgets/base",
+ "_view_module_version": "1.2.0",
+ "_view_name": "StyleView",
+ "bar_color": null,
+ "description_width": ""
+ }
+ },
+ "520112917e0f4844995d418c5041d23a": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "1.2.0",
+ "model_name": "LayoutModel",
+ "state": {
+ "_model_module": "@jupyter-widgets/base",
+ "_model_module_version": "1.2.0",
+ "_model_name": "LayoutModel",
+ "_view_count": null,
+ "_view_module": "@jupyter-widgets/base",
+ "_view_module_version": "1.2.0",
+ "_view_name": "LayoutView",
+ "align_content": null,
+ "align_items": null,
+ "align_self": null,
+ "border": null,
+ "bottom": null,
+ "display": null,
+ "flex": null,
+ "flex_flow": null,
+ "grid_area": null,
+ "grid_auto_columns": null,
+ "grid_auto_flow": null,
+ "grid_auto_rows": null,
+ "grid_column": null,
+ "grid_gap": null,
+ "grid_row": null,
+ "grid_template_areas": null,
+ "grid_template_columns": null,
+ "grid_template_rows": null,
+ "height": null,
+ "justify_content": null,
+ "justify_items": null,
+ "left": null,
+ "margin": null,
+ "max_height": null,
+ "max_width": null,
+ "min_height": null,
+ "min_width": null,
+ "object_fit": null,
+ "object_position": null,
+ "order": null,
+ "overflow": null,
+ "overflow_x": null,
+ "overflow_y": null,
+ "padding": null,
+ "right": null,
+ "top": null,
+ "visibility": null,
+ "width": null
+ }
+ },
+ "863d2a8cc4074f2e890ba6aea7c54384": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "1.5.0",
+ "model_name": "DescriptionStyleModel",
+ "state": {
+ "_model_module": "@jupyter-widgets/controls",
+ "_model_module_version": "1.5.0",
+ "_model_name": "DescriptionStyleModel",
+ "_view_count": null,
+ "_view_module": "@jupyter-widgets/base",
+ "_view_module_version": "1.2.0",
+ "_view_name": "StyleView",
+ "description_width": ""
+ }
+ },
+ "8a8ab7c27e404459951cffe7a32b8faa": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "1.5.0",
+ "model_name": "DescriptionStyleModel",
+ "state": {
+ "_model_module": "@jupyter-widgets/controls",
+ "_model_module_version": "1.5.0",
+ "_model_name": "DescriptionStyleModel",
+ "_view_count": null,
+ "_view_module": "@jupyter-widgets/base",
+ "_view_module_version": "1.2.0",
+ "_view_name": "StyleView",
+ "description_width": ""
+ }
+ },
+ "9f3f6b72b4d14e2a96b9185331c8081b": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "1.5.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "_dom_classes": [],
+ "_model_module": "@jupyter-widgets/controls",
+ "_model_module_version": "1.5.0",
+ "_model_name": "HTMLModel",
+ "_view_count": null,
+ "_view_module": "@jupyter-widgets/controls",
+ "_view_module_version": "1.5.0",
+ "_view_name": "HTMLView",
+ "description": "",
+ "description_tooltip": null,
+ "layout": "IPY_MODEL_be55ab36267d4dcab1d83dfaa8540270",
+ "placeholder": "",
+ "style": "IPY_MODEL_863d2a8cc4074f2e890ba6aea7c54384",
+ "value": "100%"
+ }
+ },
+ "a275bef3584b49ab9b680b528420d461": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "1.5.0",
+ "model_name": "FloatProgressModel",
+ "state": {
+ "_dom_classes": [],
+ "_model_module": "@jupyter-widgets/controls",
+ "_model_module_version": "1.5.0",
+ "_model_name": "FloatProgressModel",
+ "_view_count": null,
+ "_view_module": "@jupyter-widgets/controls",
+ "_view_module_version": "1.5.0",
+ "_view_name": "ProgressView",
+ "bar_style": "success",
+ "description": "",
+ "description_tooltip": null,
+ "layout": "IPY_MODEL_e310c50e610248dd897fbbf5dd09dd7a",
+ "max": 14206911,
+ "min": 0,
+ "orientation": "horizontal",
+ "style": "IPY_MODEL_31475aa888da4c8d844ba99a0b3397f5",
+ "value": 14206911
+ }
+ },
+ "badf240bbb7d442fbd214e837edbffe2": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "1.5.0",
+ "model_name": "HBoxModel",
+ "state": {
+ "_dom_classes": [],
+ "_model_module": "@jupyter-widgets/controls",
+ "_model_module_version": "1.5.0",
+ "_model_name": "HBoxModel",
+ "_view_count": null,
+ "_view_module": "@jupyter-widgets/controls",
+ "_view_module_version": "1.5.0",
+ "_view_name": "HBoxView",
+ "box_style": "",
+ "children": [
+ "IPY_MODEL_9f3f6b72b4d14e2a96b9185331c8081b",
+ "IPY_MODEL_a275bef3584b49ab9b680b528420d461",
+ "IPY_MODEL_c4b2c6914a05497b8d2b691bd6dda6da"
+ ],
+ "layout": "IPY_MODEL_520112917e0f4844995d418c5041d23a"
+ }
+ },
+ "be55ab36267d4dcab1d83dfaa8540270": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "1.2.0",
+ "model_name": "LayoutModel",
+ "state": {
+ "_model_module": "@jupyter-widgets/base",
+ "_model_module_version": "1.2.0",
+ "_model_name": "LayoutModel",
+ "_view_count": null,
+ "_view_module": "@jupyter-widgets/base",
+ "_view_module_version": "1.2.0",
+ "_view_name": "LayoutView",
+ "align_content": null,
+ "align_items": null,
+ "align_self": null,
+ "border": null,
+ "bottom": null,
+ "display": null,
+ "flex": null,
+ "flex_flow": null,
+ "grid_area": null,
+ "grid_auto_columns": null,
+ "grid_auto_flow": null,
+ "grid_auto_rows": null,
+ "grid_column": null,
+ "grid_gap": null,
+ "grid_row": null,
+ "grid_template_areas": null,
+ "grid_template_columns": null,
+ "grid_template_rows": null,
+ "height": null,
+ "justify_content": null,
+ "justify_items": null,
+ "left": null,
+ "margin": null,
+ "max_height": null,
+ "max_width": null,
+ "min_height": null,
+ "min_width": null,
+ "object_fit": null,
+ "object_position": null,
+ "order": null,
+ "overflow": null,
+ "overflow_x": null,
+ "overflow_y": null,
+ "padding": null,
+ "right": null,
+ "top": null,
+ "visibility": null,
+ "width": null
+ }
+ },
+ "c4b2c6914a05497b8d2b691bd6dda6da": {
+ "model_module": "@jupyter-widgets/controls",
+ "model_module_version": "1.5.0",
+ "model_name": "HTMLModel",
+ "state": {
+ "_dom_classes": [],
+ "_model_module": "@jupyter-widgets/controls",
+ "_model_module_version": "1.5.0",
+ "_model_name": "HTMLModel",
+ "_view_count": null,
+ "_view_module": "@jupyter-widgets/controls",
+ "_view_module_version": "1.5.0",
+ "_view_name": "HTMLView",
+ "description": "",
+ "description_tooltip": null,
+ "layout": "IPY_MODEL_e1a3dce90c1a4804a9ef0c687a9c0703",
+ "placeholder": "",
+ "style": "IPY_MODEL_8a8ab7c27e404459951cffe7a32b8faa",
+ "value": " 13.5M/13.5M [00:01<00:00, 9.60MB/s]"
+ }
+ },
+ "e1a3dce90c1a4804a9ef0c687a9c0703": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "1.2.0",
+ "model_name": "LayoutModel",
+ "state": {
+ "_model_module": "@jupyter-widgets/base",
+ "_model_module_version": "1.2.0",
+ "_model_name": "LayoutModel",
+ "_view_count": null,
+ "_view_module": "@jupyter-widgets/base",
+ "_view_module_version": "1.2.0",
+ "_view_name": "LayoutView",
+ "align_content": null,
+ "align_items": null,
+ "align_self": null,
+ "border": null,
+ "bottom": null,
+ "display": null,
+ "flex": null,
+ "flex_flow": null,
+ "grid_area": null,
+ "grid_auto_columns": null,
+ "grid_auto_flow": null,
+ "grid_auto_rows": null,
+ "grid_column": null,
+ "grid_gap": null,
+ "grid_row": null,
+ "grid_template_areas": null,
+ "grid_template_columns": null,
+ "grid_template_rows": null,
+ "height": null,
+ "justify_content": null,
+ "justify_items": null,
+ "left": null,
+ "margin": null,
+ "max_height": null,
+ "max_width": null,
+ "min_height": null,
+ "min_width": null,
+ "object_fit": null,
+ "object_position": null,
+ "order": null,
+ "overflow": null,
+ "overflow_x": null,
+ "overflow_y": null,
+ "padding": null,
+ "right": null,
+ "top": null,
+ "visibility": null,
+ "width": null
+ }
+ },
+ "e310c50e610248dd897fbbf5dd09dd7a": {
+ "model_module": "@jupyter-widgets/base",
+ "model_module_version": "1.2.0",
+ "model_name": "LayoutModel",
+ "state": {
+ "_model_module": "@jupyter-widgets/base",
+ "_model_module_version": "1.2.0",
+ "_model_name": "LayoutModel",
+ "_view_count": null,
+ "_view_module": "@jupyter-widgets/base",
+ "_view_module_version": "1.2.0",
+ "_view_name": "LayoutView",
+ "align_content": null,
+ "align_items": null,
+ "align_self": null,
+ "border": null,
+ "bottom": null,
+ "display": null,
+ "flex": null,
+ "flex_flow": null,
+ "grid_area": null,
+ "grid_auto_columns": null,
+ "grid_auto_flow": null,
+ "grid_auto_rows": null,
+ "grid_column": null,
+ "grid_gap": null,
+ "grid_row": null,
+ "grid_template_areas": null,
+ "grid_template_columns": null,
+ "grid_template_rows": null,
+ "height": null,
+ "justify_content": null,
+ "justify_items": null,
+ "left": null,
+ "margin": null,
+ "max_height": null,
+ "max_width": null,
+ "min_height": null,
+ "min_width": null,
+ "object_fit": null,
+ "object_position": null,
+ "order": null,
+ "overflow": null,
+ "overflow_x": null,
+ "overflow_y": null,
+ "padding": null,
+ "right": null,
+ "top": null,
+ "visibility": null,
+ "width": null
+ }
+ }
+ }
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/MMClassification_tools.ipynb b/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/MMClassification_tools.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..ee87e7199a7b19591f126792a47e61d5493c31de
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/MMClassification_tools.ipynb
@@ -0,0 +1,1249 @@
+{
+ "nbformat": 4,
+ "nbformat_minor": 0,
+ "metadata": {
+ "accelerator": "GPU",
+ "colab": {
+ "name": "MMClassification_tools.ipynb",
+ "provenance": [],
+ "collapsed_sections": [],
+ "toc_visible": true
+ },
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.8"
+ }
+ },
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "XjQxmm04iTx4",
+ "tags": []
+ },
+ "source": [
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "4z0JDgisPRr-"
+ },
+ "source": [
+ "# MMClassification tools tutorial on Colab\n",
+ "\n",
+ "In this tutorial, we will introduce the following content:\n",
+ "\n",
+ "* How to install MMCls\n",
+ "* Prepare data\n",
+ "* Prepare the config file\n",
+ "* Train and test model with shell command"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "inm7Ciy5PXrU"
+ },
+ "source": [
+ "## Install MMClassification\n",
+ "\n",
+ "Before using MMClassification, we need to prepare the environment with the following steps:\n",
+ "\n",
+ "1. Install Python, CUDA, C/C++ compiler and git\n",
+ "2. Install PyTorch (CUDA version)\n",
+ "3. Install mmcv\n",
+ "4. Clone mmcls source code from GitHub and install it\n",
+ "\n",
+ "Because this tutorial is on Google Colab, and the basic environment has been completed, we can skip the first two steps."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "TDOxbcDvPbNk"
+ },
+ "source": [
+ "### Check environment"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "c6MbAw10iUJI",
+ "outputId": "8d3d6b53-c69b-4425-ce0c-bfb8d31ab971"
+ },
+ "source": [
+ "%cd /content"
+ ],
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "/content\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "4IyFL3MaiYRu",
+ "outputId": "c46dc718-27de-418b-da17-9d5a717e8424"
+ },
+ "source": [
+ "!pwd"
+ ],
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "/content\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "DMw7QwvpiiUO",
+ "outputId": "0d852285-07c4-48d3-e537-4a51dea04d10"
+ },
+ "source": [
+ "# Check nvcc version\n",
+ "!nvcc -V"
+ ],
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "nvcc: NVIDIA (R) Cuda compiler driver\n",
+ "Copyright (c) 2005-2020 NVIDIA Corporation\n",
+ "Built on Mon_Oct_12_20:09:46_PDT_2020\n",
+ "Cuda compilation tools, release 11.1, V11.1.105\n",
+ "Build cuda_11.1.TC455_06.29190527_0\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "4VIBU7Fain4D",
+ "outputId": "fb34a7b6-8eda-4180-e706-1bf67d1a6fd4"
+ },
+ "source": [
+ "# Check GCC version\n",
+ "!gcc --version"
+ ],
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0\n",
+ "Copyright (C) 2017 Free Software Foundation, Inc.\n",
+ "This is free software; see the source for copying conditions. There is NO\n",
+ "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n",
+ "\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "24lDLCqFisZ9",
+ "outputId": "304ad2f7-a9bb-4441-d25b-09b5516ccd74"
+ },
+ "source": [
+ "# Check PyTorch installation\n",
+ "import torch, torchvision\n",
+ "print(torch.__version__)\n",
+ "print(torch.cuda.is_available())"
+ ],
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "1.9.0+cu111\n",
+ "True\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "R2aZNLUwizBs"
+ },
+ "source": [
+ "### Install MMCV\n",
+ "\n",
+ "MMCV is the basic package of all OpenMMLab packages. We have pre-built wheels on Linux, so we can download and install them directly.\n",
+ "\n",
+ "Please pay attention to PyTorch and CUDA versions to match the wheel.\n",
+ "\n",
+ "In the above steps, we have checked the version of PyTorch and CUDA, and they are 1.9.0 and 11.1 respectively, so we need to choose the corresponding wheel.\n",
+ "\n",
+ "In addition, we can also install the full version of mmcv (mmcv-full). It includes full features and various CUDA ops out of the box, but needs a longer time to build."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "nla40LrLi7oo",
+ "outputId": "a17d50d6-05b7-45d6-c3fb-6a2507415cf5"
+ },
+ "source": [
+ "# Install mmcv\n",
+ "!pip install mmcv -f https://download.openmmlab.com/mmcv/dist/cu111/torch1.9.0/index.html\n",
+ "# !pip install mmcv-full -f https://download.openmmlab.com/mmcv/dist/cu111/torch1.9.0/index.html"
+ ],
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "Looking in links: https://download.openmmlab.com/mmcv/dist/cu111/torch1.9.0/index.html\n",
+ "Collecting mmcv\n",
+ " Downloading mmcv-1.3.15.tar.gz (352 kB)\n",
+ "\u001b[K |████████████████████████████████| 352 kB 12.8 MB/s \n",
+ "\u001b[?25hCollecting addict\n",
+ " Downloading addict-2.4.0-py3-none-any.whl (3.8 kB)\n",
+ "Requirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from mmcv) (1.19.5)\n",
+ "Requirement already satisfied: packaging in /usr/local/lib/python3.7/dist-packages (from mmcv) (21.0)\n",
+ "Requirement already satisfied: Pillow in /usr/local/lib/python3.7/dist-packages (from mmcv) (7.1.2)\n",
+ "Requirement already satisfied: pyyaml in /usr/local/lib/python3.7/dist-packages (from mmcv) (3.13)\n",
+ "Collecting yapf\n",
+ " Downloading yapf-0.31.0-py2.py3-none-any.whl (185 kB)\n",
+ "\u001b[K |████████████████████████████████| 185 kB 49.3 MB/s \n",
+ "\u001b[?25hRequirement already satisfied: pyparsing>=2.0.2 in /usr/local/lib/python3.7/dist-packages (from packaging->mmcv) (2.4.7)\n",
+ "Building wheels for collected packages: mmcv\n",
+ " Building wheel for mmcv (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
+ " Created wheel for mmcv: filename=mmcv-1.3.15-py2.py3-none-any.whl size=509835 sha256=13b8c5d70c29029916f661f2dc9b773b74a9ea4e0758491a7b5c15c798efaa61\n",
+ " Stored in directory: /root/.cache/pip/wheels/b2/f4/4e/8f6d2dd2bef6b7eb8c89aa0e5d61acd7bff60aaf3d4d4b29b0\n",
+ "Successfully built mmcv\n",
+ "Installing collected packages: yapf, addict, mmcv\n",
+ "Successfully installed addict-2.4.0 mmcv-1.3.15 yapf-0.31.0\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "GDTUrYvXjlRb"
+ },
+ "source": [
+ "### Clone and install MMClassification\n",
+ "\n",
+ "Next, we clone the latest mmcls repository from GitHub and install it."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "Bwme6tWHjl5s",
+ "outputId": "7e2d54c8-b134-405a-b014-194da1708776"
+ },
+ "source": [
+ "# Clone mmcls repository\n",
+ "!git clone https://github.com/open-mmlab/mmclassification.git\n",
+ "%cd mmclassification/\n",
+ "\n",
+ "# Install MMClassification from source\n",
+ "!pip install -e . "
+ ],
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "Cloning into 'mmclassification'...\n",
+ "remote: Enumerating objects: 4152, done.\u001b[K\n",
+ "remote: Counting objects: 100% (994/994), done.\u001b[K\n",
+ "remote: Compressing objects: 100% (579/579), done.\u001b[K\n",
+ "remote: Total 4152 (delta 476), reused 761 (delta 398), pack-reused 3158\u001b[K\n",
+ "Receiving objects: 100% (4152/4152), 8.21 MiB | 19.02 MiB/s, done.\n",
+ "Resolving deltas: 100% (2518/2518), done.\n",
+ "/content/mmclassification\n",
+ "Obtaining file:///content/mmclassification\n",
+ "Requirement already satisfied: matplotlib in /usr/local/lib/python3.7/dist-packages (from mmcls==0.16.0) (3.2.2)\n",
+ "Requirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from mmcls==0.16.0) (1.19.5)\n",
+ "Requirement already satisfied: packaging in /usr/local/lib/python3.7/dist-packages (from mmcls==0.16.0) (21.0)\n",
+ "Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.7/dist-packages (from matplotlib->mmcls==0.16.0) (0.10.0)\n",
+ "Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib->mmcls==0.16.0) (1.3.2)\n",
+ "Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib->mmcls==0.16.0) (2.4.7)\n",
+ "Requirement already satisfied: python-dateutil>=2.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib->mmcls==0.16.0) (2.8.2)\n",
+ "Requirement already satisfied: six in /usr/local/lib/python3.7/dist-packages (from cycler>=0.10->matplotlib->mmcls==0.16.0) (1.15.0)\n",
+ "Installing collected packages: mmcls\n",
+ " Running setup.py develop for mmcls\n",
+ "Successfully installed mmcls-0.16.0\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "hFg_oSG4j3zB",
+ "outputId": "1cc74bac-f918-4f0e-bf56-9f13447dfce1"
+ },
+ "source": [
+ "# Check MMClassification installation\n",
+ "import mmcls\n",
+ "print(mmcls.__version__)"
+ ],
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "0.16.0\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "HCOHRp3iV5Xk"
+ },
+ "source": [
+ "## Prepare data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "XHCHnKb_Qd3P",
+ "outputId": "35496010-ee57-4e72-af00-2af55dc80f47"
+ },
+ "source": [
+ "# Download the dataset (cats & dogs dataset)\n",
+ "!wget https://www.dropbox.com/s/wml49yrtdo53mie/cats_dogs_dataset_reorg.zip?dl=0 -O cats_dogs_dataset.zip\n",
+ "!mkdir -p data\n",
+ "!unzip -q cats_dogs_dataset.zip -d ./data/"
+ ],
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "--2021-10-21 02:47:54-- https://www.dropbox.com/s/wml49yrtdo53mie/cats_dogs_dataset_reorg.zip?dl=0\n",
+ "Resolving www.dropbox.com (www.dropbox.com)... 162.125.67.18, 2620:100:6020:18::a27d:4012\n",
+ "Connecting to www.dropbox.com (www.dropbox.com)|162.125.67.18|:443... connected.\n",
+ "HTTP request sent, awaiting response... 301 Moved Permanently\n",
+ "Location: /s/raw/wml49yrtdo53mie/cats_dogs_dataset_reorg.zip [following]\n",
+ "--2021-10-21 02:47:54-- https://www.dropbox.com/s/raw/wml49yrtdo53mie/cats_dogs_dataset_reorg.zip\n",
+ "Reusing existing connection to www.dropbox.com:443.\n",
+ "HTTP request sent, awaiting response... 302 Found\n",
+ "Location: https://uc88da1070f63f9a78ee48b59098.dl.dropboxusercontent.com/cd/0/inline/BYb26ayxWasysNPC1wSer1N9YqdOShCMIBzSIQ5NKaIoKQQ47lxZ3y7DkjKNLrYiSHkA_KgTE47_9jUHaHW79JqDtcSNEAO3unPfo8bPwsxaQUHqo97L_RjsSBhWg4HZStWRbLIJUl5WUOtpETbSQtvD/file# [following]\n",
+ "--2021-10-21 02:47:54-- https://uc88da1070f63f9a78ee48b59098.dl.dropboxusercontent.com/cd/0/inline/BYb26ayxWasysNPC1wSer1N9YqdOShCMIBzSIQ5NKaIoKQQ47lxZ3y7DkjKNLrYiSHkA_KgTE47_9jUHaHW79JqDtcSNEAO3unPfo8bPwsxaQUHqo97L_RjsSBhWg4HZStWRbLIJUl5WUOtpETbSQtvD/file\n",
+ "Resolving uc88da1070f63f9a78ee48b59098.dl.dropboxusercontent.com (uc88da1070f63f9a78ee48b59098.dl.dropboxusercontent.com)... 162.125.67.15, 2620:100:6020:15::a27d:400f\n",
+ "Connecting to uc88da1070f63f9a78ee48b59098.dl.dropboxusercontent.com (uc88da1070f63f9a78ee48b59098.dl.dropboxusercontent.com)|162.125.67.15|:443... connected.\n",
+ "HTTP request sent, awaiting response... 302 Found\n",
+ "Location: /cd/0/inline2/BYbEOCLrcXNg9qXvYXbyZZ0cgv3fSQ1vs-iqDCz24_84Fgz_2Z5SkserjAUpmYgty-eQkchAlzxQPbgzayZnie5yCipe42WVTChJJiIQ6m5x7GxgWJOn6_5QP3eRbFuYyrc1yV61BKlYuCJDHH0eyNaN8paR6bjevwMJ7Alip-gvf3c9JfjJmMgZrzcpknENyaI62FSgxFkX-Kc-FS41RYQadnMfUmhZCfMrFDSzTcmRprDiC9hQ-zJkcW_kbjI0whA1ZLQ-OG9-8Qf7jn8qd4g_tQLneL8X44qOUX4hRs2LE23g4n0jz8DeNt8KZ48WhGs8_20rBIgHH0dut3OjHF5DZMI8dVyHFAiJGyxOknZ5aCfImtz6MGgHDwbiipkICxk/file [following]\n",
+ "--2021-10-21 02:47:55-- https://uc88da1070f63f9a78ee48b59098.dl.dropboxusercontent.com/cd/0/inline2/BYbEOCLrcXNg9qXvYXbyZZ0cgv3fSQ1vs-iqDCz24_84Fgz_2Z5SkserjAUpmYgty-eQkchAlzxQPbgzayZnie5yCipe42WVTChJJiIQ6m5x7GxgWJOn6_5QP3eRbFuYyrc1yV61BKlYuCJDHH0eyNaN8paR6bjevwMJ7Alip-gvf3c9JfjJmMgZrzcpknENyaI62FSgxFkX-Kc-FS41RYQadnMfUmhZCfMrFDSzTcmRprDiC9hQ-zJkcW_kbjI0whA1ZLQ-OG9-8Qf7jn8qd4g_tQLneL8X44qOUX4hRs2LE23g4n0jz8DeNt8KZ48WhGs8_20rBIgHH0dut3OjHF5DZMI8dVyHFAiJGyxOknZ5aCfImtz6MGgHDwbiipkICxk/file\n",
+ "Reusing existing connection to uc88da1070f63f9a78ee48b59098.dl.dropboxusercontent.com:443.\n",
+ "HTTP request sent, awaiting response... 200 OK\n",
+ "Length: 228802825 (218M) [application/zip]\n",
+ "Saving to: ‘cats_dogs_dataset.zip’\n",
+ "\n",
+ "cats_dogs_dataset.z 100%[===================>] 218.20M 16.9MB/s in 13s \n",
+ "\n",
+ "2021-10-21 02:48:08 (16.9 MB/s) - ‘cats_dogs_dataset.zip’ saved [228802825/228802825]\n",
+ "\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "e4t2P2aTQokX"
+ },
+ "source": [
+ "**After downloading and extraction,** we get \"Cats and Dogs Dataset\" and the file structure is as below:\n",
+ "```\n",
+ "data/cats_dogs_dataset\n",
+ "├── classes.txt\n",
+ "├── test.txt\n",
+ "├── val.txt\n",
+ "├── training_set\n",
+ "│ ├── training_set\n",
+ "│ │ ├── cats\n",
+ "│ │ │ ├── cat.1.jpg\n",
+ "│ │ │ ├── cat.2.jpg\n",
+ "│ │ │ ├── ...\n",
+ "│ │ ├── dogs\n",
+ "│ │ │ ├── dog.2.jpg\n",
+ "│ │ │ ├── dog.3.jpg\n",
+ "│ │ │ ├── ...\n",
+ "├── val_set\n",
+ "│ ├── val_set\n",
+ "│ │ ├── cats\n",
+ "│ │ │ ├── cat.3.jpg\n",
+ "│ │ │ ├── cat.5.jpg\n",
+ "│ │ │ ├── ...\n",
+ "│ │ ├── dogs\n",
+ "│ │ │ ├── dog.1.jpg\n",
+ "│ │ │ ├── dog.6.jpg\n",
+ "│ │ │ ├── ...\n",
+ "├── test_set\n",
+ "│ ├── test_set\n",
+ "│ │ ├── cats\n",
+ "│ │ │ ├── cat.4001.jpg\n",
+ "│ │ │ ├── cat.4002.jpg\n",
+ "│ │ │ ├── ...\n",
+ "│ │ ├── dogs\n",
+ "│ │ │ ├── dog.4001.jpg\n",
+ "│ │ │ ├── dog.4002.jpg\n",
+ "│ │ │ ├── ...\n",
+ "```\n",
+ "\n",
+ "You can use shell command `tree data/cats_dogs_dataset` to check the structure."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 297
+ },
+ "id": "46tyHTdtQy_Z",
+ "outputId": "6124a89e-03eb-4917-a0bf-df6a391eb280"
+ },
+ "source": [
+ "# Pick an image and visualize it\n",
+ "from PIL import Image\n",
+ "Image.open('data/cats_dogs_dataset/training_set/training_set/cats/cat.1.jpg')"
+ ],
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAASwAAAEYCAIAAABp9FyZAAEAAElEQVR4nJT96ZMkWXIfCKrqe88uv+KOvCuzju7qrmo00A00QZAcoZAznPmwIkP+p/NhZWVnZTAHSBALAmig+u6ursrKMzIyDj/teofqflB3S89qckTWpCTKM8Lc3ezZ0+unP1XF737+KQAgGEQDQCAoIgD04MGDul7f3Nx0fTsalbPZJCa/XC5dOQ4hGGMAoGka55y1tu/7LMu89865zWbz+PHj+Xy+2WxGo1G3rokIAJgZEa3N9L3C4Jwzxngfu64TkbIsq6pqu40xJqXU922MkYiMMSKS56Uh61zubGmtM5QjGkTMDZRl2fd9CKGoSkTx3gthSgkRBCGllFIKnAAAEbGXPM/1q7vOM3OM0ZBLKcWYUkrM/PHH33n58mWe5yklnlW3t7e5M6fHJ7PRaLOY3769zAxNsvz06PDJwwe5ofnV1eNHD2+uru7du3O7aeu6Nha97/7n//l/fvHimQ8dIi4Wi/V6vVk34/H0+voa0Xzy8XdfvXrVtt39+/c//vjjt2/fPn32TZ7nDBKZY4x37997+/btn/zpj9++fftX//E//vCHP1yv1yWS98FaK2i+//3vv3nztutDluWcQMjEwNfXN/cfPHrz5o0IVlVlQvfpp5+mlH72s5+NZ9MY48nZ6Xy5XK1Wn37/e3fu3Hnx6uVvf/vbPoQ7d+6cn5/317ezg8lqteq6phqVV1eXm83q0aNHTdOklFKSlASBrM2yLHM2ny9eiAgB5nleFBWhjZG7kMAUybibTXdxs1yHaMspOtf7aAiMMdZaRERhRHTOOUNd13FKIfQxRokJUay1xpibxTzGKCLWWiJCkizL8jy/vb1JKT169Kiu133fHx8f397etm07nU67rqubdex9nueTycQY5JjeXF4cTmfHx8fG4Pz2drVaTSaTO3furNb1YrHw3mdZllKq6xoRx+NxWZbM7JyLMW42G+dcVVUppc1mlee5916/6/T0tGmaZ8+e3bt3DxHLsgSAvu+dcyIyn8/J8PHxIVFs13Mi/z/+9/9qOrWzcX7n7lEKTQi9QLKICAAg8K2j7/uUEiKKSAih67rEIYRQ94uiKEII0+n06urq5OQkhAAAMUa9iJQSAFRVZa1dr9ciwszGGN36RDal5L1HIGYGABHRaxARXe5vXwrAVoaBmVl2B4CIiE/RGOO9DyGQNYMQhhCICA3q240xiGj0f4jW2rqui6Lquq4sS9/HoiiOjo5fvXo1mUx+/OMf379//2/+5m8ePnz4y9fPfvSjH/3t3/y1M/aPP//8F/+0qKpqeXuTCSBi0zTZeDSbzbquy/P86uqqmB5WVeUyU9dS1/XV1VVMvqqqs7Oz5XK5XC6rajydTlerze3tbUppPB4bY4qi0KUmIu9749xqtdKlOz8/J6I/+7M/CyE450Lb6foj4dOnT7OsCCGEEIu8stYSwsHBASBba5m5qoo337yq6/WdO3em0/Hd+3frun79+uXZ2Vni8PTpVwcH0zx3JydHTdN88+zrsspzgL7vjTHT6bQo87atQ+ibpgkhWGuzLBPBGFIIqWkakM7lGcfEzMzsvUdIPiYfOJ9UQGSMIWsNCxElkARCAqr4AACFRcR7j8LMzCnF6FNKFinLbFEUWZYdnZ40TdN1XQghhOBD1/e9bjYVifV6HUIYjUZElBeubVuBlGWZI6OLEH3o+x4AyrIUkfl8EUI4Pj4moouLCxa01qrCreu6russy0RkPB7Xda17O8syIkopxRiZue97773esu7YsixVjJ1zRDRsY2stS4wxWgPWWmt1JwMiJh9i4pQSS6Rhl8v7h/ceEVVy9r9YHwYijkYjABiNRnoPw14XEbWBjx8/zvNcf6NXP0ia3ttwV2ru9CnuiyUi6tbUW8L3D/3GuDtCivsHMydhERARRgAAIkLEBNLH4FNcbtYhJUHMigKtuV0uNm2zbuou+Murq+9//rnNsuvb2zzPz8/PP//88+l0aoxxzumNq6LRVTo6Ouq67ujoaL1e3717dzwep5SyLBuNRrPZjJlXq9VqtXLO6T7I81y1qd71crns+17dAedc13UoYoy5vLw8OTl5/vz5D37wg/Ozs6PDwzzPmVkfZmbp5cuXKSUU0MtAxLars8xJ4uOTQ/VN7t69+0//9E+67Z49e6ZKJ8Z4cHDw4ZMnP//5z5tNrdvx0f0Hq/miqde31zd92xhCjoEADdJ6ueqaNoVokMo8K/LMEkYfurZGREEQER9DCMHHwMyM0HVt27ad72OMMaXOt13X6T3GGL33IXh1Urz3XddlWWYt7R7rdqsw87Nnz54/f/7ixYu3b99u6lVKyVqbZS7LsizLdFPpNiADRJQ4OOemo/F0Oi2KAoD188fjMQC0bRtDyLJMDd1yuby+vo4xZllmjCGi8Xh8dnZ2584dAPDe13UdY9RtrIrDWqsbTLdTCAERp9Op6ik9Qfe8un4JpO9DCMHaLM9LvVRh8N7HGEUQwZCezcxpdwwibq0djUZlWaoa0C/QbeScCyGMx2MRybJMnQoR6ft+vV5fXV0h4ocffnh6ejoajVSRqIx1Xafr+L6PalWwU0qDJRzEbBBLRCQilVh98U4g3700QoiI+pCstcZYEBSGFDlF7r0XgN57Y20fvMszm7nRZFxUZe/94dFRluf/5e//7uPvfPKTP/9n63pzfHz8/Pnzf/2v//WDBw9+9atf6dqpsiyKIqWkznnTNKenp9PpVNVn0zSbzeby8lK90BDCb37zG2vt2dlZ3/d1XRdFoU+rbdurq6vlcpmEp9Pp0dGRcw4AiqLo2857//vf/q5erfURTMbjsiy7rh3WIYTgMiPCSGJAdspbPnz85OT0qOub2XTcdvXzF98kDin0PnQI7H2XGTo7OyGU2/n1Yn5jCI4OZ02z6ft+uVyq8VksFl3XuMxYR71v62a92azUThZFUZalc86nkFIMKaaUYkqIaDNXlCUQCoIxJitcVhY2z4xzLs8QkZm93+p0IrKWdD/oHTnnNMbRM+/cuXN0dKSO1aCRdds4ZxCxKAqXGR+6EEJd11VejMtK/UlriQCNMS6zR7ODvu/bth6NRqPRqGmaul7nuauqSpcxxjgej+/fv3/37t3pdHp9fb1er9u2TSk553RzWmuLotDtp0ZPhVDV7mCo9F7UzAij97Hvg6AGVsAhMbP3UZIYpNwVpHel4qcirnKoFl9XRKVFhTDP89VqhYjz+Xwymcznc0TUB6OGmJnrum6aRgVYTZBzTkVCpUtvYJCbQa50lQch3JdD3h3f0hr6sIczhRCAEI2xmTHGGAN7QktEYMgVeQKZHh4kEJtnPsWsKNQe5lXJCJfXV6t644ocDLVtu1gs1Dm/vr6u63qz2YiIOkjOOdU7RHRwcPDJJ58Q0ZMnTz755JOyLL/88stvvvkGAL773e/OZjO9AFVn6l8YY5yzIty2zXg8un//3tnZ6cHBjAhR+PDo4JunX2eZ+9//978cFcXLZ8/KLJtOp23b6uecHh2v18uUUgpxvV7H5HNrEofet5PJ+N6dO9Px+M2b13/ywz/65puvr64u//zPf3JyeHB5eVGV+Xqz/Onf/5c//qPPLSGBfP797/3iZ19Yg9aStSTAXdfU9TrGOBqNzs/PsyyLMa5Wq+Vy7n3nMluWeVnmiCiEYBAIhRhIyFrnXDmqqqoqRuVkNj04OJhOp7OD6eHxgbWGCNWl6vs+hF631mJ5u9lsvPcAbK3VHQIAR0dHBwcHk+moKDPdIQCit8/MbdsmDjHGuq5VIZZlqVYhRh99UIMxLisAcM6URWGtTSl436FAmRcff/zxeDxWpVMUxWQySSldXl7e3Nzoe3UXqQEnIjUY+vtB8FRpMrPamGG3ExGA+rEphMBJhJETxMgxcIoCQIho33dHh+BQ+r4vikz3SkoJgMkYRHRZpjAMEZ2cnCyXyyzLxuNxjHEw6HVdLxYL9bJ0rVXwjDEpSQhBRPKsGELBwdskIpb0LXdUzwkhgKAxKaWESAxRBEXEUhRJzFFEEggyi0hiFo0JrUE0REQWt2qCpe07RCRryqpq2lZEYpLFallVo7quRaQoy//rr/7qiy++QKLb29vvfOc7f//3f99s6ocPH8auXc9vl32neuqDDx719abv+7OzMyL66KOPlq2/d+/exZtXb968bttWndLxePxv/s2/+cd//Mem3pycnIzHY2a4vVmUZbl14Jv65OREg73cZcvlEgBIoCzL4L3v+8s3bzil9WpV2swgoUDXdbPZbLFYdFknkDarFRG5zPjer7vm9cXLsiwn01Gql865er3Jsmw+nxPR2fGJc261Wi2Xy6urq+l4TACb1SqFUGU5JHCZTSm1bcucEocYXVHMRqNqs9m0bde2YG2mqJgxxpmMUiINNwQCJ0keAGKSNvVNkN5zx+LFJiRBHFRvSomjD4GMMYaAmSUxAItYA5g46Mbb1Ku2bdu2Vf1rDDkqHRm2FCO1bZ1lGSQGxEk1MiDq76UUuqbt+56IMmvAmKbZHB4cENF8fpNCrKoKcthsNhoX6Is8zxFxtVpdXl4y83Q6HZS7ao0YI2AkA9aRCIfYAwBLBOTRuGyaJnFIHIwxxiKSLas8Nd6iYW77nr2DIfISZiGQJAn4nQnat0tqZwerqLpqeL96I0+ePDk+Pp5Op6PR6IMPPhAR3U+j0aiqqsVicXt7KyJnZ2dFUXjv+77Xc4hoELwhStRYVj/hW+7oPlTzh2Hh8JOR9YUAIKL33qeYkjADoDHkrMmczV2eXd/ekDVN137w5HHne5u5pmur8ajre2MtGVONR19+9fvJbDo7PCiKQt3OqqpGo9Gf//mfP3z4UL0mZn78+PHx8XGM8fT09MWLFymlm5ubzWYzn8/VCy2KYrFYfPHFF8fHx/pPdXjOz8+n0ykR5WUxnk4Wi0XbtqvV6sWLF8wceq+e6t2z83q1PpzOXnzz7PToeDVfKKwXY4g+1HVdliVymk6nHzx+GPo+BC+QMkvPn399c3PVdc3sYHzx5uXJ6WGemf/8N/+xblZHx7PQN5NR8dGTR7/77a+Cbwn59avnTx4/9H3TtQ0Bhr7zfZtnVlK6vb5ZLm4NQe6yIssIxfdtW29C7xHEWLSOjLVElERSSiHGEEJdr9fr9XK5nK/m8/n8dn59e3t7u1wMDpuxuIsPO0U1jNmhgH2j/vxqtQKALMum0+l0Os3zTLXDpl4horWkf82yDFGcM9bavm36tvFdrxGawW1smTnHzE2z8V2v77KOBNLbt2/X67VzTjezWjPd5JPJJMuyrus05NZ4SiN5NbaDX9Z1nbqsek7btgMU5JxzRW7IKXZojHMuR0ZDzhpjAFFkcNNokMB9+6PH4DFaa7uuUxX44MEDNd/OuTt37uhbFJO01up1VFX1ve99786dOxrOqgevTrmKlt6DSvtgAP/wUIR6ODRm0AMM6X/qNuDO+UyAaipZUASZgRlEsKiqdV0XVeVj/Og7n9g8Ozo9sXk2OZjdLhfj2TQri6vbm1W9+dN/9hO05uTk5NWrV9/5znfOz88vLi5++MMfnpyc6FV57w8PD4uiUP/wt7/97cuXL//xH//x9va2KIrZbKaPqmma29vb3/72t4vFYjabVVUFAOPxWCMTl5lqVLRd3fXNerO8ur5EEmMRgcsia5vN4cF0ubh1lgyBcJzP5xqBiPBmtT44nOaFOz06/MlP/jQvXAy9c/b09CT6vl4v1svbm5urosgeP35EBASSO/v0q99H3x/OpkXmzk+OFzfX7Wb9wYP7yffL25sQeiIQSEQwHo+so029WixuQ+jJQF44a01KofdtjB4GvBoSAACKSNI9swvIt3uJmVmiCLddHZMnAxrpEMHOSw9bwCb2KqXOuTzPVZdZa8qyqKoqz3PryFrbdY2i9+r3xRjbtl0ul7rXU0pkMLPbXAincHBwsFotrt9eEdF4UnH0vu0Kl63Xa0Q8OTm5d+/eeDweHDcAUIezaRqVKI3xUkoqhAPerkZF10FxpqZpFNExxhhnsywja3ST6+5VxzAz1lpryWzFTz9XDaC6ixqeWWvH43Ge53pxAKB46Xw+1/iwqqrXr1+/ePFCjaR+AgAcHx//5je/2Ww2RVE45/RD6rper9d93zdNo8pG10ghx6Io9BtVC2RZpiC1Yll6q3onapb1nxpDF0VRVVVZVU3TZFmWlZVzzthMAej5fEHGhpgOj45Diud375zdOe98/3/8X//ng0cPL6/edn0vAOWoCilOZtO79++9fP1qNBk/evzBZ599Nh6Pf/Ob30wmkxcvXnzzzTfq1Olz+sUvfqH48KtXr87Pz3//+99/9NFHZVl++umnZ2dnuhSImOf5V199dXx8/Mtf/hIATk5OVqvVJ598olFQ3/ez2Wy9XnddN5lM1DXS9wLAcrlURHS5XFprD2cH6+UKRRWT2azWfdt99fWX8+ubTz75KMssp74s86LIvv76933fssS2q1+9fhGTn87GL14+G0+q3rdX15fXN2/bzVpiiH3XNzUJHx/M2ratmzUixhgXiwURHR0dKDaY0hb922LxBgZrYMlYS5mxItJ3zaZeT6fTssrzLBuX1fHJ0Z07Z4eHh7l1VVXps3PO5blTxA6AQwjWUVFuN7e1xhgS4aoo+rZt64Zjij6AJGcssBAgsFRF3tZN9CF3WdvU41FlLLEkH3oOERGJIEXfNM3Fxaury7cicnJ6NCrK29vb+Xyu9uPo6EizoLe3t8+ePVPtUJbl8+fPnXP3799X4+acSykVRQbAKQVEEUld1zTNxvuu79s8dyH08/lNCH1KIUY/m02Yeb1eA7CGuCIphP7wcDYZV+PxuMwLYwwNDqe8n6AbPEZFhBQUUo9xOp2GEN68eXNxcaEJrsvLS5UuPV/zM+pfPX369ObmRmGMIRuxb131ngeveECi1B/eoRdOA9+iKPq+L4qirmt9Wl3X9n2fhPvgF8vbLMu64K21LitijHXdANJ0dti2PaLp+/CTn/zk5ORkvV5XVaUZBSIajUbX19eHh4eLxaJpGiIqiuJv//Zv792790d/9EfMfHNzs1gsUkp/+Zd/qeqQiMqyfP36dYzx+Ph4sVhsNpu+71erVQjh6dOnimDleX5wcFCWZdd1mqW4uLjQ/G9d18457/2ACetT2OpXQGCRxJIYWHTbWTIaKRFRkeW5y1KKIXiO6eunv0+hn89v8txNJ2VV5p9+7zsHh9OUYowhxpBSJAACkJRSCNH7FAJw4hii70PfpeCB08F07AhD16bgncFxWYyrYlTmKAklISfgCJwgReSEkjgGjoFDhMQAkFlbuKxwWb1ZJR+MMWWRFVnujEUBBN45WSmloNspy22e58agKmUVv8E7G2DzEPvEYReA6OfwLkoRpG1g0tVN6HqJiZlFEiE658oy36zWVVWdnh1PR+Msy0ajMsts1zUhhNlsNh6P5/P5L3/5y+l0ul6vDw4OFK3RdMAQWCkmR0RqG3Qn617VZ0dEapk0mdQ0jXPGWHSZzXNnLaWUhKOztm3btt40m3W9WtKQlhjQkSESVbOjO7Isy0G01Ka9ePHi9evXCl71fT9cxAByKtz6/PlzTdnroQs9JItUzDTqHdB/VbT6NFTX6mld16niVJtZluVoNGq6thyVWZbNZjMRKUYVACmQBYAhxjwv79+/H0JC6y6vb44ODo8PjzimB/furxbL3GUoIMxnp6dd007Hk+jDqxcv7925+4uf/fzDx0/UNKl38fjx46dPn+olaf7m+vo6hPD48WO1FXme39zcvHnzRm9EobajoyOVHMXfXr9+rVn7V69eGWM630YOAIpJJJFEBJk1qm45eP1PYkBOFoEAUMQ5d3BwMJvNcpdZa0ej6sWL519//dXHHz55ffHq9cWrH/7x5yAxc+QyQwYA2RAYi2SAJYa+TaFHSQRCIChMIJk1ZZ4hCiICSgh9Xa839SqEYC2Nx1VVVdYRAHvfNW29Xq+Xy7mkFL3vu8b3vXAkAINkkKIPwGxAJHHsve86jh5YmCNzHJQsIA/wIzOL8ADux+RZorUWkGPyQ8CieQqVQGYW4P19q/Kw3cJbdQm5y4iorHIDeH19/ebNa9/3usOIqGmau3fv/sM//IPqSn2UxpjDw8MQgtJllOalSXl1d9VfVauufqb6hppn0qxy3/dZZgUZgF1mjaXEIQTPHB0hGQRg4bgVwn2kZIgJY4xd130LUGmaRhHh29vbm5sbvRoF69WHUYd20O6avdAIFXYZc13NQc4HRYCIm81G8564lz9UKUVEBWY1Lq3rWgHihw8fZpl7/Pjx4fGxc246nW7qtu/7LM+Losrz8vT0vKjGs+nhcrH+zW9+o67vvXv3UkqvXr2azWZ1XSuGmef5d7/7Xc2YP3r06OnTp4vFQnXe27dvz87OTk9P27a11qqKQcTb29uDg4O7d++KSFVVRVG8fPny/Px8Pp+rlFZVNZ1O9QllWdb3/cXFRdd1mr1Q/bKfZbFksiwDAEgcYxRmYU4xgojR3AVHg5LlNi+cdZQ5MxqXhHJ9/db7DlJcr5fX129fv37ZdU1RZFlmM2s092AMIqcYve/bGHqWSMAEbFAyS6M829RrlpRl1lrbNM3bt29vb68VnFQIepcqiokjS0IBSZxCTMGLjxxTip6jz52xRBxiW9eL+c1yfts1LSRWy7/NMO0g0BB67733vUqmYjPqPaEkSIwsJGCRLAIkTqFXNS8pShxOYORkLWWZzTNLiIr6pBBFJMstsrRtu1ot6s1Gcwm6/k3TXF9f931/fn6uN6jpQWVfXlxcPHr0SDfAkydPUgptW282q75vEUUZkESwg+gTEThnjEHm2PctOWKOPnTMUdWLD13bbVyGuTWZM84Z2reBg/iptCgSpYGpopcas+nmUxF9/fr17e2tAkqDN6Uepoh47zUS1bSP7NKsKpYauw/uqMZ+aiSVQaIGTe07Ik6n077vJ5PJcrmsqmo+n6uLeHx8vKo3eVmWZbnZbE5OT0XE+1CWozzP27btfDw+Pn748IOTk7OLV68OZzMCCH1fuOzy9cX3v/tpmeVvXr3+3ne+29XNw3v3/+xHP754+eqjx0/+z7/839+8eXP37t2yLH/5y1/e3t5+97vfnc/neZ5r0uz09FRd8fPzc9XB0+l0uVxqQL9arbqu22w29+7dIyIlH45Go4uLCyLSuPpb6m/AyQqXGWMMbQEni2QAM2N1GUMIoetVB6nWK8vy5Pj4//u3//nxk0enx4f/7//X/7Oqiq5rUgocg+4PADYoZMAQeN8l33PwMfQx9MF3vm+7trZkJDEKVGV+eDCdjCoQburNzfXVYn7bbNYpemuwKsrpeDKbTDNrCmty6xwSsEBMEhPHxDFJYkicQuA+BO+TD7zTXMbgAMkwb32iAZBPvHWLBiWle8aYrW3w3ouoalJ2VFRASFFQ986HikqpA2SLFGME4MODg5OTk7IsU4p93y2Xy/F4/Pd///d/8id/ogp3vV4fHh6ORiNjjKbEx+OxhkWz2Uz5cX3fa/6jKArl0zRNo3jMZrNZr9dqS7quMxYBJKXA4q2DIndE0Pdt7zsfuph64R13VB//gMfssgUppcSSiIAIALd025TSeDy21k4mk/V6rRen1qksy4ODA2WNrlarGCNmOHgIuOPEqEwOmY99yVe4YrFYNM1GBVt3YUrddFKuVpuiKEKIjx49IrLe+zos1019fX395s2bpmnmy8VHWVZV1XK1NsYgmtvFyl1cHB+fnp+fA8DbN78rsnyzWndNm1kXen/3/M6ds/Pr6+u2ae6cn//i5z8/P7t7dnL6i5/93JL52c9+dufOnclkotv90aNHX//ut6EzKlH3799/8fTr29vbs5PjLMvatu2bPqX0+9///uOPP95sNnnuXr16dXh4qNB23/cnJ7OLi4vRaKJ2nnbMISAgwMFByLIMAaJzFqlwmbr6lkxZZoMHgbx1zrqmjpyOjo7unt9Rnsfx8eHRwfTNmzdNu2XbEpElEiKDRFlGAsYYQjTGAAsKhN4H8LPZtG3brm8Oq8N79+50XfPm8kKNkjHGbqM1gyTqP1siY50lI1HUxAEZFOPbLoJDkCLLghj2sWNIwYNRdU8iggCIAkAxBjIIrNe5TVYrQKraX3dgSluUVcVywCQHDHbA22XHQ1a7YkBZ/q1qAQBAhMzYLMvmu/S6eqF1XaszCQBv375FxPPz89VqxcwPHjy4vb2dzWa6jXU/a3ilnBtVqcqXUt2R5zlzsg5ALKRABGWZIzuQFPoOLSBHRKFB++qLAZgZMgHqDQ8KW/Njmp0fjUaKT04mk4GHpXmL6XSqkOY2Zef9AMmoXA3wxi7Pg+rgTafT8Xisfp0ybJQ+PgikgoeHh4f379+fTCaj0ej169dEdHFxoV7TYrHQ98YYwRAiXl/fImII4YPHTxDx+vq667r1eq3a9+uvvy7L8vz8/He/+50CZUpMf/v27dHR0fPnzxeLhTHm6OjIe399fX16eppS0rsry1KBzaurq4ODA72A8Xj8q1/96t69ez/84Q+rqmLmZ8+e6bPR3aO+9MHBQYxR3csQ+hi9ujQx+tC3gKzhk+71FH3wXYg9wZb2oKsxLqsss+o7LJfLzz///uXlxYsXz773ve9tF00iICMJS4zRh9CrXySQhKNIMghZZrPMWgLgqMrxnXkB9W4kz12e51nunDOIol7cer1u6ybGaACJiABRwAA65wgQZWsVt+moFHkXCirYrlDC4IKJvMtwqBnIc6ffrn/VN+6CoyGdtkUitgQpgF3A6bfMctmGPGqylsvl7e0NJFbc3lo7n88//vjjL7/8Urfuo0ePbm5u6rpWL0xdQt2lr1+/HtIn+gjirsJDYYI8z/W1ZjJGo1Fdr40xZZkLQggeCVxmicAYdNZkWVbm7r+ZIRygywEgUeHUr8mybLPZxBjX67X3frVaDUkVBQkHVTF4XCrVGsUOf8rzXNOGKjZE9PTp04uLi9VqS9VVudUiKb2qt2/f6jemlBaLxQBsKmxTluXNzY3q0YH+qr7N9c1tlmVv31w+/+aZJVNkue/7tmn+y9/+re/6zWqdQnz14uX/8G//+8l4/PLFiz/70z99+eLFwcGBItQi8uzZs7/5m79R1RO3Kem6LMumaV69ejUajbqu+/DDD8/Ozl6+fDmfzx8/frxcLu/cuaPMJg2PVe8qPNu2rZq+fXhsx2/eHvpdmsno+36z2SinVF0yDd5ijFVVdV33/Pnz8/Pzo6OjL7/8crPZqE4cXKZ3JlREKwbatt1ua9waYV1YY4z3/vLy8uXLl8vlUlXk4M4MWhsAmvXGt12MUdKOSAhgCcejkTNWVU+7qX3bcYiay+77XsOcATMfLNiA1WvegraS/c5BUIB0gFDxD+jEg2cxwA0q8FdXV87aw8PDqqo0A6EKHXcpQe/9YrH4i7/4C2vts2fPLi4u7t27Z629vb1VRsrt7a3m2BSe2HeMrbUDCUF/PyiL9WYJwM455qgR1iBuxqB15JwhMVkQamPsUoiYxAlbjuSpgOqgooK8BDboRRabdr5ummYlEvq+nc0mGpuqsW7bNkWxJntz8fbe3YfrVdPUPYKtu5SVU3JV6yWKZbStT00f123XxXS7Wv/5v/wX1XSSEPoUPafFKgiVqzoKlU0PJhv1EUfT4/HsKAh5Rsoyz/Ly8uLt7Q1YO6tm/+Zf/ZsMs9RHi1lh8na9aTZ1ledXby6OpiNH8cGd48cPzn/9i39Y3l7c//BhE/2ybT2Qq6bgRid3PnpzU883sfZ47/GHXz57Fk367/6Hf/XXf/9Xk9MiXne0kcrOTk4fZrOztbjrjt3ouKyOTmYn9fUybVqHPJ1k8/XbYmp/9et/StyenR39zd/8ddNsfvSjH725eHtyfJZn5aiajMfjEPqiyMbjIsR6dlCaLhYJ8wjY+LhusAuZYIamcnmZ5dPR2DnXtm1iNtb23i831ybj6awwNq3W14vltaR+XGXs20mZZYSxaTAEJ2KZnYgVxMjiI4SkJREi0ocwnk5NniWEgOKB6xRaSdFS4l4gWEshhOVyzQmLfMTJ5NnYmlyYmJXDxGQYKdE485br1NXcBytSuGig4fD87cVNvWlBAoInis5GlwUwI8s5+ALDNMdRBlYCpd5RwuQJIgGjJOaYUlBvxSdfjArKsIstYDKGUvIxdCCJJBpIBsRwgNClrvb1qmtDDGLAVcUkyyqJEtoUfTo5OkXG2MfC5IXJmk2zXqySj8eZbS4v4vzmBx9+8IMPP7h5+c2r3//mbDbC2D7/6jeWosFAGA4Pqq5dFjm2c5hmp7P8rJ1zO+fj0b2HZx9O8+OSJqnB1CB6wy1QsOjN+ro+KU6y4Py8cR6PyknOGNdNJuBAJETugwS2A2VMHVy1IUR0e3ur8WiMsSzLoihU38TYISLAFkce3Nc8zxG2/rEa4sGNfN/f2Lq1CqLc3Ny8evXq/v37P/jBD7744ouvv/7amhIARqNR33eIqKFmCOHg4ECkSUkyl2/LF3xKKV1dXX322We///3v9bSu6+7du/f02YuBF/bJJ588f/ZSWazffPPNarUqyxKA6roGgNlsVlWFiKzX6yyzbduen59+9PHj29vru3fvapS7XC6b6GcnR6enp/Pb67quHz9+3Ly90uWKMcQUWZBRdB1ub2/v3bv3u9/97ptvvlkul8aYuq5VTW6x0B1BbyC+D5CVrs8+WqZRB+8IGQMdAnaFY/qZqvXVYvCW7gsiYmkbTQwxhT4LDcsHaFofunNOJO7jc8O71BPZ2aitdTLGgGEAQAEEQjSIhGCI7EcffXSzap1P0+MiUN4LMJksL5//7p9gR8dXbE+vX9NO+5tEz9EsubFb+5ZSMgadtYPjoC7sYELRGBCWrX8RgBkAGMUYs80qEjKIOmtE1PZdSNFm7ujoqKjK1Wq1rmtF4Bnh5OSERbZMtOBDiqFfbWpnjMkLhwgCse+7ELo8dyGEmDoyGSKyBBZBsogGjLHGQbJExCAAQGQSgzAjRBHZAjODd7vLsbxb/WF/6J/UNVVGGO6x24hIQzje5ihdlmVN02h+dvAThvM15z6dTp8/f350dHR0dNT3fZ7nIEbLL66uLvXzFa/fppUAlElDZBEiIq1WN2/fvh3SNVojOx6Pl8vl4eHR119//fjx46Zpnj9/PhqN3r5969lnWUZkEXpni5RSXdd3794PIfR9cXt7O5tNHj38YLmc3717d7Vaee99DHXoJ0cHZVm+DWE+nz84Poq5HYprYgwCQM4YY8hkSjguiuKnP/3pv/t3/+7ly5dd16l2U19F10oXITdGwzCidxUtvKtiGVZVAa2UEjrUDbTrS+B1M/GuUlbPHNBsZ+ywWYf119M0IlA3VU/Q/Bj8QR2Zao0BAJe9hPCe2LBBQdziS5vN5vXry8v5WrLCk1s2XUI7Gk8fnZ0MW04dRfW9YVfXpqpBP1m5oAMwkbYLYlRiCWSLJsp+ols0fbiVSRHljTNICGn7+YJtUMl3LDiZHliXL5bruFiGEIzLsqK8Xa271h8cHgPAYrUUkbIcgbHzNzd1tymKLK8sInahafyq67p8lPt1E2IkAUMUJQBLgozQEJIxVtiBoRQ5cBIN01iAhVOyWhk5RCOqQTUStdaqa6vPVTl1k0kJu+rgfb3lvdfiWY0lFIQYlPc+8DNsCAV8r6+vLy8v27Z9+vTpyclJvfFaLFtV1XK5HI8rxRWbpjHGGrPVx0WRgVDf+9PT01//+tdKBEPEk5OTb7755t6DR+v1ej6fHx4efvHFF/P54t69e7//4vc//vGPw3rjW49opqMxkX3+/OXyZvnR4yeTquybenk7f2Xo8vVFmZUWLYqs1+vJbCrONE1jcqsG/M2bN2OirUEgIiEAJgJj3HrTKOnx7Ozspz/96TfffFNVlYhoQkLdBNxh0SIylBUMcRHs2hToybQrXdumUolUCDWEVuMwRE2D/Mgu9k4hiAiIbEHHnVfSd53G/EPFatu2XddV5RgRQQB3ZWXMggASE4hovg6UzSOJkRModiPILGCMSSAIQOf3HyRy2Xi16tO6iz1DBIMkX3311QBAyC4OFJGDg4Mh4Bx0BO5VvRljtP59wEJpKAZIMmgHRAEQFXNEERJNhrR9TwAMeZ7nQEaQ0JDN8rprZ7OZyTOtUytHo7wsg/Cde3cjp01Ti8h8tayqajYeReG8IsaeEfMiF+G2W3ddl1KoxidoBCSSY2MpQWJIUQxGQyTWIBExQxd870NIHJPWuKaU0rtSpm8dbdsOyk+XRsml+td9G4i4LTVC2C7K27dvNbi31qb4zhHaX2U1fUqUcc5tdvnTtm1VkouiePPmzWQy0hJYpdID0Gq5Xq/X1mYx8nq9/vjJ9xSeKctSAIeLF5G/+Iu/+Puf/gMzf/rpp0+fPj06Onr58uVkWoTQp+Q9hXv3Hty9e/err756+fIlACCJiFxdXf3DP/zD5z/4bL1et02vd0E2C6AsmfL27eXr1+nju3eVTumIDCBuZVJ+9KMf/fKXv1Sq0Gefffazn/3sRz/60d/93d9pC5NBcoYQQP5rbEG1ErqxhjTGcAoIg4BwEhAQJgRD6KzZuSrvaboYw/DI9qtljCHlsgFIWZZa5qdyKvLeT70MVcGD+hiEB40dPh9xdy8sX331e6E8pbRY3C7qPtkMbVbX4Xg82jv/3aG0Kg15UkoxBACQZIuigF1hgKokDRcRty1qmFlSGKIqJ1vpFQOAuNOTGAMzAHJCTgmBMsfMEaT1fRY8WDM7PDR51jQNgwjCJx8+eXnxet3UzJyYBaBp26vr60nlUkpJApADkQSeMZDDBN5kAMaQBTRinGGJfWwFMhRGJGROzD6ktvNtn+bLlSTPKcQY7a7MXoabBAC1PIOVG57/sGm0bcT+7jHGcAIi0mhQ/as8z5vYyc6hVX2mLzabzWazSSlpNkKTE4N3VBRFSkYLYfWXTdOMx1Nrs5RSSn0IwZpMKX8ff/zxixcvrLU+pM1m8yd/8ie//fKrzWajqY6UkoLOeVn87ne/m84ejcdT7/311c3HH1ff/e53U0qvXr84PDxMKRVF1nXd119//cHjRylJ13WT0WHTbKjIEkEm+Xg8Xt5c13Wd5zkipJSM0VgupQQMcOfs5Ory6Je//GVVVZNRObc0KvOzkyNmRpTQt2qXfBdUnwuqchIkQRAkAREkAWQWiUkTzRYQkIQAkohyjwaO0YAhx105GO6BmcLvMTEGVFOLPwa+oTo+RVFs6yH2NYIwAIS4rY9BJERAEBBmThYdAFi0iIigKDomgOhDORtVRG5hyfSuyMjl0IeUwhC1yh5Tcthp72ucXTG3JGOMEsS998H7PM+1TGlwH7YheuqNUdwR0zb1bZAImYksEIYUBSjLyz6GNsTj05OmaTZNff/hg2lmF6vlKM8Ojo+avgshtG1LxoxGozzPlbJiTMbMiUNM26xbnmvTsFYh28RBIBlrkE0IwWAQjiZYSwKEzBAj+5CQQTiyMCSm4c6H4qDBBRpeD5pvoHoO1n9YO+34gIiHh4dKUsM9LFt2xfuDy6QtrtRgdl23XC5ns1kIQX3R4+NjRPzoo49SSrPZTJdAO7jpJlND/eDBg7dv3ypTnJm7rqvr+uTk5Pj4+OTk5H/9X//XP/7jP14ul7/5zW/+/b//903TPH78uO9DURTnZ3estX3fzmaz4+NDIlByCRE5Z+q6/s2vf3t0dORcruvgnFssFpt69fHHH3/22Wd5kZ2eHk8mEzVTuyAnppT++q//ejabnZ+ff/LJJ1999ZX3/vnz5//8n//zDz74QNnnGrnxjoe97yAM/KH9/g6Kd6f0LoGm7cZSSpp93SviBtiVfeKOG2h2x951bg/nXFVV+uBUF9PgY79fUzYo3OHRDxec4raHDxFtjakIS5zOxkSEzFlmtVhWhK0j/aLhdoYb3F8Hs3foJoGdgz1ETMPGG96orrX3XYxRgJFgl0vcfhEaAIAueCGsJmNN4drMuTyr20aJX8WoOjg+MsY8e/ZMCZsiorlxETk9PdXLAyEtjSciLXHue2+MtdZ1ra83rTAacpwASZJsOdjO5WQzQStC5ArryiyvsmL0Tvb22Rv6gIfFGvaHBpDfEr/BEurJVVUpJU1XbT/a2cfWtBJPmT6qj6uqurq6stZqn6imac7OzpqmUS7bkJ3Teo7VaqUdNKy1T58+VR9PE18vXrw4OTnJ8/zTTz/96U9/enp6ent7+/Lly//wH/4DIq4Wq67pZtPpdDKZ394+++br9XJ1MJ2hAMcUfKfX+ptf//r0+HRcjYno9PT09PR0tVp0XffDH/7gz//8J7PZTDvKqBYkA7Rl8XNK6eLiou/7L7/88u7du2dnZ1dXV7/+9a8fPnw4Go1UDQ3aTUEX2KGgeuw/jkF5DSoMOeXWZIaQEwdvQHJrHKGBLXnSgOjrLZfSoP5HKIQCkkCScOza2hBMJ6OD2STPLKfg+zb4ThPuKECA+lp/WnpX2KEMG2ds7jKOkRXy3WX4tK9E37Rv31y8fv16tVr4rms2q3qzijH0bRN9r5WNVZGPyqIq8qrIo++j71PwWpYhKep/cddeSaEs9bC0h1W/O/b3qsYIzPE9cw5pvz8akozGpXaXefbsaZbZqirqep1SuHPnbDodf/PN13W9zjJbljkC+75dLee+bw8PptbkzhbW5JwwJRE2nMj3MQYgskS272PbemYxxgKgMQYFNOfpnHMuI7KAzlBGrnC2zPKRVX9mXzzMrtRotVqNx2Mt7jg+Pv7oo4+ePn2q4ZbITvkZY4zDHYxJRH3fi4AGeOPxWBibpjG7ekWtu48xLpdLEdFWUSrDNzc3GigulvPb21vNvE8mE2PMYrE4Pj72PhJR13XWZsfHx1mW/frXvy6cVVWq1ZnW2ouLC6CrO3fuNE2j4UTTNIvFIqT45MkTjun2ZlEUb/6n/+l/+l/+l//l6dOnxuJqvsrzvKzy1Wo1Go20edazb1589zvfe/P8ddNubt6+Oj09Lcry6dOnn3/63R//+E/++q//+l/86Iez2axvlr0PKXljzXK9zh12bWMIq7LYrFciQgiXby4OZtOubTJnASDFkBTJjASipQNGy8ri1usz3nsRVl2uh4hyuaXve+U5DICKcgn1KWgdwNZMpRT3ukjum46Ukmp6RT4066Nu4uD+hBCHOFOZDztojfWTVDWDVk4Y48gxgomCDLPJgQdadp4AkIQEwaAlODg7GxzjfUs7pLv0RoawBRGLokACZlbZ8z7kRdF1nbW77MsuctZABgBiZMRIWu+fOMY4nY1jYEA+mIyrqoLEHH2RWeB8vV4agylxjD50bbNeFc5uNhv2/aiqjHDXNrlz4/FYgp9fLZ1zWW4NmeQ1IpDM5FmVYzQppdKOwDGxjR1DpOX81jqDPhUWDdq27b//vc/HVebbNqaOY0B6v8fMoJX1cZ6enmrZnppjbb+3c+Jxf/kQseu6ELbdINOOLN/3PcC2y5PZFoamAePBHe2Ad2VQzrnRwUHT1tqv7dmzZ1o66JwLIXgfjo9PM5cb4xaLFaF1zhWF0dimKAqXFQDQtq2PbK3Nsvz29vbTTz/drJv/8l/+y2c/+DzGeH529vsvv7x7544z9uH9By9evDg8PDCIoe+vr68//PCj5XKJAqfHZ4v5/L/7V/+qX7c8F9c1Mcj19dt//Md/JE5N0zx58sFms7m8eTut8sm0jLFHIwcHBxzfsfz2j9vbW6VfDjHMgBVraPeH79rT5O8dg084OBf7zuq+J4mIKcRviZ++yF0GAByTkLHOWWsiYOAgIPp0RYR2SIs2z3r31He/BO39wxIALFKwmTGGgQBwuZyvN+uurb2PLARkARmAm2az20Lv/czzbTcjZhZR/U5E1Ie4vw78fo5kf/du/4RGTURktjsKCwFGH5gZEgSQThgAfNdx6KzRloPCMabQ+Q5TSpJ8ZtECE8eMgDJrrbHAEvrC5tZYh4aQxFhGZo4ShQyEtheR3NgYY7tqnHOjvBofFQAMHKvMlWU5LsfT0WwyLuf+GgAEDHN8J4S8l4RVRTiZTHTrpF1nYg0bREQJuMO7hueiSlQ16DaGTDDkKrTtpHL2dDvKLv8xCOHp6en1DTRNE6NXOyYimizRMOb4+DiENJ8vu64zxmo0qBWPdbMlxT969Oji4uLJkw8/++yzGKP6t1p+VdhiOjm4urr6T//pP//Zn/345ub6+vr67PxE/V7FWqzNmHkxX11f3T558mTT1G3bUm6stW8uX//TF6m+vvl//Ot/nXOKm+UWXiFBRAEevKZ9SQOAq6urIevDu654zAx/IIT7Dv8AaA1SJxw5SQIWEU5JRPSfnKIwIwhoty4RBCAUNIql6ecP2hY1uZxS8J4RxRgTow/BE2X7KmAQ6W9FicPWz2zOW27A1jkiskAm9pF2smQAkwinlCjwnr7YnU9q7lJKnJJoxk+2tObth7MwM/KWPmr3QlzFZId1I+N0WzFLSgyAJICEMUYEABYW6aMXkRgjpoQYSEBEiAP7GJERsSRASalv+uQJ0REZgdRFn5ITR0lrhQ0RJhGfJCbvKO99MERllvcM63ppUirK8vryjXOuKnN0Lvp0efF2M58jp5gCSmCOKUUre3Ed77IRiKgFjog4VDG+efNmUD/b9i3bnbEtjLDO7ZoFy1A9mOK2eGxnJLc0drNr9pjeb1cBAN772Wz2/Pk35+fnV1eX2iKp7/s8L+q6fvjgUdsu7t27d311G0I4mEzU7RQRbZwxnU5PTk7UT/vTn/zZf/7P/znG+Omnn15cvhGR5XJ9enq6Xq+/+OKL73zn45OTE+0sjoiPPnjw4vkrAJzNRqvlpiiKv/u7f/jxDz8PoTcGq9HI5FlTr+fz+fLq8vnz53/83Y+rR49Wt5dt2wqIIbPZbCq7SwCqAdmlBdbrJjOEYCQGFMmc1bWFbUF9Gnb5nrliFRnmbTKDmWVPYvchCt7rjcB7CcPBr5FdRmT4Pe11lYYdFlAUTvEXAEAUIv18VkxH3qv8Vli1ZGaJCREtGTKGyCSk6WzsAVph6byEGCNHjhykIDNc8L54D1TPwX9O24p1YWZAdbi22MyWijCkWHeOq4gAGr1XSQIgJAIGDRICATIgCAeJzBwNoiVsfEAkQ6SNok3iLMuwqqxq0r5HJJvnRi/ABxOFiAwmZ8GREySK3Idkk1datqVEhtkRJ9+vF2KjBVtYVxWlA1ovlldN3debyXRkichAkmhlh3QNClhFsaqq29tbhfiJqKqqi4uLg4OD21st5YJhB6gKxL3ksnqwupTaUk6ldyiY0EUcdsCAqqeULi8vQwhaJzHgE0P6/vr6On2cmqb5sz/7Z79Mv765uf3kk0++/vrr6+trtYez2SzG+PLly3v37n3xxc8Ojg5DCKenp/rkZrMZFDCfz++cnR3OJv/H//aX53fOPvv0e8+ePd3Um+ODw8PZbL3acIiT0cjZvN3UP/3pT0eT8cnJiVjazOsQwmw0Oj8///1XX96ZjWd5posQUyT3bhm/pdcQceDjD+yF4a+yo5IN0jJsTXmfljR4lcPD0m9Je/zB4YTtY43v3LnhJwAoOc1pjLBzN/KiUCuq56g7qo2ctsqXRa8SEYUFQGKMqG6gAANLEC8xCIyzDAA0sDOgrSgQgEXe9ZseLph2hD7YFbKKOroANq+stYACAGYXIOsWSgj7cK6+TmAFBCAKCidmYENEgAJMIAiJOUpMBtgQGTJgyRkgAuNIEjvk3EBmTWXHSpQ3iBmBIUAkJudRjAHnqCxNnjsA8Bl7TyGEvNhG6ZVz03K6Xq8Xi+uDR/dFUgo+9H1wOTprwYgtQ8eQYUbWorHqB6oIfUsVad8hremoqkpLrXY8xndjJBDf8dZ3rd23n5lSgoEastNVKg/69oFBMnhcl5eXxydHbds+fPjw7du3h4eHfd8rUmJ2pZMppcPDw4ODg6Zp5/M5ACgpHtAolKqklrIsf/WrXznn8ry8vr7+5JNPnj17Bv0W0z88vHNx8WqxuL1z5+QnP/nJX/3VX3Vd8+d//pMv/ukXFxeX9+8/vLmeHx4eC3ej0Wher7VqhAhTCr7rzo6PF4tFw+FoOqqqar6ouy5ZS5hwX2x2QY4ob+adbOyA/uFM2mOBKYI6iOX+MUSS+4L6LYu37wkP5++bQdkVpuj66++13qLetPsfPlwS7eo/9w+VJWSBrQuscs6SZLFYLNfr9bpuQgxAyIAIZCzBUE84FJGqMZOBCQKACkGJiPY6ARQV5gFD7vte5B11ZDiECZCRHKQEAMwCCdACsBAkbeQBJAaBCCxBUY220puMehkZgTNorTMpOmBEtCha5GWdHbltC2wlkJCBlHS8Sqc9rELoVVPM55ZS+/bqrYiMq2rscimZkdknDly3tbGoUNe2JTbtVfQOUO/Z2VlZltrNQWuC6rq228r6fRSHiCiEwCnsdNK29CmEEGHbutvspk0MmeU8z4cpNgNRq+887AiQfd9rq3OteAohFEWxWq2I6NWrVwo6f/nll7PZbDKZaAcATbIx0OvXr4+PTwRB/dKUUlmWIQRfx8PD2e3tbQj9/Qf3NpvVN9988/3PPj05PWqb/uDg4OGjB5eXV1ptmFK6f+8ug6zfrNvYlVVpDfV9v1ksvvvggXMOeu1Mp2wpZGbkd1zZfediu18RtQWQuvqKaorsLyZ+a2N9S9gMwZ7carsVARAEJNWGwsJRREAIwRhjB29lPyZEVLsESrhDBJGkcfhwPYOd+dY10C73KyKOTJJ3aXdjjDXOAc3XdfTBex9iCkBREMiISSgWAREJcPdFAsJsrdsqd5ZtgoS22c7hfvn9lRzUFsi7SqiIQEgGELYXGEVEkmTGkggIWyDrjDO0raatCjXjAA4AaIf0SkyFNTnliJhCDCEQ2sJmZNg5Kgp0jo0JiJg5wMxgNTZW21Jti2AlZvb+yepiISJVUU4mk+lkQgJtEsZY5hVLTCFEZDssK+3R+UQkz/OPP/5Yy7TVvTw4OLi4uChLx8wAabBvtBuKMFg55959GoBkWab9KbSoQs/vum40Gk0mE96lJTWNIZXVFoBv374tiuLm5kr7fFZVdX19qz2wqmr8u9/9jhNog0O1kMvl0mXF0dFRjPHi8mo8Hj979uzj73wiIutVfXFx8bOf/ezw8NCM3eXlZe9bY1AgqgH/j//xP5ZlaUz86quvjo5ODg4OLi4uHz54slwuq9JkRU5E4/EYne3axhlzenr6zTff3PvjH47H465rfEjOubwq1vVKb0Rxf7VmqlO08GxoM6OnDUOs9j3YP3RQh9/DLnje/82eS/Jt1rXswuxv+aIiot3r0l57Ic25OVsOH7j/Ff+tQ3YJdxQYTKs1bjSCJsa8D530feSUYmIBlC7G/c8c9JROaOFdUy/ctfTcpvKBY4wkoDxH2v11q+n2yhHFCIAB0HZViKyOGGSZwSQgkBvKHVmDBCiSIhILE24vHgWIyJHpu84ao01Bfds10lhriyzvwwIBQEgYtEPX1psz0LXBOVdVZUqp3iyD7yfj8tGjab3eSExd067sKicLzLnLXWV737ZtijFa4h5iFDHIbCRaSAwMSSZFUS9WVVFMy9F6XberJrYhQ+ebNoQQ+sgpGSJrjTOOiGxeKo5NgBjZCFQuL4zrYtJ844MHD6y1V1dXcdcFeLVaaS3z1dXV8fHx69evi6KgSblpm/rVsyKzjrBw1K1uoa8f3H/ULRZN2374/c+7yE+/eTk7OBTnkPnm5mYymdy9ezcGdsZm1pUum1YjOpb55VWe5xnRw/O70IV2vuq7W0tQzAprsGmS75OzJFDkxcGL118FeUtFNjsfz9u38+7F5Hy26dvYbLRCP8VYWMddl3p/UFTduq4mJSFaaxPKstmgpcwCIhNEFEZAAnZGDIL2ESOIvvMpxtwhEUTfZNbsQBwBiAhoDGbWisRdCzbRdg+KHLJx+4KmLxCBCDVGQARrDcC2GALJ6LnMPETvCNArpxS3BEvZ/gPEcoqRWQNCIrSMLkHsO6/uInOPws4Zm5El8cEzsikwyzJjsWmXb19dLxaLruusy9u2596v3t6mKE+efGTYvKHDIs9VuhAxt5aZ+75f1w0RIRnjUBR3QQIylFKGWFWTvu/X66UBmY7GRNuuakhiHSWyMfrESUASrhMPlcfIiAKYQFJiC2gFYwhtH4iZhBHFWnLOCVHfbNsjiUid0ng8rpt6E4JW7UwORjHGNjYA0nRtH/zQhFtRfe/9eDxmz7fzt8aYyWRSVrOmae5Wo6uu71LKDCDyslutNss+Bj0HTVqsVnZQZoNl0ye8XC6JrHY3bNtewRJEbLtaHU6zK7cfyBzGuK1Fhb1OaizGmPl8/oMf/EBd0GfPng2emJIwlTiiI7smh0dlnqXgtcslAgAnrTGv26btUxJWVpcirjqobOjemVJyzo3H4w8++GC1Wi2XKy0eTykxWwCwWY6IeVYaY0JiFyIYUldA/VVt02StjTH0TQtBABEzq/xnIlK0feuw7aRh61wJ7E1b2AHoIog6tBQHt21A2AdD9IcWZvjTkOfAve4m8H4stO/9Dif/V79C3s8lfuvYdZt+l4IzFomydlUfHEzzPNvUi7bbiKQYIcQ+chyPxyL8zdMvv3r6+67r7tw9v//o4ZuLy4PD8b37d8ajg5ub29cXl9533zx7WTz5M82Xpr3iD94l9IYrl11kKwmGdpiyQ01T2kISsHM6hze+t4AAoD63sO/7wmrOI6a+hRgdaeqLNfWl66yyrdMptNbxWx0hQPodB80Nj1Ur67Ub/T4B0Hs/Pbq/Wq0SyGiigyizyJULYbVa9FtbDnZ42O9DxsjM2h9aZFe1hViWZdstjTGGnOwBANbapmkQeZf94303Q9u/P3v27Ec/+tEXX3xxeHg4FEbUda39aYhIh54Cc5ZZJuzbOnhvAKwxArhcr4EskVzd3BblpCgrtK7btKcPzxTI1baLIfbW2rt3z3VMD/OWf2etJQLN5xhjjMvQGPQRjGWGvu+bts+ybLNZ31xd51VWlqXWEkIAmzmLxhhjEcHaRJR2vd9FRDNy260g/xXiJe4Ym/sCM/iHw7J/SzZ0a8oOsBlcL+97+IOIEfcqgPSj9uKL9wR7P7T7rx5DvICImnwi2gLUyuTUcorRaGQMth0eHJy+vHj98uXzlNInn3xwfHzo8izG+IP/8b8TxvW6RrJFaY+OZtZm9+6efHH17ZpV3UL76zMIp4gQGM1VKLPXbJt0JGMMIO/QnS3KJdtSJlDhZBESYAEBDSMtEYEgx5RCT9Y4Mpu2Tikpdq3fq/kw7YKxTx4EAGutvCPEvbuFIcdjjKmqSvt3aCBwc33ddZ11LoVoEI0ydV0PMDUWLZkin1nciwNhANNYk6eiXM0sM8ysA4ZCnCBiirIbcGGKwunAakTULwZB3BFqxVgFBl+8ePGDH/xAu6NqCTwRDYMju67TvvHL5XwyGjuDHFPf92WWj0ajqhwtFqvxZOayeHl1nRfd9ODYutzaTmlK67VhjsrzFkiz2eznP/+5cy4lSSkaQ3meAWDTNJ4zS2AASIQFEzOHVDddiFdFmanqcdksd5khAkmKyLldffpWGFAAeAdOsQgCAgnIH+TlcC9sG3wN2iHGgxb/Q6nYV/D6pX9owYb34q4rwrcO3BUZ/qEc/rcOZ60h7YUHacsyT8aYMs/apu66xloajSfWbGdUfPX110gwnRQHB9Pj48NyPAKAkOLt9YVzOQtOqrwsRmFSJIbrGxos3rDpdavoNhgiveFqdzIQAECjrxD6lHCr9N5fPcR31VQqhCAALAicOWcI1e8mIiBi5hCCoq8DRE97fN1BHIY5MESEe43S95WpiGjhkfakh53aTYElMViRmDiGzOaZM0i2Kg+bpo7JF1n+bvDi8LmIyABEpKCl5up41yTb7Goq425ksYqic455V/8uqIOmiqJ4/fbq4cOHX3755QcffPD3f//3n3/++c9//vM8z3Wmmq6+RgUHBwchhL5ryiLLbEEGRCSBlNXo9OxO04YuRLCYfIwsPgbK8icff7RcvlXKpQhro38AbtqNy4xzhqLE3TRibQhPMZGxrGNDBQQpcep8X9f1eXaa57lOJmFRgJsdkUFi5tB1AIIAkN7LcaEAIpIAoiC82z3f2tw0tDbcQxe/dfL/jYF693XvS/Xwe2XqDP/8loTvn/x/L4ckoN3yQUkCwigKmsfk+8zSZDIS4VcvXlxcvGrbdnSYHx8fn54eF1UeQttcL40xNssuLy8ns1melVVhgSwSoDCIR3S8x80aFoTfJ2zpb5iZU0xp4M/B0PcJcJtyHIbS7BZHbzltH44IQCIRp+MEQzQpkEFkE30ffffg0aOhKe4gVzFGnTlJu+JPHMpcCAlQPdhB8Wl6sO/7tm6AJcsyZyxTSill1gJnhMQpcYwAzlpCsoDYdnW9XhVlZoe7hf2c0p47FEJANPpivV4nbq21hrbtfZWkol4l7Lx54XdNfjWc03Y1Oo+BiNq21czeeDwe2hlqIgESh67tYevUNk2z2jTTg1hNptcvXorgdHZcVKNN3Qv2f/qnH//8P33JzCjJEqTQaVP3xe3VpCpTSol70NGFQRCxzE2P6JAYQQQYAY0zwAAQOaWU8rzQftAsgUC8D5kTZubAffICkBEyR0hsUVEodUC1heYWFv/WXtefQ58Y3qOzDHI17KFvOZmyxw3U9xr4tvRuf2rlu+wq3wUgsbDA+wTL4ev+W3Ko+SlELQ7czuchQiLI85wlrtfrm5vrq+tLY+jBgwc//LPvzec38/m8blaj0ags8xhjs1l9+NFjBLNYLK+uLkVwMpkdHZ7cu3Mmr1aaYgCwzMi7kkjtlyiS5H2Xvu+3TEnmLb0OAJCE+Z2wicjW+3r/nkh4C0Vtqw3FMFprM2PR2YjoA2SWrEFEUJDVEokAGpyOK93AzGy3mUNbVZWzOEwC1bAQ9iIy9e/yPB+Px+qU+s6AdUja8D4hgCUSIESxRpBYYrT7fs47Db2rbBhyMgqE9H0PGEW2LfJUVaj16/vemG0VXIoMAErS12TD8fHxer2+f/++pvWU4anW7/Ly0lqroAgAOEtt0yQdI+5svWqur6+tzWyWA4B1+RaPstTVm2a9Ikwh9taAiHRda60tclOvV7vJctGQqrdEaLIia2JKwpET8DYHjYCAnFnXdZ11VebcZtOSkVEx4hA5Re3EM3h9aAwbRERLGomhJsWRUQnO+9H18EZr3/XUgvexlj8UiWEXDqF1Gub27DCh4S24o33ty/AQYpBz/39ZQoO0deO2zxdEmFOKAgjSbNZvry7btj2cHdy7d+fw8PD1ywvmCIy99zqnZHowOTo6atYNWRcjg5Ag9l24vr559uyZMSeD4dqJFoqIpk/3HezhancrEPSFy4wj1/e9SNrl6/c8CFQlhMiyKzVAQokxGkg6OqogQINioIzZarUaRrvAziZr5/jh6WhKSTEYzswwpFnhHL1arfIbCNLj8Vgly5GxGQCRIwRgiSFG7kOXldloNLLWoIT3aBm4A9mIcGjppc9eCym895NpzrueQrJjXROR9z7PjZJRIiWN+rz3J3fuzufz+Xx+fHw8n8/VuI1GI21We+fOnYEMoFWFRZGv15sQwmg0Go+mXes7H1abGqEdjSZZll3fzIloOpldX19//btfZyhd6Lf+QL1y42lGxWqzzMwUUp9brMoxETVNE2OPmGLElGwKKIIpBZRtTwxjsOu6IrdIkFLKXVYUed+3yYslAmssMBFkWSbMYq0ytpGEGBGBEAnf8dr3PUN9kKrRBhH91rIPz/IPj8F4bsk3u5Kl/ZgKERWyG97yTgj/W9L23zi0DUeMmiAB2XUequt1URRNW7dtOx5XT548Kcvy9nb+9MWLoijGk6qqptZa50zmcmGLIJzI9zoP1BgqQCgGGjwCtWm81zzlW8W+sq0eBO2Zv9VWJDvGTLv1muUdTxARaadqgDR3AwhCIpvVqspcljtAjswkbIlcTovVUvGOYQUUmJnNZvtCMQSrOo8gCUBioB3VHmDdtHmeI0vyIZKhEVhjc+tYCGg7sx0FQgh99E1Xu2SsNUWRhSjbTl4AYK1NiZumQcSyGGVZttk0tCtLSynpqmm3D2bRFzokNaWkkyQ0Ka+sF4Uly7LUpL/S35RWNjCttaC+bdt3A1CNVFUZQqzrOs/K4+Pjum4vLy+Pjo5EBFiOZtPMke/WBOm3v/5Fe/nryWSiTdYcudRvbppl2/bEARGzrODQJBGIyYgYwYPJRFhS6JgBGAxuya5d22ora4NSZnkIvu87YxwYk1ICBIvAAL5vIXiDpIN+OCbmmBkQ3A7rMiZLu8YT+7EN7MovB6EaktG0O4YQYDhZa754x4D/Nq6o+1VEH98OVNum4AmRjAnbru/vObrD3sJd8b7sKITMuFrXZGA2mzHzfH7jnLtz53z+5c1vf/7bxEFHL15dX79584aZZ8dHxhgRC5xbk1nnCK0w5WUlDOORrZtOUuZ7WS4XvhdEyTILAEQ6FVN3V+z7ranfqRJgTlqbO1ybD526XTrTN6XIvJUQ3LWqMwZj3HZ8tGQAEZhjilmW5YUziH3fBo5Vnim9qCoLAOjaRvNnVVU5awihqTdbxhyANQTOikiKQex2Wq7Z9YLhHbklyzIVCgVKtr9hA8Axhab1NrfFuEBLXeo361ooaXNxq7tfRdEYN51OU0q+D1VVFcUWrQ4hKdUQERWG4fROcxuznRmg4R+9T2LW7nQqk4PhNsZoenD3CWbgyoHwsEcnk8l4PO37fj5fAkvTNBG76XjifbdY3EYfRlX1Z//inw1CKIJauLTZNNbaruu6tu/qOoRAZEej0XRatn1iYWBwZMlaYQzBhzYaY5BFUmLablDVsgQsKQoy4xadEyLZoqMMYLboKChSAPtCsi8z74MH2xMGs7lvOQcFvJ+i2LdUQ6C472fyXqpDD33XPmq6b6WH7T6YHdzt/dPTUx20WFXF/fv33rx587/95f+n7/t79+7duXNH1ejR0RERXV9fI2TWZC4rsjzPstxZZzJjjPE+JuGUDEKBJgdyLF1i2/luX8vAu+qNAne1vO/fguCOcWB4O6s0pW0/KwAQSCwMu4o5JJKUgBnRAACBUpsNCKMAkhChAUJJzNtWUYNKgj0XRvYycPuqc3go+6uq8cLQZ0Sb02iCMfap8z5KLPMqQZwvbiNIgkTWCCq1Ba2GmLDthyk6I7betJvNJoSkGTzEd22aWLRv+buGJeoVqBCqM73Ph3z69KmurCI3tIOGBwap3r+6u2ppNUvp++1IDSKq15uubogTSAgNO0PHk3w2Pjk6OPzux8daPyU67rzzmeGM5IMPPggh9H1Yr9c3Nzc3Nzf16qbdzO3hnZQSImUG87wAwYa4TRFAOMUArNMqCS0gJwm4I50DonVkjAXC+C7bzpoVlsQi2u3vnRgMUoe7STiwxzuT9ysJ9yVQdvn9ISAf7NguE/seYAN7Xu6+SA/vkvePQS98S1mIyGx2dDu/9t5XVdHF8Ox3v7m8vOhif3R6dHLntBiXN7e3bduPRiMim1Uj68q8KMvRqCxzZa2RRWNMv+kDp95DAiIwiclH6QN/ywnXK/wWODl4BIgIyID7Dvz2T9YqLLHN16ekk8kEaRh0lVAMEBMSQRoSSoaIIAGApJQ4kXtXSER7lMBBx+n6w676PMWIAIa2FF7Q2hDm4L3oOBaiPsa+68qicNYai6kNgkyORGRTNz1Hl1ubOQYEBkTZNurCXTSo3Ss0hRDj1iJpabxKl3K+hXEQJ5EteWfQavsR9sXFxXg81lntuBuolnblhd573PEAt7cagoYHbeeX65W1mfbCsCCzUSEhhHYzPTr8/qefnB4eeO8dhNj6br0drxVjCiEQx8XVhXOuKkeT08ODUX40KZumAaDny42RXdUZApDNiEzp2s4z67RZq85/gsSccmcjs7AXABFLIAwASaEZ1PZnIEPi/tv5Bt7V76Y9QGU/bBu07H4QOMjGALeYXU9eiTu2pwiKwBYOBQRE0Yp4UIDIkjFkmvRecnn4in1XindV14jYB399c5NSQnPw+vWLX/zyZwcH07/4iz/XgbUvXj1nBmbo5rd5Vo7HY6HcFkVRlnlRkAElsANgAI5JkjALJAHfh7rp6qYz06kumAZ7qGE1Q+JgjAEEEY4p6MUMgpG2JRFxZ7KEaNtqUrYlB2lno5JaCEQUSMg0GDqRBEy715xS5JicMyxsQAiBhCUGANAOPZAipN0kKkOQIHCKvI0jhhhhsIQxRrNrESQiurfROJvbJJxSMmhdnvmem7ZHvSMAALDaOcIYUxSF99si9Lt37x4eHs7nyy1DD2XwgHc7YEi+JZGtKTPmXcf14cyjoyO1hIr06HUrFroDALYToLYZSCKXZS4rEm/6PszncxTg6F2eEbKzcng4efLg7P7pzIH0i3lEUTdYRFDIgpCRjGy7mW9SmgMNnsPBuCzL8rrZIBgRDLFrVm0SYygzrsAtSxM5JjZGSPW0yYwRFuMxgkjihNpoNhKhQVXDiRlFy7Pl27jLvmYdfrm/PrjjbeFewn3fZH1LRFN8Vzo4/HL4/H2TqDtYOxjum53h7UPlmiIiehmXt2tErJv66Yuvmqa+++DukycfFONysVxe3rxNkc/Pz7OsWC7WImjzLDAKgRgBC2CRwGhzKOscYEwExCQgXeia0HapD3uTpb8VVg3XP+xvVSUi4n033Jpzdmc/cQfgx2FBCDQjvzMJIAKIBIYIOSUI1hgSBE779h92ePXgsOCeo067ahLZVZlpMK9SN6yqloYT0dArNYTgysrlLgXfdI04Gh1MI+HN25X0yCDCKAq+D1w4haacc9qX/vnzl5eXlwrIqkpWl09ECN+NTCMy6tDuXfe7/TFYAJ27pC6rao7Bs1KLui2rZ0+0lcmUYtd1lrDIHIqs57cns9H3Pvn4g3tn3Dfr1cLElPrAkR1RURZqq30fUkqz0VTHNbZ+O+RIRKy1ZydnxjhhU7d+uWrqto2QmJnIERBvnU/GbaWkRUSLFIh01ntCIP52Da6IZvATsRnEabBp+1L3LaM0/NR12A//BudC9oKT/bfsX8OwzvJ+Q+FBsId1HmRbt4iujO4nVYvz2qeUFsvbvq8fffDws8+/m2X2xcuXV9eXzuZFUWyamjd1ilKWo8RCFsGkJFEw2iwnIgCOwlnlTDSUOEXxPvbcBe5xx5L8Q4gY9jzAQSSYmSzIXpf7YfhSiD2JttV8j7gn70Zq75hrBlGIDEpSi7ol+oqIMpf2JW3AqAaZVCLx8ASHWHowIYqVKOqrYqmx1bZxhMGyLAW57RvIbCkTMBRYfO8ZMKUUGWzadddSdajPrGmaoigGyZa9Y/tE4V3DBUV41CXY3oy8W5R10wxokgbfQ1cL1Rm4NzdbRFigaZo8z53Ls4x81yeBPHPsO2fkYDI+Ppw65Pnqln1/dDhbxcgpEYo14iwAAyEDSdusfd8agumkJBq1bbtZN33fQ+iJKMttnlWFy5Z1X7fBR06SCBHQ6HQRECFrDRnmLX0J49ZWGMQhSwuwNYHvjBez2auy2d9YfyiHg1wNdmz/YQ+PU4VE/Zw/tKWw52TCnuUcXgyfMHQqYWZtf64/dT/pc1lHXC4XZZV/+tn3P3j8IEl6c3XZ9U1WFEVRANBivQo+jUaTSZ7ZPCsnFREah2TBWkKLKYGEBJTQgjEklELX+9BG9mjA7LVRU6ALd3nOwTXY4bS8j2ztqycRUVRZZ/furSeEMDQuEI3XEC0RWKLILJIYAczA9SPZC4kHoBjfr3G39h27E1i0GSTHFH0wSGjBkhFjRUQSi0YGzNonUiRFiUREzvoUV5t1BKiqql+tE0NkiilZZtZEn6a2EVGHSVxcXNR1S7s04LB22/IF2AJrMUbmbVofBwWDZlBmx8fHOuTg9vZWa9jUqOr4F8VUNDjU3TAuy9b3jJDnuYikECVFAhGC89Oz87OTvqlf31zGZjmucodyOJ1sNpsQAqQoSCgps0SU5c4RcN9tk6qOCFksweWbl1U5ns6Oq/FsMq6AsiRN2HTAghYNQhLYGnMjiBhDxAGIE0ER5ZHQtiRINdI2Rh+YxIN07RulQU5x79jfQ/siKrt2r2mv5/Jwzh/+1EzXvrnbZjXUgoegY2q6rtNRFnE3qTPuzfELIdw04ejo8PMffP+zz74rEJ8//6bruvF4rOSHGPn4+DhzRZYVo9FkPJrkoyKlgIjkCJ0gQgyhD10IXsiAEHMKyXe+jckj8f4iDC9g17eW9+jjaa9nx55DuAUdYorGGOZ35cUADLCNvfUz6V2967YrsbCS3QBA64Zp/xpgzxoPOnQf4BDZUaMAZFf+PiiU4QENiJpzLlgQETJYVVWfuK5rdG40Hq/qRhBYkAQsGmRgESFril242dY1CkBMAmAI8zyjCBDT1JXXkBAzQIwA6CjLjDD3HIpi20MlxpRlWOYFMzdN0/k1GnDW9mHjY304neWZvb6+nlQlokhsY2gBwIoYEkHYdGvnLJi07JeJGY1khiRxFvCDo/sHWLroesB1CMYdzu2Me4/ZyBSEBAlALKNI4pgwJPKRWEBSClGSLbPZqDg/P3v58uXtm2/idDaZHpyOZgelu856L3C7Xq67hK40tgiMEcRY22IGkgCIrKEo0ncpJeAYebxsVliVxjruehAuyKFAKqMzCJB8vy1pQWs0lRclAYDNHOzCG2utj7hcb2KMVVWhlb7vACDPs6ZpEnCm7eUdEEkS38VQGmTmwDFFVtsYY2z6zqTtwOembQGoKArLKbUNJdpsNiFJjHFdbzwnBlm3Xd21XfA+BCYDhIHTum/btv3OlH78Rw8+/6MPEndfP3t+fXNlrAsR13UDQNFzntOonFow/YYNBxqfkhMEhsjcobMmiz71YtHVdW+yPImpN2HVpkBFdE5iop27DtpoUGTrAao9T0kArHNIlEIQ7t/BM3HLq0YAwyA+KkxigABsTJB8mhRjBawTSBTmxH1sNpKmVYEZYuA21Chm7BywD21D+di5HLaYolhyAACMEsWAscZmYCmCCJuEObqeODA4Z/JqlAATi48hz3NIrOADQSLaNUKLMWbImKx1gMYaIUDGKLGd5M772MeYhO0gygCIhCKoVfMuy5iBmWNKFAICEZFB3OIPahZY0q5HXdt3uSusddaStRYIY0wxRi9bzaR6XafGxhhDrw2jWEmAg/YKIQiCMUS7Rg64y0keHByMrK3Xi8jh4OBgMpm0bTseF9sP3zY810dgRCwBZ1kWow/9dviWMcbm2Wg6cb7YMveB8moym01uFptRVYLl1kvnvRBLhLbeGAskQCgICCDaBQCjz3KbZZnNnEmJOXFSshc3bZsza4SQQAREQtAaSGZOOl1ExKctIrVarNfrZUpp1PdK3UKS3Od932VZlnNuzHvueqJ3LBZjjMnylFLf+5Ral2UiqMWfQzHO7XJRb1pmjpzW9SYyC0LdtDbPYtsg4nhU1k2nI8EfP3z0b378nclkcnl5efn2mpHu3r1XN82zZ89Oz+7ArkWA9z4rq9FofHJysgF9vqAt5y1u5+uGtM2zxyB9CNtG/vzOO9j3CGhHWhgcbLMb2MZ7qbx952I/lh72j5qBbXkTbkNiBCQgUTKmvDtwz6Xfd0n0i1RLGsC0ayKRUpLELnfMyfvtQGwkAOUMAgCAQRTZYxQKpJTAkCFnMpdYQuKQxIe0c0AkJbHDRRAZ7e6onm1pDFIS5iTsUyQgi1aHrWl3Ld6dLIwikFgSJSKKBBCYk7qHVnZjD9XR1yY54/F4GRbqFEncBl1DJCA78ioRgTAkBgH1iFCEiDKblWWW5a5PnhkQAVGSiAFBJCVmx5gyV2RZlpKO9UD12ep1jUhlWbLgarXa1O0osHVFVRQ5ZbaLPqyT74Qy6wCMNeTIgEFjkAVsTFGAWSSl5LeCseOhKqpnMAoIS2TxIWnHjz74siy74AeI2Mft2Oe2hrquvfebpiUi7zvF38oyz7IsLzLFuzWoY47ljuafUjLbelNUfkbb+T74EIIhR8Y1bQ9t33a+7Tt1SjvvGUQIIwe/CZAYCNtN3XbtqMjvPbj/0UcfffDBvYuLi5vbhTGGBd6+fQtoHjz8wLncGNP3Xu+1Cz4LQfcmOGMMWUJryRpKbKy1ne91k4SQutb7PjLS4G7vQ01q6OKO2bPvYyMip3ftZAZHXXasN9nLtQ54iWYoYPcuQjIoKXoSpm0XDEhE2p5+P0AgQG3tiEMCc1eerhK19Wt2v7FkdH5OCtG590rJEBEFgFADycxmZVm1IUJMSXwInYYA6u5aEUFBAiJAwXcKxseYQIRQCBmBgQEZQXLrtpC6XqsQE4sIIXICzzrgKVhrHdk8L8TIUGisSUIiUshHVzC9j0flmSXlRyRmBNqS5UUbrgXm6WSSFa5uVyGEsix3HTs1ZjDacAnRel9bS7k1iJhlKcaYQkwp9T7GJLm1jsjarO26erUMsrr/8EkfORiZFC543/QNEY4q5zlSQiJRIM2KCBm2KYTgU98G4xAERNEoFiRbMFDwqW37rut8itq7JSua7dQEYQ3ANGbg6JS1p3bAh06Xazodq85S9G8ITgraRh0pimJduoPvPXywWS5Xq5W19vBw5IpC5+TkWdmnXYNJTiLCvB22Xo1GrQ8319d5UXz3O9/58MMPR6PR119/s1qvjTHVdFY3jV+vJ9ODR48evXp1kecFkVHPpm1b9uycy04mFo2z5Cw5g0SgfVmEEYA4Qdv7vg8xCjqjlSZDlMW7JOqQ4pIdf2OwbHGv6H4/iv6vGsb9SHKQQ8JtRgK3USEnkESEtGXADJI/fObWt8KdVLOICOmov/e+MXFiEbFbT1rj0l2zEKOqwSCDc3lRVH2oEdnaDLhBNERICGCSJdgHvklAe9NhHzwzEBE4BDTMnBCIhBT90ZQMKKEBRYi23GXU1STasr+MmAE9DyFo4aP2UPiWR6EvxmUVhQNLSL0IEqBhVA5NSokkIlZ57pIUZG2C5FyOiFuaC2k9H8vO/ds0Xehb5TqJCAc+ODndLFcA4JybzqgoyxBSaFqOXd94g+b8aFoV+eX13KfOcZ6SERHRShdOBIzOkMnAAAAETglYUiQAnV4SIjNz23XDxMU+buswO9+HEBi2WXjded0GYowC7yZAAIAALFeN7LqYkXnXiWujpaIpKdWpTEnZQk3b+RDJWJflAti0bV3XddMEgbpvVYabrmVmIGzbNssyTglSPJiM7969+8mTx7PppGmay1dvjk6OyZrb29uirD77/g+S8IsXL9br2vtARAQmcwUAiNliUWTYOnSWLKlHshUDYfACfR98zwzWYf4H5Lx3x+AH4Q7O1Rf7juK+qHzrGM4hIiCEXWEFM4NyDBHhHXosKalfL8wRUQxZRGSdTrd9Aru6YU4sUZSSuOvUaMkgiffsvbdkjMtEBCSJCAoIyja+Q7QmY+YsKzJXxLhOgbMsI7TWEkAERqZt3lMLzQd9YBAlCoM2ijQGtn3VGQg5RLXX246uAsIiSRgAALPMTSaTIq+0ikIzs1vvGVFEdDvGGM22AGg7OHowjBllMXGKPih7BW0SEpa+rauqsJzqek1WxtMx5fbN1ZtqZIkIGBFZBLeeoSQBYo5d55umNSg6BdEY44ppSNzWTRQgorIsi5wRcT2f+5iq0exgXExHFUm8vpmHzU1enQKqk8CIWrwkBq33fiPcdW2MPoaemTnEGL1PkFLquq5uGpW0JBxCsNb2MWyZH7vUjmPqg+ogirBt4AkAkIIxJsawTVvZd6rNsFL/gBlBKCeHrjBEN4ultbaaTK21rQ+NfrvLWt+3fjviS31gFNQs1nK5nExG3//edx8/+iDLMt82BuTw+CjLchbJs8K5vPN914e27cvxyJBNIQBSlmXO2jzPq2kFJMZQbo2zhMASZeetUeTUe982wUdBsEg60utdr/EhGQh7Gc6BeqL4pNnmG95J4JDVwPdR6MGxlD2JBQAWBmCrBBsmQB3XyYzbTomwI83hNtcPAMAh6mVt/T5tJ8OCZQksQqwpAI4pGQbIUJJoIIcCu17JBtCgBeIir7Ks8H3wMbisRERLBgjBqkiTfd8iEaKgNRnaJIwEIsKg8TEBCSniZwjRCFIU7ll0k6bEkNiaLM9zRNP3ISUv8G7teI8Cq5lGay3gO8J3Sin0fYghxsAsYChBSIw+bqdYgt+RPCRlxtrMKUmOSJiREEWSSEKRtu2cswp1MEdD23GLddf1IbEgpBT6AMDOWmtwuV7neY7Jt6t5WY3Pj6YQ+1evLrL8YFC3hKzWKUq6rZfGGBHu+ib2fpeKi8wEu1Q47wrefQxlWcbIUcQYIyBBGAGEBY01O8Jk3JXMMcc8p5BSVOeKDTEAQmJOQAYBkISQyZB1Li+std53USAmMdvCbQMoaLDrW5+2Dp4YskhElKXgvS/z7O75nQ/uPRiX1Xxxq1hOVY3Wm41z+fHx6aZrv/rqqQCc3rlbFAUn6TpfuOzwaOZsDlsoQawR68gQYsLA2+SHiMTAbeubrg9BwGQINkWvvsqQcoC9rADuEV+HWJE4DUIoeyTPtGtkTjteGw8jHxEGUQQt6dUWISBIxLtIklHofWhHY0IRIYG4az+s7zWAIJA4lZkJTc+BVZA0USkpCYEkHUIvoA0KjEo5EphRURV5mVLyPpbMzO/lriyhRe08AGrhtkuQZVnkxMwC2whSa2UMiEGwRMbYKGwEQSRItNbVofNd37dt7pzmP40xWnugYPqg81QIt0uD24SMon++awNHFRNCHUnLEMPR0VH0PrTNqMizzK7X64p4Oh13rRJNlPEgKCiCIknb5ufjsXNuuVwu1qubRUop2XLW9T5zziK2bRv7jp2TxIagLLKU/M3lcnZweHb3XpyMLyH6zRJ2BCsRSaxMg+BjIMKUUtc1uudYYkgJowEABUKMMcAcUup7b4siioA+ZtAAUoC5770GfiICZDSGIXFdCImFAXXbJUSFVS2RJpcAUBCZCKwlayfl4XK5XK5XU5xOp9OsyNfLVdM0jQ99H8hs8cZk0JAg4ng8fnD/3uOHj6wzby/fzOc3ItLXG4MgCIK8WteBU1mMQorL5VILtZnBTfLJdGrJaS+jHMkSGQTa9uDdTYNNECP7PvZdjImNNQyUmIDSt3xIeR9cUZFTJiMisu/3fdFBPtOuB8zwOe9SqSqE9K26LVQhlL2ErQgIJBFEMQSoHWkIdKC9oqJskZzVMd0ppe3An+iDM9uaJo4xRp9Zt20nxYKonTc027ntiZhlGQlxTKLde0CYBVlEwPKWhC1qKBInffdms6mqKsvyzvcxBCIS5q5tpzGVWZ65LMbku9b76EPsvC+KKiND1qbgu6ZFQwbQIkUkLTrue51jTDtmzDbrqvxj2SF+ZW4cGBbxiX1sDdoiyybF1FkKoS+yPEbfNDw6mCCi9z4mgpSIyBp0zjhnsyyzBvM8P5xNYozz+fzt22uRdHp6enh4+PPfPs1yW5aj8biajMfJ98JRUry9ZkvALM5SDL6v10cH43/24z/+T3/3m6qq8sxpqts5h4Rd15I1b99etb4vy5xBdDjUaDSCd8OPrSBFQAY0LgM048mImVf1RuuyAaHpehKnII1WxO58LY4xWpMxKTEqL8ttr84UkYwBgKLMTo6OFcxwed40tTEGskKT8syszXK0brMsy2dPvzLGTEbjFy9eHB0clmXxL//lv3Rk/tNf/V/GmO9+8slicfuLX/zi7OyOdh5JSZq28zFMDw/OTs+/+PnPsix7/OjJZ599dnxwuFwudd9nFlMK3ps8c87aFCMz+z72PgHA9e18uawnk6MmyGq1mc6O27CQPZIK7UYwDCJEuyrHbVlcfEdAlx2cM+xYld6dD6LMUkfWZFlm8wwQvfdNvW7aBnNnEZx6IhFjjAbRWScMW4OlwpkSKCMlRBFBFsZtAE+AlozvWo5hQMskBREmsmQQmEhAKGk0qMQag7Sp6+Vi8ejJY20lUde1mkoAYSIRtrtoEAC0/eY2SwNaIBeTA0BjAMABOGNOp+PpdJqVRdf5xZI22GSElXNgbIicUpIQJQuIGUhKHBjfURBU9WxVEG51vArhAN4k30cWhm1HdMaoEK4iaXnhCKzNyTkDBMCQZYX+NTFDRCJOiRHN3bv3r64uL169atv6yYcfTqfTzXr9+vUbk7nlYlUVpUi5WCxKZ8ej8vrt5ePHjxeLRXc97/tWhyWdnZnDw6NPP/7wzZs3r14+Ozk5+eTjJ6/fXNzc3Nx/9PDizRu0iEGSsHG2Go9CSmLICDGzziVDQ1u/SMtHgldhE9z2ZcuKHMM2JNaqU9qWhPY2c1mWqTSKSOTkDNnM+eBB8OTkJPownk6Wy+XBdKLEmsy5osySD13XpBQz58aj0fV6vVgstMckIjZNM5vN7tw5/7f/9t+G0H/51ddNuymK4sWLZ1VV/It/8c/rTRhNJ3meb7ouAnDXtk33+s3FRx995H2s2+bnP//5wcHB0exgPBk55wLXzlhCJEFh5iTIW1b6el2v12sfxIKklHxIre/fG3G4J10Dc1r2yJy0BcphL1x6j2/9LfO4r82l70TNY0rGmBACEFoSQ6RET2vRWpti0GopAgbgpKMBmELotaaGiBBQXRTQ4lGl+CBqfIuIhMDMkFhprjo8BhABIYRQ5oX3vsxyBFbGUt+1LssQwZDANnggFBbeVnMYInDWFVnuuy7FkDtXWBdjNGRGeXZcFAcH07IsV5s69R1EF41jIgbs+tCGKJyi95ZFOGFkcPsJUmEZOjRu6020cxHvampC9FFYkNTLkJiYEjnWTGNRFMagdWgzl4iBPVGmFBZmFmHEhBhE+KuvnlZVcXx6upzb5XLVtt1oVD14+LDY9KOPPmrWm03dnpycBN8tl8vDw+NN07RNj9YcHBwpivv27VViOTs76bpG5yePqqIsMuZYVdXp6anJ3PXtTd02ITIYQoS+D9mu1dU2ttnB630MUTgrcpdnGEk7RGZloelWJDF2W9KlQC4ROWeIHDN3fZOYRMQ5V4zGOpo3xqjjeqy1y/V6Mpls1svlqrEIVVWNR2XbtvP5zXaydNMeHBxs1kvvuz/6/PNPPvpouVy+vbxoNquPv/PJ8eHB1dVVu6lD9F8/e+6cK8pyNJ2cn58fnRwbl4cU58tllmWZDskqysJl683m9ubmk+88BmBOECFK4r5pvdeaDFiu1pvNRkwFAFE4CseUjPmvVE4O0jXAE0Og6Oy7+knYA2D2iemDWCqWo50afYqsWWVnMudSHwGQiAwYEmtSwl37Q9y6hTt5VlKbgDquQ05/QDRo+1h3k60JcVtilkREYRuRXXFCiOPDg/V6rbig9x3H0PftQKHVwVAW2SFHEBIRBDRgCOBoOruNIXCqbGYM9iKGqDTOSjTRYyDLMUeIBiMBI4J1zpALzidOwftdh4XB+x9+7uszRER493u9T2RBw4Ysx5g47vSNNnsnY4CMIYcMhBG3DwuRyOqixSQisayqEMJ6vVpvNpnNRqORMabr+izLvvrq6fHBzFr7xRc/m00n9++cP3vxLPTeWjsaTfKi6PvQ+dVytal7f36Gp2dHH3/y5Pmrl7/5za+q8ejo+ODq6vLo5BitScKd77vWW8qMMUmYZNvHdptU2AlhTFvKi7ZP7/s+gZTWtrFGRBRKIfodO8Qa1M4sAEwoBncfxDw7nHVdN5/PtRxsPB5772+vr/K7513XNetVkbuqzEGga5vlYk7FdFxW9WYTQ+i6bjaZ/OAHPzg7OfmnL34aQg9Em83KOSpHJRlcN/WP/uRP1+v1arNumu5Xv/rNum0EIc/z73z66e3tbd/3uXNnJ6d37tw5PT09Pz+X5GOMiZOghBCaTdM0nfeRWbTlkSscIDKzMegyk+I7IRxEbvBC6Q9my+n+GcTyWwL8h0IYY1TMTwOo4UOMMdYaa8GqwVO6LIu1dnDWAEAjQ2TZVpnv1/gmrdwHRNEWHCJiCEXIAMYdOgrIoIkGFkRABGvNcj4HlKoqueEMnettSkpAN4RgrXUppaD9a4UEOKVEKFVR9nnRM5TOWjIUGQUyQG5rT8i+jyFYiaUlz+JZMmOKLK8A2t43bd+FiIjWZmk3RWgLKMN7oqgrtL+y2VBkbLSrR9RhD9p+N6WECMYZIiJIiBhjQkTnMiI0qN4fg8jp6fF8frtaLazNxtPxaDxeL5dv3rx5dnnz8OHDzWbTtfWd+/fY+5cvX84Ojha38/F4bG1WN33XdSbLKaMY4/X88v69h5999r0udL/+9W8fPnp0en7+1dOnaKgcjU5OTgRhvaoZQS8mttsGWZr4gl2ePQrrMx7kE0ALoOKOrxxSAmN1RNmYSIOfRISjUaXNZrQWwlrquu7u3fPr6+vj48N1V1tLF69fl5k7OT4kQt93m3UnMR1OJ1ervigK7z2HOB2NP//8s/Pz85fPn4vIZDKJyS+Xi7przk6OjDXz5XJx82VIsSgKk2e9X61Wm/O7Z9/77LOrqysdv3P3/Pzk7DTPc2PMaDRq1qsEzJxEMPnUdb7v+5TE+xhCIGvzPI8cIkfjLFmM4V1ufZClQVT2LR7sWOzv9PUfvEv+4AghoCFjjDNkt9Are+8tvPuW3Wdtn8XWZDHT3hB49T4HydxOQBZJuzJrEQEZulduc/RqUrd1ppiYJcsLSaHrm75tjg6mQOJTJILOt7oNLJEV3BYeASECYhIB4SgQU06WXGYEjUBBhgBsEkoeghEQEsgBjLOGxbAAgXMWXaaFEa7tAydEQmTaNTnfFzbeVZQleOeKEJElDJQUUVZo2FpblaVzjhBTCsZYYzHLMuCACJo32nUsRxRhjszx+vp6PB4/+fDjxfxmPp+/fv3aew9kHz165L3/+MMPJ+Pq+bOno+Pj6WTy9PdfHhwdTyYTYfC8Ikbn8igc6hYxzRdXjz/8+NNPv/P6zcWmXk26KQDU9Xo0mZweH00mk+ub+XK53LRtSh609MkaIiAC1p6vMRoEMATIMXoR2TU4ClohSqTwOGZZNhqVZVl67/u+875PKWW5LcpMRBKH1WpljHEuOzk5+fJ3v5mORwbw/8fafwVJlqXpgdj/n3OudC1D60gtK0tXV1frnumeQcNmsRhgbWcNtnhZmHEfCBiMLyQf8cAXgHzh8oGEGWwAcgcYLGZmZ3pqplpWl+ysqqzUkZkRGTrCPVyrK4/gw3H39IysbgIkr4WFRXi4X79+4/znV9//fdlsttNuUoaUEkbRQMOgikexEIIRIuKYKHBc5/Kli9evXgs8b2Njw7SYTzGXy8zOz/mDfhBFhGp2Quz3+z1vUCyVSqWSm0path1F/JVXXjk4ONBCHf1+H6TKZDLT09P7QSxjKQiCEqCIrosiEk10QBnCkF5IMNPUn/2URelHdG9wPGI6dk1jB3jKPeoawfhP43QGxvPTbCipHUdxFIcWRQpKIJEg6XClMYNQQlAIDSuVGjZGlMZmiDF2kqKuFaGUOIhjwzC0eKoc4UuHmwVwlDqJAqWEjkhNk4Wh7zhWGPn5fDaIA8KJ7bD+gOJIt4TFeqoa1Gg2EkEpVGowGICUBhDgAlAZenhSKAPBZtS0TA0NUUJalDJGQiERpEHRchKu63pB2B/4YRgOSVieDSKOlCWfL9GMjRBAaLAoGKBvgWmarmtreigdVziO4ziOisaOdDj8MYw+pFBKuk4iDEPPG/iep4UQe72O53nJjDs9PR0EQafdTCQSDEmtVqOm5Vq2BARC3EQKjTgK4yiKJKBpms1OO9k4KU+VX3nlxubWdrvdpmyYfhBA23IdJ+j1+iCezTExypg5FAzR4OVxkyOOIsqYZVkaDG0aVEqpJDcMw3Vd17W1k4mjEJRSkmuGIgIolQApGDN5GNkZBxEtyxKSI6pBvzc7Xfb6/TDwJBLbMU2k/qDf7XZNlh0MBulkanZ2dmVlBRGfPNkElIVC7uSk0u1iOpPqewMp+dTU1NT09KDBAGm91Ww0Wplc2jRNz/Pu3btXq9UMgxYKhdLUVC6TYYQGQbC7t82kCUBAciSoFAiuhBBAqQClhhAKLoSyLIuYpucHDJ+TKz0VWI7tc2xg/Hlpt0lXeeoMurbnOE7E9Qy6bqRRSankE20MkCglGXfnCSGj9s/IrSnQ/Ktk+MNkVzOOY0YoMJCjUWOdBTJCNY3NMCIdKdVoUuDpuVkh4nQmeXyilIiTmbTWltRnYJrbYIxYRaQAAhH8/sBilKJSsRAomWEwJFJKXf13HcsLIi+UQnBC0KBmzCPFBUhlMiNp26Yd69hAied2tbF2I6gRqQE8SxoJIYJHQojx1BchRGO+dTNNt1y0pmwkY333uRBSoi70AyiiZUlTjPNI4zZt29Tk0I7jSGY2Go3I98LAAyXmpmcymUwQBG4q2W51+p6PQCMhB56vgCSSSaH6cRydnJwUisULFy54fvjw4UMBKITo9XoAhJkGj2JCiG3bjuOEg6Gco2EYSMmIaIREUURHlESUUk3vGYYhIZpBTHNGWslkEhG5iA3DkJJzbgkhGCOUIqWGlDKVyB3t7wnhDvrdbDZLKY2i8ODgYKpUSCQSBiOB12/WG74/EFGsQColAs8vFmZLxSIhZHt7+8mTJ+tnVjX4wTCZrtZyKSIeD3zPdacvXbrU972Do8OB7znJRKFYFKCYaSSTrms7mog9k0pblqH3PsGVlEAVSCkF53Eck2cUjMM6p+VYwMxOt0ufzUM/d5ARgh8ngLIAwONo8mmT/nDCbGDcZ04kEuB7Y4C7/l+A5AaB4TYohBACheCgkCiDWc9dhgLdzbdMc7g+1dDxDucNxHPjhUSBntMnhIAiUmoU57MrJCA5jwqFnN40OedRFDI2HNceRkNggGEYCFKKKOQxRbANajBK0ANEStBMAiqJyhM8lko6juPYaFlGECsujTBmiC5jqUHkZdL5dD4nQAipyqVSKpkJw0fFLGk0Wopzkxp6rsIwzCgIkSAIqUBSRgjKOI6E4AAykozZyVhBJJAL5ftxwo8JtSSSMBIGEgIUJPJQEMR0OjNoj/lFqByOnQAimlai2eq46aykuL3ztBjmC6X8Xu0AY5pIJLKZDGazeuqq1R0MwvCzn/40m0rNzc0pEMsL0wcHB77v9Zq1lDmdYSXRj092TtbWV65fOCfDwaNHj1yWQNELej6CEQlJubARFChuYq1WLRaLCpVBTcMy6s2Gm0qGPK4326Wp8szslO+HQRQRYgAxJaURjyWQGNVhrXZ1ZqbTagSeBwAEoVTInZycpJNJilAsFsMw7HfYfHEBQA5aPQDZjTxC5OrakuWaALLrd4hrd1oNK5Hsx23GLOTe8tqcZVnpbOLoYD+KIsdyMMbNp09npqeZYvWDtm2n1ubOhmG493DPnBLJKKmUAgsybrZcLudyOYL4k/d+lnCcbDY7Pz/v5u36cdM0zbm5OcP0wshLJ5M7OzuO5QouAahrpsNwAMLkgW8Y1KZG6EUCedKyMAp0vYoorXqiuJJKSlBKU8XHnKNWzlMyiiKLTjAbAZCJFjyA5GJoopQhZQwAvD4KaTJCJUoAJRQoUMSgIoqEjONIMURLMgMojSRR0BFd0zQN05RSxoorVJGIwjBMpVL6TRGRoF50SlJUxI4NhpRQpJKgjoYIxZ7gqAAIEUopgaAkBSSIQRiFlAnLvPd0q+N1uypyStmuCjiNJEoBsULBlAAkChUSZIxRg5GEZTkGIVIYCqgWu5QghUCJKDGKRSwk4TLkIop4LMEmjBpGrpB3k0nbtoGCk3AWFuYME4ul/Kdbt30/9DxPSR0wEFTPxqhhJF2hYRYAkiqqw1ZtS2QELxzVdSiORxPRBUrEcFRcMcYIVTxWSilK6WAwqFardsKemi5Kudjvd9utlmO76VwmCILBYKCU0nt5s944qVWuX7+eTiRs2z46Omq1Wp7nMcay2WzUE4VCYTDo1ev1bC49uzA9MzOzvb2tlDIYSyRTBnMGQTiAgFIjkUxS0xiqiIVcckG0MoHjMsa8QaCENE0zCKJBr0cpNSjV8xPT09PddkepeH9nlzIUMUeEhGsrpISQbrebSaU1JYxUaNu2aTJAPhj0giCwLEop7XQ6ALLf71NK41gQEmt/4vf9qamp9fX1brdrMqNWq6XTaSnlwsKCVo9Lp9PNZvOjjz4ql8uXL1++tbMlpdQBfxRFR0dHnXY7mUx+7e03DWLoRlEcx5zHpmkAqFQq5TiONwhs2263O4yxQbOF1NI54RALBUIIEKiEksZwxEfXu4c4ax0L6aoaGfYIhkUsJb8iIXyusPf8EfNQSikUBwqIlBCChIGCKI4kgAQUSsZKEY20BkUNkxomZYyMY11CFRI55NNHDbIBAAlKapQSoNDsUWQ444IgRcyJFjgBlFr1VaFSGIahYVuI9Lhy1Op2qEGYZXQa3Vw+g6h0h44RhShAZx2IaAAzgBpg2BZBwYFTGYeCc8WFkkoJqVwWxahQBCEPhQA0kVFmmsx2mGmYtl2aKi6vLK2tLXERJRLORn2n3/dEFEeR5rEbMl8ITRen2zI4zq2FpsyTSkqgSIY8RRoIrltq2okDAGPMMRyZJDGP4jhGNBCJUpFSSCnd3NxcWFhYXl7sDrrHx8dRxA3DSDjJhw8fzszMzExPR1G0u7t9cnKScNy1tbV6vR75PiGk0WiUivl0Og0AWmAUUWmS/Hq9nswk0+nMxYsXP//yFqEskfJcJ+lHca/bt22XGYZFiRJy4PmaYdEwTQLACHVMK+m4ANButnq9HiqwDDPiscGsOI6z2Wy33bFtt9frJZPJOIp0wc515fTUbKfTieP45KSulEo4icFg0G4HhomWZSSTySjyarVaOp3EkTSdaWr4LjJmFtKF8+fPr62tPXnyRA9/6c5hrVZLJBJWKkUpLZfLmUym22p/+OGH2ZXFRCJhGUYYhqZpJnNpg7Eoio6Pj9OJpGVZmVS2XC5rNcwgCAKLaeYLABgMBvl8gdbbBGkQRVIpRKqGfmz4X4bni5y6SgkAZGJGaSxnT5GMZzImLfC3GKFSQik54vBHpYYrmzJGQKEY9hC4BKQKFYQ8VERx9SwnFIoIoCIetQFHLBhcgBAqVoRzGcnYZGpYVAPJJUpigC7voAQKAFSTOiqFUgIgNQyLIOMxD4Mojnm301cwZGpkSdNWSikhCSiGYFFqokEVsZmlFAqpdzBUiqBCQhiyRCRp4Md+LKnhWGbCTqUN2wJGy9PTFy5dnJ2dNh2mEAaeF/GYkSFoeHjrhwPBkiGJR4mxbuzAqMxFCEopJShKh0odQ5E2BZLKOI6jMGZRxKiBjCYd2+uRiIBuJCJIQMIYOXdmPZvPBUHQajTS6XS5XGy320+fbv7gBz84OTlpt1pKqWKx6DhO4Pn9fp8QMjc3p2El3W5bCKEzTw/CZrOplHBd1/f9jY2NQin/+uuvH59Um612v98HRYhhas1TAGBIMslUwIyE7QAQXS2zTavT6+ZzGaWwWasLIc6cOZNMph8/fpzO57qdTuhHOo20UmkAKTifmZlq1Oq+H87PzLdanV7fC8MwnUoN5MDz+mHom5wZRtJ2TCFCz4+DIEA9jKbQdV3TNB0nQSlN2YlCoVCv103TrFaruVyu2WzqLcx13W6v19rdXVlZef3116MounXrllMobGxsDAaD5eXlYrHY67ZN05yamrp47vxgMDg+Pq7VqtlsemqqpP8v3W5Nc4L1BgNEFAqR0SAKIy4kEsKQUoODIkgQqe6gDf/XI0scVwSelekmquV6avZUGea3HIxIQRQqlKik5FIolIhKSlBEylAoTRLMpGQKKWLPC41YjqeohsWz+Jm6DpWoWfc5V5xLrlApCSAYU6aJBtVeIR7SAYNSQkmJAEAZUARBoNfqxDGfm11wEqmtvad+GCWT6YHf1+UkUIo51FBKEQYGoZbJEqbpGMzSwH6FwgKBRHGToDIoEkBFjTCGMOaxok4ykcwUrERaUQMYXVhaPHv+HGO48ejB4eFuFAdRFHmep5mFUI8x6lm4KNbJLoJUBJXmvudCFxKUrtngEFWolAqCQHAFQ2VEzfYtFCMI0mJoWsyKKUUQSr8LJYTMzs42Ws1er0eQ2YbV7w84l/Pzi/V6vdvt6mE/3x9wzpNuolgsZjIZxzQPDw+bzSajWCqV9DRWKpXs9/tRFCTTSQDodDq2a9m2c+XK1Y1Hj/YPj+I4zqTSqWRaSpCKM8B8OhMlEoSQKIq6nb4fBq1+3TQtygwASDquk3BXV1Yopft7O8lEQgoRhmEqlRl0e+l8odfrJRKJ2dl5iqzbbXf7g06nWyoVCwVLSlk9qudyuUIhF8WB5/XDyLdtpgNmXR7QwNQo4oaBSqluv1c5qQaeH0VRo9HQaFLXdeeXFuMgjKIomUrpcDSfz8/OztYD7/z58/1+v9vt9ru95eVlx3H6ne7BwV46nZ6bnXYcZ25+xrKN6nHl5ORkupQMw9CyjV5vwEyzXq+3O71u3yeGLaRCyhRBJbQPHJU0dLCmRwdx7P1QKQVCAR3NhIIidEhCObZA9Tyn44uHVLEeNwcEBVKzAgLBOJKcc+CCcE6FYqCYBnYKZSiFOCY71/YGenwPh6OquqKmhADK2BBfDcCJpIJIqYcohhAcJaRUHACYZJSiohDGXCpkpmOYvpIoOCogrpMcMosrxUTgEUIMZjgGS9pmyrFtw2REqShCYiqCymAUkFE0mUEpPWkNhJIcKDJqOik7mWG2LQGtRPK4Wvnww1/Fkh8e7XZbTSHjMAy6YXc42zacJgHFh3PlUghERQjgsN4rpRIMNWuY0HUmHbjqMyhiUkoZM7UlA0cEajCwLENKHkcKhKQUGaOUkP39/WQyOT8/32zWd3arOu9ijP3VX/w555xRmkqlUqkUIoqY1+t1IUQcBJq0f252emZmplqt9vv9lGsDyFYrHAwG6Uwym80KoR4/3rQtN5XKWFYjjngQBGiTOI6DKFRh7Lou59yyXRAyDn0Z8X63Vy47rU7HsqxyMZ/JFWzTEkKkkynGWNJxlVKmabYbTSEUj+JEIvHk8VYi6czMzPm+n87mL1+9ls/nNx486LXDZNK1bIP3AkTUPX3PCzXjE2PM933DsMIwAIVCiFKhYBhGebl89+7dVCrVaDSKxaJtWu12GwByudzy8nKn09nf26tWq8VicYDq6tWr8/Pzjzce6QAkDv1K9SiXy6VTCUTa63X2duJkMplMJq9cuVSr7GoYXRCFScPs9gfdwaDVG1gOBBEnBuFCxlxIQNDyZlrFTvP8g8JRf5yMBiMoosRhFYA9rzM1jpV+i1eMooAQApSgREUVIZQxhoxKKSSlCrkEImE4fScJAQJSgyU0WQ4xAEAB0+y4AECkolJbPpWAgpgaECIQASjXieuQj0+3yJRUDBG1oAKAzKRzyWQ6CKKe5xumQw0SRLqDioiKIjCXAaKymHIYWKhQxEJEQgkDEQQHJQxUjFDLoJbBGGPESlBEpjBWwIH6YUgUcFCDOKw+qnqhZ1qUMWIZKATvdDoh5XEYCc6RGdrudRnGGOm/0RGOnlKKSmMH9C0XiAgoBedaxckGUK6rgy4hpIwFKkkFt0ytpTEQIjaZyUyGqNKZrFJKUzxlMpmlpaUg8H/yk590Op2LFy+ur615nre19aTZbKYSyWIp7/v+/MxMLpcDgNpJ5eDgoN/v53K5fqNvmMx2LM/3hRDUMAI/Ojw8nJqZKZenTctpNpteGAEq0zIIowyoyYw4jFAKg7JUIpkoJ3K5XBiGWgZIciF5xJCk08nFuflBLGr9gWb+z+VyjFDTNMMwbLfbQmQXF5Yty6OUJhKpmZm5Wq0x6Aau63IRSSkt23AcKwz9TqeTz2flkFQfDcMIg4gxphSm0+np6elkMqkZbNPptGVZvX5f92l1hJJIJM6dP4+IjLHPH9z/8V/+ValUunjxvBDi1uc3oyhaXFy0TSubzZRKJUqI7td3um09AMl5NPA9wzD8KFaUeWFEmOnHsQREMuQyH/sxAc/q9/C8Hqic1KUR+tUAEx35ye9kQpJl8lAyVshGAS4qpSQCQ2SWJWPOla6pSKEAKdUCMYpSqRTX0qiEDQ3SpHLEXCiR0hE5Oh+GyQYiSpAEkDKDUiJijogU1RDshgoJRUoJCKRATcsGyxr0FRKuAAAHXoCIlAAhwKaKGaUERa3MLhQP/SiWUegYDJSkSkokwAgFk2CMwAwnb5qmAtIPwiCKgl4fiKcQeoO+H/mOYxNC+v1uX3LbthzH6QUdOTpASAUElAKldEeIETQMhqhAClRKCFRiSESiS9aIKJWK43gwGCSobqkpIYTkClCCJIxxy7IAjL6SGkVhECoActn0/uGBZeeXFhcPDvaOj4/q9fr+/v7Zs2fjOL5582a326UU8/l8KpHknF+5cqXbaj19+tR1XdsydD+g2+026r1sNmuaJhIihGg2mwohnU43m+2pmelsLmea5u7BYRRFlmU5ju0q0zGtdrs9XC5SJpNJwzCazXa5WBRCdFttKeW1K1eXlpYYxf1K7WBvl4AKgmBxcREAENXR0dHly5drtZrOmqNYPHq8mcsXi6Wpva3ddCZpmowQ1em2dIMxl8sNBv04jhkz4zjW+RZjplJxrdFYjqKjzc3BYNBoNC5evFirnuzv7y8uLvba3TAMbdsOw7BWq8VxnE6nOedLSwvFYpFSOj87e+XSpXa7tb+7l0i4fjA4Pgp1n1aJGKRAZboJJxa8XTmxE26t3kJCewPfspNSoSTUZAYhDKlEBIKUiOcsatKhqdEBz0oDZPI5OGJAkxOq0i8ejDHCKBAiQQkllFCKDxuJMZcR19xiQBANpQhRAFQoopSKJQKAQRARhURm2Aq51AyLGotECAB4XmAySoEioFJAkCAalNHQj4aDxEM/D5QqCsoistvs9gf+3OJCpMTG5uMwjrP5DGMGDikMgQne15+RAJGSICgETgj3A89mVF+mUkwqFfpBL4p61E5nbSDoBYEXBMyxlCADvz8IfEQQMmIeEAIGIyMAK2o1L4bEdMwo4iKWOpWnSAijACCiOAg0wZH+B3DGGIDyfZ8ApQpCHu7v71vz88V0OooiwtA2HUQS8ZiYpNvtSylt2zUNNxZSCMGY2W6352fnfH9wfHxICPZ7nWajdunieTeRiOM44bq+77uuvba21m62tp4+yWaztmHMzMxowLuu3ff7/cXFeanH5qWKAiElSFDtdncQ+IZpnz1/LpFI9bzBo0ePUqnU+sKZ6tbhYNBjmgQIFIA6PNiTCqempnZ399fPnjk+Pl5bXX/48OHCwoKUUshYKh5GPmPG/v5+JpPRsOzj42POuU7VBoOB53m9Xu+f//N//sufvLdkLezubvf7Pce1bJvZjqVAmibzPE9Hhr1ejxDGOU+lMlJGWkjQtO2FhYUoiphpaEbmmbnZdDJ1dHTU6/XKhWKhULBte+n8mVKpdHBwEHjezs5Ov9+bmZpOpRPJlJvPZpRStm2LOAy5KBbzhJBBv6fp83jX84OIS4yFpABomI5BFRkKJAohIhEJKU3TmbQ3HOG2fc8zDMO0rCHIUbudOB4r4E6+amyKMFHXGXoqGUMUSyBAEDSXklScc6mQS8GlUkjBAIJMAAgA27SH56cAABwIJZQyM+ZcEcaFxqYRykwuZRiGhDFgTGgpQUREGkkVDnykLIpjgso0TQTgYaBAUmYEQYiUmo49NT0tQCTTWRfEIBjosTVKiUGRKRlLKWPJoxBMxgxKTEKpRTlSQslI4UVKyaWUkYgCwc0oBIJ+FPhhQCSXSvW9PuccGBhEa8eAjFUUAgXkJJKcy2FSCGPAge7SCCEoDrkJdBkbUQ/djxJxEEoRKdWYN0mNaD8oEEpZMPCEEEoCUN2QUswwGSPBoI+oDMNIJtyB35eSMwaIYBjG5ubm3OzsjRs3Wq1Gr9dDxGKx2Gw2s6lUGIbFYpFRTCQSnU4HEU2Dca61uJg0QICSgvNY5LL5/f39ge+trKwsLi56ntdsNg8P9w0cj9gIBQpRK5+SSqVy5sza1tOnP/rRjx4/fpxMJra2tmq1mmUaBiWuYzuO0+v1gtAjhOQL2UqlQik1LdbpdXd3d3//93//5s2bf/Pe37766suEEMsyFxbPISohYs8f6MjTshylAh6LKI4BYkoNznkUBc1m0/f9ZrNpmmbSdZVSGuVTKBSWl5fn5ub0Teh2u41WKyYKpMym05zzRMKdLpdPahXP86LAV2I+m0unk66UVq1Wq9dr3W53ZnoaEahhIKGU0kgoRRk1TDQsociQmkGjkyklE9DQsQPUKZ+eKZ9M/+AU0P/5Y9IIJz0qIQxGAEl4xmzKQElESoimUBzOQehdQPvlIfGPlFwIHZnrkwz/NPLdzBjOfEqpWw+EAlFIheJKKc15QBCRGQpAIjFMI/a5pro0HduwzFjGFtoAGu0qpFRsjKlVUnHOlQCgjFGUSlGtdqPZhygBBQoJECVBaAAgoNDEmjEPCSEU0WQGMwhFApKLmMeCBxgM+7aoGIHhdMioUUFAciAaDQRSaSIPADKsjqHSA1YAONItkXHEKWNxLMCgiiACoYQCAaUw4jHnQg+CZjKZMAqoYyRsJ4r7yZSTzizZtj0YiK2trSgMv/71rxsG9X3f6w8454Zh9Pt9RIzjOOGm6vV6u90Ow9DKOUJwIUbcwcqIueCc7+zszC3Mu26iVqt5oee6NkC27w1EGFFKkTIFoKRSiiM1CWIQBPVmo1jMNxoNpPTe7dvdbjeZTF44t/Z0+8lg0A9Cv9frcc5jLmzbbrfbqVSKC5VMJl3XtizDde1bd27/j//4v//www8qlcrA6/X7XUIgl88iomHYmsswDKIgCKQEBIMSw06wMI78cCjNGUQRATBNs9VqPXr0qFarpVIpRiilNJ1OT01NPdne0swgX355Swl5/aWr8/PzP/zd3zmpVWzbDj2v3WkIIXgcJpOuaWQ8r0+ZCbpkLZQXRlKBQMIIlRI5KC64lFKNJC75hLo1Uc/MTJNZDJe6HOq9kd9skzqbnTS/oVFRJqXU8w0AgIoQoIQQPxa6+KdGMaN+iRiRf8oJqpvJY7zp40g3e/wqgKHyCKAUXOgWHIwmpzTdkUTlR0G31wvjiDEGFPTcj+bOAK6k5CyMhGEYJjN0uVLDMogCRjAmhChAkCbVVV/KlZIghIwVokIJFAgqQE5QUAKMMtOgtmEySkUUBrGQQgY8EEIqISUCl8NcdNiNFVLvHkqOlcqVkEIqLpDoyS39yQmgazsEWRRFvu8jReYYRCghOAMNmDaVUiYFzbloW9bZc2d2d7f10HQYeqDidCrtum42m3rnnXdMw6hUKo8fbyilVpdXXnvttc3NzXajkUgkKpVKMuHs7e3phTXwepxzHksglKAWBkEhVD5fVAqPj4/jOEplU/l8ljJstOoYEwMMgyAo1Kp0RApCjYWl+cPD4z/8b/7h559/ns8VT05OYsG//vWvnz23fuvLz5v1EyFEIpUqlRYHg0Gz1SEEgygYDLz1M6ulmdLP3//l9PR0q9X4kz/5nyuVyrnzZ1KphO8Pjo6OXNd98uTJ6uoqY4wSRqnmQFSIGEUxs4aex0m4tmkFQeAFgSPEK6+8cnh4WKvVTNNUplk5qZaLpXQ6ffnyZc/zlJCvvfJqIpEAlLdvffHZzY+vXbtWzOUMk+bzWQLo+d1Wu1av1wv5KcO0FbJuv+eHQa8fhLEwhRJccAlCgQaO6dE+JEoOSXcBETWSWFsL0xetFIjhikdEVCAmFDjGdqi+ijNbP6LtV3N3a4dJlAKBMuaSc8G5EAIpIYoQZADDnVI+ryc3aYSjuGZonFJwRERQhCAAUgJIlJKAcsjPj6BluVApikoSShRAEPmRiAyLUUo5j2CksSRkLLlgUUyRGIyaoBSPleCUAiOEcsGlQJAKJMRUCV2lFdgXXc4jRVQQhZxzhcilUErEMQcpBEFJUCmQXIlQhH4kkOs6gZQSUBqUoa53CSm0rLEiI1C3ktobAwhUcjh7hwSBAEkmk4QQ3/cpAjNZcliwIoNWn1JqWJJSQylkyBg1GWMbDx7uH+5ZNi2WcgpixkABD8Lep59+kU6nGaXb29vJZDKbzSql2u22lJIx1u/3bdv2PE8Py+ZyubAfyBEp+LCfKaWUkEtnKyfHCuXMzLRS6uHDh4ByemaqXekhIh/q/gmlRzYJC+KIWWx/fz+ZSu0d7Nqu9fTp05WVlf297W63FQQDzsXUVOnCxTOBH20+3TJMWq2dKCUT6SSltNtruwkbKXEcZ3p6+vr16z/96XutVqPX73734nd93x8MBjpYIIS4rkuJQSkDgEajoSsKtm2PQcOxEIyxqampZDK5srKiyVpiwR9vPtHBOedxPp8HlNXjyrlz54qlfDAYKBCDgWdQYlrMMIxk0nUc6/CgZpi25ST7/X7EhR8GElAoFccCCAUAwhgq1E1UNam1OKxfIpkcMR1GeaAFTwGfCzsnQ9Nx2eaUwWgNU83YggIkAckVEI4KUA19rJIKQCmqzQYB9GzueIBDW5kkCJSgQD1NKEEBKBkGoZZIYhQJISbVjG0xGYrpAUihkV5aaMO0DTdhIyLnUcJJGhaTIAgQAVKBkCiBKMYFgwjjiHPORRQzStLJpJNIRH6AACKKYxEQgVIRooBzCNGTKlaIseAAEhklqAyGQkgCSnIuCRcERcxFHIuQc8Y1q6KGnSmiEIGo4UATQQV0dCul0qTjCoiiw5heKaEm/kmc8yjiccx1OZ4xSixHSskjFcogiiKpeBzHUvHDw4MgHhQKWSlTrmvH3Ds5OfKDQavVtm17EEWI+PLLLxcKhY8//OiTTz5ZXV0tFAp7e3tra2sn1eO5uTltpchBCCm4EgoEf5bPHBwc2K6VL+YYY4eVw4OD/WTKzeQy6XQ6DMPBYBBEoZSSMUNTJtdqNcMy//LH/+v5cxcrtZNOv7d6ZrXZae7t7SDIYiHXbLfa7ValchzHvF4/iaK41+tkMrlOpwVAvv+7379///65c+dq+5V0JqnBPQDged7+/v6DB/ey2TwhRArNo8mIOUyNQh4TLReNmEgk3GTCcRzXdZ/u7MzNzCilarWabdvpdLpUKmlITRiGT548VkrNzk2bJut0W1tPHuXzuUIx51imSgshVLfbFnHUaDQALSGBWk4QRZRSjZhRoAMZQgihBJFQEEQqopTy4yHdPZKRsqAa5nhCKzMoRYeGqTEbz6FGxynlqdrp2FtybYFAQCuHSVRcKSJMw0A9ykS4ZgDSuFXDGColD7dZeKYGqcc8KdUAHjFCTMYGQYKMIBAikRCQCpUyhkBzIblUCITBqOihCEMv9Kq1k6yMhgxGFA2DAiqiCEFgng+MAVEqigSPhcXANNF2DMMyQUglIn2XlGICQCEjZCAlF6CU5Eh1JkgoNVCaSkoKKIUQAiXnPBIy5gKHeFGpZwjp0OUN4+whTzdSQKGUlEPeSDWBYhNKcAljympEDMPQ9307mbBMo5CfGgwG/X4/9KMwDgDAMDjnfGFhznaY6TDLogp4t6d6/U4YBr/zO7/TaDSajcZgMNjY2BBCDHr9tbW1MAz1jFmz2RRC+L6vUeaMMUqVoDLmIAVXEglhpgnEYKZltZqd/qBjOua582fa7davf/3rly69HMVcIWgaC4mgZckyubxpmgpxEPjpdJoQ8s43v9FsNs+eXUdU/X5/Z2+3Vmvs7++GYdxsNcIgSqUSs7PTlUrFNM2FpXnDMFKpVI3v1uvBxsYD0zSz2Wwi6Uop8/m8lGBZFgLxvCAIAh5L23YYG85Yj1vPhmEAVQCQTqeTyWSn0xn4XqfTUUotLi5qnHc2mzVNo9vtHh8eSSmYwRYXF5aWFwiBZqNWq9U0L2CpVMrn81tPD3SoJoSghgkAgJQrSRGlUhIUKgSlKCGUGADgcT6emyNIYFSCGyL4R434ob1JOZJrPd2dHz+iRlNy+lepNO0ZQUQKqAVbpATLoEAkJwACpVIwhqQqAUozbymAZwYvhSDaw1DNNKN5rrlBkVAALWUmhh6ZgKSMooKYx0IKJpGAoUCikn4YCSE63dbu7vYgKHe73TAMqElNO4F6mgEo8wahbaNhGACUi1gKafQjAoNcJgtaf4LaRAEzTAIoiFC8K+UQ8IogKEWlh8gZihilkLGIpRRxGMVBGIahZHJsTqg0EkKpZ6ovEwEAgFKKMqoQpB7i0gLVCnSlWIs6EUDO+WAwcFJJalFmuQAwisSY41j5fDaTz1SrR6m0I6UIoyibTS2vLM7OTaXTyY8+3P6Lv/iLOIoWFxcRlZ4toJQ2Gg2/308mk59//vn1a1f29/dnZ2dBC+5M7AgAQ8lyg2DMuVLKsiypZLvd9n1fQ1IIIaZpJxIJYrAgCJq9VqvTzuTypm1/7fr1drt7/+GDVCbdbrcJIQuLc8eVQ8/v61Q+8LyIc8YY2LC2fiaTL3zyyScJN/Xnf/7njuM8fMi+8/bbX3755e3bt/VgRzqTOjo6Yoxls/lUKgUKa7VGFDU5F9rkZCTHA+ae52ksnoi5ZVm9TieKoitXrvgDb2Njo16vr6+vb25uJhKJdDpVq9WymdTa2srR8QEPw9u3b+cyaSQqk0kxZsRh0Ov1NDxFjlwZ0qGn4rGEEdGRQKREKYrmaDoWxuNIBHXYqUbsvSAlRZ16IygllRqDuV+0Q3iBZkYppWmvh05M5zmgtf+U0hSGUkoQIIfg1UhGL4a1iKgZ93BEezN+L4sZlFClJBccERkhehyeaVuNQQkuKdHESFobiRCIoqjVahmOEfFYaRJ8AogUNT1Tww0MEluGySwaM8GDMFJCgFIxWkgJVWBIVKAgirmI49hESwhBpFQEqaJEUJSoADSZhSIgpezLIJRhSENuSzUs2VAlUAghFScAkkhiAiqCiDFILlAxAwAVZ0P2MaUMBQwJGfI3QsxDI2lxk2ZS6ZNKZffexqtOaqY4MwgqMfcUDEwmHCeRdC2I/Ppep1mrYugDU0HkH1LpR8FJvdoddNdnX3rj6kthGFqWEYahi4QQIv1gZXbG9/1z587UasuWbTgW7XRajDEnXdIZVxAEIY8VoOmYhmlGUdw8qYdBnMlkTMceDAbKdNNJM4o7UspYhhgrEUnfD6mJ03NTnX57Zn768y9vrq6snT27qlR878Htc+fO3bn/pDi1sLVzlEjl+GGVGvbmxj3LMouFstf1ZCSDjmcqtjq7kMlktra29maevvXWa/v7+7nc2ffff991Enu7+7lcDoEc7B9ms1nP6wPw2blpKaVtk8qBX3ByKJWbzkZR1Ot2FhYWhBB60LnRaDze2Dg4OFhcXDxzZv3BgwdvvHI5DEPGWDHr7O3tdVv1TqMxMzNTyOdN09zf3+9141TKHnjQ6/cBIGW7hmmFvkg66d1KM5LScGxghkA1gnAKANDVQAWYNp1hECT0fAGICYV6ZFQhxiBRKUBAg47y8FEtlKBCIqUMeDzkaEXUJU/tXB0eAAAqBDFkk0dESUFJ1N4PJRJFgRJGGaWUD6ujihCkhOKQz1tSZgqphgzoQJQCRGKYTMSEK0REYAoQQ83ARYArSUChRSg1CUWpqzLIlOkKEfcDLE5lGLUdaszM5I6PD00CBBVIJSVnRI3GiHAYZGvahZCGQBlVILlgCoExRACCfFgAVqjhsXq70o4ClJIqHivLgtIDY6fKWZPpNbwAkNdFAiklyOHQPQISRNu2O50ORTI/O2cyFgXhYDDodDqFrG0yI47j6lG11Wim01mTMs/zas2aAhGLuD1ouUnHck0pIQ7iVqsVRZFpMsZYtVq1LGN9fX3g9S3L0FQOepoJAHTXvuedSClBEcMwtHQHYyYzjMHAY4xFJBoMBoPA17z3ngdCeolEwrZtpVQYxFLKRCKRzuYfP358clJ7urU9VZ4ulUr5fPGoUm23u4jqz//8z4Mg+M53vhOG4f7+fiaTTiaTURjt7Ow4jgMAmUxGTw8JIZ4+fbqwsJDP5/WU4BdffJHL5TRoZqzFa5qmZkPsdDr5fF6HCdq0MpmM5tGYn5/d2trKZNJhGL7zzju6ZmOYtNvt6t5MPp+/ePGi4ziJRKLVaiWTyX6/L4QwDOPp06d62LLf79vUVEBj+az3oBSAUoKPWmugBAqFIBEIKGoZ45EaHA3R6wx/vDYmazDP6WHqFHJysP2Zgq86ta5e9Jn6h1Nn+MpXTSafX+V8n6vHjuJ8hkoqMYyNpRAoRRzHxDEJMaTig8EgnXE5571BP45DiRaj1LQppa52/MMLopQCpVIpL/ANQgVlRAEKSQAtUAyJRD3aqIbOXLfxcWiBQiohRDysC3KlFBAE8WwsGl+oL0/eDngB/SClxFH2rpeRrq0VCgXJhWEYjUbDpMyyLIoQR4E3CEzGhGEMvL5jWVEURTwSsTw+rFRODhvtFjNZzQ2+9rU3TdPM5/Nf+9rXKpWjo6Ojp0+fLi7OE0Lq9bplWb3uIJNN6aKo53mGYTCGoBSPoijiQnSFkgSZ49iI2Ov1okDLmqsgiAgF3w/jWDDGTNtKJtJKqVarxZhx69YtZljZbP7mzc/jOHZdd2tru1Y5OHfu3Jkz5/b29m7duqWLlvV6PZvNtnZblNJCIaflPnU7u91ub25uuq57/vz5hYWF//gf/3J1dS6RSEgpXdcFgH6/r0ag0CiKHMNtt9uGYQCA41oas55IOHEcnz9/fmFhXncLhRD9QbdYLEZhjEAs087nCvNzC1EUxRH3Bv7hwREhJJvJFYvFzSdbiUQik85aps3DiAv0hYpCTVmNKJXUdJ2jf7dUoMFDkgCdaK/jaEJicq2f+lnh6U18WMWbYIWZNIZnrx1jw0eHHKNtEEZzR89Fs6fW3lcaIQ6fpvSQudJRMwiltMqT7n4rRYYam0HgOa4lJQz8vmnOJNJJLoJkJikUF0hjUEJJxiiFMRUfpcQweBQHUWQZkdQkcFygAg7KoFQpJRVyQD1nBEgkICjgursghRAi5oJzTZyjG7Ry8iZqKxrDYU8ZIQDoWFxKqYQkCpDptUd7vd5UseS6brfbtQxjdXVVKVWr1aKgL3kgeZxKJW3DNCiTXKCCfC4fC14qlWbmZir16vsf/NKwnRs3blT2W81m03XdXq/3H//jf7As67vf+07MI9/3DYNKCXEcm6aJQDOZDCKGnFuWOaRwRilEHARBGHPHcdxEghDieQMQyjCZkhCGoSYy1/zblBiIpN/vNdqtXLZAiTE1M21Zzu3bd13XLZSKqVTqpWsXFhcX6/X6zZufuq5brZ5wzpeXV3K5XBjE2Ww2nU53u/3BYJDNZovFot9ra97RTqdzfHy8uDilDe/o6Gh6erpQKBiGsbOzU6lUtHShpnzWk5PJlJtMJrWHZ4xNTZU/+uijdrvd6bZ++MMf+r7vuu7du3dc152bmwOA3d1d27ZbrdbMzIxhGLlcjjFWKpX29vbCMKzX6/V6fX5mMYy4F8ZBEPKR96OAxDSlnqlVAqQkIBEVUaMOxATv49gNvmiHv8UjfeWD4/KMmrA/OS60vhCCfeXJxyY9Xpww6T9RV1Oe1YqUUgpUHId6uAIkB0IZAjJGAPt8YLKEYVIE7rhmKu10Or5tm1JFiMAl5xFnupA8pMgkqBhDISTnoeBIiSb5UkLGsaKCAIBBUKtAoxadGkEKtPgv5zyWQg9c6Q80NjwYhR9f6QnH38VI5FEpBYCEENM0HdOKwlD/yfO8MJl0XVdzqDE47rTaMQ/TSVdYdrfTC/2IEmLbtvC9KOS9bn/Q8/xB0O13T45OEM1MJnNycuK69je+8Y16vb63t6er9gBSCNFs1h3H8X2/kC8FQcBVqOnPhDDthEtKRAgRhHG1WiUUDKCWZQgQqDV1UCmJmXQ2mUz6vl9vNDwvYIxZphOG8fe//7u9QX/76W4+V+ScV45P/u7f/bvZrCWEcF338uXLmUzmZz/7hZRydnbWNM1sLm0apmmaUvJEwikWi4h46+Yn2Ww2juNWq/XZZ5+9/vrrt2/fbjabcRzrMFVKWa1WdcGTc86U4bpuvpBljOmAQjN8P3782DDY/fv3F5fmV1dXLctqt9sbGxuri3NBECAlQRTWarWFhYWt7adra2u26+QK+W63G/H44uVL9Xr90aNHzXZrtrzo+0FvEAQR17V9RKTUoIRqIxwiREGhkkCGGrfjiFRXvMlIqfeUHSqlJt3Zi1v2i5HUUAVswgKHdDQTbnDMNqaeT4VOLcWvPLQPRNSjvajGqqAIBNRIcEwAGNqxJSyWSdjZXJrzMGkbQcIZdJQEnk6nhIijKJKSM30j2LCUq6cYKackFpwqxgiVBLiUsYypIkopm1Ht2obTgRp5M2QclAJ0p0oB6h3omd7VJFsMvBCaT+5Do/uCDIfcaq7tpFMpPWmacFxKqe/7QohyudxvVYLA8we9hJ1GRCUFYySdzj68/8B2HS1wmS/m3nrrbSEEEMwVih9//LFhGMlMcmtnmxk0mUzuHx0uLi4iEiF4z/MTSPq+V56ZBkqIZemxa87RMaxEIgEAnh9IyYWScSwy2RTzWRBEQsSJhGMwWwjVbnd93+92+57nWaaTzBiUklqt4QfB9vaOUkAIZcwIw+j48GRpaWV5cbHX6dTr9QvnzqwsrymlqtUTxUWjXY3DkBGyvLhYKBQoqjiO9/f3NfeEBqweHx+7rouIvu+3Wi3DMIrFolJKY9+ogY6bUEpFUSSECENfCNFoNFZWlqenp7/7vW/rucqbN2/Ozs5OT5cvXry4sbFxcnKiuQX0Z9/e3kbEVCplGEaz2eScN5tNx3GWlpY6/X670xv4MScUkVFKpUICugg3ZtYTQxEuAAXPEDB6sZGRAsxXrn5EeM7njEK+cX6oU0o1noGaOIEcmxZqYMyzQaoxKHRc/Jw0wvEqVS+EwRpPrWsiSikgCkEiISajqKQAKcWwrA9EKiESFiYoLk2VOQ/KuUw2YaUdFovY8/pcxpHBAkqY4oJQpusxSkiiaW0IiQVnUhBGBUUpUSklQCpQko/kYxUhisCowzMckFdSIShyOsqf9OnjD3bK4+vjGRGGJj0ekeFls9lapRoEgSYRlFI2m01E7DUroe8pJYWMURLDMKjFXNd97bU3ylNTfW9wcHyQyqSn56b9MKhWq9Vq9cyZMysrK5XqcbfX0fNNb7/9dqVSKRbzQohMJhOGISEsCCLfD/PFnBBCy8ozgDhmnMtur1ssFaonNc4jx7GQ0jBsGAZNpVJhAJqs2rbdZCJNiQapqHK5/OEHH6WzmXq93un0CCGFYvHosPIP/uvvPXr06Cc/+dtWq5NIJBhjCkSpNLW9vZ1IJNrtdrvdTCRSmkV7MBg4jqMT1zAM19bWdnd3c7mclFKPIwVBMDc3l8/nNRY0lUo1TxrJZDIIAsdxEglHr7mTk+r58+e+uPXZ7u6ulPLatWtB4BkGTSRy29u79Xqz1WpxLl966aW9vYPV1fWDg4N8Pm8Y1uzs7ObmZqVysrHxeHp6ut/3+u2o2x/ECu1ExrAMU0rOhZRcCiLgWb1EgKSAEsAwhkp4MAr8yGiQYtIRjfdoTa82GR/pHV9nuXKC83d4ktEz5cgf6tB0bIE6QRq/1+T7jteknNDoPuUeNP+FVEJKhagIaOJ3hQSI0MMHEpFQVBQpEGVEPvp+OZlQyio6iUQpv1DORlGwtb1JDMYYAaRMfzxUoIQcf1SdCxlSMgCFQyQaaDj5KO7XDNNqJIY+Cut1QqrUBK/2ZAj6lY7+xahgeH/lSOxOSkTUG3O/39cI42az+fjxY4c1TWradooAiQOp6Xx83//yyztLy8tA8LByHIvIuu/EIvJ9f2554f6Dewpkr9cbDAaFQmFra+uzzz6bm5srFEqUGrOz80+fPiWENJvNTqdTni5JKTnnYegLpQzDCIKg0+kkEgnPG/R6/UwmAyg1gXQqlapWDoVQmUwul8tFcSyEQsR8qVgslI6PKp1O5/iktjg3ryPGfD7/8ccfr62tcc41ZuXLL+/85V/+5Y0bNxBxaqocBEG73fa8frfbjmNRrVYdx5mfn7dtW88EahP6kz/5E7036fTPNM10Or2wsFAul282f51MJoPAy+ezU1NThJAwDI6PjzYePWi1Wrlcbm5u5uzZ9ZdffunRo0c7OzuXL166cu1as9m0LKs0NfWXP/7xH/zBH+wfHi4sLdWbTTeZ9MPQcpx8sTgzN/dkayvoq74XUMOw3LTFjEjEhEshuVIUFMhngSFq1D+ZoJ2ftMYXw8JxxDR2euMXjgOr8XnGT9NObXyucXlGDhEycGr9jU8++f0rF+TwDSgOC01KKAVIFAClSGTMFQwLGZSCQRmjlFFqBT0a+ElABYQFfirpJm3XR1goFFOpVL5YzBXyzGQGSBXL+NmUECihJGGUSxGLoS6v1MpVBMUQ1QcKlVBC89cZ1NAyQ2PwK4x2qTAMNMxlTETNRjP1pzY/bWymYegXMsYUF7pfIiw7CALGWBiGhVxeKVWpVHTfGVmQKKQSdiL0QwnCMAzBYTAYJJNJy7LmFubPXbzQ6bUr9Vq313YTiUIpf9G40Gw3opCvrq0RQoIwNE0zm8sxw8jmcpzzMIrCiB8dHSmltra2lpaWNDtg7fg4lUppcSjd5u50uoyx7qAvuGKMHR0d5XI5x3Fu3bqlgammab/88st7hwfz8/Obm5ux4EnH3Ts8KGRzuXy+2+1+42tvSCkf3n+Qy+Webm51262pUrl6XHnzza/dvHmz1+3EYTQ3N5fP5hBR8rjf7dRqtWQyWSwWC4WCdo+zs7OtVuvkpAYAtVrt8PDw8uXL+/v7zWZzZWUpnU5vb29r4M7GxkPHcV5//fVur23bdrvdHAwG77333vT0tG2b8/Ozu7u7hUJBA1Dv3Lmzurp6eHjIGPvlL39548aNzc1N7Yf1uKNpmiHjEhQjJJlMGo7VHQyiwEfDAqJ1FIfsLABACCWEcB6ON2K9u+kKzTieHKc2oxXyjKl6aAUjLnN9Er2c1KigYE5KHbKhsLbQmB4lY8GFbvNRggQVQBzHmtVBozJ0GVCfajJMG+8IABq7+Ez0WykhJDimRZXk2olqaCqAAignk8Vc0avWssX0oFrvVY8VUYRCp9v27Vb3pH7i6hbFRGMAEWEiVdP3YrwJAUAshh+eTmwPoAC0HAs+M0KtPCh4NDknppTiL8g+njrGD46dp5Sy3++blElmBEHQ7/cNOmwnmrYDQPwo7HZ6/b5HwKCESQmpdFohHFaOJYhUNrO4NH9cYQ8ebex+tGua5szMjOM4A68XRVEunzl/7uJ7772nlAqCwLKsfD4fxzwMo9nZ2e2dxzs7O77vSwnnzp3jnLtustVpM8a63V61WjUMQ8/Laf/T6wa+P7h27YplOSe1WiqVOjo62NvbY4x1Op3eoI+ocunM9PR0Kp3O5XJPnz7NZDKOa62sLh0eHk5NTW1vb6fT2R//+C8RaTabLZUs3ZozTTOO47Nnz56cnOhKxvz8PCHkV7/6lW3bP/zhD//Df/gPnufdvn377bffbjabFy5caDQat27d+r3f+735+fmDg/16vaYj3jDyCSFRFLRaLULI9HT50qULT548effdd7/zre/HsVCK/+3f/uT69euLi8uffvop5zyKol/96sNr1661Wh0p5RtvvPXBBx8YhpHJJk3LMUwrlU4wK8EVUMsTinb6fVBICRUAOKSSHaoyTq6u8RobpyHjHG/8tMnlNz5OvWq86Ws6DAnPXJkcucSxb4SJ7sUYJ6BPKCfULOD5MHXiykeQN5QwbHaAklyqcayIksRc24vf7ymoxGGjwoSIQh4qEEjBdR2khBiMPVMjmVBoGQ4R6TMKPeSnUD1ru+unCS06P7oR4/muYYgPwyvWbzH+AOMRaT00Of54k592+OCERI4uvJqJJCIGQeB5XiaV1ucMY8GCUErZHXj9vmdS03UThmml02lmmhGPu91+q9+2XZsrmc1np2YLx8fHSolEIqs33Vqt9knrk2vXrsVxPD09PT09s7+/L6XMZrNCiNdff9M0Te0NkBqtVmt3d7fZas/NzcWCx7FApBqaYBjW9HTq+OhOqTRFKTVN1ut3hIwvX768f3TIOWcGDcMgCMJUyoiioNuTVtOYLk7/u3/37374wx8+fvy4VC406q1arUYIyWazr7zyCiHs6dOnOslMJBKBH1YqlV6vt7q6ure3FwTBW2+9VSqVPM978uTJzMzM1tbT1dUVfbWPHj06Pj5++eWX4zjiPC6Xy1EUOY5Tq1cNY35j40GhULh06ZLjWJ1OZ3t7u9lsGobhBf7Tp0/7/f73vve9hYWFf/kv/6WUcmVlJZ/Pc865FLbrlMvljz/+uFgu7e7uEoiEkhxUq9M0LD+MZBgM+kGExCTAFQIhRElUChVIPVMytsBTRjWZhk16ocnvL+7dk1atzW/MKayUUs8HlS+2LtTIbZ4ywnHYDM8fQxFCAKDaLCUgEqWpuBVolI1SHBGFBJC2aVAlY9+LQ+A8kpKjoRhjzW5PgoZGaxaA8dfEgYg6FEClKCLTGk6EEEZ1PKDrJVqM3jAMRqkeb3wmWCO0fvdzNRj8DVpzkzd3sg8LEzID2tQ9zxuSrykVx3HMVawAqWVajuW4yJhQGAtRrdX2Dvbbva6TsIUS2wc7zXatPFu0E66dcPOlYnmmXJouLa+uLiwtWa5zcHwElGzv7X1x+0tmmZWT+mdffBnG4t7dB81Ge3dnP5vJVyrVYrGUyWTffPNrrVYnDONMOpdJ5xgzlVJSQjKZvnjxolLi4sXztVp1MOjlcrm/fe9dpcSjRw/b7SYXUSLhZjJp0zQIwSgKH9y9C0K8/sqrv/e7P3jr9Tc2H2/MzcwsLSysLC3kM1mv1/X7A5Rq0O15vT4BVS6XlVJTU1O5XO6TTz7Z3d1dXFyklH7xxRfnzp2bmZm+fPlyu93OZDL9fr/f76+sLH/55Ze7u7vpdDoIgkazVq1Wq9XjqampXq/XaNQ453qoP4yCZCqRTqevXbu2sLCwvb398ccf37hx45/9s3/26NGjR48eaSkFwzB+9rOfTU1N6XBUobRt23EtxqhhUDdhJl3boIDAAQVBhagQFRAgFBV5NkQ76eLGv45Tu8mN+5TVvWiHX/mgUkqOSjIS1Fda4IsvP+WBX3xQ52uKDLWHQNdTRpaJRKECxQXnXPBIcO6Hnhf4/cGg3+/7vh9FkYwlCBnHMY9iHnEZxWycmD5ngUoZlFIkQ93tkUKbAhUCII4/7bArTYDoiUSlNwPts4etwmcfY3IG7Ld/VP1C/TydOTi2rZSKoogD6gY0pTSKIsclgNSyXWY4zPSCQRBFkR8G7V631+vZrrW4tlyem0qXM7GIFMqjo4NSqXT9+vU4jh8/fsyoeebMmWKxvLe3t7OzUy6X0+l05fik1+tNTU0dHx8nEknGjCAIT05q29vbYRC3Wi2pqFIYBjGjNueSEGbbbuCHnXb3pFZdXll+uPGAMnL9+rVKpcp5FIQegMzlMrlCNplMAwDn3LCMKA5+57vfHAwG/9P/9H/9gz/4g2r1pFwuf+tb39ra2vY878GDBxpAp3Owk5OT2dnZl199rdVqdbtdDVe4devW6upqKpX6+te/ns1mL168WK1WX3311W63++qrr968efPhw4eVSuXq1auIeHi0Pzc3p2n8X3755Vqt2u22wzBMphJKKc3e/2jjSbVa/Z3f+R0p5c2bN4+Pjzvt3o2XXgGA2knDYNY3v/nN7333d/7Fv/gXf/RHf8Q5/9M/+wvbti3LMkzDtE0kVEgZxGbPjxA4IAUAJIgKFCGEYORF8FyKNTx0G3OyoADDwd/nGlovms2p3sZw7WmdUKXUhGOctMCxzx1XMSayUJh0g6feV4JC1GzVz10YSoGEEiSCgE7DJFBUwKlCjexUSohYKo6xIv5wbSMiAmUSQZ2yfl0XIVQTglIClKBWMJVK4uiTK6VQKkEBFQBRRM+FaaiMeBbWa7nsyQ/82+/pZAygE1SlFOdcgzxQAUUCAHEcE0Icx2l26iFXsUTXdCzblQq5kkqK5ZWVmIe9Qa/dbZkJY2Fplhqs3W4tLy8jYqVy1Gi0jo6OTNMcDAZhGO7u7jqOW61WNx4+LpfLu7u7tVpteXn1zNpKv+/Ztru3d1AqToVhnM8XgyBwnWSz3YrjjpTSdt1UMl2v1yuVE6XE0dGB4zjb20ez8zMvzVz/27/92zfeeD2K4lqt1un2pZQHBweEkHOL55XCL7/8Us/pPnjwoFarr66uPn78+MmTrenp6Ww2u5Zd29jY8H3/zJk1zwsMw/jkk0/CMPz88887nc6rr766s7ODqIdg4M6dO7qo67ru/fv3HcfR25brusmUi0TNzc2VSiVK8enTTc0ymkwmDw4O9H04f/58IpH44P2by8vLvu9nMplvf/vbBwcHn3/+udYDTSaTV69e/fzzz3u93j/5J//kww8/DIJAKREEXhj6hBmG71FKvTAOQ58RVIRJkJqJHgklFJEimTC2SV832bQY/wkm5hjG1nLK3U3+ipqzdIRYBt0h1M8ZmZ18Hs42aXjjtx6Xdk6tRqWUBKEpEIdXq7mShk+SOhHTrwSpCKAwKTEoMSmlhEkqJVeSA0AUhwQoAhClmPbaI2zZkDVRCYlIpZIUUQkJSCghUilQQCYUP8c2o/eaYf9FKqUlowARUVI6uaPgC63CF7+f/tgjVSpBqMkMRlkcx/1+nzGWSqWqTT8II2/gu27SNkzJVcwlF4oxli/mcrJwUj/2Q88LvFyykCvm0m7m4cOHW1tbSql0OhuGYeX4SbvdPjw8brc7uVxOcHVwcCClXF8/q6PuZDK5ML9Ur9dNx261WoSwQd+jlBFCbdvu9zwpwbZdQlgcB5ls4sKFCw8ePEink2EY1mqbC4tzrVZramqqVqsxRhgzCYFCoXDjxg3f9xMk+uyzzwghn3/++fz8/DvvvPOf/tN/ImQIZC+Xy/fv3xdCvPTSS/1+PwzjT359M5PJcM47nc7s7KxSam5u7vHjx7rDWSgU3n777SdPnly6dMl13StXrlQPDxNJ5+DgwDCM5eXlWq3mOFY2m7VtEwBs287mMtlchnNuWZbneUEULi4v7ezs6Lgxl8tlclnGmCYpD6Lw4OhwfX395uefuclE5aRaLBUGfW/gexAJLiUhRAJathHFQqDej4lmTyAEKGPMNMcWOF5y6oXyzOQaGNvk5AuHFjWRWOrXak4mNS40nPZjz2/6XxWUjs37RSMHAAXjEsazqgwAcM4ZstGAMpdINOlTs983DGpajDFKUAFIPbyOgERJUASVGDYJxDODenbgyKehVCA1Rb3USSCllGl6rSEGcOS+pQINHRzJzY1F5ya3txfd4Fc6xsm/djqdfr8fhmEURb1er9FoaAEJN5VExN7Aq9fr1WrtpF47qdfqzcYHH334+Ze3Or32zPzcysqSbdtRFCAqKWUqlZqdnV1aWtLdtoODg2QyKaXM5/MahmLbNgDxfZ8xdv/ewzCIEbFUKnU6HUSqaQVbrZZpmrMz8/l8XodSjJnJZLpSqVQqFe1ker2Onk6o108AwLLM+YW5a9eura6uFot5w6CVylGn0/n617++sbHxwx/+EBGbzWYymVxdXb148eLu7m6tXi0Wi6lUamdn51e/+tXu7vb58+ebzebU1NTa2prulywuLq6srGxsbBwcHPi+3+v1Dg8PLcuKoqjZbO7t7ywuLtq2/fTpUyllr9dJp9NvvfWWlFKL/i4uLmoqxGazGUXRtWvXHj16hIjb29ubm5s//OEPdXr59OnTSqXy/vvva2EcXRZ+4403MpkMM5kQPAxDPxj4vs85Z0z/xxWgUjDKl7T+5BjeGMdRFIVhGARBEATy+UM935Z48RhXccZeC34zGelXL6pRxngq/nrxh8lfXzRLbdrDduXzua5SAkymGOGAoRBBzP0w6Id+3/O4FLEUkeCxkMymBudccK6egV+RmEaslGb4QBnHSGzLkBLCMFaxP747hFJCEVDEikuUwEBRNXkHAcAQz+BpMOEPx7cbJrAIiCgkR4YoFZexjuwpoFRRIulQmwZy4McqwVxUYMcsibbXEUEgTNOMlWh7bde1E4lEPOhGKjYs48mTJ5mT1PnzZw3DCCM/DMOZ2ZVmo7u+vl6pVMIgRqBnzpy5devW1NSURqjk8knAeGY2XywWc7lc9fiwPJ358MMP9/cOX3rlZUpZo94ybbNcLinEhw8fUmok0qlOe+A6yZOTWtBH1yycnFQ6DS9fyLz26svbO1t37ny5Z9FsNjtTSGdT5pnF6ffee69VOajVaiDS5XIxm17kkflf/70/+vjjD4rFYr1e29i4T4h68OAeALEt9969hzPTc/VaY25u4dqVyx988MH8N75RP6lm06mdp1tn19cSjl0oFOon1YO93Rs3brRarU6r2Ww233n769lsNplMzs/OJBNuu9n68IP333777f29vVKpaDCadBN/9Vf/ayaTefTwQaFQuHDx2o2Xrjx48ODv/uiHtVrt//F//79ZltXvtdbX1w8Odvr9fiaT+fLW/ptvvnn50rlf/OIX8aA/l0/P5jI3f30LgU3PLpbKM082d6amptt9r+cHiGgaVHAZiyAOQwPpcysflCKodIRFkIMiqCdkiZJSSGmBBCWEiJWUusmhzdh1Xc0nQ4Y+UIESinNAXajQ4ocjrkAc6q2hUkNaaQCt/kkQlZRIiA7pYcipARp6CQDyuYKlIhyJ5iZVFCUSJTVxBDVYpFSoFJiOZFY0MoG5uK2UORQ+UlRKoutFQUyHjlQR9uIeA8/3EqSUOgETQoRhyMgQdjR2gJNbxW9ycS9+nzTC8QamtPa30uwWMMpch7NWpmnapouIFInv+/v7/Xa7baOpQZK+P8jlcoi00WgwgxSLRcZYOp1OJNxeb2CarN/v375z6wc/nL5z545pmk+fPg2C4M0339RYMMMwFhcX7969q0d46/U6Iu7s7IT+IAiiQqEwP7dYrdfq9cbc7EKxXP71p5+5yWQcx5zLM7PnKDEGg8HOzm4ul6vVahpLoMmplpaWSqVCPp+7ffv2wcHBzZs3y+WpK1eubW5uIuL6mTOVylEun3306FGxlB8MButnlj/44INer5tMJnO5/Orq+p3b9wBgd2+bUfPu3buGYVy9enV7e/vs2bMbGxv9fj+dTj969CibzepPsby8XKlUzp8/3+l0CoXCxsaGUuqb3/zmn/3Z/1IulxljzWbz+vXr1Wrl7t27lNJMJnflyqVCoRDH8dbWlg527t27Z1nWt771rVqt1u/3Hcf53d/93VarpVPoSqXy13/915zz6WLh448/Nk17dXXVYPaDjU0u4Oq1yzv7x6DLLZQKRKEkgCTEGOsNvuh/vvIYgiKV0msDR7yDp+Bm4zPIySI8PkOujQc1EBEmIjIppSbBUBNwGTlRySHquVKqhptP4nX0NZjGs/M/l7gqPUfCQLNFSQVKKoWUapsngMAmDeZUsD52WZMoWzGUCnzWToHny1zwvENXLzyifzjVkx1f9yj6/4pggDGmiw2SiyAIBr1Ou90uZEoLCwsS214Y5Sjxo7DTH8zPz9Zq1bnFhWvXrlqW8Ytf/mxjY2N+fr5QmvrTP/3TTqejad5v377d6/WOj48Hg0G3202lUmEYFgoFvQqnpqZardbc3EKlUuGcX7l8bWFhAQDjOK5Wq91ud2pmxrbcdrsbBAGPvTiOGaNra2vVanVubu74+HB1dfXOnTvFUl6IeGdnWzcAq9WTpaVl23ajKBr0/bt3bwdBUCjmX331VR1nci6FEOfPn9/f32+1WicnJ2EYplLJdruDiIuLi0dHR1NTU7OzszpPS6fTH3zwgeu6UkqNQygUCr1eL5vNRlF0++6dUqn0+PHjge8hZe1uR0r55ptvbm9vOY6TzmYQ0XVdpKxyUj04OLhw/qpuAuVyOb271et1TUizv79///5913VfffVVPZUPABcuXEilUo1G68njbduKzp5bz+aKBwcHQgBoqRWKCMi5Ekqh4AoovGA8z+zheYtSE90LeH4A6tSSm1hvEysKQBdG1W/OgHR/W02IW0yWiPRBX3ihmoiTx4985fUMWd50tUgB6tEjbZCjy2OT55WjyS4cVWxhDB4f9QYHcXzKnE6Z/viejj3eixc3ebyQLo7pZ8a3FWDEpazTibECrmEYvX4/jKJkKhMLPvCDMPQJpaZt+WF0+869k1rt+kvXCsVp09rlQp05e6ZR76VSmZ/85GdKqXw+L4S6ceOVTqfz/vvvV6s13w8tK6CU9vve1tb2yUn96uXLiUTi1he3Dw8P3/7GNwihW5vbJqHlcrlQKBweHBsGPTk5AQDfC7UQfCqVIgQWFxd3d3c3Nzc3t2SxmM/n89///vefPNlUCsIw3tzcLORLCTcVeCSby8zOTi8uLr7/q59HUSBk/Morr6yvr//4xz8OguDBgwe5bEEIUSwWNAHy7OzsnTt3vvvd7yYSienpaULIX/3VX/X7ng7q6vX65uamnsbqdrsEmWMnms3mzZs3v/3tb29sbHQ6rSiKpqZmFhfnnz59Wq/Xa7WaZVmpVObq1WK3MwCAZrN58eJFpZTOD7WAx+HhYT6fTyaTzWaz0WjMzMwUi8VardZotKSAbDarUbupNC8W8/VmN5ZCciEAFKEAmmRTmuw5rOKpZTPOdMZP0FTOkwtJP2EsAwzPV93hq4zhK+1k0gGMf3j2oEabvGDpGu817pCPsQdj2t9RZ214fjb6IoAKlVA4HPDi2o6Ifs5wExpbnd4ShBBDwPgEu5GcoCHAiSLyqf3gOU/4G27KZA49aYSaoQilAgCCY8eI+gPjCNNgmqZB0bKs0JPNZtt1XdOwm80moZBKJaqV2ptvfC2bS1cqR416k3NeqzWq1dr8/GIqlXrppZeEEAcHB+vr6x9//LHmz/3BD35Qq9VgNFWsFSBefvnlzz777OqV6+vr6/1+/6OPPmo22pTSkpsoFoue5+3sPj2zfk4Auq7baGwLrvr9/vz8/MbGg5dffun/9T//29dee6Xba7/zzts///nPDcOcm5uLY+G6rmMnXDcZBEFD9DmPZmZm7t2/c/Xq1Tt3vkylUoVC7uDgYGpqilJ6eHgMANVKLZ8vFosFXSy5d+/erVu3zp07Rynd29u7cuWKHmLK5/PdbvfBgwfLy8vVarVSqWTTmVar9d3vfL9YLO7vHbbb7W9969ubm48//fRTjYY/f/68RsN1u93Dw0PTcPSM0t7enpaIOTk5oZRWq9U4jsvlsmVZ6XRaA3empqbu37svpVxdWc/ni/Vao/pkM5VKlaZmB37ox5HkoQCiKANAJTiXihFj0ga+cp1M/jze/cdQfr0etBGOPRKMYyiYXJajX/Grl+I4xD3lCSbOdvqQExeMiFrQdtJXP+9RlEkoI9QgI/MhIKUCpCPnotf5RAg6WVNxHEerz47xrPrQd2TS6sbH5B2c9IST8IgXb+5kADzeDsY3YgzNIXq613G0gK5pmvofk85m+t6g1qh3ev0gCgk1FJBqrSaUsp3E9s7ehx/9mhrmN7/13StXX1KAX//612u12q1bt/7sz/7M9/1Lly4RQjKZzPHx8e3bt3d3d3V0qgd55+bmFheWEXF9ff3ixUue55mmqQX9giDo9XqpVGpubs4w6GAwUEoBylar1e22S6XSyclJKpWyLOvq1av5fLHf77/77rudTi8MQ9dJvv32O9lsFgA5jwBgd3f7s88+syzrtddec103CAK9RyDi/Py8aZqFYo4QyOUyL7/8cqfTeeWVV05OTnZ3dzOZjBaEymaza2trZ86c0ZIy2k6azeba2roQsl5vcM63t7fzuWKz2fzlL3+1tnbGMIxGo7W3d3B0dNTtdmemZ+dm5xcWFhBxdnZ2b2/v+Pi4VCodHw+zOwB49OiRhgecO3euVqvt7u7Ozs6+8fpbKysrJycnnj+4fv3q6ury1uYTQiUjoNkBFQiQHAAYQfVVx3jRv7hOxj5AE3xMgkX/i44X7fxFy3nRGvGFr7FmBk7AueI4PmXh44/Dhl/IFBpAGICBaCA4Bht/Md2PmrQrbRLjeeexkxy+8cTN0vHhqTsIL2xyk1c2dqGT/nPcGH1WLAVERErJsBdCIIoiOaLGQAKEEFRCKRVxbrtur9fzPM+yDWoayOjUzNztu3cLpVJ5ZvbguPL+Bx8xxpLJJDWsmXLfdd1yuZzNZm/evGnbtu/7nudls1kNwjRNM5PJCCFM02y326Zp3b17T0q5sLBAkAkVN5vt3d3dleU1LsXq6ioXUa/Xq9Vqi4uLcRzXq82Dg4OZmanNrce6BWKY9MmTJ9/73u98+OGH9Xq9WqlFobx69drq6hqP1Te/8e2NjQ0/GDx+/PiXv/zlxYvnw8jP5Qqu687Pz3/00UeImMsVNIupkPHZMxfff/993Vd4+PDh3/k7f8dxnI8++ujoqFoqlXRLXZdegiBwXffWl3fOnF1rNBrbu7vNdjvi/O79e7NzCzdeflkIsbTsD7xeIplutNqLyytCwe72juM4rVarUCgAQLPZTCQS+Xw+iiLDMObm5nZ2drRKlGEYlmWls/njkyoI6fuDXK6QTCVs28zlU34QIBGubVEpg1hyyRllhmEE8VdLfL74oH5EN4S0M8DRJBSMREVfLOw9t/YAlJI4JM6dOO0Ly1WdjkVPH2R8VvLc/PEp//niIyPrHY24j/i/2bAYiwAw7OZNOmV9oiiKoijSRF3jCtWkacnRyMmk7zp1nDLLU99PeT/9LuPLOAVP1VA1fUlKKS25nkwm2+22TlSklJpVxTTt69evFwqlarVaO6kzaliWk83mE4lUpXLyk5/8NIpiRPKDH/xwdnYum81dvnzl+LiSSCTn5ubjmHue3+32Go0mpaxeb3S7XcZYv+8BkLNnzyYSCcMwfvSjH+Xzec/zhBCPHj3y/QGAvHz5crlcnpoqLS0t1ev1lZWVZrN59uzZTrs3GAz29/c9z9vbPbAsp9vtPXm8NT+3KCUsLs2Hkf/aa6/atlUqlW7fvp3LFtbX1zmXlA7p1bLZdLNZZ4w0m/VHjx5p73ThwgXHccrlsl6pc3PTFy5cuHLlyhtvvPH666+fnJzoIZ1Go1GvNU+q9adbO/Nzi8VikVJ27tz5RqO1tbVtGIZtudev3Ui4qXQqe3h4eHJy8s4775imefbsWT1xn0ql9Kz95uZmuVxeXl7Wqvdnz54lhOzs7Ekp55cW19fX0+lkHIQxj2zb9LwuKuXYLGFbjCBIwRBcyz71r//KZTO5oPUY93j/1UBlc3SM/dL4tF/Zb/zKWEwfQ8D3ZIo4+pmoZ184+hpbhLbb8fqc9OfPfQSJ4y+QGsk++SVRSWaapu/7ajTnPx6cxVH2NeYg1NOAMLIQGHk2bTnPgDzPQ++EeDa4NLYuHE1djKec4NmMswAYQsBjKaSUQBkwMjMz47puIuHGcez1B0EQMAKU0kwms7+/77jWxUvn9e44GPT+5m/+5uWXX3769KlSSvfNs7lMEASlUulw58nW1lapVELEw8ND/T8GgA8++KBYLJbL5b29PQ0Z09JL8zPz3W5fi6483txcXz9bKpW2trZSyUy5XE6nU7du3fpv/tv/9vbt241GLY5DnSvq0cdz587NzMycO3fu019/XDk+mZmesyynWj15tLFhGu7Pf/7LVCrzs5/99Fvf+ub9+/f10Mo//sf/eGNj4xc/f//MmTONRmN9fX15eTmKwlQqyRgtFPL9nqeFEwuFwvT09L/+1/+6VCqFYfjtb3/76tWrpmnu7OxwzqenpweDAec8m82bpu374blz5xBVoVBAoIVCARFN06pWK2trZ/7Nv/njH/3o93d3d/O54ltvvKlXtsb3HR0d2bady+WazWY+n1dK5XK5Xq8npczn87pO+9lnn+3s7OXz2Watvrq6+uTJo0KhBEpkc8lYkUazA0oggSgMCepxgGfH2CrIV024A4DeWOM4HnfFJt3AZGFmHEaN0xwBI5AnwTgWw/MrpZ5TCFZKKQGSjOqd+i10FqfhKEopzrmIYyEEY0yHh8P0bCRPgqPGnhztA8MPJUH7OTkxi6yxCmN7GcJfTkENcKI6qibKxFJK9lUjHpPbzH/mPqeeP8aP654kQ/0uEoa1Ncjn845lE/Ksk6GUjON44IUntcra2oplGZVKZWpqampqtdVqbWw80DrP8/PzlUrl448+sSzr1VdfzWbylNJGvTWw/bnZBV3Hf3B/QyNUHMdZXzu7uLh47949wzDeevPtJ48egSJrq2ds237waKNcLqdSqV/+6v1ioXzn3t2rV6/atrWzu/3Sjevdbvfk5CRUMaX4ta99Lebh3/zNu3fv3p2ZmWk22oyxIIja7a5SYNvu/Pxit9u1TDtbSObzWc/r/+Ef/uHNmze1AMbly5c554lE4o033vj0009937vx8vXt7e3dve1SYVGb0MnJybVr13K53K1bt2CUY+uuhud5W1tbOsw+c/bq5tOdfLEc8nh9fZ0QEu7u2G7y3XffNQx648YNapjf+u53ugNPSvnw8ZP1tZUHDx7k8/lsNqttT0fpxWKx3W7rjSmVSsVxPDMzg4g7Wwf5fP7b3/52pXKUz6YZY3MzU/VmM5dPu7Y5CCPDoGnimpbwgphSRuG5mGu8GH7LOhkvPzI6xvHaGPY5PmccBuPtHhFBzwSLUywyvy2rVEppV0MAKRK94GE4y6DEb5hAOGU+zz4doUgZUkaI0iT/qKQatk+GA5Zs8l5IKfWIkM70JvFockSTTC3rK2/ZOFA+ZY2TofbIeJ79dbKyNLxmQhhjhpYnlYIx5tqOY5saKsBRaX4RwzB4FARBADJKJSx/0NvfHXDOk+7S/Ow0gZf29/efPn363t/8TalUKpVKxXy+VCqKOOr3+6urq+12W5dVNA5zfn7+5s2b58+fz+Vyh4eHug0gpXz48GEwCHQvpFQqzfa6iUSi3++7rru4uLiytlqr1XK53Pz8fL1en5qaqp58rGKyuDjf7/ePjg/OnDkDAA8fPkwmk41Go9/3qtWTTDoHALOzs4LLc+cu9P2D48rht7/zzQ8++GBra2t6ejqfz6+urk5PT3t+/8MPfzU9PRVG/k9/+tMw9PU0vRBicXFRx8nz8/OffvppoVDIZDKWZWkumTiOe70eYyyKoq2tp3qJbj/dXVs9I6Sam1vY3Hw6MzMTxzEi1fLAukV54fzFo6Oj/f39tbW1hw8fdjodwzAuXbokhDg6OiqXy8lkcmlpSatWcc6DIFg/e6byQfWLL744ONjLpBILC3OWZXARmaYlZRz6nuAhY44BjEDEowCo+1sM4DdZxbgwcSp70vHXZPc8iEJ8VnQApUBKKZQEeAYsUc/X9odvgUBG2kPjtUq1nUulkAgcmc5EW04+G8Ianhafb9dJQKlQAVGgEToEQJCh5x9eBhundkOfO3K4zyEMJmb8XizIjt8PXnCD6oUcVz0fb0we41CWDJG4ilKqyaRTSdfzPIpEh6AmM6SUmn/FMoyz6+uVSqXTap07d67X6fz8pz/N5/MEYGVpKZVImKYphOh3u65t8yiuVCpLS0tBEBweHvZ6vXa7nUwmE4mEZsUFgLm5Oa2sVK/Xu91u0knrUqdp2jMzc73eYGtr69rVl1rtxrVz1wmB3d3dpaWFv/7rv06lEufOnSHSiONYVw7X19dM06zX60EQJBIpzwsdOxEEkWFYJ9Xa3t7BwsJSp3/cbLa++c1vXrx4MZPOPX78+Oiocu/eg1/84hf1ev2b33pHiOjjTz58++23Egnn888/55x/8cUXCwsLpVLp4cOHUkqtRm7bdrfbdV1XZ0qFQqHZbBJC9vb2Ll26tLu7pwmCK5XKpUuXfn3zk9XVVcdxoigol6ebzSYAefDgASHk0427c3NzQRAopXZ3dy9fvryxsaGVNmq12oMHD2ZnZx8+fPj9738/juPl5WXHzhYKDzudzo0bN0BGs7Mz9+/f9/v9QtkRUiqQBCSAVFzwKJZSCniW9r8Yjr544ETlHF844Pn4EwA0nGO8BE/5i7EbHL+vtr1T61MpUEJqL6qE1COykxY4sTuM3+u0Yesj4AqFJuAf5pmAyBUwJMPWIoFnQbZ+5TDQff5Ek8Y2tsYXTevU41/5tFPH+MHxD5pURtdgdBZqmqZt247jaCIQz/P6/b4u0Pm+X57KWzaN4oAyTKZcypAZZHZuOoz8bq+dL2QvXb6wtr6iGV++9rWv5fNFIVSj0QrD2LKcIIhqtUa/77322hvpdDaORbFYTqeznMswjKenZ5PJZCqV0ngUSmkYhkqpZDJpGMbNmzeXl5ejKKpWq/Pz8w8ePBhDxtyEvbKy8vjx4yiKLl26JIRqNBq+75fLZdM0C/ni4eFxFPFWq+N53ve//733338/l8sVi8WpqSlE/Pf//t8rhf/oH/2jdrudy+VWV1d/8YufdXvtVrvh+z4hRLfjpZSa996yLMbYzs4OY0ynT4VCodVqcc6npmc9P2y0mmfOnbUcJ4iiIAovXb5anppZWV3N5Yul8jRSEvH40ZPNh48eLyws5HK5vb29jY0NAIii6NGjR9vb267rnjt37urVq6+++uq5c+cIIb/+9a9/8pOfvPvuu4OBXyqV5ufn0+l0r9fjIpqaLuTz2WIxXy7k8/lswnUoAYpK9zn+i45TIejE6n+OPWycECYSCcuyJlf1ZEB3alkO/Qo++1VNNOqEECLmIuZxHCsu9JA6PB+OTlZ9XjQWpVSsVCRlJGWsMAaIAWIFHNCPIz/mARd+HA8TQv1dC2jhaD5t8spwVFA59fnHzzm1sb14/KY/jT/P6ANoVCtoPiXHcXR5Wn/aMAzDMGSEOo6TSqUcxzEYebr1hKBaXJiJAt80zXNn1l3b+vt/77+6efPm4eHxg3v3TdPMpJMn1eOPPvwV57zf7wNAuVyenp7W1q5r+rlc7vj4+NNPPzVNc2VlJZVKdbtd27Bbrdbq6qqUst3rJpPJGzduVE9OVldX792/PxgMrl+//vDhw0KhQCn95JNPrl58SYMwdaw7NTUlpcxkMnt7e3HMwzDsdvsGs5XCs2fPz88tommvri1fODz/2We/tm334sWLnEs9S6Vjvw8/fH/gddbW1iqVytRUOQzDa9eu3b17lzF25syZ27dvdzqdpaWlsevzfV9XTXSTybCsZrM5VZ7xvXBzc3NhYaFeb6ytrepNwfcHu7u7nudVqkda62J/f7/b7RaLRdd1L1y4sLOzQyl1XVeTcGvU6NzcnBBienradV1GUlPTpUTCeXD/Do+8J5sbQsSU0pn5OUBDIUFq9vyo2/MAQE/eTPqx3742xstj8vt4Bb7YV6OUGsYzODQhiECklEoNSRO1J1QTWdJz16AAAKieldeoaRkLQDJUiFXk+e6alBJHKDmNByCEjONSbTgcUCAROOTdp1rpGRA1EgglIn22M403DP0e+qInyQj0E+REr+KU/ZyyzNGHe26E99RrJ1+uf9VhMENiWVYi4TqOg4hRFPX7fQIoRzVVy7JSCYcxdrC9oThfWFiYmZkdDAaDQa/fbfd6vaODg3q9XigU2s16IpO6du3Kndv32o1md9DX6RMhpN/v60omIaTZbPZ6vWazmUqlCCG9Xk/nfsCRxyKdylSqx0opz/NKpdJxpXJ8fJxKpZ48eTI1NdVut4WIL148/2d/9mfn1i5+7Wtfu/Xl5wCwtLSUyWQ++OCDMAxnZ+fiOD6pNqWUBwcH+Xz5ymWn3+9fubH8F3/xFz/43d978mQznc5KAfv7+8vLy4lE4l/9q3917vyZP/zDP3zvJ3/9y1/+fHllYXd3N2HzYrE4Oztbq9XOnDkTBEEikXBdV0fUUkrHcXQSqzPVdqejlEqn03t7e6Zprq+vSykbjeadO3cMw8hkMvfu3UPEoWnxMJVK5XI5SulgMLh//363233jjTeklBqFI6Ws1+uZTGZ7e1srGTbr/rXrVwyDVo4Pzp9dTafTntdNJpODbk8h9cNYSDAINShhBAlD+I3m9tWHnJizgVEFcrxyyAjUNV70WqJchy3EYKCQc87HpdEJOxxb7+kFqxQqoBS1aJLGUOKzOtBzNR6p5IuLf/KcQgGXigxh61Iq1KB0ZtDhmD6oZznh+NBp4fgqJ9uAOCJO/crj1O7yWxzjZCBx6i4MadIp0/5ZKRUEgS+57/uu7WiSC4pkvE3oFdnpdJ4+3dIlmHQ6c/bs2XfffXdz87FlXZmZmbly5cqFCxe2t7cNk85a8zMzM61WS8d1k9ZYKBSy2ezc3JxSynXdw8NDSqnNHC2yeXh4OLsw/+DBg8FgkEqn/WCQTCYXFhY++eSTXLGQyaRN0zxz5sze3p6ueUxPTx8c7CPi9PR0s9nc3d1LJtKtVqtQKJ5UG8lk0jTNRqMRx5lcLieEWF1dff/9Dy5euCyl3Nvb63a78/OzhJA//dM/PXtu9dvf+ea/+3f//vz55UEX9/b2Xn/99YODg263a5rmG2+8kU6nB4MBpbTX62mfHARBPp8/PDx0nIwQsW4wZLPZyvHJzOyUUuratWtCCN/3AeD4+FgjgZLJZKO6c/HixV6vl0gkbt26NT8/H8fxu+++WygUVlZWjo+Pdb6wurraarVeffXVW59vrK+vP3nyaHV11XWdTCbTatVeffXV+/fvS2BRLNFwKXUsy7JtGykTg/+ynPDUHg0TIFIcUdpOeDmIo1Abp27fgcLxEMZvWrdf8Y4KCCFKyJEVAn3GUPwcEdszp/obCq4xl5RLJFLz0BAplFJCxGGsADRtKZA46pqGUDII/R4jYJsmSpQxyBhRsnymOF2aTdgJRggBCDxvMpaQE5AimLDYF2ue8LzrG0cU8ELB1zRchqYShAi0wHTRMAQQXywWpmgYBe1O2rbnp8vFbFozRKLJ0GSl2ekLVy+jyRLZtBcHH938JAbxO7//g53Dnb3jvYPqwa8++dXm7maz1/zy9t16o7W5tX3/wUat3tx49EQBqVRr2Vzhzt37SNj9BxupdDbmkjKzUCwvr68EPPzi7peLqytuIlkslW/duUOZ1en4Cu39w3o2PwNg+z5dXr7suuVCPt/tdF575VUd7208eSxAOalkfmYqUEKY9KTbVo4ZMezwAJP28WHv0cO9Rxs7J9Xm4uKi7bBLl9cTSWY7mM44c3MzjFkEEj977/Nz6y/Vq6o4taSII9HeP6pVa+3Np7tcqtnZWd8fGCZaJpyc7B0ebnW7FX9QTyVBmBgz9Hg0CHyCiinJ+x4LedzpfvnRhwlGTg73Svn07Gzx7LmVRufkf/jf/I9WIjkIg67fNR1ycPyUWfKP/ru//0f/3d/vdRsH+7ulQjlhpB7d3dq8f9A4GlRre//n/8v/6enWxtWrl3/x/ocDjwtpv/ezjxsd7seGmSpZifxAQDsITwa9p0dHJkhDCUMJJjmTnCrBQDKQqLj+UsClivV3qeIYZAwyUkJ/xSAFAUEADMpR6QcFAUmRowol1/QWAlQkeBiGcRwjom1aJjMMpFQhSoWxIFwSLqlQTFGmdMZKlEKtdxQjDoTwQAWMhAYNLDYwSI9ClyiqJJGCKmkgmAQZpQSRIEqpxxuBS+QShSISqEI2sJNNNE4E1pXRRKuh7CY4HZquS7cu3RpP1HiCTSa1zw9bgW3b2WzWsiylRH8AnHPdwDiV48oJYYBTlga/oSOjXmhdwAuec2zP4z9ZlpVOF+YXF6QU+/v7YRzl89nLly/rJDYMw62tLc0qrcsht27d+qf/9J/W6/Uf//jH09PTL7/8MiKeWSNHR0e1Wm1qaspxnGKxWK1Ws9msUurVV1/d29t75ZVXfvKTn5w7d+7hw4cvv/zyL37+829+85uXstl33303lUpduHDx0qVLd+/dnp6a7XRae3t7qVSmenKSyWS+/PKLbDbtMJrL5bRchC5dLi8vd3s9ANREt1IBoEJE0zSTyaROIOM43tzcPHtuHQDOnDmzu7t7//79a9eucc7PnDnz8MFGoVCoVCrZbE5H5lLK69ev1+v1RqPR6bRmpsprayumxUAKyzYBIIqCQa8vZNw6juM4FlLpd9cgeCFEFITvvPNOPp///ne/92/+5H/5P/7v//nV61cR8Ve/+nBvb+fSpQuZbGp5YZEy3N3dvn/33vT0bCGff+utt9KpfLPWXFlZ2ds7uHXr1oXLF37v937v6GD/6OjozTffBICbN28uLC0KIYIgEApiSToDPwzDdDKVzxU73fA/0yP99mPSHY0fmdzNdXN/zP8yLmoMR1Wfr+hMvvwrUqqJdXsqdpsM6L7yhZHgiEjU6drSJLkhG+PRxkaoa6kanqNpiXU55CsvaNzTP5Vwj211nAScMjn5VUi3UzasxvP7hnF0dDQzXTZNc29vjzE6MzNju04chz9576fr6+ual/7C+YtBENy9cy+fz6dTmVKx/HRre3t7+7vf+d7Ozs4Hv/pwfX393t3Hq6ur3/jGN3Qb8OLFi6lUan19/csvv+z1eouLi9Vq9R/8g3/w2WefXb9+/fDwMJtNP368kc5mbMfMZrP37t31wyCdzuwf7Nqum8lkGGPZXNr3gi+//NK27aRl7u3tZbPpo6OjarWyuLK8u7u7tLzc6/UopQQUNUwA1E1ZAOh2u5ZlWZbl+/7+/r6UPAwDRNTRMiJms9l2uz09Pb2/v6+USiYTe3u7u7u7b775+v1790ql0t7eTqPRuHjxvB9wPwhMyygWi4wRg7I4jveaNSEECmnbpk4XpZRRGN145eU//uN/owd//w//u//tF5991m639/Z3pVALi/MLC0uua5+oI0Q4f/5iPltAxHarG4YRo3atVgsDXiqVlpeXj+p7m5ubUkrGOvqC8/l8s9k+f+ESECoBu4Mwijrtdjvi0rJdJM5vWuL/XxwvLv1xjDa0llEbf4yChOez0skh4MkFPF6HL3qXU+/1m4xWn4oaxvgCYMLTTK7/8TTU0AjHl6jrbHpSrtNpScUdx5m05smkEV6gMxz/iZLTO9aLHwZeMD8yum5KqUEpYUYy6Rby2TgOB4NBIuG6rouUVKvNV199tVwub25uptPplZUV7asXFhZ+/vOff+Mb31BKxXF8+/btTCbzox/96P3333/ttde63e6jR4+63W4ul7ty5Uqj0fjTP/3TpaWldrstpZyfn6eU6uwuDMN8Put5XrNZDwKv0235weCkVqvX6zMzM2EYLiwsvPfee4VSsVqtaVrOs8ur77zzTqfX/s53vnPnzu04js+cWTNMO4oiEcUAYJkMkIFS/sDrW53ATS0sLCQSiStXrrTajWw2fXh4UC6XhRC7u7v5fKFSqayvr6dSGQDodLqDwQARG41at9u1LCOfz29vbwFAs9m0HRMAeCw6nQ4iUkqTyWQqFTAkgkd67oRLLuI49AfvvvvuK6+8tre384tfvD87O/vWG28KJQkAEDY3N/fe3/5seWXJYjTm/jvvvNPr9A3DMJldOT558uTJ/s7++XMXUqnM/v5+rX0Sx4elciGRSMwOG6omYca9e/csx83k8nYiU8jmJKAfxoyZvQGH/38cL+aK8HytYZgrqmfSDCNjeL4iODH6BJMR3MjAXrTDF4+vtMahLSADRBjqdg85tAFAPA9QIafeXh+6DaU7clJKnVjrf+SYvuk3vXbypkzGrpM/fGXqOP60Y1zsuEmYz+c1IGt9fV1XBfb29gAgjuNPPvlEKZVOp//6r//6888/L5VK/X7/H/7Df/jrX//6z//8z8+ePcsY63a7U1NT3/rWtxYXF3UhsVwuSyn/+I//+MmTJ7lcDhFzuZzWCfvFL37R7/cfPXo0Pz9PKJSnismkm8vlwtBfXV2eX5jN5TKFQi6Xz5anStMzU9lsNpNJeV6/1+vU6/VPPvkklczoYk8unfnoo48OD/YAgDFmGQYhBJRUggsehZFfrVaPjo7u3r27v79v23a5XC4Wi4ZhrK+v625kv99PJBKaTK3dbm9tbeXz2Ww2u729rZTq97u1Wq3X61UqFcMwEomUlHJ7e/vevXudTs80zWI+m0g6OGIuD8MQCBq2pQemHj58+Ps/+OHS0ko2nalVT1658XIymb5w4dI3v/ntt978Wqk01Wr2OJcXL1xZWV7d3zu8f/++ZVnLy8uGYSgl5udnL125XK2dzM0urK6uPnz4qN3uXrx4eW9vz7UdKaU3CHQ7d9DrN2v14+PD32pZ/wXHi0vu1AJTEx2/UwUIMnFMwjbVuD34nEjmc0CU33Qxp2LAsc1zgTGH8VcUK/1dSDL+YmM2NCE0Y4VSapgWju3Btm3TYoSQMAzHVaxJKC28MA/2lRsVTDhAOTG5rx8Z/zBErhnGeOaQAnY6nSiKbNuM45jz2PM8REylUp1OB0aVaz1wqAljPvrooxs3bnQ6nTt37gwGg7W1tV//+teXL1++/fhhtVrVyz2VSt29e1drSDiO0+/3X3nllY2Njampqd3dXT1SqJTsdNrMNC5cOLe/v28YtFgsahZ6J5FCVH/v7/3Bk82n6+trX375peu6x3tVQumnn3565eqlmZmZIAiuXLnS6/XSqYSfz/V6vTCMfS8kjmOajBFaLucZo8VisVarLizO1Wq1TCaTSqXS6XQmk6lWq4Swg/0jRKpRAaZp53I5ANjb33ntlVc1E/b9+/eXlxejKGo3WwDSdRKO42i5mGLRDf2g2+4IyQnBZDKZTqYQZNtoV4+OX331dWaxZDL55MkT23b/7b/9f77y+tc+/eTm1HQpCKLFxSXbtr+8dUdJaZpseXnZ98NarZZyU6++9koYhre/vNMJBm+99bUgin76k59PT0/nsumnT5+eO3uh3W4PPK/T9RJBSA3bNM1SqeQ4Tq0d/Bcb3Fcd4/UzubTURINuHJeOVuZYHez5EVZAnFBHnDzGS3rSE04iV+EFz3nqQESlkWfaGU5c53PhqG7pwnAbEFKCUkgIjoY1dDNAjmnnTdMcv8Gkyx6DEiaNcLjrP98knPw+PtX4B0IoHYnj6LvDOVcKUqlUsZATIu71eoRgKpVCSnzfO7N+NpfLPX78eHt71zRtzuX+/uGNGzfm5xd3d/eiKJqfn5+bm0PEzc3NL7+883RrL5FIZLPZIAgIIcvLyycnJ4i4tLRECHn48KHWkCoUCoPBQJMapdPpYrnUbreZQROJhOXYvV4nm806iUSn0zIM4/GTjQsXLpw9t57P57kvd3d3FxYWHj16lMtl5+dnj6r+0tJSrVabKpfDIGg2WjyWtmEalEgedTodPZWjuyD1+onj2I8fP37y5Ekcx61We3FxOQxix0msrKycnNSIaUVx0GjWhBC2bV6/fv3zLz472j+IosgbBK1WK5VKZTJpPWVfrZ4QACUFKOladrFYzBeLFLHXaZuWNbMwbxhGMpn84tZtyvDi6ophGEopy7JmpucMg0khHMc1TWduZqZer3f8ZiKRWFlZQakePXr45MmWEKI4vxALhSCpYbTb7cXFxYXFZakwlUp3et2IAxqmH4Rerx/EkWe7QBP/v9je+JAThNnjR04trbEdSikBhgRlOKEdNo7G4HlfMml+MGGHAEDoc4IZpyz2xXdHYpw6J2gsJ04Y4Rj8qi9VSkCgAKB9kZ7eD4KACeI4zrh/OPl+k1c/6fr1g5pd68Xj1LWOf9Dx8ThCkFJy3eNErFarhkFX1lbn5+eUUt1+L47DDz/8UHMNEUJu3boVx7Ee+avVaqZp6r7Z+fPnf/rTn66srHiex2PQ0XW32w2C4NKlS/v7+ysrK7pDvbOzk8/n9cWsrq7atr22dqNSqUzPzrRanTNnztTrdT8IPM8HgJmZmUQioUkxwjDMZrP7+/vlcjmTyZgmk4rHcby3t5fNZm2ToVSJhFMoFASXYRhbJlOSD3qhbchisaBRL+lMslDIPX261e12k8mk53m1Wo0xc3FheTDwi8WilFJy3uv1PK+fy+UajcaZM2fW19cNQvf39+fm5rSsfL3e9DxP19l6rXav3eFhZNtmNp9xE3a9Xt8+2Hv77bf7ne7jx49TmfRrb7yue8Wm5czOL5oWe7q9mclkirlsFPJCodBstsIwHAz84+Nqt9vVknUA8pVXbqCbabdavV7HsRNB6B0dHW9tbbZarU6nC0gz+UIxn7YdHoQx+tS2nX70n2Vj/x+P8dp7MQQ75ZrGbmfkCUcrcyieeRp8MunT1Aup4FfWRH5TmIqIoVQwhJU+F9NOIvgYIcTzvCiKCKGccymBUSaEFiIdyneYpknokHVGf8fnDzVBkXjKSjXGZfyS8XNM09R4cUTUBq+U0nNWzLKEEP1+nwI6lsGF8Lo9QqBYLAoRHxwchGGwsLCg0cmpVEoLOGslCaXU7OysnrRYX19/8ODBgwcPdK1F45tff/31vb29vb29dDqt1Y7+36z9V7AkWZoeiB3l2sM9tLj65k2dpUVXy6kW0z0zmCUGoC3Apa0ZZ7kGGtb4vEvjE1/JB7yAfADNCBJY0NpADBc7GEwPMHpaVFd1VXdVV6WoyryZV8vQruURfDj3Rkbem1XTDdCtLCtuhIeHe8T5/Vff/32O42iatre3J0fRMcYSdVmr1Vqt1kcffYQxxgpRFK1SqaRp2mg2CVEODg4kVf5kMpHU19J1F0WhqiohqNlqr6wse97k6OioP0Ddbrcsy+WFXhrFEwQELaej4fXrN7M8khUUXVcRQpqmrq6uTqfTer2+v79fq9W++93vnp4Mut2FSqVy69ath08e27bZaNTW19ffeeed23dura+vO5b92WcPRqPRjevX4zgsy3IymdCSFzl99PBTXdebjdrXvvpVx3WTNIUIbVy9WnB2PB5OkygTLMjT3d39ZrNZ73bzIh6OglqtBqFgjHmet7K6dOfW7X6/f+ocS9gahFAOGWq68slnjzzP46wMw7BiGghAhLDrVhcWFoMwPu4PipKVjE+nnqabeZ4CaM0WKIQQn9fz+PlgHhVndT6ZKF0uy8ttFr7Nm9AssATnGYosziOE5BQFnxMXku/FWLlgvfK9EigyM7AZfrUontHhnGE5ZR8BPOtUZse5bNJ8bhLyGUCtmMOYz+jEhQSji6d3lHnv/J+/yY+Y2XBZUgwhApBAwDknhBiaZqqa49i+76+vr46nE8kjdnRy3Gq1To5Lw7AqFbfXW9Q0LY7jPC9939/c3BwOx3JOvNtd+NM//XPP8958800ggjzPpceo1WrtdlvKm5ZlGQSBEAIhJNlydV1njF25chUhVHEdKbV9fHSyvLqyuLjY7S6wkxNKabPZrrhOq9XinOd5fnrobW0/BoDrhvoXf/EXGxvr7U5THlzBxDRN17HdSoUxTss8Cn2AhK7rnHPf9xvN2sHBwZMnjyuVShiGb7zxxo9+9OOiKK5evQoA+qu/+qsoivM8v3nzhm3brVbrXVb+8R//seu6tmFKzhvp/23buX7dWV+/4jhOf+AnSRInicwSBYITb5pl2dif3nn11Ve+9CUhxOPHj70kspibsnKwt7m1tZWmebPZvLK6Vq3WPvroow/ZLzY2NnwvCMMwCAICgbwvk1M0nnpRFGEIsrRAAnh+mMShEGI4HBJFkwBDzIWu6wjjLMugYc8WK4RQXB4VeFYS74uRNJe3iyUGMYvRnoad8w/mSxtiDh8Gzxtvs1M9C80uAGXmPuuC2zz7FAjALBA9w9ZAeRx564EAPDNPeOEi5/2seFbI+8L1XDih+S/oaSg898YL39QMdsQ5N00TQ0iLsijLJEniOMYQYgCyLJNTFI7jMEYnk0kURYyVt27d2t/ff/DggZxk73Q6vu8Ph8NGo7G0tCQJiP7pP/2ntm2//fbb9+/fZ1TIafetra2dnZ2Dg4Pl5eWrV69+8MEHvV5PUjPFcSxTNdu2t7YeyHYIoxxBWRDGEOLBYBBFiarqiqIM+qO93QOpEb+xseG4dpZlN25cG42GnPPj4+MiyxcXexhDXdcd26q6lSwrhBBlnt24c8uyrFqtJqnya7WaBFL3+/1vfvObf//v//3RaOJUqh999DGEcGNj4+6n9z3POzg4SNN0ZWVFOoqTk5OlpaWFhYWjo6O1tbW7d+/euHHjvffeE0IstDocAsrZldUV1TIKynXbNCp2EEeKqh8eHnqBr1n6a1/5MoRwa2vrcPshgEA3UJxM//wvPr2yfvXo6KTTahdFkaeZnGnSFVKW5WQ62t/fh5Ue5wBiWDLqhVlZlkkcYozr9bpl2zhKiiyPkiRLUt1ArKTEfGqB8OmA3lOMKEZ4Zg8zM7i8PbfPfHm7sLw552cZ2fMqmTOvw8+nZ+ed3gXruPzGC0sdPC+rvOAJ5UZm38j80Wfl3HPEC4Twojk99/MuHOfyns+1z/naFEIInPvGLMviOFYw1jCxLGNpceXJk83l1RVVNQ4ODhYWFh4+/HRxYcEwjFqtFgSB5GKwbfvmzZuDwWAwGKytrb311lue50nZsKIoIMBJkty/f1+mi2tra1mW3b179+rVq+PxWCIwW62WEKIoiv39/V5vgXOua4bS0LtdRVV1RPBoOKnYrlWpSfmKvf2Dk5MTQghCOAiChd7SyemRhI8rilKU2UsvvUAIYZRqKtE0rV51OAOU8qpTURRla2vrtddeK8vytH/81ltvZln6l3/5l9evX8cY37hxI0k+efz4MSHktddeOzo6RhhoulJvLIeRT2lxdHSAEMAAGoZTFFTXzNFwoqlGmuSKommaBqnQNK0UXFGUJEnCNIGahi3DS6KNhYXbneYnd+/9+Mc/Pjw8tCzLsqyuCXu9HsbKZBx88N57mqbs7x/bpjWdTvI0H4/H4/HY1HRCUBQHeZ6TRCnL0jL1sqQInBGOSAxDrVZDiIwmXp7njDEExXysePbg0vqR5Amz4sLnGeEXOA/wjCeYfcLMxp6prEomh1kVcMYqKF3ObGh4Zp/oeRYl0zTwrNWdfeoZH6l8aW79CwbOWaDIvOVcvs4zjbdzpOnsOufd8bzhzYfFZ7udv2vey4NnXeK8Hfq+TxBCACoYywl6TdNs3VhbW1MVLKPWPM89z2t3O91u9+S4jxCCALtOLYqiMIgDP1JVtdFo6Vr85PF2p937ype/9uDBg3/5L/6Vruv379//2te+Jif3fv7zn3uel+e5pJp2XVf26Hzfz/O82WwuLS0F/jjwgzQryrJ0nGoQRIqqcg5s2y5ZGQZRpVLpdDq1WmM0Gu3v7xcpsyxreXkZQlGvVuI4rNdcSUiRxBFC2K1YhqZpmlakpWmaaZpKNc80TSV+TRaiNU2TCsHdbvff/k9/+Lu/+7+Qx3/ppZem06kUeFnsLchwenByypjY2dl57ZVX33333TfffPPg4OC1V1/VNO360npW5MeDfqtZLwColPkoDIfeJCny/8f/+C8++uTj0XgCCe52u9evbrz55pu3LF9VdM8LojBVCVpeXt16stuotW3LGY9GxyeIUsoIU1XiVKqoCrjRyLJEVRSaF3mWJElGCHIBCMOwYruSpFRAmGclVpU0zeeNRC4jcY4gma/+P9dj/OpGOGchswDtqRz3fC4373bAnIu+YBczI7wccF5Y/5f8zVNqtmdf4s8Y4YVrkKf0bMQIwbNgnwuObv7GcNlhXth/fof5T5eXTQWFEKqKqitEahLKud6yLHe2nywtLem6PpmMAQD3799/443XmvXWZDIZjUb1er3dbkMIT09PkyT58MMPV1dXGWPvv//+4eGhZIw3DEOGrJ7nLS4uvvjii91u9+TkZDqdGoaRZdnp6Wmj0eh0OlJGoixLjBXHqVbrNc65ZVZURbcqtqKoSZLkZcmY8LxA1bVuZ2FpcWVxYfn48IQQ8vrrr1cq1mcP7g4GQNeUzz777M7tWwghS9fVBlGJYpp2HMcK0WLGGo267FIsLS1tb29//PEvFxYWqtWqYRij0bgsmZQBjeN4dXU1pzljbHFx8eTkBCFkWVaj0ag57vb2ruu6YRjWarXT01PTNPf3969cuTIdDpGq5ll2cnTsZXGOoJ/GkyT59NHmz+9+nJel3aojhLws/WznSXtlcblTtFqt3kJzPPJqdfvRo89sq1qvVzudnm1ZEMKVlRVT04sik1FDobdCP+CcJklURCUNSwyFrmrNVl2CrvI0lWJehmkWRRFfWNyXVshs4aA57unL2+flihcynVlO+FxnMx9tzm8SYQuetUl5evjpeV5c4fNWMNtHiAu293TlP/24p+f3bKJ7vuvFZy4ccXau8833+X3QXKg9u/LLXnT2vGmaCsYqUVSMIIRSQKtUy93d3Sj0Fxa60+k0DMNWqzX1PYTQhx9+KE2UMSYVMw3D2NjYkL3+9fV1Xdffe++90WhUrVYxxrVabTAYfPbZZ/1+X9M0ORk8nU7zPJdxlIRxyuJNo9GIIwIAqNcaEpJ/dHQcp0me565Tk0xbnhdEcdzpdBYXFxFCuq7HcfzZZ581m/XDw8NGo1arOoqCMcaGqlmWxSjFiJimiQE0DIMnmaap/X4/z3NVI8PhsCiKer3+xhtvSNF5RdH+0T/6R3lenpycMMaBwj1vsr+/a1lWp92R+hz3Dg51XW80GlLBdzKZvPHGm48fbXba3fh02F7oOZYtiWdU16k4DtNUP4kAQablCATTggIAwjQZTidDHDSa7sLiGkKo223/hz/5s29/+3v1etVxHMAhhFBRFBUTqbmNMd6bZEmWyuEyTdMwhAqGtm23W92iKDzPi6KEcoEVYnJTURQsnh9eAjZTin+6QAEA5bnswoXtgg3Mthl88uxfMbNJNHfUi8eZDSGAZ7PQy0t3/tYw840zw7vsJwV/DvgGnLFuyx4+eD7dwOyDz1NBIQSfN5sLbvC5j794E8/mkPIxQmiuKPuU2hQAkGWZ1Ljc3t1RFPLSSy+9+vprURRI/XrGmOM4Evgix8CHw6HjOMfHx5PJRAKgZWlnf3/fsqw4jnd2diQc/MqVK/J2GEVRURSS4VvTtDAMi6JYX1sLgkDq/pUlHY1G/eFgNJxomlYwLnF8aZYdHBy9++7PptPp22+/vbK0vLW1hTHsdrsbG+vHRwerq6tJHDFS6rqexHFRFApK5aoFSTadTqXWYlEUq6urGxtXDg4O3n333c8++8wwzN/6rd+ZTqf7+4ebm5tLS8ubOw8BAJ1OZ2lpZTqeHB4e/vZv/3an2XrnnXfDMOy02kdHR6+99ppUtknTtNPpWIZ5kB2VBZR0VVGZH03Gx6enOS0rFTtJUwGh67pMiKP+qbFcOz4+KgvGGLx567pbrVSr1dPT09XVjdCPJENkgXAURUKISqXS00zdUzllRZ4mYZSkcZnnUIBarSbp3hBCpq5xIMqyTNMUW/aFdTC/HjjnApx5p3lzeu76ee7z8/BmaYTnK/ai/cHzouDMhcBnyzBCUhg+K5OIVMznZoh/lXU+/+CpEc5hrQlEDEIBoayccggBwgAhPLsDibNRYikezqFA880TCKDgZ3VUxsCMqRsAAIXAUPINX2AiPbs3yCuE51g+eRBTVRSsAEaDuMAYI9wouRLlotlZHftR4MWdzqpC8KA/zZJiMhm9euuaFEK6d+/e2mL7ypWrg9HwF+//1LAsADkmsN1t6abmxf76+kYURZXmchRFBTTDqV/SwND0NNuqWHYYeLwsrqytMz0DhglNff94U1XV7bt7jHPN1qGOBpOjadCvtbQXX9ugNOp224phT2N25BUP90eHB+PQ0D/2Dh6l48Od/esnfVDQ5fanL968rhtOo95u1Jw/+P/+fzauXL/9wmu+nx8eDUZhXnG1ovAcxwVAJGnAGKO0LGm++fihaentdvODD96L42R1ddXzR0QBb73y5Xffffev/+PffO1rX1tcXGSOePDxZ4ZhfPMbv/mzn/0sjuhCb/3e3c0XXnjh5q1X9vb2kKs9PHo8CYZra2uL9SZSTNNs3v20/8f//hc/+vmH/6f/y/9ZrSuNpv3w4ce9Tv10MhmewKsbNxE3LMPByNhYf2l/d7S8tE6palh1t8a4KBSFVEGe8mgcjcbTMwknQytME6iqNp0mUEWD6TCOU4EgxkgIXhZFliQYoSbKfd/XDUvR1IkXKIqqmZafxDrGDMJSACY4g4goaiF4URRVeO6XhFx/QIqoMM6FlLEFApwPCkEIgaCcPxNznZcYz9pgMoxlrGQMYIwxIjOPBM+A1tJYJLSFIygABAid2WdSzPUtz5NBcN7qkO+VL8mTgORchercp8r/YYzA2ZS+OCP2nXOvQPqgz7PsGSgWzqXU/NKE7vnRBL6kIzf/6lzofPYgTdOyYAAAwABCiFIqmSa2trbWVpYBAK1Wa2lxYWvrcVEUb7311ns/+qs0zSGEDMDxeKqbJ7Va7c23vvKnf/qndhAWZalpmh/FeZ5LtqWFpRsSylwUBYCiLMvxeDwZjS1Tp3k2GAwEK23TarVaAgJN07Ks+OUnHxFdfeVLLwdB8JWvfOXll68ppGA8UzUSpQWaJiEItGFsOhXFUaN4gLFi2laz3bJV3VKU3YP9z+5/dPvGxtpq7+2338ZIPT09dd1O1aktLa+leZ9SurK6dHx8VJwmhmHs7e1CCI+Pj2/fvv3yyy+//7OfD4fD733ve3fuvPjRRx8dHBx885vfLIoCIbS3t5ckme/7Jycn/+v/6r+u1+vD4QgAsLKyAiE8ONh3HCcryvF0ktMSYmBVTKxaJ9OIw/Lf/MH3d0cTiBjCOEziJEujKFpe6RGixnEchsnGFUfGF+NRIPmRdV1XFGU4GuV5lqSBEMAwDDWmqqpKnvwojGXPWla2JWk6hFBisBRFkQUw3/ctRm3oQiQEYAIwVVXiPOcICybkxCoXgnNelJTjc7qjp0b4nO1CKDh7EpznRzPm7NlME4QQYyxX+uX1OctF5x3j7NVfK/q7fHA4ByoAM8pDxhiEZ10Uzrn4vGu9ZDxgzoTmP/LCPs89lQvPz79F5h4SQi0Jj998801WFluPH2EMFxd6N27cODza/+u//uuX7tzgnGdpQZk4PjjojyfXrl3r9LpxmmQlZYwRTT0rPFI6HA7D+GxUEgqmEEVVVU0hBGHL1IGh27ZtaMrCwsLaympRFJSV/iS1bWvojUxTv3Pnzne+851Wx84yryhDiFG4fzTyp35SKJYFtcwbBy7mKcu5ggtIia7Vm4121TYJfPcnf/Vk67MvfelLluUcH41cN4vC0nXq2/ubvV5vd3fXMHRGxUcffdTptA8ODr797W/X63WZsrbbbUqpZVWuXbv20c/vjkajOI6vXLmysrJyejqQrI2MMamvWK83X331VUldRQj+bPdxWZRX1tfcei0p07JIh9NJtWH+wR/9a8WpqSZOioQIUK1WqWCMsSTOYiPpdpYQQoEf7e7ucobRKqKU2rad5+5gcOr7fpZHElAYx7FlWfM2INHF4/GYMQEhlBQ+CCFZfDKwIgTXDMO2TaSQOC8YKwnGqkIEQogAwAXCClYVDoRSMp7O4dwE5BBIsXg4K6UCB215mAABAABJREFUAKAA4pmexOXU6UJ2d2EFznsO+YDP6688b60+jfieV4mcP7pM5y5YFD+PB4GcJzxvTYLZSV6GqM82eGm2av5cL1z2F5zchXfNDkgwQRBDCME5dhSpqorU4+PjimV+9RtfR4L/0R/9Eef0lVdfwgqp1lqnp6cZY4ZdqVRrRFHCKBl/9khR9ThLi7xEWV7QkhDCudA0fTQamaZpmjqGkHNOCK5WqzXXMQ1DUKYouOq4Gxsb6+vraZwMh0PE0At3bvz0/en+/t5v/Rffwxg/ePiZYytE4UEa+2mqmFYyip/s7wYFvnL7BoiOB6dDVtICCrddc6uuW3O7Nfs7xnf/3f/0B59++qnr1peXNpr19uPNXyz21izLWlxcFIBVqy5jpedPFheXiqKwbEPX9U8+vgchfOWVV/K8/OCDD770pS9Vq26WpRjjoijSNL179+Nms/29731P1cje7gnnvNVqyN5XtVrd29s7PBkuLnRXr15hZer7U4FRkI4Vw4iSISKMKHo0ndqO1e22R8PTLMsYU5Mka7U6gkMIcOBHjUZbUkhJqFq9Xs+LhE6KsT9N09SwaoqipGkqqfoAAIqiyJ1loxIhlGdlHMfT6VQIsdBqckGLIs+LTGoWUM6IbmiWLRAWlJWM0TJlvGRCUEqJmCtbiFn1UK5pLteOEAKcq2DPFub8DR2eswbPIr5ZWw8h8txVOmuZyG1mwxdM47I5/K3bZaN9qk94Phr//MrvbJvhqmdZ7HlR6xnDm8Wo8FKzdXbGzzVCyhhCEAFYlqxIs0KnhJCKa7uu++DBvTgJf/Nb3/zG279xcnRYr9c1TTkdjTJK7YrjVmumZZeMjkajew8e2Lbt+wHEZ9T/mmkAWmJVWVnpyrYH4JwxppwT5CyvrGCEaJ5pisoEjOPYtu0OwYjziW9UHPP45NAwDD8KNdXQbaOk6XB6RAFeXr/yeBQ/3N5url3/vf/qH6w2lH/5//of7//iEy8Lsa1RAsbBBIN84+qV/8P/8X/4b/43v//b3/svNjY23n3no7/7u7/34MHDRs+5d+/etesbn376YDIZdbvdd955p9Np05K3l7t5VvZ6C4ZhaJqRJMnhwTE6J6e6d++ubVcIIdevX5XC4IZh9Pv9yWSiaZptO47jCCGKkmu6yTiY+FMBcrda1QIeJpOr1xe2T0ZJmgKeIa7GkZ+GQXNjw7Z1VdF9P6jX2lE0vX37NqWiUqnoui5XW6VSSbPqdDr2/XA6HX/rO6/leT6dTqXu1Tl5NC+KgpCz6VNFAXIinFJ6OjihJSeEACSIqiMEEACaplQdmyOcFwVI0rQoGC/OCFuwMrd0ZjO4s6xPyBUr7ZALgcHTkgnnF0mZnpsWzfYRz7KufF78eWG3L7bDC5Hg7LPmWyxPBd/OjQpjjOd95XO3C37sQjg6yzOFOE+EL4XOs1sLvFRoEkJwCDgXZVkKAUu7hBD6YXjz5s0sjbd2d9rNhmlbnud1Oq3JNDQtlxDk+/5w6mVZNplMsqzgII7TrNlsAoSyLEOQFDmVWk5lWQrGTNOoVqu2aRGEGCt93+92Oo7jVCs2QiiM02q92ak1/MGJPx22m/UCMkXFiqKsrVwL4lEeZrphjcfTx4NHx4NBc6HbaLf60/GdazeBSqChelE4CTyaJW3bchoLJaBxFPzDf/gP/+gPfyCY+spLX7l7916vu2w5jBCCIDEMs9PpKCrudruu69Tr9SiK2u12peJ8+OEvIYSNeivPc4nelLTi9XqtVqvdunXr4ODA87yl5YUgCCCEq6urEq1aluVgMLl9g5RlycuCgyyLPdOATU2/cXXxdDIIJoGtq4iV/aO+AtHtjQ2FTxYXV8qCIUR++tP3bKs6HEx0XbcsK8sy3/cluD8M4zTJTdOWA9O+75um2W4bsr6f56V0MpLQrSzOiCoRQpZONE0TAGiaYVZsh9WKsjRte+wHACPGBOQFFoxghIhGFY6edihmqxYCAATkQMilyyF8imgBc9DTeTuRswGSThqdb/J+8dxgbWYtF1zi5f7kvHU9d7t8/AtRLpmdEDzTSJKn+LlN0vnYev6eMSNcE+c6TfCckQ48z2vPF4TmD6UrhHMAITxjcRUwL4soicu8uPrWm6enx5988kmjVpUaRv3hKReqbdtpmg5GwyxLAAAY47X1jdFopJug1ekIITzPAwCkaWoYxmQ6NjW9Wq02m816vW4ZJoRQsHJw2lcUxTSMarWqa3qWJQUtSVlAxE1Ly/txiVAcx05ePT0ZJHlAQa4ZlbwcPHryeOSlrYU2V+CPfvLDn/3ojz755ce9WttwLAp4lIQaoF44BUxrVCpf/upbP/jBn967d29p4ZpttTgH0+l0fX398eNHlUpFVev7B9s3btwYjUae5z169Gh9/YplBZTSx5tbb7zxRqVSQRioGmk1W2tra48ePYqi6ODggHO+tbXV6/VUjTSbTV3XKaVpGt+9+7E/DlnBYi80VK0s0lH/ULXNTr0WZu4LGyuc7k29WEfqSq3RqNc7lqukiW1VVVXf2d7/7NNH6+sbhmHpuo4QCENfckxlWTYejyGEVzeu9fv98Xg8nU7b7bZTceWvXJas2WwmSTYcDqMoCsNY/sqUUsizaq2R53lWFpVKBUCc5BnR9HqrhTCCUBAEORYQAogFAZCx+UX/1A6Z4BCdiS6dCQc9uzIvL9HnesLnGszMU8086sxG5LjPcw3h+Rt/jtohAM8QoJFnEANn7Zm/3QjnW/PS0mQ6PjPCp/oWZQmeV7yakZ3K52fXTAVnXBCIsKIQQCjlSZJAATRFPTw5btVrnXZzOp1wWtTr1TDy949GWVH4vh9FIcY4iKMsy6pVx3YdkqnVajXPc7l0ZLw0HfmSQtNxbFqWcRLKNvrrb74RBeHp6WmSpq7rIoXoli0gXF1brLj2J589OJ1OOAOKouzvH968c3U0HaRlVKs2bt++UzzZe+/+I6aGt15uZ97IMAzHcQqac0HtimmamhCMENRo1LzheG1tzR9njx9v/db37uzvHXVWdM/zTNOMk5BzpqnGaf9YCKaq6pUrG6urq7u7+1/60pcG/VGa5kFw1G63R6PR1BvfNK5Pp9NutxsE/t7e/srKymAwcF13cak3Gg9URZcyiXWzWoTZwdbexnqz5bpZPIK0YHFY15U379yOvPgXB6eVhvPCyy+0ay0eRLV2y/fiTqfy859/WK3WVVW7c+eOpmkyBWWMqarCqIiimBBlZWVtc+tJURQQwmq1WqvWx+OxEAJCvLu7WxQ0DMM8z+UUtWmaQoiNK4vdbjdNcj8K3VpV07Q4zYmqYKJyCJIkHU8mXhjkeU6ZYIydnkbPLkB0vn7kWubnz3AAuLgk/ALOfRQhTznNZhV+earzOz81krkYdb6nhxCet4JfxbAvH/bCxxGpHyKtglEOISYEUPqUgu38bOTbhGTBgOcwvNkdQhI8AgAopUVRSA5fCKFyLm8ozjm85RtnsofgvMEqznuPEjQkmBAQqKqqqxpRFUzI6elpMJ1YtlGxLKKpJycnU2+cUZzmOUJIM8yyLDudjqZpZZlLrlvD0FhZtho1AECe50mSdFvdXq8n2RxHw2EQBAsLC2mafvjhh9VqtdlulWV5dHxsmrZuBJK2rNqovvTSS8d//cPxeHxTu7OyfOXRp4+RwoECNVVturWVRdYP0n6YRv6kaTu9ZjsK/bXOEuAMAaASJABzHPvDj37RqjZef/317/+//y1CzuMn206lUa06hmHJryUvEhky5Xkqv42yLI+Pj994/Uvr6+sLC0vvvvvu4kJ3aWmJcz4YDG7dvoEgQQg1m804ToSIkyTyfR8hJKsgo/HArOij40HrxmLqxXmYmETRFB2rRrVi+Rn71htfXm2shF7UMCvXF5fWlpZZcHLr1p1/+S/+1Q/++D/8t//tP3r/Z7/43/2jf7yzs3d6etputz3P++lPf7q5+bBarVYqlYcPHym6urKy8vjx448++qhRb8p46ujopFKpEAJee+01QggtOedcesvj45293YOJ743H45JRy7J008YYE1VhHCRJsriyXBSFomqHuztxHGtW+3zCG3HOqaAAACAQwgicEWmflWfmSy+zuHFmJDOhMXGu6TBbhxdMbj6aA+dB3Aw0oigYPGve8BzZMxtBlGtYsiRr+tM5+Hm3KQ3kGSNECHF+ZmZfbNyXM0B4PtQ478HRpSneCzWl2V1E7iZzCTFHbM6ogEIICFIAGGN6tVaWZY6hUuBRniVxSGmhKJKNDY/Hw6IoGo0GpXQymRiqstDpagq2TR1Cg1KKIJBAtsFwQhTkuLamaRDCPM8fP37s+76u64ZViePU8wLXdevNFlbIcDx68+byLz+5ZzmV2y/c8f3w3t0HVzZW6tVWyZM4DSM/CLMUFpkOBcjjcHiKPMLzrGraTcdxdN1QiKVqFdNgtKzXq4KJ034/itMkzSeer6qO5OGXGUueF3lecgYgxJqmHR4eUsoVRRmPx5ubT8qSdbu9Ws2RpKkff3x3fX1d1+FPf/reCy+8ILWZLMtyHFv+lJJh6fT4sa3h9W5NdetIEF6UhGITmWFcsIx13PrG166CEsReoCJsUNpcvf6z937x/e//6//+v/8ffvjDH373u9/9/vf/9T/4B/9gc3NTCLG1tbW9va2qapqk3jS4fv26bmt7e3vT6fTVV191ner7778fx3G1Wh8MBmEY+75fliWCRELVfd+nLJtf4rTkvDxjTqlWqxXLrujmydRPo1jDuNbtTtMZ0b2AEMwGnQAAQvDzsI4DIOaC1V97uxypfcGTF8xBPpCsKNLZzkA2GGNB2cU3CgEAwHMBKZGKKzPACgDPcGNf+NTZ85ftUHZp5+808t+ZdsWFs39aPj3Pd88OiyCngjN2JuWNiDymqmu0yKUvjaJ4MplgDFuthmCo3z9ZWFhYXV3lgrl2pVZzw8ifjieclnmWWpalKDhJkmA6Kcuy2V2SFfw8SygtVFWdTqcyoLIsy3acwWBw0u8TVau41bUrG15wZNiV6zduPtk7+uu/+tHXvsFevHMn8Ee9Vmc0hb7vGxC27cpyozGd+mNvnEDV0PSeW207bkVRFSBUCDSMCBA119nbPtjc3AyT2I/inYMDy2kR7GiapusqQkDPVdma4YKORqPV1XVK6d7uwd27dw3D0DTd9/04jnVdf+edd7rd7ssvv/yjH/3oysbawsLC9vZ2GIaGYQAAKC3KsvQ8bzQaxaF6uHe003KrNm7UdShIFpUCpu2FFRgkcUphznRFdet1BSFdUf7kB3/67/7dv/vOt793/96nN67farU6v/M7v/uTn/zEMAxJJhDHsaZpACBVVTnnH330Ua/Xe+WVV3q9XppkUq1xZWXt/v37um7K2xxEghAiKSFP+4Ner3fz5m27UmGMSf2PMAw9LxhmQw4BLUuMkKkZkEvWWUEpo+WZaDTA4NwBivm1PcsMf93tuUv68/aEc3rA84VJMIcJmwHCznBg50rVF6xgHqFKJAwCISS58sEZYOZp1+EMjH5++3nu6V5w+uBZ/w4+56Zy+bsQQsivWABAMFZUVVd0NIfxgxBSzhVFsSsVBGUNIL9x40ZZlnEYrK6upmk8GQ9ffvnlvb2dyWiYRqFOcKXqqArGiKcpiMPg+Pg49L0sK4Ig4Jz3+31CyPr6+mA0PDw+ct3q1atX6/W6LAiJMGh1e3a9bdm/2Nt7b2nlIPRix3QQBRZW67pBMGwYJgAgDkOFlYaw3EqlZ1VdRTchMiDUEVIAUAnOkuSzzz579OSxgCBMk+3d/cWVa+MxBgAoKqEyaOM8y7IsTwzDkFXHjY0NTTPSNOec37hxI038/f39lZWVOI6fPNms1+v379+vVCqyPWDblkQ4AAA8D1uWwbh2Mjz5+BNqqOLO7Y2KpVHBC5+O/J1mZ7lRreVpAblAEB7v7zx++Nnf/OyT27df8Lzg5ZdfXltbq1Qqm5ubpmlOJpNf/vKXMmQYDoftdrvTWTo8PIqiSMo2QQgXFhZ0Xf/ggw/+/M//8stf/nK1WpU/epFTibiIoqjV7uYFvXvvQRAERVHIQbBud2Fxcbnb7kgl8Pv37+9v7cjBTlmeKGkua6EIQelpEIZn6R886xqeL7BfI0Obt4rPC/Fmz8AzSbIzK0BzIsGzHWQ8jM5lVL44opw9JjIWvbDHPKj84pmBZ56fWSa6JKN99ufchP6FS5pPUp/WgdiZ8CKfUXcDAITwPM/UNQEYpUXFMhcXF4syCwLvxrWbjx8/rlRsRXHu3f3YsoyNjY2tx49u3bp5Yqj9/gktU29aAiA0jJ1Go0RGGIamaUrpIkppGCUQwp+9//6NG7ekWj3n/OjoyDRs16l13KoAEGLy1a//xnAUPnm08x9/8B//m//6f5V4I0MnDcvBCaAEL7huvNB1NVVjtq7ojm03NLNqW25FrzuGo6tlmjzZfPThhx8OBr5VqZWUnQ4HI8+3rEXLsjRdBUBGXDAIzgg48jxP4qxer+d5SYhyfHwchmEcjQEArVYny7LNzc1vfOMbUjxYVRUt04QQnjfVNB0AIHniJnHqTb008xUVlKzodFpmxdYs5+B0kFCj1uBJFKeRlwaT3ScPHz246zauJ3H+0ouvttvtdrs7Ho9dt7K9vb23tyeJXgEAiqK4bg1jZTr1v/vd7wIApFc8PDzc29vjnP/O7/zOkydP6vXmeccil3NPZVmGcVypVBqNZqfTjeM4CIKTo5OToxMAwEeMVSyz2Wwu9HrLi4uMsTzPx4OpDNdnEdYswQGAnYWgUAAA4Fk4+p9ohM99ad5U5J8zZwDOvZ9czFJXFyEkXRpCSApFKucTueCc2+IsX5sPR2fWMosM590X+JU9IUKIzammgfOAEz1vygM82/SfN3g+tydjLAMFgQgCgMQ5SoJzRdeazSZlBQA8TZONjStREKoK/s53vsWKcjA8NW17f28HQ9BuNfIk8X2/yFNs27pmUwZs25aaoWVZTjw/L4swiJeWVlzXHQyGp4PhK6+8IrOav/rh33z7a7c13QJQuXLl6mtvvLGzs/s3f/k3q63WG6/dabWbnZp7PD6ZponQxJVuu1N1Se5CCFWEK5bdrruNaoXgktHk0aOHH3zw/vbWYyGAomgCgKyko/H45KTPGLNsEyEgBBuNRoPBwPMmkqzRsqzJxEuSxDAM3/cffrb5W7/1DSmTeuPGje3t7R/+8IdXr17t9/sLCwtydv7w8LDb7cnKDcY44rlQcQrY48ODsTd13Uqr0220F3THff/De0kWZ2kYTAaJPzQUXqtZnIPl5dVvfes7JydHw8HolVdf/tGPfvRnf/Zntm0vLi5KxNyLL77suu7x8fHS0tLW1tbp6anrujdv3lxcWMqybGdnZzLxbNuWNE2yNysl3IqimEyDOErLgkmbLPICQqyqhJe0Ua0tLi4iCIus8DyvUqmYpiFbAnL1I4TgWaGSF6WUsj2rjkIIf13zmy3d2eN5nzaPjp49ObMUmb7NFvYsHZtvQs6CVThnAjNDmD8ymbUKwFMn+0WcGQB+ruMW562LeRc3M/3nxqWz/qTE6wghOORIIQRhCDGGCCGiIEww1oiiaYpCEIZAfgWWZa2srAhKgiDoLXQajUYSRrqhtpvN05Oj8Xi40G03mw2nYhm6qhHsuq5t23/yo/d93x8MBlLp8nQwTNN0MvEsq1Kr1Uy74nneZOKNx9M0TRcWFj57+OjK1Wu64RIOGvXWV9/66kc/e+8P/+0f0sRTv/b62nrP1UzGSoghxrioMK1oAQAwQpaqVyuWjsBoND493nvn3Z/cu38/yzLbUZjgEEOiqSPfT9NUCKGqKsaQsVLX9Xq9btvm3t6e7/tJkty4ccuyrOPjk1q1ceP6rTxPfvCDH3z729++f/+u69YopQ8fPnzrra+UZVmrKUmcCSFc18nzIsuySqWiWInlWCrkoMjHUeIn+TjKjX6o206YZhwwVQGs9AXNalWnt7xYc178O3/n7wjBoii5c+fWD3/4w3/2z/7Z1atX5OylJOGv1+tCCCDg4sLS3vHj27dvb2xsJEmyvb3d7/cVRel0ekIIxrgkFqlWqzJq9X1fMy1KKaUcAESIqiqKZCSheTGdTvf39sqybDdbk8mkf3I6nU4bt+9I2S94Rj9RzhdgZgtyzop+bU/4zHL9nJee+cRz5yHmhErBeUAnzguTMiiVStlfvJFZTw88Hbf9Ih89s+bLkeeFWYp5v/rc65n5dEl6L30jA0xRNE1RESIIQIwVXVE1VRVCYCAIhgTBsiynga/pzXa7vfV417T0KIqGpyeaprVbjcXFhS9/+cu7208whnmWjkIfA1Gr1bI8yYv0zp07w+Hw8PAQYyxDi4WFBdtyHj/e2tnfM3Sz2+1eu3bj8PBQMilyDpI4U1THsuyFhQXXcrIg+MW7P/wX//z/mYWjb//m13Rbg1AYWBEIlYIbik0QxggpCCoQRb6392T7088+uffxJ/vHUaWmVlTbC0pVgbqqx3Hc7/cdxzZNU1ERpQXn3DRN1634vn/jxo0kSYIg8jwPY2yZZhzHUTR4+eWXdV3f2tr61re+s7Oz8+Uvf3mWmAGQSVJjSqksOFlV21AVHSHMmSqQgnHJYJKxNEwUXSNEFaKoONWVhdWbG8uLvWav+Zbk1b969er3v//9e/fv/t7v/V6axr7vHx4elmVpmvbR0ZFcf1mWSR1lSRgnhEjTNE1TySk8nfplWZqm2Wp2FEUJw9D3fctpIYQMTbcsC0KYxkkSZ3mSFkVR5oXquqxM5SCibdudTiejdLbSGGOUczkQe7kW+sVFh8/b5nPCv3XPy4t/vtEtzkkDAQDSJRJCePH8wsz8RggHsGSgoJADLgADgmBVanHOducASKlSABCY4z6chZSzNuCs5jOrfFqKJq0RzY1Lzk56hpvheSmTwjTPVLWgigIA4JRBCDVNM1QtjmNVVTVVPZObREoYUQATQwNxNEYItdp1AMBgNDwd9B9tOqZpW1ZF0+pYMz0vmCZFl9V7vd6Cgxa7PVPTf/GLDxlj167e0HW9iPP15ZWDJ9sKUYswnp6OKpVKw3Z2Hm7S5UVdy7JkdHxwWq/X7Yp27YXrQe7/8pe//ItPHh/k6ObNm6urq7WaW9E0AIBmar7vjz0vjuPJZLK7u/vo0aOjo5PxCHBhT8YEIUyAAuNMFaGmCH9QUzYUR7dVlTBI8yROBbd0c2mxE0UTzrlhwDyH/cGx1MfOkrRZ16sV9bd+81tlQZvV2ulBX1W05eVlb+ClWdLptGge58nUsbBgQaPQVFY6FRsiHAc+wqBXdXAFj8d9XOLXXnuz013gCDY6XdO2sEKqLTVJaCHYJDhZudLLWRIm/vb27rWrN/KS5SVvd3sYK5ubm5RSRVFuvHRlaWnp8fZ2HEUY44k3zcvC96cPHtzTVdU2rSSYHoY+hFAn5LUXbwqkaJo2Go08b6CqKqMlRqDb7Z6ennIhNBMvrV4Nw1DL1FIUmqbdqLpFUYymXpDEUIA8yxhE1UYjyQoAEBAIMAYFRAArBGOMS5Ryzjk762ALhBjjjDHBhQIBRpBxSUgtIIQIQoFKAIC4hGyBT4WMoPzvrOCPz94Lz0QEzwyFi7OJLbm7AFQAIQAFc33FWU4HzqkTz4xQ1icUReEcQC4YE5TSgrIZ3f3l+8FzrfnCHWVmb7KJP/tzZoQz1MIF2IGiqUKIPM+FEIALIURRFAmIpKbXzP7l3de2bauuHh0dlSVzXdcwTMdxbctZXl5+8OAzSdq/uLh49epV3w/39vY+/fTTrJj87u/+7te+9jVFUX7845882vzs6sb1Gzdu+L4fhuHh4SFCqFKpWJaVpiml9PT0FGN89erV5ZWlVqtFCGKsFIKtrq7+7P13/+f/+Q+rVffmzZvr66u9Xq/ZbCZ5Eoah53lh5Pu+PxqNhoNxGMZu1SFYEwIWOeMMqaqpEFKW+f7+/sbGhlutQCgAEWmaJkkWx+HW9uNrN64RQo6OjtI0dR3H87yf/OSd3/nOV4+Ojr3p9PHmzvLyqoLJytIyxsSbThVF0VUtDqNHnz0cj4fedOrYlb3jsYqJ6zjLS4sV87ptGY5tQChM0+QQLK+v6aYhMLFchwlAVIXmdGdn5+4n9waDwXg81jSj1WoRQp48eSKJsGzbFgJ2Oh0IoRwKk97A87woisIwpJQeHx93u908TeXcY9VxJddzkiRPdva73a6maZJtZDweSzwqhHA6ncqCsLxHt1qtRqNx75f3HccxbKvWakKVHA+GByen/f5ptd6QSwYJDASEAHBOOecQw/l7/SwQO/sTPNW75vCMYuK56/zX3Walo/mkUVZrwFwl5XKKByTvKDwXIcMAUsplQfnzjHB2bZcrNBf+lEfmJbvwpHww60xe+JpkxfJsCBqcpbyUcZmgz2fGckg0DoMoSgEAQoAsy05PBhAO87y8c+fOw4cPP/30U1VV19fXF3qLL730wq1bN4Lo9Ec/+pu/+Zu/efXVV7/73d/85S8/3j/YlfGbaeqqSqI4HI2HktuGMZZlYnt7GwAgAJ9Op7Wa67ru7du3d3d3S/q667rT6eT09PTo6EAS3XJRyAhfCIEQxBhbhmabZlkwTTM4gyFLqWAYMlZmWVb0anVCiKropcgF55pmaJrmeWGr1UIASZ5Vx3GSOM2zouaKvb296dRbW7tSr1fX1tbiKEuSaDSaXLt2TbLIcA50Q1VUwjmXqteQi2q1euvmnZWlrmloGiGMl7ZrCwg0w0jyQiCYJNnx6YlC1CeffRaFsVTCkqoHp6enjIk4StfW1mq1epIku7u7nhc0Gg0AAPKZN51maer7/ng8lgO+0/Ek9H1d1y3HgRBKy5TJv2maiqJIpnMhhGEYEMJKpbK2tra/vz+dTiULhvzdB4OB41Qghr7vT0Kf6EZeUsexDctOs6LkDDAmECFIgQIwzigtVXyGgBEQ8fPqBhMAzFkghwiALzK+52ZPX7zN1vB8asY5n+8HXqjQzJ4neZ7Lm5mqqgQTQgAEZcn+9r6nmKvciGdHM+bDX3xpygk8heGhWUNydk5Zll3IIQkhCJ89D88rsVEU+b6PMRZ5Zhp2pVJp1FsQQsExIaTT6QwGA0VRNjbWTdM2DMMPvKIoOOclDVZXV03TVBQ8mXgQAl3XsjzBGHc6raLIfD/M83Q8KQBAjUaDYMUPvP7gNC+ysixXV5dv374NIej1ei+9/ILv+++99+4nn3wSx3FRFOPx2DAkaz5RVdW2TckmDACaTjwhIC2BpqpZWpQlZ1SYBg6C4OTkpNVqKLpCWU5ZVhSFELDV7ERxIDFoqqrmeW6a5uLiYh4PbfuMpuX09DSKAjkemaZxWZZ5nqoagVDIFfzw4fbqxutFkUnlqVqtoavKGeaEM83QAUB5XgKMfD843D/UDYuWLMuyRqNh23a93oQQJknW6XRUVV1dWY+iSPorhEClUoEQcp4NBgPf9yEXGEDXdbvtDispFKLVarUaTc6553lREMrf2jSFJICSN744jgEARVHI+4WmaVmWycQ4SZK9vT1qFKZlIYwBgkIwVuR5nlJKEVFYyShlGBKocYIUDIWAnAMoIALwbF1SfiaGIgCSNRsOEbhU87ywdP8Ttpl6CpgLAy87KvC85JPMqjKzPWSZZC7oFGdXcXaSYmYk86Yyw6xdCC+fjnvN1XNlJHPZDQIA8iyVyoRCCEoZAEBVVYUoUpWNYIwQkhM9UIAwDF1DM00TAHJ8PJCGKimG6vW6hFD2+ydhGBJCVldXr11bj5Ix57wsS8Hh8vJip9N6/Hjr008/vXbtmqoRw9TyPFdVQikNQy/PY8epCyEURSnLMgi8MKwGQUBpIesKlUrl6tVrUm17OBw+efKk0bAoLeSQq6oSRcGc0zzPq7VKFCWMlYoK8pzFiQ8EsqzKxPM2n2xbTmVpqct4UZSZXN9pmkoM7XQ6VVVV0zTLsDEkSwsLYRD5fqgrqj8Z16uugqBVqRzu766trfU6a0WRTSaTg9290JuaGgrDsMyLwI88z/erDrPNim1qmtofDvSSCoymU09AGCUpLQGxlIWFhdFodHx8jBDpdrsY435/6LruwsJCFCabm5ue50uV76WlxbIsp0F/MhxJo1JVlVOW53mZ50tLS5qmyV/Ndd2KZQdB4Hme7H9KPlLGmCTRYoxtb283Gg3LsjjnhmGsra3FcRyGoe9NDNNUFcwgABghDIBgrMyFEKygJS05JBgCRQMAAYwhnaO3ZbMSI8RACA4RhwDIkQuAZnWOeV/yn2OH89tz1/bnbWS2CSHKomBMcPYrnYeYi4Bn+8/Z3nmvjz4jFAPnkLVg7spnm6xHS3i3YE8LvrZtE0JkYUbi7CSFScVYtJ2a67ppGpdMlnjK0cSLszQIgiiKyjKngqqKatiaU3OSbCphPRCJJI2iMFFVcv361a2t7V6vt7a2CtdhmmYnJyejcToaD6bT0LIMVSUY4zRNZemSECK5qJut+sbGxu3bt2XxsNFoKLiQM41B6CkKllzURVEYhhUGcRBE06kX+gFnBcGKpgJccZIkGY/H1aoNIGe80HUCOdQUJUkSAlGv3YEQjsfTlKWGphUFK4oiDP12uyvN+Ojo5OjogDGqKNgwtLLMkzTOsowQ4rpuzgVCiFMWRZE3DRACmqYACDVNS/OCcjYeTcMkLilPo5QQdZpGCBEIseu6tVotCCKJAjk5OeFnrIQwTZN+n8pSwng4Ojk5iaKo1WppiprneRyGMpLnlOU8l+m9/NVqtZofpaPRSN7UpCcsyzLLsuXlZdu2IYRSyVSqJiOEut0uRCgMgzDNDNsyTNtxKpppHh6dAAIQwBACDDnnJQIYAsEYRAgJiAQAZxSBECP4FMsFziwQzC/U/3zD488O5cFz2Pe8LXxeiEtkNogQopRleUEpx+isJ/MFnnD2eU+NbQ4jOl8LlTetmfOcvQTmzHj+G4ECzSZH5IyixLmbpgnO+xkSdM5KWhTFg/ufTcbe8vKyW62Ypk1pIb8RBAkhxDR1IbQkiZIk2tvbo5TG0WRpaUkIsbm5CSG8du1avVHd3toVggnBGo2abdu+76uqYlnm6enpdBJRSkejEUKoKIokibIsWVxcrFaraZoGfgTPZ8yWlpZu3LiRBKcAgDRNoyhQFNxoNCzb4JxHYQIhPDnpf/LxgzLLLUPTNZMQcnRYTCaTvb09zgvLNqq1CoF85E+XV3pxHIdhUHOrCCF/6uV57tiVTqPmjb0sSQenfdu2gRAYAoxEwWj/9GTQP5WORcGIFuWwP20tLAjCMMZZlkVZZGZqGJKcTmzbnvoeAEi2SYFAURzHccJoKvWqgiAaDEZlWdZqNcdxNjc3u52FdrutaVq/3/d9jxDMOR+PjvI8B5wrGOuqmkKIEDIMA4ozYighRJ7niqJUq1VN06I4l2mIrusyQpHez7IsmRkJIbIsk0KurutOvSGCpGDUtoyFxZ5pO1ESx0lmaSrXVcHPOPMpZSUrGGMM2QAADKGY08FG5zT1f+v2n+wM4RyaR65zcd5RBM/OWzwnHOVzrBtlWTImsKrIm/cXn+uFY81GM+D5SKK0bZGXYq6JL/vy0o9d8KLwfBJKsi0ihBQ8i0vPtllOyDmnRZnned1t5Hk5Gk2EEISQvEgxhpZl7e3tWbbR63Wq1epoNNjc3Nzd3T05Pb62sXp4eGjb9q1btzjncojh+o2rQeidnJwM3ju1LadSqfR6PcexAeCGbkMIJf44SZLBYLC9va0oiiT2RAjFcSwBkLpmpEm+0FsyDEMAFoY+Y8yyDEXFlFLLspxKlRBy9+7dLI8VoikqTJI4SQrJ+KbrRFE77caqZRlxEhBCVpYWj4/h0dGRRpRuqwkhFpTlacYZbdTrURRhBMbDwVlHWJRFnsqalq7rFdvWNU1wkMYhQijN4igKk6SSOTaL/CAIHj3ePD09tStuWbKJFxBCwjDyfR9AtrKysrd3kGVZq9WSSdrDhw/7/X5RFEmSMMZ0Q7NtuyiKzc1N28ASHGMZJiEEAYgQ0hSVc+77viw3OI5TrVYRQp7nmZar6zpCSGZ9cviGMWbb9mg0AgDIzv5kMpmpNRe0zPNcFnVqbqUsy7AMTF1HCGFMKGdZliUsKYq8LEuhm9IYEARM+gN4tlbROYkgBHzmDP/W+uKvuM0a3TPsqFztn2f88+ZzNkSDMcZYUEoJUXVdT7IcnutjQCQAQjPwmjSh+dbCzCCl7YE5zdT5aFs8C02QG3+Wq4afz3fJ90IAZ/cV2eqY/TAzp1pSzgT3Ap9y5rqVSqVimjpR0GA08E8CAKFbq66uX6k16v1+fzgcAoH6p8OD4qjT6aysrPR6PTkAKTFfjuPohnpyekQUJAXrb926dXp6Oh6P5WC+FPTNskwKJzmOs7S0BACQyvL1ej3L1DwvEEIQKrquQYhoyRln7Xb78eaW3G0ymTiVaq/X8zyPAwggz7IkTsIktU5PTxEW/cEJEGWWZVHgs7IQBGOECMFFUQxP+0Wec86btVql4jSqtTAMoWCAKxgyhFHBKCtyQ9V67U4axaMwKcsSIVGxdFVDELKClsfHx2EcRVFk6BNV1bOsiMIYEgwAeu3VV588eUKwevXq0vLyMmPs6OhoNBrJezQhxPO88XgsDa/eqAKaDwaD80DdPT09VVV1eXlZiuEcHBzI8uDR0ZGu68fHx9VaCwAgoaTSknVdl89IdeQ8zw3DSJLE9/12u61oGhNn925W0iSKaV5oRDk8PLQct1qtGqrKypJTBgHXVFIAoSkEIZSXFHCGMRac5UUBIeQCQM4xAOIcniV7ZE/X2yUfNV9unN/zwlvk+pwfWZQB+YVwdOYbpR3N6iNEQpllrUIatLxgeo5UEOdsbdJsZjRVs6PLE50fZbpQpLlwYV+crcI5mJuYXfmMGh0AuRRmB8mLlDGmaZpt25VKxXVdxso4jr/yla8GgV+WpTcNBIeuWysKenR0kiRZp9Nrt9vVqsMYm078LMsAAN/59ncRQoeHh7JMV6vVIISUUlUjr7z6kjRgwzAqtithmZKLOoqi0WgimyULCwtXroAiDg3DsCzDtHTZDZb3rqPDkzRNCVGbzebKykoUJsfHxwcH+7X6jeEwjCKa53Ych4PBKVFgniZFmdMipbSAgmMIIGCcgjLPu0sNqR4DABCCY0xM04AQZFm/LHPOOYCIEFUWjdrtNkU0z1PD1BACRZF5gZem6XA8BABM/Cn0/ZXFFSGYW3WajdbS0sruwQ7G+MqVK+12+/j4+Be/+AXn/MbNa81mUyqEjsfjsiw5p6ZpIgSiKP7aV74qyTgIIcvLy7quf3r/wer6Wr1ed113dXXVsqzj4+ODg4ODgwO7UpNz+icnJ7I4J8OKsizjOK7ValEUXblyRQixsLCgadrj3SdYIVLIFQCQZQUvSgLglfV13/dZVpiuDoSYjEd5UXQ6HVYWZQExxrKJDwUEQkDBMcICinOoNwMACyC44AI9Y3i/lhucT/8u1ClnBsbn5vUuLP7Z24mkfpBtN9M0MVYkH4S8Pz3d+6lhPGNCsw+bJYEzP3m5DSg+X9/juZfHnyaN8Mz45+ZEZBdRwqZMU7csAwAQx6Gsqu3u7jqO0+12KpVKFCWKolRs98r6VdfWJ5PJ4eHRZDJ1nAqldDyejsfjPM+bjXbVre/t7zx48GB7e3ttba1ardq2Va/XHz58uL29LauCnXZPktg2Go0kyYIg6HQ6d+7cUVV1PJ5mumaaZV7ygnFdV1WVqBpWFIVyBLEWRX6SFbppJ1lRUFZtNARnWR5xoRAFaZoCIAMQGKZepElRZIJTjABGQAgGOAW8APKeKITglNGCYwEBUwhQCCqKglIOASq44BwALtyKAxXshyFRkBAsTiKAOWUMEkgp7Xa7/dPB/tHhZDi5c+fF3Z2dD95/f3XjCmNMlqaKopCS4PKLGgwGx8fHQrBer9NsNsuynE6nNcc9OTyK43h1aVnqcOi6/vrrr0+nU3mfkjFnlmXVeu1r3/g6EMRxHIyxZIWr1WqLi4tS9dU0TZkHyX7PZDLJ8xxrqqYaQhEEY1YwKigUwNQNxgSkIC9TjDHNCwIRNgzLNGMvp0VGZxUHrnDOaVkqpk0A54ALiBCCAEh2Xc7RU3qLy0WKX3GtzrvEC9Dt+ZdmJjMfGAohiJR31nWdsZQQASHiAsjZ0LP88pLdg7kbxnxGd+E2cNawvuT3fkVTFEJIiByEEIjzsREhZPSIEIIII4QYYJZlVNwKA+zo9EiWJSCEvV7PsoxOb6Hdbo/H4939Iwnnj8KYlpyWfDL2wiC2bKPb7V25cuXk5KRWqyMEEEK6Zk6mI0n+vbu7K4TgnN26dbNeb0wmkzDygyBkjCFEhBB5nodhvLOzJyv7v/3dv8NYnhfM80OiIFXFbrXiODbnAkASp1leMLda4wJRSru9xU/v72GMDEPTdaIbimnpCPI8jynLIKCqAgEACFBOM4IU09AiPyjLEnJGsIIAF6zklNIit009x7DIKaUijZM0zTkHhqZpFQdjUDDKRZkkAVGh6Vo9sxVGSa+3WKlUsjSP/KBimVEQJlF0fHzc6XQIIYPBYDqdWpbJGEvSSJZSrl+/Kr+Wk5MjCVrquXVJexNFERDCNE0kwMnJiWwRQQ3KPoQfBmEc2bbdbHRn8Zj8V5bfdnd3FUU5OTnp9Xr7+/u9Xs/3fUVRGMJZSWleIAAVpKhEwZAQpAz7J1gqiARByVi73UYIcSGqtikjZ0oZFwiCEglBECCQUQjgGff8LLbjCCnz6/PXssML3ujC47NQc07gafaWCyZARqNRURSSOjIrSowVTTV005IdHiEEhGimpSHE0wlifmlK8IKrlbGrnJuaDzJnl/p5FzbvOWeecHZkfk4ZTpAs8EQIcUoLIUSWZYwJ2VV7+PBhURRbWzvLy6uGYViW1W63a7UaKJKtra3BYIQQWllZqdfqURTt7u5yziVRN6XUdV3G2OPHj8fjcRz/Ynl5Ncuyer0ehmEQhEVReNPgjTfeaDQapmlqmkEpvX//Psb4zu0XppNYCAGRIAQAyAmBbtWq1ap5kVqWoSrGwsKS6zYODw+Pjo4o5apK6g3HMLSSFmHoGxpSCEjj0LZNTJCqEMA456wsctUilmUiATDGlCKE5YCKAEAgBBUFYayrKk+TUpZPVFV3HCcTimmpOlQBEmmRh7FPDKKbxpUrawih7kJnZWmVIJQXKQT8zTffnESxqurdbvuVV16Jomh7e2s6ncqcByGEMSSEuG6lUrFkP7lbb/e6vTSKj46OFEVpNBoAwslw5LpuEAREVer1+urqasnowcHBaDSyraoQIkkSecx+vy/rApJPMY5j2bhWVbXRaGCM9076nPMiy0zdUBXN0o08SYs01xWNEMKhUJjCgeAA5GVRpGmz3cqyTPZpKaUcCACBimQGxgUQACEAuBCCAQYAK4UKnvUlX2yH8wne/KpGl4Zyn/uuC4ZwZoQnJyeuU6vX65pmlIynaZ5n5Rn49Tz+PJ/jfWY4anYq8/Yji11wrn8oKdzmne8XnCu4dHeBF9zx+T4y3IUQVhw7z/PJNEYQ67puWTrGihDCNM1Go2UYxunJoCiKdrudZ+WTx9sapKZprq5cGY2HDx9uQiiazWa9Xi3LstGsqaoqi2xSoVT2x2TN5ujo+PT09Nat2zdu3ECQfPrpp7VagzE2HA51Xa9V6zIBGI0DxpiiQsPQiQIAoEwAxkGSRu12yzAMwyQAKpbtANifTEe6qakaUlWFiSLNYsZtSzMMQ0OQIwAR5ggBWjAhABJcxcjUrTzP00yUZV4wijBECBAMozAlhECBBKdQAE0lmqphiCjNLFO3KpbA6Lh/HEQ+A8yq2GbFzrJMlAAAvrKynETZaDDcfPjpKEpd15XoIk3TbLsShqHv+RAJVSWKolQqlm3bMlYqaWEycu/evUePHtXr9VqtNhqPDcO4fft2VhZFUWRFHgRBkqXLy8srKysrKyu7O4ey6CD7THKSCwBw9epVKRoph/cfPnyo63qSJABhDLGuIdetNmpNTVF9BrIkduyK53kCgmqjjgieTKcZE5VKBbAciVJBnGNIS0aLAiKCVZUAxhEUAkAoOEDiDLT9nKDsV1yolxf/hUU+81XzQSJ41lfJjZRlaVmWadqcA6Jqvh/2T4ccQErpzAgFhACetTtmLY15OwFzFIaSX/XCqxdO7le8tlmSiWYSxxCCc2dIIQUAEILKEkAoMEGarpqGiRDiHAwGIwk8yLIsCKIwjHd29j3Pe/XONTmyXZa5aZqtVrNarRKC87w8Oe4jDGq1Wq/XlSp/JycnlUolDMMrV67s7R1sbW1JQnjD1OQmFcIajYahmwcHB48ePer17uR5hnLAAdKBgjHiAHJANMNiAiVZLpHiaV6ougkQ0Q1EC2DZhuOYCsGMlZRi3dBYmVHGAAeIEEXFgAHKiiSNFKzO94U5ZxBiCKEMpEtKpcvSdYUzMZlMiG2bltnttgVBfuIFSSAgxyoejQYQYg2rg8Gp67orSysKJh9/+NEgiLvdbrvdPjnpHx8fCiHq9drKyoofTG/dutHr9Uajwe7uLmOs3Wk1Go3+kwNNUd2KI9M/WdLb3d2VIHin6h4dHR0cHGRZVpalpmlhGBuGIWv3sj1o27asjXme12q1ZK7heV6v11MUhXKiqSoSQFMNxkTJcyiAqZlJkhQFpbRQDV0zdFnUIESZjscQQkSIShDDsOBUAAABQVAAOREBBEKAc8EBR4DPE7LMp3C/7jYzLVlhmX/mQh532YGRIAgGgwGlp1lW2I5LKQ+CiKja3Amdy0ZJI2T8gpHMMsCzlsa55czO7/JVfYEdPnPw809AEM76kLNAlyPMGJOeStM0QpBMBizLMgyr11t89PDxcDjsdHoLC0uci+l0Gsex53mSji5N8yiKxuOxALwoCggFALzdbiuKwhhXVVUyt3e73SAIrl+/3ustxnHsedOjo6PVlfWyLH/+859HUdLr9WzbfnD/0/F4/PWvfz2KSFGwsswYYzbVDVMVAmKMEcZCiKJgYRhPJp5Ee2OMNUVJBbdts9PplEUaBZMk9lUCHdsqy5wJQDBWFEUAVpa5YBwDVQ6qqSoBkMnRASGYFIgPgwjjXNMQxkrgJ5PJZLFWM03dcRyOhUSZVutup9erVCpBEGlYBwIMh/2DvYN6tfG9733vL995//DwcGdnx3EcJKV287zb7d64eS2Oww8++ODo6MA0zSsb66Zpjsfjg4ODRqOh6zpWiGmaIEuzLJNCWpZT0QxdCCGJWCWjsVOpdzqdNE3H47FsD0odGyllMa80LBuMqZdhjHlJoyg6KakKMcHYta08zy3diFIxPO0TQ7MrFYTQZDIp81zWOFRVxec+w9BVJgCCgp8NIYkZK9Q8XcWva36Xl/G8M5TbbIrighHO+OYAACQryP7RSMafYZKfHbpIIYScMdnePFPb5RxwUSIhzoVv0HnnAAHIOccIyT2xAIBxAiHEhM1dsAACwLN/ARACyBInOPv3bJ74/HoA4EAgIRjjJQAEYwEABQJDKKeAOQScM6RZjPE057SkJQRCgyY2dJ0cb23DPNVAKQofUogx0vWs0cCawSEsq5ZzeppNJn5ZijwrHceROXCtSqbj5KPp/XrdabbqhmF+9NEn+/v7EKg3btxY6C1vbm6GQbq/f3h0dLS6uq4oyqNHj5I43djYaDabH330EcAoCILJxPvN3/zNW7evTqdTRsVw4C0sLIRBGIaeputRFNQbNUUFtboNssn6UldV9DQNypIxirOyFLpWlgaCmooxAggzhIGQrIiNXgVCGMVBFkQYobIsiixXVZUDoaqaQqGOlZSVWZZM/dHEG5cH6k3rJkIEQtZt1C0VY4zUIjt9coKIIjTr8OBUN8wvf/nrllnxPB+ohVklUZSlxdQ0zXbPtW2L8vjkdNfSjV6n7joqhsjSlTwOIt/Xm2aGi0Kl0+mQRKTd7tZrDbNmjYaT1fXV3d1dwzCvX79+fHz86suvbG5unvaHURRwzuXwsRCsLHMAQJqWrlspy7LfPxGC27ZZFFkQeJwpAAFdVXNGwziQBpbHNKQZAKBEgmFM87KkPuecl8yoVDRNo4xVHQcQUm02JQheEkBSSuU8qkKwqkAhMBUlZ4xzjgEmSMEQcQ44EwAgAZAAiAMoAOLnYDcCmezeYYxl0V5WX2bhiXybOP9PkTpLsx7jrGCDZ15KnL2ZzzFqzxyRDC/BeTZ59id6jtzx3+rZLgSis6T2Cyo0zz3UhZ3F+bXOTl46SUrpN77xjTAM/WAaJVFZ5hCAbrfrOM64fzKdenmeV6uOU3GLgnpeYJkVRVEgEq1WgxAyngym0+HJ6VFR5L1er9vtLvSWfN/f399P01Q25b/yla/Eceo4jqqqhwdHw+FQuk2A0dtvv40QopTLSkO73WaMRXFACHr55Ze3d54AAN5///3FxR7G2K3Xq9UqZ2DiTYfDcRJnlmU5lQrGGApMsBRUIRgJBRNVJWmeSU8ufzchBCKYqEpRFCWlaVGmaZrmlHIBISSaeu/evZWVFcuykjyhJVcUDRFYlvTVV1+beEEYxpVKxa5Ui6Ioco8Q8vbbbwdBcHh4ODztCyEsy9I1jVLKWNms1SGEN25eOzo41DTt/v37k8lkRjwhh5KCIJDa4zdv3D4+PvY87/S0X6lUVFV95513GGMvvPhylmUzWiQJqUEItdttGbJyzqMoGo1GUuVXjozI0Qpd103TlPjBGaO2XBWyCIkxJqpaFIXMaTnnUiBRKp9HUZQkiThn6JSVRV1RAFE455wKTnkpGIFEwbhkshUEEAAcQQgBBE9LE//JznO2PVMdnRnDfAZ5OX6d3+Bljhlw5uTPXuUXnfJTm3k2RAbPGvCvEqZ+3jMQQuk1iyJLkmhzc5OygjGWl3lRZAJBDhjGuFqtWpbVbLbKQhwfnw76Q01TllcWkyRRVdJqNSqOXW+4aRZmWVoUuRDiyZMnO9t7vV7v6tWrnHNNM770pS89fPjw5z//EADQ6/WOj06Kouh2uwght171vKmqapTSbnfh4HBimJqu641G5969Tz797F4Y+s1m/aWXXlhYWCiKLJ2eSGLcOI4Hg0ESZ61WS7Q6RVFAATiGgmPEmYIRhgBCHKWJoWolpSVniPGyLBmniGAuAGM0y/O0KPOy5AICTFRNs22Hcx6nGRdCIuMNS1cUxfdC3w/TJLcrzvLyqqroYRjV681rL93Z3d0Npt5xeQghlDYWhuGrr748GY6EED//4Geu647H44WFhW63K3XRIISWZTHGJhMvjmNJS2WaZqfTURTVcRzJf/HSSy9t7+xNJhNJ9atpmqxIG4ZRFIX0jRIkHEURhNC2bYxMCU7AGDuOY9v2DOE0W7cSRyXNksKnY98AAIlcUVVVgpyeqjOcz+Bahi2n7WhOsyxjJccIE6JwVvKzXAxAwJFAHHIAgRDsfCxYLmBZImHgIreNOGfmR/PLdWYX83A2Mn8xYC7NE3NjR/PmLj6fnnH2AZdfuuwJL9ve31qwAXNWLea8Hzh31BDAsiyldkUY+QAAXdecqttuN5FCIAaEEFamw+FwMpnqmtlstq5cueJ7AcZKvV6ltBCAp2nMWFmW5WQyGY0Gn3762dWrVyHAf/VXf8UYW1lZsazK4eHhm2+++eqrr8ZxfHR0tLu722w2r127trGxsXu4U6vVOOfv/vRnnU7n1q0buq7//Oc/z/MUAAEhvHPnVhgFlmUkSeS6bhkixliSJEmSSD9QFEWcJgghBARnQJSAl1hBUEEYIoAARwhRAbgQTIC0yPM8LyhTFEUAxIAs0UBKOeOcQ3Ttxq2spP3+cGlpYWFhZepPIBS27TzZ3tV1vVarIKw4lWoUxScnfUXRFteXLd2QtyrP8+Ioarfba2trWZwkSeK6rqSikgNWQghN06T8E8ZYdtiLopBI0S996UsPHjzQdePo6OjJkycSKGOYdlmWcnJC/qaappmm+eDBA8aYoiiWZQEApKsUQlQqFUqphIAbhiG1biTDvPRm0nmcAZWFKMtMGjw6n1YFAMi5sBkj68wIMcYZ55ADIQCCGCkaA5xzwCkjQDAgmEyJABdQcC4EBBg+053/VdbtZT90wX+Seej3BSOZf/N8mDp/eHDJE86OAP+2pudlT/sF0enls589I2bdTwGYYJAzCHi16srbSlnmeY5VBA3NNEwtDfNbt264bm17e2d3d9uyKoSojuPIoDGOQ9+fFmVWFLmmKaurq9VqYzQa5VnZ7XZl7V7y7Z+cnAwGg3v37g0Gg+WllRdffFFRlOFwqCjwwYNP6vXm8spiveH+8qNPqtXqq6++cnBwYFdM01Idx4mTIMsSiESzVQfZZBa4YowNgwAAgyBwKw4+v60xJiAAEHIkEKWlwlQIASQKBBwRzHNRMlqUDGAkIMKKAijPaBqlWRTHULHzzGCMaZqhaCTL8yiKJl7EmHDdumlVgiDK8wJCbFdct1r/+OOP5QxUq9UaD0fHx8eWZa0uLW9uPmw0GnEUvf322w8fPnzxxRefbD6WnL9SGVsIked5EAQAAEppv9/f2dk5OjqqVBxCyO3bt4UQuq5L6if52+V5Lu0kiqJGoyGBaZRSQogMK4qiGI+mYRjKvh8AQNK9yUbibEHOLLAoipIXjluhlMZJlKRxq9XSdJUL1h+MZrEepVSCOhBCll2RfGiEEJOoHIE4TYu8UFQNQ1QKToFAApSccSAEFwAjCAAEQMKpZ4Y4W5fw/En54LnmJy7oE15AeV+Am80A1uB8oWPl+TnhBTf4jJE8j0T1/3+eEHMhtcgBhJKKWQjBh8O+aZq2bcuVQQV3q9VGo+HaqsRblWWxura8ceWazEBOTw/b7XazWXerlhCcUqppSqfTOTg4Ho/He7sHe3t77Xb7pZde2tnZ+4u/+AtFUer1Zq1WW15eXl+7IiVsr1y58md//cfNVmOht7Czs5Pn6Qsv3nZd1zRNLuhw2E+S6LR/dPv2TcbKZqvuOI7a6Y69qYxPCCEQYCGEnKkjEBEsR+MEAAAhAAUUEDIBABcAQVXRhRCUA4xxGIYIKgACJkTBWZKlQRxFScpAsLy8WrHd8cQTgOm6XnHraZq2Wz3TcgWH00kQhnmr1VlYWFhcXD69eyIR6rqqLSwsUFYSiA4PDxcWFhCEw8Hg008/Pdjbl7P2lmVJi9V1Xf4cknXbMIzr127KqvLu7h6E8MaNG+dM3mMZOkp7m9UdsixzHAcAIDlmarWaoihxHEOgKgq2bVMIQWkRRaWmaYZhlGXOWAkAVxQs5VSKosjzFCBBIKKMp1nMitI2zLN2SEnP2xhkZoSEkCiImBAAAA0T2zAhhARhBWYCIy5kKCoAEwwIweWk7Rdh0L5g0V4wivn9ybyZgfNolT/LOjH/GZdjTgBm+qnPGQyZBbfzz1z2tLOjffHFXC7MyB+Rcy4gQhgRhDSVaKqiaqhSser1mqKqaZqHSSx/aUMV7XZ7cXFRJjbDUT/LMqKgvOAlzcsoB0A4joMQSNN0OBzKW9Irr7zy6quv9vt9KQb45S9/eTwej8dTCGG1Wm2325L4aHt7+/r1K9vbu8cnB0TBYehPp36WZbdu3RoO+/V6fWNjPQj9drt5cnLkeV6e523TcCmtVqv1ai1K0iKnaVHAgo6nnkoUQ1V0RREEQagQAQHEmqFDDLM854JhjCEmRNUQQrplcw5KxgtO85JmeVkwziEwbUvVtYKyaOIRQjTDMnSdUnDt+vXJZOp5Xkl5lie6HmZZsbOz0213hBBlXjDG2u12r9PO83x/f79im3GWLSws7Gw9uXLlShAE3W7X930OZrQDQZqmEuTAGNvf39/f33/llVfiOJbd1CAIMMZS/LQsSzltOJtR6vf7EuokRx/lsqxWq5p6xuUhg/aiKOS4xs7OjhyskZhnVVUl2kkQKKsyssMk1VSlk5SDCzLgl64bAKAizASDACsAISAIViqGrhGlYLQUgHAGGYWQCnYOGQP8jH7wrEzDn00Rn67NL84J53c9Y8WZDRPNnB6ao/ieRdK/lnmAOQv8Yk/4q2yX95/dIM4BdBwhrKhY0xRdVw1DK8tyPB5iogIA8pKGUTSZeFndOKOrgVASfuV5xjlrNOqe521tbUEIX3zxTr1el1xPk8nENM1r165JXJVlWZrGZWNa6goeHh6envRfe+2169evR1FkuGxpaQFjfHh47PnTWq2W56ZhGEKIWq2WZUm1Wk2SZHFxceqN6/V65g0IIfV6vdUKseePpz7PspyXcRxzTScQKYQIiBFRsKIqqqoZhqTDK/NMwhgoY0gIx6mmRc7STGSQUUEFwIiomm6apu/7qqq2Wq1qo06wOp0E4+lkbf3qyUk/zTJdN1VVrbi1oiiCKMon4cLCAiHk5OQkjWOVYNd1DU1/+PBhrVZjJW02m4qiLPZ6QRDEYVhrNQkhsgGbZVm1Wpe54mAwiOP45OSk1WrVarXpdHr9+nUJe5B4QFnykaNMskVUqVQqlUq1Wh2Px1EUAQAcxxn0p3J9SsxqkiSmacqcULYHZJf4XE9FMEbzPCWESBQUQiAIQkppterIZVMURZYlYRhwzhVF0YEMa1nBhWBcVUuMFVXBEEIEOGACACIEZ5IsigPIxVnvQgAAzqA3kAt0Po1xoUXxq6xnIhG00mXLe4n02hjj2YgUAEByHIhnqzpP3SMAiqKIWUsDPHVx7JIW79m5ztHgg2cd9DMe8rwVOfvcWbQsj1CWTCEEQoyBwBhrmoIJzPNkMhkyxoqiZIJrquHWG/VGy7btyB94XvDZZ48cxzFNHZxjfZKEm6b+0ksv5HkeBFEUJVJlZWNjAwBwenqaZZlhGJzzIIiiKGo2mxDilZWVv/t3/65pWE+ePEmSRFEUVcXj8bjIabVahRBnaa7ruu/7zWYziiKEgKZpcRyapl6vNSGEEAFaMLkQJ55/Nl3OmRdEpX42jcY5l+SZRUGJpaVpDhAyLJszltMCAUSIGmdpxXbygiFSBlEc+GGz3ZlMJr7vX716tdPp5CXNs2IcTQ6OjoMg6LQXS8qTJPM8j6i6W2s8ePAgz/MrG0tCCMuyNtbXhRCtVrPMC9kq4JTFcaxpim2alUql2WxeuXIFEJymabPZrFQqnueVJZOFTV3Xm82mLJnqun7r1q0wDBuNxvb2dp7ncq5XrhbP86bT6euvv46QbLFO4viMnkPTtIpj27ataVocx5ggRSVCiDiJIAK6qkkPgQkyTJ1xO8vTeqseBIFt20WeK4qSpWkUhoqihEHQ7/dd122326WmlbpOKQ2CIGNnxNMqJipxTV3nAGRZtrS8Mva94Xjke76fRJQxxdQty6IUnS+8clbdkTWk5xobf1boYba8nwlHJRLlPK7js5fn6Srms8R5LPVTuxKAUipnmTDGEIpZLRigzw0vn7td8Kiz5ubnhakYY4wwIUBXSM216zXXMlQI+GQy2d/fp7RstttOpZqVNAgChBAAMI6TarW2sbFRluWDB/fCMJQYjnq93mw2G41WWZZlSQkhUuekLMuyYAiher1er9fjOI2i6MGDB7/xG9+8fv16nud7u/uU0kajwRir180oilRFr9ebSZKlSSa/BzmPl6ZpWVCpXZckWZ6nNUVACHWFGIYhWQYZFXme2bYDCaZMZAXFGKsqAAgjTLJM1gWZQAxwUZZUSqJEScIZTOKMM6CqqqIoRVEUOW133c5Cp6Ss3WkdHhxhRX28uZVlmR/86euvv9nrLa5dubqzuzWeDFfXlgEAjq0Dzv3ptN/vU0rTOC7L0vf90bBvmqbjOIamFEUxHA7jOE6SRDUN2fdLkoRzLlHjhBCMlNFoVJalpukYYzlfJpm5JaeRZMeSTg9jfHJyIsejJLuMbdtCiN3d3W53SXLwJEkiR5zkjx4EgbRSybiFEHIcxzCMaTANgkDGHZL2u9/vM8YWFhYcx+l0OpJpSlZoIYQcCICgPI7jOoamZVlRluXHH3+k6ppZsddWl5M8mwZ+nCZ5mii6fZZOAQYghxBAJCASYmY7MzgK5AByhM6CzflA9EIk+NQIZ8YG5/oTGM9rMn5RAMkYA+eUFrKJfFY+fl5z/1cxxfkYevZgdjGzHQhSAOCUUoakNgBASHpm7LoV0zQNQy+KIi9Kt2q1Wm1As6tXr4Zh+Gd/9udFkb/88stS1aTdbhuGITlXwjBK4gxCqKpqliemaRKsRlF0xu0HsWEYvV5PFiHkMMFgMAAANBqNw8Ptfr9fqzYopScnJ6cn/Xa722i0IMQIEc5BmuZ5VmCkViqaadowPhGcFUVBWSG7ZIyCgjImOMsKmtMyLwC3CcIK4QhxGiaUFZALBgUQjJcUQM45BwykaZ7npYDY0E1VTfKkoJRubKxXKtZwOAYAuLWqlNzwAv/FF19eXFwsKHVcuyzL49OjSqUyGAy+9Nqrsj5p23YUhHEcc841VV1fXwcAEInkFQIKYBmmoel2/YyNQs7ixHEsw6V6rWlZFsZYtl6kqUyn0zjJKKW6rsvSyGQyybJMdiYIIbJvYVmW67ryOJVKRaoOW5ZlWZbs5TDG1tfXj46OKKW1Wg1jvLW1BQBYWlpy3cpkMpL65K5b0XU1DH1K6cnJkeM4kv9O1nIUBVuW4XsJJgRgBBBkgheMFqxgvGy26hBCQDBnOS8zAoRjGFhVgpQBAYAAGCKIAAAAI4whOpMREwIAIdm75QixuDTld9mUyOUpeHFOWjo/DzE7BJ8rtCB0bq7iqVcUQsBz+5GjGL/WdsHm5zEK4HMeCy4Y4wUoSppTVjIOAWcQilrNDYJoOp1ipLZ7C72FJdu2G+7iX/7lD9M0/upXv+xWK3fvfhJFwbVr16RYqjykqujIViGUNKy8Wq1WbFdO2QghdN1sNBqyLHF6enrz5s2VlZUoiiR1hWEYMnxijAEB6/Vmp9MzDXs4GFmWZRq2aellyQzD0jSFc753+CnnXK7Ler0uIFaVMMnyMIgppQXnQghVpQZlCmUAUEOFBBKAORRAcAEQQhBjiLGuFAUtioJyIBgQVKRprqtGo1kjCuoudMqyWFlZznP69d/4BmPs8PD43/7hHx4dHd154dZoMtQ0rSiyLEvefffdpaUlQ9OhAIwxzwuAEJVKpdHopFGcJElepARhwzBkuJ7xM55oyeobxyk8n0va2NhQVXVrazvLsoWFhUqlsru7W6vVdF2XTUXOeb1en/3usnHvuq6iKFEUKYrS7XYHg1NZ15G8dVmWpGmcJImEZCiKEgSexMRK0CTgAnIRB+FxXgjKXNd1LJtS2m40K5WKlLJREEYCpGnGOYeKQoGgZZ56uR8EukwviYxRIVEViDEhmLIyCMMyKqlSkUY1I3MBc3wuF5bxM4nVpZeeGqH8BueD1HlzuuA9GWNYUWc7Sy1VIQSUOd7Ml3JpogghVPJfieXqbzVCedu5HJcKIRCEiqLoOpaCZzXXIhh4njfoD6MoAkD0FjqLyyuUga2trYdJkcSpAODJk+1er7O0tMw5E4JtbW0ZhuW61apbd90aISpnglKu6VC2NDDGrVbLNM0sKySxRaPRStP08PBQIWq32x2NRtvb23deWRccFgUNgkhV1VqtBQTa2to2TTPLckoppazIqRAwirLpdBqGvqJomm7olm3ZVUS0JC38MDJNsywpKynEmANRMiHJIEzVkLBDwUoKEEGKvFfmeR6Eke8FaZZTAUI/KEq2sr6uaIpTdTRVj6KEUhrGgaZp3W6XqPq7P/tZXqTD8eD4+FDTtKOj/eWVxSJlg9O+PCBjjJ6XCSbDkSRAURUi6wVRFDHGpnHIGJtOpxLpImfEy7K0TF227E3TlNQn0mijKKpUKhhjSdsjYeKe51mWpaqqLOokSdLv9/GZTBCZFWaks5UeQg4fS1mLarW6uroqhDg+PlbVMwJBic6XY2hhGEoZ49kksTiXs/YlZoCLssh5STVFbdRqNbcqBM9zWhQFURUAoUawoRAgWJxn6FzCCM7J2c/Swrl6jADgc0eZnskJ5ZvlPX5WzJTOcNarkJmrRCqgeUDMJccq33hWOPrCMccv2J4pqIqngSi4lDECeV8giBBMiCzhMggFIQohCGHYbNZr9bbj1HzfP+2PwjDcfby3sNC9fv3q6tqKpilJGoRhUJal41QBAEmS5BnD2IMAcQ44B71Fh3OO4NmXwDlXFE3yjrZanWq1Gsfx5uam/BqDIPjpT3+6uLiMMR70x1lW6HoCBE6SrN3unp6e3rt3rygKyzIMwzRNI/CjhYWFsmQlZcX5byn7YEAgCFEuBKMsS3MEIGNMV1QdCkVRIOdZljFaYHjG8+X7fhgnWZJFScIAKQqqavqt6zdWVxerVWdra6fd6o1GE4zVD97/QDXMRr1FWbG4tPTyyy+/+urLtVrVC70XXry9fX8rSZKjo6MkSeSZZFnGSypXv0oUOQifpmmZ5WWWc3JWPJf6BVmWSROVReCiKGSrVmoYKooCQCYh1JIqSgJiOOej0Wh5eVnTNM/z0jSV2fhwOHzllddkLTqO4zRNEULNZpMQ0mw2kyQBAMg8YjgcysZGmsaUUtM0Za1LDoVmWSbFTPv9fr/fl7UuCfSJiwxCiACUfCGc8yTLTLOoVh2JZQWcQQRVRanYtsXNeJpKA5F3Fmk7su9/wRDOndjzPeH8M0TMlWTAnKuZTXzNyqfwfF5pdixwHoXC87nfs8IM/1Wb75e3uTuKAADg88H8eea1eVOklBEEAECU0iSJPA9jyHSNFEVRr9fVjoawNpn4h0f9OMlVRX/hzktc0DCMt7d2KMuLImu26isry61WqyxpFCbTqR/4cVkyjBWFaHfv3lUUxXVqruvKz63VGp1OZ0ZfqyjKZDyNosh1XQDAwcGR69YgxHEcm6ajKjqEqNdbpCWPwuTw4HgwGFi2cePGjbW1NVXVqlUSRUkZRvI+KLteecYmnlfQUgqG5SgXQqiqCg2c5zlgnHMeR1GZZ4qi6KqEPUFVVXXDVvWUCUCI6rjVt978Ur1VyfN8Oh3bVgVhXJbl7u7uxA++9a1v3bp1i6j4+vWrjlNRDXU0HigKls06aRhFludFnuc5BvBsqQHIGGGMZXEi+bW4RiRRmBywlHpmcvWnaTqZTHTdgBDK6ovUDJVJnWziSbI/WUlijIVhmGVnViqEIITIuFQattxTjixgjKXSweLiommae3t7CKFr165t72xOpqO8SG3b5oIGoRfFgaLi3b1t27Y9fxKEnm3buq7XiKsoSjQc5Xle5gUQnArOGIuSWMGQFrnjOK1Wq1qtljQfDIeDwSBJknZrMc/zGdYcAIDPWWXOPIecKDhHzMxXMcDndMLJbA7wQiFEfimze7Ps6lBK6fxQ75wRCiHweWEGwqeaZ7+u6M2FgUg0m+rnz+/jl2WpEYLQDFwgpCWbpmkYRpoUB4dHnheqqq7pVp6VrGTj8QQT2Ou1iIKTJJlMgOM4YRiZpmnoVrVaV4heFJxgVdf1Vlf3fZ9RzjmvVCqyspdl2cbGRhynsgAohGi1WpZlDYfDtbU1SunW1mPOxFe/eqNWbUwmvmnYh4eHmqatra2ZppnlCaVnbBqD07GASFVVy3EFIHg0HU+8GdpYToRICjzTtKrVKkpiRcGspBhjhhCBiGAVIWTbGsaKXXGCJC0oM7ygVm/cunXr0cldINDS0lJRFKpqPHnyUNf1OiaO47z8yitTb9zr9aIoPD4+pqzw/enJycm1a9dkc+9gb78/OCWESNyJpqi1Wq1WdymlIzIIwxAAIPGcMjaTLmuG6pReKE1TKTAqwaira1fkejMMQ1q7LALZtn18fDwYDOQkZ5ZlEMJarTYcDmUWAACQuG3Jzy37DTIclUVX+a7XXnsty7LpdCozbUkz1Ww2Hzx4IBmlZPUVnAt7tbud8Xg8ybIiz3heAi6Q4BBwWV5OkoQLmud5EsemadZqNVZpSx2UOI5loC5tUMbql23sgtuYZXbP5IRsjixx/mU54Cw51yThPITQMAyp4HF23BlOTkKQhBDn8SoXXEAg0Fw/Y+5fcA68lrgDcS7XxoXAkuJQmrYQQsyIwM+usAAAMCTQ2fSKiQudkIquWaapaZpODAJNgswoDOOpBwFvOU5d1+I4LmmCLDiMioojABBFEeW5KPI8oGKH7ne7XV2pVBpVmbyNx2PP84bDUW9hQSE08MeKUnS7blEUp8d9XVeXl5fjOBa0jPxJFEW0MEJ/VObR9kH21ltvLTL48/c/+L/93/+vv/3d7xmafnBw0Gv3KpZz5/oV19QsrdJzOya36q36ONVomWuEaIoCeF5zyMaKZQD19HgQ+lFRZEbdbrh1t1LXNBWXhauHSADFQpqjYahhca5koGhBkkW5h0Ta9/2GbrzywhorxrfX1v743/8AEWV1/VrBkEqUarW6vLauEG2hu9Rudp9sPkrjyLR0moCdzZ1J4D3eemKa5iuvvPx3/5e/ByH8xS8++NnPfjYZeLW6u967igkpswRVtCSaXL16VQ7j6qamGep4OomS0LQNoigIIcrLrMgVRUEMUc5sWLEqdp6k0+l0Y2Pj4OBgOBzKEd6z9l2WVyvOYDBQMeGct1qtMssnw4GgZRCFL770ku3aDzcf1Wo1igUjQABm6kTRFUGZppJOs2FZ1mDY1zQVABGGAWMMEVJxXYiVVm+BAVJrdjvLa2EY9/v9PM/1Sfj6izcCQg4oHzGQYS1J85xjU3O8HAhViCQ3SmaqpF6t8SJP0ySYnnjTKeCiZmiUIsYYLbMoiiqmJUHnBtEBYPI2SghJsSwmnxc+uQAAIclgJgAACApAZuv7spOZ7xzKZFRuT73cHNoGzJnKcxzcnO198Q7PVGWevWfIz5JLbnbXqJhmlmVRFBm62mq1HMtUdU3T9DiK0jQtyszQdALP4XgQIoRqtRo6E2aDtm0jhDgD+/v7w+FwOp0uLi7KwEnX9UajAdCZ4M50Or179y5jTFe1drsp7zuS29P3p1Iiqtls5go+Ojj8xS8+ePDg0xfv3HRdlzN6enq69fhxu9l5/bW3bt++XeRUJjBurSoEVBStyPMsDU0dq6parzUNRQWcaopXmnnFchrVlmU6mmKqqmqUSCVIU1RDIRhBwCkUAEDMIYxLmgRRnCTNduv2K69du3lHcPjhhx/WarV2Z/Hje/eJZn/1q191q48mfrC4uPjjH//44ODgK299aTrOTk6P4jC6fv36p5sPp9PpdDp95513/vAP/1AI8fWvf/Uf/+N/LIT47OGDP/mTPymKotVqfPOb38yybG9v7/bt23Ieem9vjxC10+kkSbK4sCznlSSoRQjBGJdTFEEZLC4uyo4oIUTX9cXFxf39fUmuJRnWkiSp1Woy+NzY2BiNRmmaPnz40HIqGGPP87I8F0IUZRlFUZkXrCiLPGclJYQAIqrVqmEYYRiGYZSXpRACQry+vr6zvXd4eGi7jhCQc+44zsrKypMnTzAmEMJqtUo59KM4z0skQJZlUHBGC2YaqmPrmgLkkuMcnNcdFUXRNQ1CqGmagrAkH5NjSQAALpl4FSixbkJgzrnEugkhIH8qz0aeeqpLmM+ZHQIA8Dlh8PlE6Zl9yqR8hn64bHKzg8O51t8X2OH8cS7vNisNn2OUmEwzbNt2HLvZaFQqFUltYBhGMJ1QSpGBFE1BWVYWVABOiFGtugghz/OLolAULJnmZBcrTVPGSk0z5FCcZZmDwZBS6jgO53w6nSqY9Hq9paWFo6MjuYZ83//kk3s7O1umaTabzQyw69ev/29///c3Nx9alkXLoigKKDgh5PDoqN3ai+N489FW6Ed37ry4tnGFx9ypOoFfhmGsKRYxdKTrTsVs1Nzd7Z2j/aMio9NgGoWxZVWqTq1qaQrGEIg0L2iRQyB0RcUqTNK8P54UDCyurK5fv/Xi629g3fj00aaqaMNk2hKiWmuYtiuEiKJoeXn53/ybf7O2tn779u00Tbd3dwAXZZH9+Mfv/L3/8u/pur6zs5OmqcSRHR4e/5N/8k9ardZv/873/vk//+cHBwf9fv8//IcfSJu5f//+0tJSr9djTMgxwkePHslIPssKmdepqiab7Lqu66pRa9SDIIjTJC8LytnC0mKcJlmRU86iJK7VakVRmLZVFIWAQFpys9ksKRVCuK47GAyEEIZhFBBqmlZ1XNeutFutquNSSg9PD1RVlTqtGGPbcWRG+uDBA4VonU5H0bXxeCqLOnEcB0FgmhbGCiYqhyjNizhOoyio1WpAgDzPC4I4N4UQlLGyLKGqAsGEELTMAQCQEBUTFSOCkUIQxlhXVCEEQWfCEBixubDuHFjDZ7UZhMScEV7e0Dml/rxNypwNnStjz3vCC43IeWObFTwvZJ6Xt8vlVvlAFmzm01d5nNF00m21a7WaYRh5ng/GIyEEbiBd13XLVBSl0ahhDNMolikWMJjEMcjsVQhOaVEUmeM4EMKizAbDUwiwnLjhnFoVxzCMVqslGC+yPE1Tz5skSSTLgEdHRycnJ0mSvP76m6+99lq9Xj8cHDPG+ifH4+Go025dv/5K6E0nw9G9e/dMS3/hpTtra1fCKN7aeXf67k8m3viNt16p1+uEKEAgoupYIYATw9DddgcAoCBlOp6Ox9PTUR8NBrbt0JpVr1erFQdAQBnkjArAVMwnQeiFaXtp8dU33uwsrxLdiLKcCq5pxkJvCSmqbdscgP39/TAM8Wj01a9+9f333ycEJ1G0sbEBBXj5lReTKH7v5+85jvPWW289fvz4Zz97bzgcrqysmKbZbrf/+N//yR/8wR90Op3f//3f/+3f/ju9XmdnZ+cv//IvDw8Pj49P4zi+cePG+vr6yvJavV6XJOWyhCidw9nMrm5J3icZC2RZJgnwp9OpTPlUVZUSogCAPM/zNJPLwDCMWq1mOxWE0OlggDEusiwMQ8E4BtDQ9TiMRqNRvV2ToO1+vx+GUavTOVOeUZWlxRVd15/sbO/vH0rBxiAIFhcXsaIWBWUcQKIAiCBCcmSFiVIwAIAhVzvSNGZbumlyWpZ5DuS8Py04wmVZClVVEEYIQsAxRkhTGEFCCJ2Vz67nC9VRDsDzjFBayEzmeobenrUxwLMDUfKlC23GCx2FmQX+iuHo5SNIGi95d5lx9SOEVlfW5FSb7/tFntq2XXNcVVWDwFdVXbfsar3GSko0VQbSCEPOKda0Wt0FACCEyoJVq24QBJZlCSEk/EpRlLxI+4MTJ00BAGWeMcZGo0FZllmWJEly584dz/M8z6OUdjqdWr2+f3Dw45/8pCizmzdvLvZ6t2/eSNMUcsY5f/PN169fv+p5gVt3j06P9g73GWCdTqPeaUiaas4BxAQhAgQCiGi6GUVRrdZo1ls0L0+OTjc3nxwfnnjR9P3Hn16/fv3G9WuWbnBAMlrkrIA59+K81Vu8fvvFdm+pACCeekhRm91eMBkWZfT48Van2xtPw/d+9kGl1rDK8v6Du/3h4Lvf/S7GOEuid378k62tLcPUsiLd2dmRTK0vvPDiZDKWTmwwGFy9enVzc3N1dfVf/at/ZVmWaZpXr1797/67/32e5z/5yU92dna2t7e3nuy8/fbbQohKxZ1OfcuydN2wLEvXdYnJPj09DYJgZWVF/qaGYezu7uJzjSBJZiGpKOTYhFtxCCGPHm+61apZsVGCu92uFwSu62IIoygydUPCvmlRyuUqZTB2dnYOD08o5wsLC9Vq1Q8DCfWsVquKogEARqPR/fuffvs33srLJIjikgnLqgAAMAJlWY68CeDM0DRD0yhnEELF0FVVVWsuhjBL0rIsEYCUUgwgBxAJgCBEEMqYU8rbc84NmEMIIXhqLHwWlZ5v5MKin23SCM9haGCWB+I5OkMxt80m9Oet7kII+kwD8HO2+VcvPBYznhsA8LmS6cLCQlEUEgOlaoYso3mBHwUhwQgphDN5LZhoKoRQswyJZZPHJIRwDsxcl7FlURSTyTTLMoQAY2UUBSfHfXCOX4cQNptNia15/PhxURTNZvPWrTuO41BKR6NRGMYbV1am48ng9OT2C7fzPC/yNM2Tbq+LMdZMw7SMJEuXV5c6vW6z2VpZWbE1Q1E0WuaaahRFKRjHiBcFFUJUHcexbU65pmkAQTnZEE5DAYgfxJNpIOVvVV3DhBwORm+/+MrGzduckDjNKUKM0ZKyshSj0WTryXa7szD1vcdbW29/a63Zai0t+cvLy0mWxmGUxuF/+Q//wc7W9tbWVr1ZV4h2fHy8s7MjhLBtu1KxJaZMwmgP9o9UVV1aWjo6Ovrxj3/8/e9//+tf//rGxsbf+3t/b3tr9969e6urq7u7+5LZkVIWBAFjTNfMIj/D1mCMbdv+/5H232GWp1d5KPp9v5x3jpVT5+7pMDM9WWKUJSSEQBIS0TZwbLiC4+NrAT72se8FTLA54CNfc4UIAgQCC0kocqWRJkdNT3dP566uXDvn/cvx+84fq2qrZkbisZ+7n3762V1dtfeuvX/rW2u977ve1Wq1QM7W7XZhbWgYhrquwyHreR4hxHEcFjMLCwvValXVNNd1G63mkSNH4P23xmNCSKRqmqxQSlVVzefzURR2u93xeEwISaV0SVHgyD569Kjr+LVarTvoW5aDEFJV9fjxo8PxCCFk2264P9s4Ho9c12MxE8UhIonnO67rcizmGIwQyhsqi5EgcDyLSSyGIWYQZhnEcRzPcgghEkeI7plrkDgWaDwBSClGFFECxSiDEUIUH8iE3y886Gu3mgFeOuHxD7KLB3/kjSGN3lCR/uO3g2nwdaEIumrYE0oI2dzegm6YFziO48IoanV7/X4/n8sgjKMoGQwGYeQTQgRBCigmhIgiz7IYhkfT6SxC1HXtbC4NL09VFYbZs8oTBIHFnCzLsCZaFGXAxyH+fT9sNFqm7UA3mMvlTmRPDloNSmmSRK9evmzbNiewpVKB5/mhOWQYzg8ChmEWlhd4XhRFuVyp8DETxyHLCalUmpIgSgKWZcOYpI20JCsxoZ7rIo4tV6teEFieXTx8VNf1kOLRyHQcj+M4HsUEx0auVKjOYlF2w1BSNTPwu91uQtGw1jAyueMnTzle0Gp2Tpw4cfbs2d167Z577tnbKkPi1Tu3EEJrq3emp6dfeumlpaUlSmkYxNMz1Wq1OhwOgZELw/D06dNXrlxJp9MvvfQS7HuhlK6vr1+8ePHa1RvHjh1bWVnJZHLpdPapp55SFJUQ0ul0RkNT0zTQx2iaFifJcDSCQ9S0LFGSEMYJIRzPR3FsGIbreQhjPwjwvkva1NSUrCj90dC0rVqt5vo+kBwMwyBCe72ewPNpI+V5XkTDzc3NWq1m27YoSkBL2rZru46q6LquswJPSKvb7QKuWyqkZFnlRRkWuULDFRlR6Pm2bSOaoIQkSQTEYBT4jMBalgUAIYuxyPMMw7AMTpKEIoIJjcMQISRQnmEYShIGXHs5WA2ECKYIU0IJphjhPej/u+jo68IGhBEHWzLo/SZOO/A70AME+vesbP8Ho+514Tf5WXSgHUX7e3yB24WXNx5ZmUxG0zSe5+MkimMiCoyqKqIscQwbR6HjWEkYMeweA9kf9NKZFKV0OBzCYATLCkDyAMcF4Q14j6Iord1WNpUGWpnneUri4dCFwXDTtG3XkWX59F1nzpw+q+janTt36ruNM2fuqkxVv/3tx+rthqYpR48eVlX1W48/rihacThADMdxPPVc1226vjOdnXIcS1HlbCYTx0GSuIrESxIXRJFCEcYcwwsZ3WARtl1vc3tnZ72WcnxBEMIwSjAbJtjpjnqj4S987Jdy5anOyI5IUqhmEs+3/aBQKIhVrtVqFYvV6blZ03K26/UwDgqFQq1ZazRaqiTXajWO43q9Qa3RtF3v7rvvNgwDnM6iMGk0GqIoPvTQw/V6rd1u7+zUoLYEtbc2ZbAMXylPzc9xYRhub2+3Wp1ut3vmzJl3v/s9sNHl0sVXd3d3YaQ9k8mA+ygMWCVJUq/XGYaBDhzML1RVdRxHFEXLsiRJymeyjUZDlCXdMDL5nGboN27caLRaGGNFkniedz1nPBiSJImLEcZ4dnEGvBQ4jqMUQe2WyaRN2xJFET5ZQRCq1arjOK1Wi+NQmk6EL5EoivlMmuf5Wq3mOjROYoypKIqiJBFCfIpgdlkURZ7d64k4lmUwHo1GwKwEQcBiBnY0YIwJZTDCDGL298bHkKMoQyfD8Difz74xANCB+hMhdFDJDa6sE+nMJEOC8TPGeDKXOAmkSSwdLF+Z/eVN+IAWllLKfZ9VMRPtjiRJYKYAT5TRjX3EKWEYJpMy5udnZ6anFVHoddvD4YASgkkSBEFCIo7jRE20LCuOklQqlc1mOY7z/TAIgjCMdV23bbvT7hmGMTc3Z9v2zZs3jx86miRJs9m0LIeXRNM02+0uRYhSmspkDcPI5wqHjh4xTdNxnB/+4R9ev3r9nnvO9YeDxx77xiuXX/nRD/5IdbqyvrFx/dbNr33tsamZ4g++7/2pVPrxJ566du3G/ffff8/xu3u9XqtdP3LkULmUW1qed50xpjHL4nwuo8lSGAUkThBC1tjsdrvPf/PF9fX1VCqVxKTdbntesLSy/JM//VOCosqqmmAaIyqocpwktucSRKOBOxgOr1+/furM6SCJrly7tluvlavT6+vr1WpVkpRRfzDoDSmlHMM4jjMY9AzD+OAHP0hp8sUvfjGdTi8sLFy7fuXRRx8lJAGraMsaZ7NZXdcHg8H8/DwhZDwe9/v9u+++98knn+R5PpvNtVotRdYOHTr0Az/wAxsbG48//uTFixez2ayuq7dv356dnZ2ZmUmn0zs7O7DLiVIKFSlUKLA623EcXVFTqZSRTm1tbyOWOX7yhOd5t1ZXB4MBgxDLstPVqXwm26jXPcddXl4WFB7ETN1ud3t7p9luI4Q0zdBTBkZsEARBHIVhDAvestksw9FMJjM/OwdHT+B5Yeibo3Gv1+v3+2k9nc/np6enYbUGy7KuZ8VxXCgUDE2HopfnOMuyhsMhhxlw9Tc0HY5pSZIkp78XXAyFC53lGMrgMNzzuaKYcP9IRjoYP5P4BLXOwViaZFEIS8Bv9g6JA8sGJg/L7E/xf/90+D1u3P4NcDa8zxkORyOO42RJ4nmexShM4vHYEoVOEgZh5OuqVq2UGYbpNBuDwQAhVMgXU0YaftxxnPHY4nk+m83OzhYdxynkS4dWjty6detrX/uaqmrnzp3b2dwqFIpT1erO7m6z2TQy2XPnznh+yDDcxtbm9tYOywvVapVh+eMnTjVbneWVQ41mZ319bafeePDBhxFmeE48cuTI5s728vJsJpsnURIGcSaVPnr4yOnTp48cOtZttUWRV1UdM1wYJJbp+b67tDBHqWC7set6DEWKomhGjhPlueXBwHK63f5wPNK11NnThxdXltVsMaEoxnyUhAmDSRDbrjsyRxhjqzEybWtoWnfWN+5/4IEwjvvDwclTxwuFQrPZZFnsRyHLc/lsrtVqkYS++c1v3tzc/M3f/M33vve9/+pf/etvfeubly9fXlxcvHXr1mAweNObHoEfzOVyzzzzVDqdXl9f932/WCzzvJjJZHzfVxT19u3bSUxLRyuNRuMP//CTnufdf//9jz76KMuyq+urnCg4jtPqdgbjEcaYEwWBJI7jjPs9hFA2nwOSsNls9kfDSrHUarWiJM7lcpbrXLlyxbZtUZbDMGQxTpI9WlwURd/1hsNhVa9A5QyAKmRUx3HS2YwkKoIgcGHAsiHYTCZJwgi84zjtbsfzvCD0aJxgRBGmi/NzAsfGcTIaD8BTw/E9RdFWlmbBQzEmCULI87xREPieV6lUwjAkPeJ5XkwSnGBKaRRFHCvuBRFDMcYE05ggRAjFAqyWQIhyb2znXheErwubiR09eq0GZ3IHQnTCqgNgc7B7nOTVg4/zPe8fvIGAkGGYiV4W/EgiipOEhlFECCEkdl03CSPbNleWl0G4ZFoWRiiIo1QqlU6n+2ZvMBghhMrl8tTUTLmcuK6fJMnq7bV8Pt/v1TY3twRBePvb3wGLZpeWlhiGeeWVVxzHfcc73lEolF58+TvNZrPRaOkp474HH3j00Uc/81efnZqampqa2draGUn9ZrNer9dnZuf/l3/+C1EcXLn2aqvRSsLk1KnTlXK1XK0oiha4AYe3IjeM41jRldnZ2Uw2bRhapVLSNM1xbMzKFPMMpixHoygw7SAIAtd1lUxuanEl4kUfc6WpmSOnzxqpzHaji1iGYZiYJJhFLIsH45HjWKIszU/NkVrtIz/2kwmNB6P+cGyWqhWGYbL5zK3Vm2kjA0pIjuNYljMy6WeffT6Tybz3ve995ZVXNjc3//k//+fz8/N/+7d/G8XBT/7kT966dYtSks1mNzY2yuXq6dOnwY+wVqstLCy88MILhIBDD0MJzufzrus7jjcYDC5dukQpjuO4VC08/PDDCwsLURQ9/vjjGxsb/X4fzlaO41RV9X3/xRdfNE3z5MmTH/3oR9u7ddM0eZ43DMP23GazOR6Py9WqZVlzMzMgolAleWZmppDLg85J07TRaDTp3IIgpBSbpomNPRMtmINxXc/3/enUdBAElmXxHEPjhCQxhxkW0ZnpKk2I7TqmaUVx4Eccz4vpdDpKCKVoZFocw8iynJVlkRfS6XQYhrZpea7vhxFGDKwjJRQFCDP4u3JOEHRSiliWxRO7mkIh9/2CcNLyHQRIDzaBB4OQvGb6/rtl6mQs+OD3oAPl7sFylBDC0+8dhIIg4AMS2ImsnEXsHneCCEJI5Ll0JpVLZ3LZtCzwsiIyCPue67ouQkQURT/0WJZVFA0caX3fb7e6wIal0xmWZU3TDoIAWpQkSdauvVoqlR568JFStXL58qtXr11r9/rNVufo0eNvffvbLl5+9fNf+PtHH330Iz/xkz//8z9/9913tzZrS0tLXuBOTVVKpYKR0m/dusGy+Oy504IgDMeW67qFQonBnGma6XTa9wMw+ZR4IU4ihmEcy3Rdt1It8SyHMd2bIbBM0zRt2w4pTpLEspxut2/oqZWVw4IgWZZVKBRYFlyCUETiwbAXx2E+n0/x6eu3rp88eZITuM6w2+o0wyS88MorlUplc3OzUCh5jt9utA0jpSlqLlcY9NpXrlxBCK2sLD3//POZTObjH//4cNj/0pe+1O403/ve97Isu729BZEGkxZBELTb7YcffvjatevAvK3d2WBZFobKIXN6XhBFUaVSabRrcKTed9991Wp1YWFhAmZeunRpY2MDFlcEQQAr71fmFizL0lOG5/uO70mKHARBlCS2bWNKBUFIGylD1TBCEAwDs5/JZMbj8Wg04ji+Pxx2Oh1BkBRNzeeKSZL0hoMgiOBEI4TouZTvuqmUXikWOIzjKGARJkmk63qvN6CU2o4XRTEnSAzLl8tlP3Cz2SzPciC1H4/HvuPm83nDMGzL2t3dHY/HkiACmCwIAo08juNEnuc4joVxe0IJgakDghFiKMGgwJrE0uRv5oDR0yRCDk48TWpU+gZmDyIN+kZQeE5IxYOw6vfsCb9fELL7G7xhGwGIg+M4FgSJxgnGWBRFTZUNQyvmC/lsptNu5vNZiRf6vY5pjnluT+yfyhgzMzOIMlevXl1bW1NVdWlpec84jKBUKqVpWrfT39rakiRpeXn5Zz7yI6ZpfvWrX33p5VckSbJshxXEBx96ZGFh6a8/+zeXr1750Q9+eGlp5T/8+v/7oQcf2djYmCnPTE1NCSIvivxubTOXyziW6ThWsVhkWdxqdRBChw8fLRQK1tjqdDpjx15eXj58+LAoikkYweyPLMssw4T7t8DzgPhGCK23ahhjx3bDMFZVvVSsFAolhmEYiiRZQCSJ4iD03NF4yLK4XC6uX98pT1V7g24Q+YVK/tbqzSAOnnjyyepURZFVjhPSRmZ3tz7o9o8dPaHIsjnqx3G8vb09Go3e/e531mq1xx9/4uMf/1fvfOc7//N//s+ixN+4cQOExPPzcxzHnT9//tq1a9lsThTF6enpp5566sTxU1/72tcEQTCMNKBZzWZbFEVAXyzfBF1EoVBotVqg7b7rrrviOIYJCQg/y7JAHu30R1EURUl8Z20tpuTw0SOe521sbaVSqcMrK5lMptfpupZdLBQMTbcsa2D2U6kU2HAlCbEcxzRNhuHS2Uw2k4+iqDccxDEY9oQMwwQ0iaIgl0mViwWRZVkGqaKAMbUty7E9RdPDKHH9IIhiy3T0dIbjkK7rNEng4zBNk2e5paUlQgiNk36/7zrOZEZXkiQaeSzLChzPC6zAciyDGIQYRBBNGEowQpgS7nXMwcHy8nWQ6SSbHWwUJ/ch2PCexwwGTgnodfzaMWS8v1L7dT3nPw6iwkPRfS93juNA0JQkFGEk8byqqpqmyLKKMQ7DMCGEEBSGoe06rutpmgqLtbudPiUYYxxF8czM7NTUVC5XQAjNzs4nMe12u83GerFY/tCHfqxYLAZB8Bef/rOdndpgNGQo1jTtzT/w6NTsXK8//OQnP4kZ9rd+63deuXjxDz7xf8Vx3Gg1wzjq9kZ31jeWlxcLhfyx43dREpJ8/tqVV9/5znffvn1ze7Pmum672a4UyudO321ZVmfcLRQKMzMztm333EG91kqlUplMRpG1IIgpQQwjMCyNk9APCMMwA8cOw9Aa25lMjhGVkesxlslQxrWddErnMI5CF9OEJoQiHLiBG4Qsy9u2m9Dw5e+8srmzdvTEUVmRXN/LF4qdVhtm5Fv1Zr/fr3vee971drTvQv/MM8+Nx8OHHnrwxo1bN27ceOihhy5fvux53iOPPLK+vm4YqUKhoOvG7Owcx3G1Wq3T6dR2G7qWWlhYePXVq4QgnucnRQd0VvlKDo7gUqk0MzNTr9dv3ry5vr4OLwMhZFkWxrhcLlcqFUmSzt91tt1ujy2T43k38MEvqxIEMDxRKBRInNA4SafTuqqBjRCMSgFcFEQRIWQ0GsQkiSNCKXU9N0koaCR4nmcUUZIksPAKkyRjKLlcRuD5HssKgpBKZwllXT8YmZbrh1EUxQQlIxMgD0opJ0j5bK5YqqyvrcmyrKczPC8CRkopYTmBZWOEEMUkSUiMEow4jsEsgzCBaKQYUVwqFQ5G3cF6clKLQvzsDfUemLfAB5ScE7AUggS+AplzMpUzybEY42ivi/sfRUcnywPANR0mreI4FgSFEMJxjK6okiTwHCdwLC9wgeuUisVqqcgLrGPZo9Ew8H2EEC+IkiSl0+lsNmsYac/zNjY2Nta3dF2fn58/c+bcysoKpXRjY3N1dbXb7W7fujg9PZ1QZBjGmXP3MCx35fqNXn947733yYp6c/XOyxcuXLt56/Cho2sb65Ik+S6ulIsrK0t+4BZzOcwkR48cKhbzKV3b2drudrvFfBFjrEpqtVrd3anbxGEYZmpqGiEGIbS9tZtL5zKZnGmavh9A6+v7Xrvdtk0LY9xMhpRSy3IqlSlV0k3TlnjJtR2JF3LZNEYk9l1VFjmW+q4bJ2Fte9juNBut+vKhpd3GjpKS773vnmvXr25sbS4sLLQbbVXVDS1T294NgzhJaOTZv/prv6Kq6qc+9SlYQL+zs3Xs2DHfdwuFwtve9rannn7ixRdfBGMewzByudzCwoKqqoZhbGxsiILcbrfPnj178eJl0zRd1y+XyyBhwxgvLS5nSilo2NLp9JkzZ4bD4auvvrq9vY0Qgmxp2zbM7ILXUzVbaDQaDMdatm17riCJcRwzHMey7M7W1uzs7Pl77rVG4zurq6qsLCws+LFn2za45Xe7PYbjCCFraxssz6WMjCRJmGMJQb1eD1pNIrDZbLpaLCCS+I6ZSxmlQp5jGd/3h8ORKMgRoUFELMfdrTcxZgmlgiDMzc1NT0+7rttutWAVR6VYyuVyHMOapjkej4fDIaY0nU6nNRrHcRyFJIoRSTgWiywjcIzIcSwhDCLMZIrijbfX5aVJfEIPPWkO9z0IGMuyJs0eQghKC6DaoKuBxVTotZK3/6kbxCrGGAJyIm0lhIRh7DMsQoTwPCIcwnR2dp4kked5DCtHUWSaFsvgQqHw0MOPAA6+sbH1jW88Ztv2XafO/NiP/djRo0dN0+52u88//3yv13ddVxCEYrHIx8uyLBtGmuHYa9eumZZdLlff8fa3y5r+xFNP37x5O53OLi4u1hp1gHBu3dg+cfIuyx7Xa80rV16tlAq3b946ddeJuempfq9naPr09HSr3qrVaojgeq2GVMY0zSAIZVlNGZnhcGzbfqfTL5VKju0BXxeGoWWPMca6phUVQZKU8cgyjDSJCWYZWVN5XrTNcRjGgW97luXLPIdJo1HvtBqmSfOFLHRusBdla2vrAx/4wH/87d8CYbrnBaIoFsvl+k69UilvrN783H//ux//iY9+/OMf/6u/+qs7d+6cO3eu2+1WKiXLsj772c8+/MiDnU6n3+8rinLs2DFIgIANnj59GiMWdPCNRgNcemF5PSHEc30E/suynCTJxsYGxng4HIJJ1OnTp03T7Ha7IL6HCUOMMcyyKJo6HI382q5t22EYSooCMxC6rt++fduznWw2K4vSzZs3C5V8p9MBLTHGGHRwlFJIITzPaykjSSg0nBhjPw4KhZwsy4Hn+r4/ogSRJI7CE0ePeZ4fBpHj+X5EXM/3fZ9hOF4S683GxAAuiiJd0yY6FhhlhqOEUsqL4nQ5FwaBS+MgTJIkRAnlODZKGIVjWUwZShlM8VS1iA5oryc3yHjkwBz9BFYBDgT6PfhVwzCE2daDuRQeVlRkTdMy6TQQjKZp9rs9y7I0TSNRvGfhelCVxpLXPQg8jizLE+4x2V/ymk6no8Egk8lJomJZznhk86yQzRYNPcUz/NLSEk3I2tqapitvectbThw7FsfxhevPbG1tWZazsnz47NlzqVSmXmtubW01Gg3XdcfmMEmiTCady2cwpq7rpnghn88/+9xzU1NTKysrzWbzgYcf0nX9rz771xzH3bhxI18sgLVeOp2+cOHCqZN3J0mCWfbSpcuEEITZ8lR1MBhRhHQ9dequM0mSlKemPM9Tdc0wjMR0KaWQ3uM4BoGloijD4RCUg6qqAvEFZYiSkZl9O3cGYT9wbdNyXddQNVEQwtDvd7qj0SiOIvCZdoMRz/NJTAVJEwRxMBjpWur8+fONVv3bjz/25je/6Zlnnzhz5tSPfvhH//d/+2uiyM/MnPvGN/7hRz7w/n/2z/7J9evXVu/c8l1na3tjenq6vrOrKEoYxrdv3vrAB350d3eXJEhNG/V6PZvNJkmyvn7n2OEjd911125t++knn1peXmRZtr67myRxNpvFGNu2HYTm7OzseDxeWFgwTWtra2dpaanV7Ph+yHG8KIqIMq7rg7V+GIYwrrm0sowY3B8ObdfZ3NwMk7hQKIgcLwjC4ZVDjmVhihzLzmazgsiura1FSTw1NSUK8ng8Hlum63qdTiedzWqaIcsyw7Htdrtea4ZhyIrorhMnBUHI53KB622sreeyWU3TdnZ2dF3P5nKaoXth0Ov3x+NxEEeuJ/i+H0UBy7I8z/IcIwi8KPGYklK5wPOcY4193w9DH2wdccQzDJPEcRT4CFNFFCSRZxFFScKzmOcYhhI8N1s9mPoOXvoHg2FyH0hziASWZcGZJwgCdKBpPBg8XhhgjAWeh6I/lUqldEOW5Xa77dnOeDy2bTuOIvCQ5Hne8pyDMTy5ua7reSEhSFUF2He3xxuGgeM4SZgoim7oaVGUWSxgxBTz+dHQXFhYeNMjDwmCcPXqFdd15xdmlTSTz+cVRWvUW5cuXa7Xm8VCeXl5+ebNm3EcJyQSRT6Xy+qG6vvucDi0O12GZU+ePMlx3MbGxgc/+MFOv/eZz3wmV8j3+33DMMaWOTc3NzMz8/nPf/7UqVOt5uDOnTvpbLbX6/d6vff84Ptqtdqdjc3ZubkgiDDL2Jb7zve8e2dnN5fL5fN5bzCGIw88pKHelmW53+8DvGYYBijUoe4gfAI1vCAIIi/EcWyZI9eyHcfJZlKiKA57/e2drWF/AP0zYiOGYQI/4nlREtX+aEwSNDc39453vfOLX/z8tetXfuFj/+Lf/O8fX1lZ/q3f+s1f+uX/x9raiNLkB978iKrK/9v/9i8vXX4lpWuf+uNPBkFweHnliSeeMIx0p9V+85sf/fCHP/yLv/Cxs+fvmZ6e3tzcvHHjhijyLMK6rp89d3ppYfHatSvdblfkeVmWqtUqyESNlFir1TBmjh8/Xi6X//zP/xIj1nVdjFnAioeDcRzHKysri4vLo9Go06zVarVMLivKEsNxDMdub2+bjp1KpXiGVRRlujrFYkziJPQDWZZTaW17e9vxXFmW44iMx+OROR6NxtVq1XKcJKG5XE5PGcPhsNPuEUIYgR5aWu71er1u9/iRoxzD9nu9Q4cOgRQEYSwpMuZYx3XH47Hje0Eoj0aD0WgUhiFCRBJ5VVVUTY4CP5fPFLLZdMbAGLdajeFwKAmioeQwxkkc+r6fxBEH2y3iSJElgWdlgedYFq8sz9MDwmi037Ml+47Xb+wVXyd2AaBpIuCmrwVLWYEnhJB9WJXneYHjWZadmZlJ60YmkxEEwbasWq22tbXV7XYVQ6P7yOpBSgO2hUD1CxQtRH7ZSFFKdT2la4Zleb12T5G16emZo4ePPProo/ls9ubNGyzLnj17OgiCF55/tjOqdTqdXm+AKKPrhqrqAi/tvTBB4HjYNUwSEoG13nypeuTIkcuXL2ua9sEPfvC/f/7vkiRRFGVrZ9vzPEmSKELHjx8H+2qe5zOZQhAE37lwgeO4fn9w733nL1+6stuo//N/8YtbWzuvXr3iOv5HP/rRJ55+KpPJaJo+V6yAwhjKCvDGpZQ2m00ISAjCSe/tJh6005LAi6JIkmQ47FujcX/QTaI4jqMkilzXdix7b7sYChHCvh8ymFM03XPDwcjEGH/kxz9ar+/+5V//5Y995MO3Vm+urt744Q+8//Tp07/x658gJE7icDweHj9+7Md/4iO+62Rz6du3bz/z5FMcx43HViGXt203CIL/49/9h1euXH722Wd7vV4+n7es8c7mFsMwhWKOY9iHH34QY7x+5w7DYLDcTqfTa+s3MpmMbbuDweDcuXOapm2sb7344ou6ntI0bTQaR1HEcVw2mz118vRgMOh32tvb27woyKrC8jzDscPh0AsDjuMkXiiXy2kjlTYMx7JJnMRx7AeOJEkE0eFwyGBuZmYmJsmtW7cxxs12O45JtVpVdW04HI5HFsMwksaDG2KjXl+YnSsXSzdv3ICLLY5jihDDsWESm5ZlmqYfhVEsQ8GJMeVYFiHCcazAsyyLWQ7rilKpljRNa7eb4+FIVVVDybEMQ2kShiElCYcZeHuTMBAFXhYlQeQ4dt/fHgrCyYc9SUSvowehoJ94gKP9EWNIiZO0OUE+Hd8DyTU8EXiPQ2G5E+/R7rqmlcvlRx99NJfL9UZD2DFo2zZoHQCdh/KM7lshpdMZWAPiDUZLS0sMwzYarerU1I99+KO5XKG+Uzt58mS73b5y9dV+v9vptP775/9G4JhMJlOayk5PT8/NLVim0+l0TdPMpPlCoaDruqIoHM+Y5qjTaZvWCESDLMtevnz53LlzU1NTf/hHn5ydnd3d3d2t11KplCzLlm2fPXt2c3Nzc3Pz0be99dlnn52amitVKtu7u/1+/95774UFQB/84AdHw3632+62W0srhwbDnm2OZEH0PI9NKMzOKhzLiYIgS0mSmKZVKJfgjYXXAJ9CQhKRl1jMERIzDBOFoeM45nA0Gg8cx+l1OmNzKLCcJAksx6Y0VVGU2u4Og1lEKMEEEaqqchjG/eHwS1/60i/+0seu3rj56T//zO/8zm/98Z/+8X/87U98+s/+8H3ve9/f/M1fZzMpyxr/8A//8Gc+85mP/eIvPPX0E+9617u21jcuXrzIMJxlWaIoq6p64cKFwyeOWZb12GOP9fv9xcX5yA/6/f7Kykqv0/3Sl7508uTJ+++/v9Gob2xsIIR6vd799z9s2/bFixcr5SnbcquV6a2trdnZ2X5/CFbZi4uLmUym2WzazjgI3SRJDMOQFJnluf5waDk2QoiXRMdxBJ0zDAMYY1BswlxLr9dDDC6VSiRB4/E4Jkkmk9nc3CSEpFIpsCGGN9bzPNsOrNH4wQcfPHbs2NrtVdu0oMuANVv5QoEXhf5oOByNoGuwbD9JEoZBgiByLCYkASIglysKHMMwOEkSRGhKN0ReUFU18SnGOEkwy7IMx4LmmUShGccRxUwcJzBF8T3LzklKxAcocoZhNE2DccyDIphJBL6ukkQITTgT6CElSeKMFMuyw+EQnNTiOO52u61W69KlSwghLZ3h92vXbDY/N7cAjpRgMs+yLABxDMPADgMJ4+FgPDU1dfbs3ebI+upXv37lyuckQfy7L34+m82mdBUhwrK4UM6qsswwDMxlY8zKkloqlXQ9xTI8dJiUUkCPYOkXQoRl2Tt37tx1110zMzN/98UvaJq2vr4+Go38MDAMY2yas7OzHMddfPXyAw8+sL29vby8HCXJhQsXwPR+bm725u3bhWLu/vvv//KXv9zrthmGOXHs6NVXL/f7fXCh7jXa6XQ6l8tls1myvwzLdd1cLgcZEqqSZH8tpqzKDMIMw4ehb9njYa/f63UsexyHIWZoSlMJjYPQYxiczRgzM9PDbpdlWYyYiFCBFzTDUFWdE4R6s/Unf/xnP/+//IuLr17+4z/58/f/yAcb7fZv/c7/+Tu/+XsXLnxnY/0Ox3G/+Zu/eeLksU996lMPPHjfr//6r//e7/6nz372s2trG71Od3NzO5VKlUvVzdqOJEmnTp2CYQswOwQTpPPnz9u2/corr9x//33Hjx+/ceNGv9+/fPmKpmlxTLrdvucFhw4d4XlREESMaRB4mqbJslguFwmJDUMjJLZHlq7rqq7JqhIlieXYgiBwPA+BxPM8IhTW0RiaLstyFEXVahUxuN/v+16YzWa9wN/a2gZuE8oogecqlYqmOqurq7OzU77jWpZVLBSCIGg5rTOnTzMM02q1wCRGUZQwiW1wFkc0l1Mty3Idx3UshBDGSBQ4XuQty5qdrhqGznOMLMuSJJimGQSByMrgBAn4sCiKHMdSQlhRoZQ6cURCn0MJwZTCchmoPjGilCSIUlCYUYoIoZQQhmUhbCa1KNqHOrl9A/M3tnOpVArKUagwwzCMKAK8IQ5C2H+CDgwfdpoduA8JGQgZjPHEHzaKInhDYZbih3/ofcVSeWw7f/CJ/7p6YxUmXADv8UNPQ5Kuq7zAcBjFNMAEgwubIEgYsRzH8zyvqYYgCDCjBJvlJUnkBdayxpZlPfjgg6VS6dN/+ReLi4tbW1uj0ahYLsG4TTabnZ2dffHl75w6dQowzIWFheHAAjXGu9/9bsdx2u3WRz/60Vs3rokir6rqzHR1qlr+2te/QimNwyAOA0Q4sC1jGEbXdRj2KRaLQA9AkQ9vHQyvJEmCMaKU+q49HPZ7vd5o3As8T9VkkeUZkSUk4XmGEOKFdqO9yzEsSQjDMighTmRxgsDxIsdxo9Fo/Ykn3v+BH6lWZm/dWTt85MT584+sr6//+Z//+fvf//7P/OWfy7L4wgvP12q1JApFif/xH//xX/7lX3744Yc//vGP/+kf/8mb3vQDt27devzxx7VMyjTNH/iBH3j44YdfeOG5fqfLsuzNmzfPnTl78eLFqampM3fdBVble8RgrXX16lWW5cIwlCT5c5/7u52d7cXFRWDwS+VCp9tSVEmUeMwkQegghMIwjEYjWVUMwzBti2EYN/BFUeQ4Lo5jgeNN03RdVxalQqHQatdFUWR5DiZjVFVleY7juFQqNRiNYLgxXyyUy+WUEYDAJZtK37hxo5FK8Rw3tuxerwe7ljHGvV7PcuwgjhzH6ff7Y9tSlJzveXEcYowYhmHw/ph7EnEcx7FsEPiQe6CeBN9Kx3MBnZY5RZAkjDEOAoAzwzDmDraC9ADHAKfypBwl+0uJgWyYzEzAxYFfy/XjAxT/eDxmGJj73ytuKdkDWqMgCMMQYywKAsMwYEEpicqkR40TEoV7rSmDuSSmXhwghHLZwtGjR0+dOjUzM/P0M0/evL26s10bDgY8J0J9EsfxwtK8IokUJZZjKkQoFvO8wDqO47t+kiQsG3CsYBipyVQUzDFNynIARXie5yX5xZcvzM7O3759R5blhcVlhmF2d2pLK8szMzNbuzuEoFOnTn/pS186d/fdnW5/Z3ubYdF4bB89evSv//qvFxYWTp488fTTTzWbzdFgqChKo1FLorBSqRi6bOjyaOjHcWRZJvgXh2GQzWY1TbMsC+ptoEMppbCqTeR4iEbPd3zXcT07Dvck9WDihRDhBRYh1vOcwaCnY81yHZbl4oTanh8nVJIVyx4fO3bsiaee+sQnPvELv/iL/6/f+I1/+S//n5/+9Kd/6X/9X8fj8Z07d970pjdtbKw9+uijmWzqwndeunPnDs/z73nPe55++ul2u/vjH/mo43jPP//83NycF4elUunFF19cW1t785sfETn+lVdeOXTo0Pb2drlctm37pZdeOnPmdCaTqdfrg8FAVdLT07OZTGptba1cqubz+Xvvvafb7bquTSldWlq6dOkV2zZFUWy3Q9d1EEJRFLmWl8qk4QqBqQCWZcFhqFQoxmEI7AvDMCsrK88//3xCyalTp6IweemllxzPLZcrcRybtg3uEp7n+b4vSXI+nw+Jl81mDcNAlCqiVC6WOJaVJKlcLnueF4RhEAR+FELAxHE8Gg2hIpMkWRRg2QvlGGZ2dlbXVLpPInCYSetpscDXdtuQpQD01lMZ8BYbDocEB2yCMWG4g2GDD0wbQRhMiIrJfcdxgiCA+vhgxzhh21+HahqGEUVRGASwe4DjOIHjwV8ZnojZ13MDnagoGXTAffQgVBtFka7rhw8fPnnypCRJly5d+qM/+iNVk0mCWJbN5nIs5jDGrMCn02nbtnu9Ds+hcrlUqhRVRaKUcJxhJZYoihwnUIKBEgj8vU3xoigaKQ0h4jg2pbRSqVSr1a3VjeXl5W984xv5QkE3DNd1y+UyxcgwDNXQv/Od7/zoj/7o7dVVTdehQvY8z3Xdu++5u97Y/dKXv/iff+/3/uqv/mowGABhpWmaNR5VSsViPgf9sKYpjuNQmoShPxz2HceCYSKAf6DbAUd6MBCYKlU9z3EcJwx9zFCMKcaIZ/Go3ytXirquWPZ4OBwmSSQIvKoqfMQTQhCKEcIIkSSJCE0IIZub6/fdd98rly4/88yzH/rQh37n9/7zE48/9Za3vO1T/5//srOz9bP/7J8cO3as3W59/R++urK0uLm1vr6+HvnBD/3QD62urv3sz/7shz70Y7/xG7/xXz/x39a2N2dmZrrd7u3bt5eXFx944AFVVb/z8ovHjhxtt5uwIA38s0HPOT21cPPmzSiK3vGOdwWB98yzT9XrdVVVVFX2fKdQKMzOziqKPB6Px+OxYRg0jGRZhpEFEKOBsMZxHEWUYKoo9H3Hss3R2PM8z7fL5XJMkl6vhxF76NChIAp7vT402IqiybJsu06j0SiXqtlsdmB2R6PRvffe2+t2V2/eKuYLlFJw6WYYhuU4gihEoCRJoiIjLNi27bte4LtJzAiCoMqyLIs0IaCAFTkeCxQYfJY10ul0EASYZcMwFESZ53nMMoRgQZRjxMYUEYbFhxfnXsdAkH0btdcBLVAfJvh7L7uYNIEHQxohxIlCFEVRGAKmAkU8wBUswlBtIkrhCvN9X9dyk4edlKMMw9x3330LCwuSJL366qvPPffcYDSYrk7Pzc21Os04jpMY9tLsQfyqLDIMo6qiIouKImeyeiZtUEps21QFfXNzM0noVHVG1w2OExRZE0WRUrq1tSWIXCaTkmVpfmHW85xvf/vbR5YOE0J836/Vau94xzu2drYtyzIM4z3ve++HPvShf/fv/p3tOE8//fReafrii77vX79+/bOf/ey//tf/+p577rnr1Knf+I3fOHLkSBAE+XyeZdlms3nPPfcMBoPNzc1isbiz04KtfWfOnGk0GmD4lyQJEGVQ5U5EDnEcK4IMjoCua0uSUCoXLWssSUKv38nnc5lMqtNt+b7L87znuYZhqIwex7Fp2cPh0PEDzHAMxyaUMS2nPxqLkuK4/n/9wz/8iz//zNrG+r/7d//+r/7iU//wD187d/b0f/gP/8edO6t/8ZefHg36iipls1l7bJ4/f/6uu858+7Fvra1t3H333W9+06N9c/SFL3wBYmM47BeyuRMnTmi6cvXVK0kSJUnSajQOHz6UzWbjOL506dIjD79lfX0dVr4QEucL2TAMJUk0zZHnO9PT06Y5EkVhNBqB5aFvEVEURVmyHLvb7/OioKqqH4Xdbndhdg4htLK0bGjarRs3VVmZmZkZjfvNZtMPA0EQwiAOwxAxOI6TMAyDKCIEsSyLYJERYhFCfuyU8oVcLuc6Tq/dETheVRTf94vF4mAwsB1H0dRCuSSI4mAwaHU7ju0HQQC+OwzDsHg/h1GSSqWqpXK1Ws1m0xzHhUHg+/7q+pbrumEY6ykDlLS5bKHRbi0uLouSZNv2cDjiDiac18UVs6/bpvu21hzHIfpd7fVBIHTys/i1KlAoEuBigt4GUwTVbBLFe054oqgoSjqd5jhuNHRBtcyyrGEYU1NT09PT2Wx2e3v761//+tbWFsMws7Ozx48f7/f7r776arlawJgyLGIxphRBWmBZNpXSSRJBDaApKqUUEjjP89PT0wzD8ZyI9u1MwB5b13XXsxFCMzMztVrt1q0b2WwWIWZ7e3txcfHUqdOb2zuO47qu/673vPf//L0/+PCHPjI9M/dHf/RH7Xb3nvvu/8Y3vjEcDlVJ/Lf/9t9+/etfb7fbR48e/YM/+IO5uZlMJjUajdbWVt/97ncHrsMxxDYHDz94vt1uF87e5fv+eNS3rVEYuDynciySJSkKvSj0JsUCu7/FlbAMolES+yQJkxj7rhP4LqGh6zpxnKKUAl6VK+bCKOptb88XFsIwtFzHDfwoiRhKMGUShGdmq6VqSZL1wWD0hc997r57726321/4u8+99a1vXV29tbm5+cQTT9x//32/+qu/+pm/+PNsLp3JZJ558qkXX3yx3x/ec889mmZsbW397ku/e/8jD/3SL/3Sl7/85a9+9atvetPDKU1fXV29+56zHMeZ5iiXy/3UT/0Uz3M3btyo1+vz8/Pf/OY3K5XKysoSITEsh0mS2DTNH/mRH7lz587YHG5tbYA6n+NYjDHIvnVdF2XJC4Juv9dqtSzXgTbStm3XdXVVBdEIIaTdbluWJSmypmk+G7Isq2iqqmobGxuNVqtSmUqn01s726IoplOpzc3NVE6DSXGWYeIgJPGe0NI0zZWVlWwuNxyPGu2WZdtw5Vy5chVhJCsiz7D7sAhhMM5kMjzDRlHUarWGgx7w2AzD8KLExYnrB1EUJRS7lsNwAsMwtuNQaHejkJsEzyTYDgYhfq21hCiKNI4m3/m6cnFy52BPSBFVFIVlGMuyAFBhGQZwJ1YQ9zjJOAavAT+IU3qqWCzOz8+Xy2WO4waDwZ07t/v9fqfTkWU5m00jhNrtZqNRS6VShw4tW/aQwZRhEceyHOYYhpFlRZWlbDozGPYCz4/CMI4JTRISJwIrXL58eXZ2dm5uAVHGNC3f913Hbzab4J1z/r57VlaWbt++tb2zqapqHMe3bt0C/cri8hL2mFdfffVXfu3X/uzP/mw0Gv3qv/m1X/7lX75x48aP/9RPfvWrX93Y2Hj3u999+8b1+fn5T3/60+fOndvZ3ZIkYXd3d3FxUeTZfDbNs3hufrpRq0mCkESRyPMch9zYn5kqMQyjyhVFUQaDAZA6cOKCjGEShBIvRZEsy6IkMizLihJLkcwwmGPY0WggSQKl1LRtThCSJBkMRmzSgMehlCIGY2BsKTUtvpAvmbZZyKdfufDi/ffd+5Y3P/zE409yj9z3Uz/1U7/9W7+5trb2pjc98uWvfP1nfuZn/uIvP33z5s2zZ8/WajWE0Obm5unTp6Mo4ljhzp07Tz/99L//9//+7rvvfvLJx+M4Ho/Hnud9+MMffv75Z2/dutXr9aIoBG4T7GHD0B+NBwiTOAnr9TrPc5Ikffvb36aUzsxOHTlyTFVV0zTr9d1Go5lPVaCLI4iCOkMQhIwoxHEMHxmlFLr3+m5tNBpVqkUoBXVdVxVUr9fX1tZGozF4Z8D5DlZdkx8EmYQgCKlUisQJRgg++vF43B8MLMfmREHTtG63u7q+pigKz+xJNaHZA5eLlKbDXVjcAA8oimLfDlU9ZTleGBOGYcBDQJZlczzEDI2TmKIEH56boa+9TUpKGNgDGADwTEVRzMADqAD2DaADdOIba1GEEMGoUCgIPD8YDGzbFgSBY1hIdJEfwK4fSRThwUVRXFlY8TwP+gfTNGFumlJaKBQ6nY7neZlMJp/PY4xt2/Z9X1Y5SimDOUEQeVbgOE6RNVVRcrlcp9MyhyNB5PL5TDpjyKLEsnhnp4YQ0jRDFGSEsCTtLQB1HOfYsWOYoY5jqarS63eGw/709HQcEFhCMB6PL1++/KEf+7Enn3zy6tWrf/B//ZdPfvKTq6urWsqYnZ29ceNGqVTK5XKYJOfOnfu1X/u1T/zX//L5z39+2B8oiuS7bi6XKxYLKd1wXMs0TUWSTdPM5XKDwci27XK5PFmE0u12wQ0FdJLAwQLAjBDKptLwGQVBQDCCxSkI47E53K3VpqamOI7b3NmGHp4Q4nuJ67pxHEqSJMkilPc8z6czmWPHjj/99LOHDx1vNluIMj/4g+/7yle+5pP4Pe951xOPfyudNiglb3nrD+xub1Wqpa9//esz1SmGYZ544qlTJ05SihVFabe6V2/dqFQq7Xb7rW996wc+8P5mrX7hwoWLly6wmHnzmx9RFGVzfb3RqB87diyO452dnVwu12g0ANuAYSW4oKvVsu/7kiTdvHlT07R8Pp/P5yVJGvdNx3HGlhmTxA/DVqc9GAwIRhzH3X3mbBRFiND52dlOq7166zbP8w8+dN+VK1fGllkoFDhWGA6Hpm2FYTQej6dmZhzHi+N4enYG5khTqRQrIvAQSuKYxglNCM9xMHefTqdZjrNdx49CzDBhGI4sM/RdIAYButMUVZZlDnOu6xqGAQaNsJI5jiJKacjJuVyu2aq7rsuy7Hg8xhgLHK8oUqFQ4DguDvcz4UFtCn7DlODBgvOgkAVuB7vBN96AUaCwdDqKKKUJwwLMBa4NsiynDMMwDGAgXr1yCUAIKF/321Hc7bUZFucLWU3TgtCDYfB8IRv4Y0IIxglGCaUxiWkcBXHAJWGkyVroeqY5YjHWVYMV2cAN5ubmbty4YZr2kcPHisVSFCW25WKMz58/Twi5/OrFdru5tLQoiBxCaDwee1YoSQ5J6PXr16dn52RZsSz7t3/3d1944cVr166fvOsuSZJqtdrszJykyN/69uO/9R9//RN/8F9+/Cc+sr6+HgSBLIuzs7O9TjuTSUdBmJrRa7vbKytL/X6/UimFvq8pAiICixNz1APPvyiKUrpMKeUYQlkqcIgmge95hBBJkhx7xLKsF/jwZjoOJ4pyQgnDMJSQOCYMg3hehN4hjmOCY8vzvShGfMQTXpSFbDadz+fN0bhYyBq6oqn8+XNn/u7vvtDc3Xrg3jN/+5Wvdzqd97///V/60hcJSY4fP/7tx7551+mTsizX6/XhcHj+/PmLF14RBCmfz7/7XT+YMKjdbouiOB6P/+Iv/kKTlVOnTrEcfuqJJ69cuXLq1Kn5+fn5+bnt7W3LsjzP293dVlUVLgxV1avVahzHV69ezWQyQRAyDJckNIqSfn/Y6w08zyvnS77v266jGfp0scjynGmaY9vK5/OgyR72BzzLMggDvf70008zDLO8sry4uDjoj2D/WRzHMzMzpUql2+0Ph0O4/PaG41i0Z5kZxzzDBp5PkgQW0QyHQ0mWwzjqDQeO6yqKksnnNE2D6zNJElmUYNdiEkb5fNbQdU01CCFhEDi2DU8tFqtVWdL0lB9ECGPYHmvbJqVJJqULrMRg8l2y/nWaT7o/OjgpLyd88Rtjj752MOJgMmRZ1vO8MAgADCSEJBRhjBVFUUQJBlgxQqZpbm5uWpbFYTwB5aEABrrMtu0oihzHRoiCcWgcx5ZlKhJKEKaUkDhKaIwSFAVB6PmqrCiKkktnaJywiMEEJQmNgpjhQ0mSVFUHSTGA3aVSybKsmzdvcjyzuLjYarUYFuVyGdM056eXUqnU+vr6/Pz8oaNHfv3Xf/13f/d3X3jhhb/5m785f//9u7u7Kysr6XS61WmPt8bvete7ut3uxVcv/9q/+ZVPfvKTqqpm06lOp3PmrrvM0bjZqnuOXSoVWq1WLpPieX61vptPp9VcBiGU1pROxyahzzMMSxNJkmKf5TFVVTlJEhL6BGNV5IfmUFG0MHAxxoTQJIh4no/CMIyTdDotCEK73bEtZ2ZmhlLcbrdZSSEMSiiJkyQiSUxjWRYLhdytm9dr9a181qBJyLH0xPHDG2u33/Oe95w9e3Y4HPI8XyqVHMf+7d/+7Y98+EN/+7d/+7GPfexLX/hivV5vt9vnz5+/fftOFEWf+cxn3vne93z729+em5u7fPny0tJCtVT+1Kc+9c53vf1jH/vY5csXm80mxzBzc7PwmQZBEIQWwxKeF1VNYRjGsseypKZSKc/zer2eKEqu66VSaZblzLHd6w1iPxIEwQt8lueKgpDNZovFopYyQNcO00PQ20OFefLkyeFw6DjOtWvXet2B4zjpdFrXDUVReoOBruuyLDfbLU3TFhYWarUamyBNVnieN3Q9Y6RGg6E5HiuKcvTo0WazORqNFE2dnp4OwtDzvDAMMylVFEUWM1DNCoIUer4VRpBCEN4rj7PZbDqdZll2c2DanhfEcYJoEsBeACZJkvGwn9JkFIdRHOwBM5PbRLAG7enBoILAIOg1JmsHRW0Hv3lyR1GUIAjCPWpFiuOYQZjjOMMwQs9vtVq+79P9spbjOA5j0LjhvRmlEE4UVVWhSCOEuK4L5F6SJAgjhChDMaIxJSiOEE0og9g4DKkoCoJkaDqoBVjMptMZwsZzc3NJQre2tizLzueLiwvLhmE89thji4uLCYksa5zL5Xr9DgRYv9+/fft2qVS66+yZb3/72x/96Ee7/d7LL7/8jne84+Lly3DSf/mrX0mn06IofuADH/hPv/tbP/MzP/XNb36zUCiUy8VbN26uHFq2bdu0RqlUajQaGYaxvblezGe3t7eDwDdHw5mZGdd1F+ZmMSUwOhCHQYioY5lxHIs8Bx5WSRKTOCJRKHKYSJwsq5RSzw8NTRlbNIwTVdHjOB4MBlFCoyhxHM+2XZqQmCBVN3K5lCiwCYm8MPB8t1wuDHs9TZF9z9naXOM53Gk0n3/m6XJl/oknvl0s5E6ePPn5z/9dLp8hhExPT//xH//x3WfOMgxz69bqxYsXK5WpbrdLCP3GN77x8MMP7+zswOAS7Db71re+tbm+cf/95zHGTz7+eKvVvOeeeziOe+6550rlNFjf67oahkGn08lm8gzDuK5LKc1ms5pqVCqVKIpUxTKMNCZROp3u9nsjc7y5uel4rmmaoiLD0NDU1BTPctZ4TBMCXYxmyePxmGKQauF0Os0JPDisjsdjUZShtmdZFuQ1Ed3zf0ilUuVSGVMUBgFsHzMMI5VOIwZ7YUAI4Xkec2wQBJqiQtOYJEkc78GN9VoNNr8KnAj5Ay5jQYyarc54PGQw9mw7Cv1sOqVIgu/FPIMFnkV0n6Cf1KKvQ0oPBhiEBN1XtLH7twl+M8mNB3lCGIEHQ2Vuf5sSwzAHdaF0X3bD8zzHM5ihCYnCyI/iAGEiiJymK1EcEBpHceAHbkIiXmB5gaUoQUmMSExRAgoGntszmCKEeJ4XBSHsWw6DWOSkarkCm150XRdFcWpqCsiDL3/5y/fccw+suXRd99q1a7qunzp1Coqora2te+65Z21tDWP89re//fd///fT6TQh5O6775Ykqd/vq6pqWdbZs2e/8pWv6Lr+4IMPHvTtS+vGcDis1+uyLKqqev3G1ZWVlcFgMBz006kURYkkC45rCSKHGcpymOOZKA54gUWYUJQIIqcbaiqta7oiK2IQehQlPMPK8t5wM5BJoDhxXY9SynHccDhutVoJIbbjMgxTKJUWV5bnFuZTqVScRKY5qlQq/UF3NB56ru3aZqfZmJufvfLqpWw2Wy6Xoyja3t7+4Ac/OD09ffXq1be//e2wY1gURYi34XDY7/fhE/z617/O8/xb3/rWTqfz0ksvaZp28uRJSulzzz337LPPHj9+fHl52TTNjY2NQ4cOKYoyMzOTy2WjKCSE5HI5SRY2NjbS6XShUCwUCjzPdzq9jY2t8Xis63q73Y7jGEDsbrfb7XZd1wX1WafTGY1GruuOx3uyb5CbEUJ0Xed5HuRslmXB+B8YpQP9SAjZ3t6GGQBCCIicbNseDAatVqvdbo9GI8hpEAggXRqPx6PBEC7d0WjUarXazeZw1Pd9H65hSZIgW4Dn4tbWlqjI3W630+mEYeg4DhTDiqLADE2xmK+Uy0zCMQnHUIGjAkd4lvAsfIXwbMziENMAkQCRENOIQTGLGUJxQmgUkzCiUYwTwiEsMCxLEUMo/MEJwQlBcYLixB6bPMPqqsZiJoliliCGosD1Yj+ghHAsK7IchxkWYZ5hBZYLYHKO7qnm9oKcIpEXcEIZgmRelDBH/Yj6kYhYL0AxYQnlowSFcUxQQnESEz/BfqGcNXLa2O6b9lhSRDd0X7rw8kyphKMoCdyZal4R0TNP/sPVq8/ff/+JwWCbF8PhqLGxeXN+aaoyVVzbuJPNZ9Y2t+4+f9/G9k6/P/yJn/ipr33tH04eP9VqtDfWNhmCZ6szu5s7b37gkdPHTp07ecaQtIfuObl69RXP7OUMqdfcVUTG9yxKwkIh1+12O71uOpPT0pkgIVomRzmhPzY3dnaxKL568+bAcvumY0XJyI8GXuhSxkP8eqt3u9bijIJamL6921EzRSdC6fKUG9EAseu7tZgTepbjJKQ1Ho5832dYKZ3q2BZWFYckVR1hpy0EY2yNarduFVU9r2TmKwuqoB9aOqFrWdsJsoUKLyt3NjcKU2UcD+49fajfqrmjcVrO3Lh4Z+Nm+7GvvPiLP/sr//UPPk0i7ud/9ucwE4+tBmYtxfADx4x9p9usqSL3yAP3v+mRh7Y213e3d2q1miBIa2sbrU6PE2TL8adnF+46c7cgqbyoveVt7z589PSNm5uN5jCdmVpaOdHpjR0varQ6mMNOYPqxlWCfEaITp08FSXRnfW08HuuqqooSTggTJdOFkshyta1tczQaDAYjc5wvFwuVUqZcwZLcGQwJxUZalxVeVQSS+IHviDyTzuilSllPZQaW2xmNsaKM2v3GzrY9GsaB7zgWy1FJ4cfWgONJEFq+MwzsAfKtoi4tlLIlRcgZeUNOCVg05NRUcTqlZWnMYMpUK9OnTp5OGZl6o2HZtqbrumGomhYMzJlC+dDsAvUjTGgxk2MxRgidO3cuncs6Qdgd9PfGQ6GPh7MEvdbX8P//2wTOId/Lbe1gXp2Yi05y7Pd8wNclXrTPi8CAhWEY6XQ6n8/D9+RyOdgRa5qmruvXrl2jlML/ApZt2/aNGzeuXLny0ksv1Wq1hx9+OJPJpFKpwWCwvr5+7ty5U6dOXbp06ed+7ueeeeaZIAhyudzZs2cXFhaAec/n81/4whcIIU888cSFCxeKxWKtVoPqALbt7cFompZOp+M4XlhYgMkA3/dhMHI4HhFCLNPRdR3QM4ZhoijSNAPG0scjazAYgG9CGMWCKHGcEMaJaZrd/rDV6kRR1O9044j0+31D07vdriRw1mjMIux5Hix5NwwDHt913evXrzcaje3tbVD8QqewuLi4uLi4trZWKpVYFkuS9MrFlz/+8Y97vguOLOfOnXv++ecLhdLU1MzKygrYb0IVmiTJ9evX19fXVVU9e/ZsEATHjh3L5/MnT54UBOH27dsbGxvPPPPMP/zDP5w/f75QKKytrYVhCPuAV1dXt7a2AKLjOA4akP7+LYqilZWVu+++O5PJOI4Dq4fG43GtVnMcR1XVXC535MgRSZJGo1Gz2YS+APKSpmkgSQNDCowxTPGDHykoLnO5TLFY5Hke3H4lSSkUCrBPW1VV6CEppa7rWrYNfVCSJJ7nWZZlWVYYhuCW0u/3x+PxYDCo1Wr1eh0KYIB/YEINdokXi8VMJoMx3tzcHA6HoihWq9MMv7+PHto8dn8D2T8SUf9TtzfGIcTG6/45ibrJoTCZ4Tj4UG8U5UyK4ck/IaRBcgEVJgjiZFleWlp6+OE3ZTK59fXNK1eu+X64tLSysnzY0NPn771f11NzcwvZbP7y5SutVuftb3/nO9/5blmWO53Oxz/+8T/90z89duwYOCAxDAMi1VarxTBMKpV605veND8//xM/8RP9fp9SOjc35zhOs9mE7fY3b96cyBXgjuM4AG/KqhLH8aA/goY5TGLLsjmO8/0QJnolUQnDEFjpKIoYVnTccHNrZ2e75gUJQszOzs729k69Xu/3+57jJlFI4oQmRBS4lKEpijI9PQ2zLDzP+74vCAKcDuC8RAgZDoe9Xi9Jkm63m8vlLly4cPTo0a2treeff351dTWdTj/+xLefe+65H/zBdxNCPvaxj/3Tf/pP6/V6uVxlGEaW5enp6Vwu1+/3t7e3G43GysrK3NzctWvXnn32Wdd1fd/vdDqqqsL+90984hO7u7vZbPb8+fOnT5/GGK+vryuKAhcxqIXgUAB2lFIqSRLoaSeXShiG/X4fLnRoWTHGsO8efKJgLBOMsTHG+Xx+amoKIQTDuJqmQWTCgyPEQGxjsAhLEp7n44gQQjDHyrICw+i5XK5YKkGkOY4DGQV0eaVSiVJ68+bNzc1NyAEgYJyenl5aWur3u2traxQRTdNM0xwOh4qiQPMFnRE3wWDI/u5B9AbpzGuD8PsG5/f8OsMyEzT1IH4z6Tkxwgef96BMZxK6iPxj6CtCCNE9rWkcxz71wzAkSZIyQkLIYDBIkqRYLILPuWMNWJYtFouVSgW4OJ7ni8Xi9evXwSTmySefzOVyOzs7cRzPzs6+8sorP/3TP/2Vr3xlOBx+5StfyWaz0JacOnUKHDc2NjbOnz///PPPv/jii8eOHauW5ampKUEQBoPB5JeNogjEk+CGlMlk2u02kFEpI8MyfLPZvOuuu9Y2N7PZXLfbLRTLAPExLA9eYFEURYQyvGA5PqU0CILhcCwpOqK42xsGQaDrumvbmUzGGo+zht7v97P5oizJU4WsYRgsMx4MBkDEgSAOXEwn0FqhUFhcXNzd3YWxqdXVVYZhjhw58hu/8Rv/7b/9f+v1+tbWRq934tixY3ES/P7v//5P/9Q/ee75pxuNmihrYBEfx7Gqqjs7O4VCYWpqKp1Ow+RELpe7ffv2aDQChvDo0aO2bX/2s581jPTJE6fuvffeVCoDLDbGmOdZjmM4nmVZ7Hlev993TB/YAlC0wwaEidceqJ2q1Sos/cxms+B1FIUBJtSyxiSJDMOA758Q2gzDwEPZnhsmcTAeZVPpubkFTVO2traGg0EulylXK1Hox3EchEESx0EQ2LbdarUIEsHwG7AZgB5gqVu/3xcEoVwuC4LQ7XZhsMF1bdhrAsvhGrs7iiqlUnqpVIrjsNcbRIHHTAgTuCX7K+y/d6ghRL/PH0Lp9/yDD7R28DfcJmzkwQIVfqvveXtjBMJt4roPNi1wfCKEUqmU4zjdbpdSCjO7nU7n0qVL169f7/V6oihCoQJzIYPBQJKkRqNh2zaUsul0OpVKXb9+/ad/+qfX1tba7bau667rttttcOOzbfvOnTv1en08Hs/MzKyvry8sLFQqFUopOJRIkgQguO/7d999N/TuSZL0er12u82ybDqdhuzEcRzPC6KiqKoqy7IsqXCJ9AejbreLMcvwQm84ajabjuPU6m3Xi2TF4AWl2xv1+mPbcnPZQiaVVmVF4HgWMzQhSRSjOGIpAc8ymI0ihMCGKVmWJ9lGVVVVVZeXl2dmZgByJITUajUwtD169PCf/ukfv+1tb+n1ek8//fTi4uLKygpJ6JNPPv2Od7wzjgkgFjs7O71er1KpaJoGwjE4cLe3twEygfWPCKGXX35Z1/W3ve1toij6vr+6ugrAI/ArcAUqilIoFFKp1B4Jwe0tWoGFH2CYnclk4L+8fVNWjLGqqtlsdk87NhzCp8yy7GAwaDQaLMtC5QxpKtm30Od5XtE1SVVYlqeUIox5nk+lUpRgkHlxomgYKUBcyuUyDGHDUCtAsvV6HRLy7OwsXCQgW7t27dorr7xSKpXuvuecJEm9XgdgWHCXRoiBF8+BehMsFSZw6D+S2f5nMyE94OGNEMTa3tTFngnifkkJqXIiVqb7S03f+JiTCEQIkYRAeMMlJXAcSRAhxDRNSnAmkykVCoEfbm5uBkFgGEa5XISTu9PpQJsUJ8lwOCyXyzdXb9dqtVKptLq6WiwWFxcXl5aWMKYvvPCcJEkTXfVTTz1x//33y7J44cIqpfTYsWMvvfTC5ub6Aw88sLu73etsnjhxwvMCUZQNI2VZDiGIUkwpVhRtPLYEQarVGocOHWIYjmHQcDgaDod6OlWv1w09Xa/XFU11HI/lBNd1CUGypnueV6814IrRUsWg00+Hie0GQehTiglBDMYIMYIgDLq9YrE4Hg3K+RwlSUpVhsOhIAi52TxMuIVhOBh04K2gFMOCIZBGbGxsrK+vT83PXrt2LZPJVavV1dU7b3rzw7/9n353cXHxyJFDtmPevn17YWFhNOrduXNne+v4I4+86cJ3rgNHBzRgsVicNEL5fB5yIEIICkXLsmB4GhZrt/iW43hTUzNhGAKVHwQewyBFlQWBkySJ45hqtQq29teuXYPyARbI8DwP+6HgzFUUxbbtOI71lOE4jm1psixLkiSKPMyXDofDbC4ny7JpuVa7PbYd6OiSmDMKBsuyOzs7PM+zLJ9KpQjFzWaz3W67tiPJAsdxDM/pvJHL5TlRdl03CAIgKrPZrCzLEIqwvNnzPMdxQBMWx3F1qkJo0mw2u902x3HpbLpYLCqKVK/XQbyZz1QZcCKBKhlk+5CRvm8m/Ed7vzfeXmc3ivadvA+GEwip4Jbsj/9OforZX839PeMQhjsnTSbk8yiKTNPEGKdSKUVRLMtqt9twVjEMp2lGqVjJZvKU4OFg7Puhquq+Hz766FsXF5cNI/2D73nfwvyS74Xnzt7z2GOPLSwsAEp+7Ngxy7IWFxfDMNzc3BwMBtPT0/fee28QBIcOHWIYBnYh2FAWWlaj0dB1nRCytbWVSqXgHQD6aHLk9YfjMCaypFqmE8exF4SEoMFgYNsuQgwvShgxruPZth3GEcPxHC+32v16s2Watut4hpFKp9OSqIgcjwmlhPAcoymqoWuKLPEc2+l04jieXLXj8RievdfrNZtNGBQE6nVra4sQsr21kyTJ5uampiuLi4tXrlz5uX/yc6+88vJHPvKR7e3tqakpURTb7e6jj7716tXrU9W5SqUSBIGmaalUqlarwR1BEFqtFs/zsLnJMIwTJ07kcrnl5eVqtdput5vNJrARSZLYtg1XsCAIuq5jjLvd7vb2Nqyd2N3drdfrvu8DTGIYRqVSWVxcjPcduyH8IJ9D6wtoja7rULczDKPrOrAacNzYtg2QgaZpURLHCXEdDySvlMEIMa7rmqYdhiHFyPdCeKPWtzZb3Q7LstBZQKSJopjL5cD2oVar9ft9WHkCg3v5fH5+fr7f7167dmU8HqdSKeDGUqnU2tpaq9UKgoBlWQ7oO9gSDqAcTK8BG/7GG/k+AjXy/TJhQiaZEGNM99drcyxLD1COE76LvMGZG2OM9nVy+A36OEVRkiRJYhoEQRRFiJA4InEcrywvi4IcBMHu7q5pmlB8+r7/4P33bmxs3F5d1XW9VCoRQgLL5Hl+Zmbmm9/85rlz5yRJarValNK3vOUtn/rUpxBhbty4cfbsWUmS7ty5Ax//9PR0v9+fmZnRNO3VV1+9cePG0tLSaDSqVqsnj8+3Wi1A+brd7vT0NELIMIxSqbS2tpbL5VzXnZmZcRwnlUpBEZXPF0VR5gTRcV3DMKIwSSjxPIfheMMwOE6IEsLygq4osiw7XgijzxiRbtc2NK1YLC0tLASe06gH5XI59IOpSrnVasmybA4HwKr1ej04bR3HWV4+BGjecDj2fT+fz1um4/s+vGxZ17e3t48dO/H8888vLa4MBj2IgW8//tjHPvax//7f/yafz73jHe9YX78zHJjf/Oa33vLo27/61a+2222e503TBPeT8Xi8vb2tqirLsuvr6yzLZjKZWq2WSqXOnTt3/frN0Wi0srJiW47r+uAr5zjO1NRUsZiXZTFOIteNZVlOpw0aU9gZjDF2HKder8MehGKxCP1YEATgVgqOWITBgIRTRZVlURQ4QgjYMsRx7PoBZIUoiliWzeVy3rAD3WahUMimM45rm6bJUCQIRNdTsiwihDzPkwQRbNQ0LdtsNuEFm6ZpWRYcIkmSpNNpaHwIIaIoIoQ6nU775nVRFGdnZ23bxBiJosjzrOu6umaompJKZSRJYqANAGAKMilwnRASE5UdSPhBb4335TITRAcdMKR5HfQCPwVSXShB9+3rE2iH4CvwzaAURwfQ1NdlxYPPC18EWhxsmuCVZLPZlZUVhBDP8/BSM5kMaDJt2waaAboX27b7/T5YfZqmOTc3B93a5ubmO97xjj/6oz/ieb5YLNx333lCkkIhPz8/5/ve2bNnwjB4/PFvLyzMLy0tvvTSi7qu3XXXqfvvv6/b7Vy7eiPwo0p56tjRE/lcMfAjluE5VnAdX5ZURdYowQzmlhZXzLHNMvyRY8eH45HtOppqYMQ2W51mu53N5hiOkyTFcbz+cIARSwm2LRdRBsqWsTl0XVdRJI7j0oaBSAzvjyLteQtompbSNZEXqtUqy7KmaVJKOY47c+YMCHemp6ePHj1arVZBLQgrZY4dO5bL5ebn51VVDYJA05VUykiSaGFh7uLFC3NzMwAtqqq+traxtLTCMsLnPve5H/3RH22324VC4cEHH7x48eLq6qogCDBr1mg0isViNpvt9/vHjh2bm5uDdjEIgq2trd3d3Wazee3atbW1NcuyRFGs1+vgr/O2t72N47jDhw9nMhlonIrFIqQv+E0Hg0Gv17t16xYE4UsvvQTX1UsvvTQYDBYWFhBCOzs7/X7fsqyFhQXYMgJ8A7AUuq4HQRDHxLZdyI2dXtf3AkVRZE1FCGVzuZu3VkHeOL+4sLxyuNcfbm1tlUql+fn5Uql06NAhOMcVRaGUguJ8MBjU63XgXTDGsHc9CDx45RPkH8qTRqNx5846BwnE8zyw44dCEYKBHoBVDpIKZH+SEL4f79u0TWLvdZkKIQQLrhFCKNkzp0n2K15CCED2EKLfO89CVkTfYwcGtLmdTgdjLMtyOpUy9LQoiqPh0PfCJElUWdZUHTp427anysuO7S2tTAFq6vvhoUNHKKW2bbdb3fvuu880zZ/4iZ966aWXgyDa2tpJovj+++8Pw3B3dxfeh5s3b169enV5eRkwgGKxePTo0Xa7PRwOWZYle+u4Atg3Au8tnItgz16pVKDesyzrkUce2djYkCVVFOThcNgbDAihURRt79YhScYJBfyAMizGOEwIolTgMc/LkiAQksiiGCfhaDQKfJfnWVmWTWscx2I2m5ZleWdr++jRI+l0uqf3wzCczO9kMhlVVWGX+Pb2tq6lYAvA3NzcPzz2mG3b5XK1Wi0DrzUej13XEQTh61//+vz8nCiKzUb77rvvvXjxIkDzn/vc537lV37lC1/4wtbWlizLKysrjUajVCqxLBtFEYjUQNwzHo+NlCgIQr/fZxjO9wLLsmR5T4O2u7t7+PBKGPpRHNbrdVmWV1dXdSUNvRbGuFKptFqtdDoNkqNOp8MwzPz8/OHDhweDwe3bt69fvz47Owu7soHYWJifNQxjPB47jieIIsMwYFOmaZrvB61Wa2VuGt7qJKF7xENCMINAqK0oSq8/PHbsRK/X6Xa7mqbdvHEHFshSSkulEsdxpVLp1KlTL7zwQq1Wu3PnDsdxkBLgF49YDiG0F4E8g/GeFzjHcZQS3/d9x+VA1g3pCMpCiDEYxp2E3yQYGI7FGCOMKaEUI8wyCMFWi/36cxKKeC94IHHB64Awg8echBP09HuN3/cpdyEI33iDQwSOeSgCoQsKgwCuLU3TGMwAGpZKpXzfr9frLM+xLNtutyGBW5YVRdHS0pJlWXNzc5TSy5cvA845NzMLGCxUQZDSp6enC4XCqVOnYMhYFEXQl/d6vbnpAqV0Y2MDnhGKGZjDkiQJIdRoNFRVBfB2Z2fHcVxZVjiOb7bb7XZbllSCmGazSQgSJYnjBM/zhuY4iiKe58MgEnlGV0SYiEMIqZLIUDI2+yIPSkVeiiRF12SBZ1mWIApshOu6lUpFVVXgkeEKg8sUpAUwfdNutw8fPnzlypWtrS3f93d2dgwjvXJoKZ3K3rlz5/r1a2fPns3n841GY2Fh4cknntZUI47jx5549vz580eOHPnSl770lre85fnnn19cXLx9+/aJEyfu3LnTarXuuuuufr9/8eJFwzAOH7lHFOU4jiVJsS1nOBxLkmLb9uLiYq/XKxQKQeCNzdHm5mYul2s2G+qCkclkEELD4XDCT0y2VhiGsb6+XqvVXNc1TbNSqWCeW1xcbDbq5nB06tQJw9BAE+d5nqKqhUKBUGZ7tz4ajRiGTafT9MCuFJ5lESIMw/AsF4ZhfzSemZnp9/ulUmlra4fn+VRKh1UZkwJwZ2fHNE1JkuBVQUqEOQe4VFzPhlIOKGvAxnzfL5VKe85dUciA9T+cRhPyAHKgJO1NOaD9Bb2Ag4GwBgQuEwXpweyEXkshQEUKrm8QM8neZOl3d4lC3QgZ4/sG4Rt6RYwxFJPwOgEEGwwG4/EYXhuoGQBtB3hwa2trPB5fuHABpqenpqZgqQDAbouLi9ls9ktf+lIulxuNRqdPn+4Pus89/0ychKLEExpLslCr7zSatfvuv7fX71y6/MrxE0c5nrl2/cqdtdv5QpZluSQhvV7fdb1isZTL5QmhSUJs2wnDqN3u3L69Cv8VhtEzzzzrBX4Qhbbj9XvD8cgK4ihJqO+H3V7Pdf0gCMa2ZZl2EhOeEziOUyVOUwRdFQ1NSulyylBVReRZNp/PMQxmWLZUKRaLeYJREEeSqoCJo2VZzWYTAAxIRLBPAiGk6zp4TzSbzdu3b0N97vsuTCclSYQx7vU7mq7ANvlisaQo6vr6xqFDh0ejMULonjMn/v7v/17TtPe///2bm5uQKMbjMQiaofVKpVIAijz77LNra2vdbleW5XK5XCgUyuUyyNa3tnZeffXVXq9HKZ0Qj5ZlVSqVYrHYarVg5IVS2ul0SqUS2IXA3L0sy/AsIASH2srzvNFoBA0LlLWe50FdCtbvuVwujvfHnCklCGHMchzHi4LAi6PRaGp2jlJ64cIFx3V1wxgMx9DJTyQKiqJEUbS+vg7g6vT0dCaTiePYdV0QRUOkQZcI7wYMlEwwPN8POJAdQBRNRnUppaD/gnUue+N/DCNJUpTEE2QPHXBkmoQHPbBoDU3I9EkiZRj4ItnXKDAsi/aL2+8Xgf/IDU5HIFrgn7AVWVNVx/ZGo1E+m52ZnpVl+fbt291u1zGtYrFouw7PC1NT07pujOv1dDodBtHi8oogiHGcJAm5c2ftLW95y/Xr16empkqlUjabvXr16szMzF133eX7/pEjRyBv9Pv9s2fPXrp0CQ6U0WgUOAz4iGqatri4OB6PG40GnFxra2sAaguCcPPmzZs3byqK4nshALmj0WhsOwQzcRxHMfG8gFLKcDxGDKWUZVlZ1QzDSPFBkiSyxEkSzzCMqkosL7AcVjU5IVEqrXMcJylyGEcJQaJiI4SWl5dZhltdXfU8b3FxUZaF3d3dMAxd14dPCs7BJElkWd7c3HRdFxa1m6YtSVKSRLXaTiaTYznc6XQAg7l9+87Zs2c7nV4YWblczvf9mzdvVqvVBx98sNFoDIdDhNBjjz0Gk6xPPfVUuVyen5/f3d3t9oaqqg+HQ9u2VUWDaQOO4zRNO3r08Hg8FgQum8sAsRkEnsDKuq6DjAbkDdBTAXjW6XSKxSKldDgcFgoF8Mt48cUXc9nM1NTUnTt3Dh9aPnv2bLfbLRbzt27fbm9uKapBCIVytN/vl3IZlucwhQsVrDlpHMdCSkSONR6PeUn2fR/qI4TQ8ePH4akNw4CZr0KhMBgMQAsOXY+iKNls1nXdbrfLgq2TwKuqIssSJhQhBEQAFJ4izzOAhU5oBvgPSE2QqSck/uQ+2AQAYDPpEics/Ot4eXxAU3bw/p4V8X7gJUkC2fL7BdtBLuQgNcLzPFSzGGPDMDKZDMdx4F8IuRoYKqCMwXEA9GudTofjuMuXL49GI9/3M5mMKIq7u7urq6tQm4HV74ULF+C0k2UZIvDVV1+1LOuZZ54B57Vut3vr1q25ublqtbqxsQHKDwAAAO4CxgwUKrlcrlqt9vv9GzdumKYJJ3er1Wq1Wp4XhGE4Go2GgzF8ojGhECGCIPGiJEmSoij5jGaokq5KmZSWNlRZ5FVFyOdScRzm89lsLheEoeO6upHOFvKIZcBPVZKkkydPTk9Pdzqder0+kYkNh0NVVaGNgZN7cXERWp3xeOx5jm2blUrl2LFjSRKNRiOWZa9evYoQk81mLdOZnZmP43htbQ00Ky+//DL0QhjjZrO5ubkJEjPf92GmwTCMI0eO5PN52HELmC20XpIkPfDAA8ViEa6chYUFQRCAlAemvlKppFIpjLHv+2AGeezYsePHj+dyuV6vB7MdCwsLmUwG9AkQJ0BOCIIAfCnP8zDTwHGcqqqQMH3fD6JwcmH7YQg6R5bhrl+/Tgg5duxEqVTZ2dkRRTGTyST7znfdbhe6tn6/r+s6MHxQf004TCgHYMkUqA5YlgHgc1JCctCoQCKC4hCCagJpwlNCYnUchxV4iEiIWLpffzL7lANEzOTvSfV88G+gKKDEBfH2JJjx99HqQPzRN9zCIICjBarNIAhsy4UlM+VSFTJ5rVaD6jqVSvXaHdM0OYFvNBrgiXT4yBGEUKVSeeGFF5aWlp599tluv3fixIk/+ZM/KZfLLEMxxnfu3AGt2bVr16ampkCjdOPGDYBkFxYWer0ex3FLS0uBY8JusDiOYQ0YQmg4HEIPBt5EzWZT1/VKpTIcDgllbdsVRZHhOUVRMGIp8jmOS6XScBQqisZwPFw0iqKk9YTDSNOUbCEbRYnrewylvCQRSiVJ4gU2SRIvDBhekHmO5bgkSdbX11mGu+uuu1iWrdVqluXk83lBEPL5oud55XLZ90JVVXu9HkJoJpsHqQqlFCaJYB3AhJTb2Ng8dOjI4UNHn3322ePHT8IvNRqNADP81Kc+9cu//Mvf/OY3BUEoFAoLCwsAJ05NTa2vr58/f363tgb1fxi2NFUH1jSXy21ubnIc1+/3NU3hLJZlMdTJQH9blgWBBO8q7KPfc0vhuHK5HMcxsIsLK8uHDh2q7e4Mh8MjRw5RSu/cuVOtVuv1piTLxWIxTlCz3Y0pMoyUoamjfhf6TEEQWMxQmvA8jzELrAb8V5jEw+GQEEQI2djYgKMf5EeapvE8n8lkQFQMM+hhGAKRKIoilsUkSTRNg3YRkoFhGJqi7knE4piDoVtAESBLEkIAj4JClNnfiABYKIcFfEA8DaFMD3iuTcJm75/7ghioSzGh8M1kz5U7EURuwrazLBuR8B8Jwu+ZEnme51gBTjUWY1lSK5VK4PsATrpBQFUE4nqe50kUNxqN/nAA2eDo0aNnz57FGN+4cSObzb744ouu6545c+YLX/hCtVrtdDqiwELbOTs7e/PmzVar9ZGPfORrX/savKpUKrW1taVpGihFs9nsnU4Teg/QoKVSqUajsba2Njc312g0ms0mIGPT09OtVuv69euipAFiKbCMIAgCLzmeixBSFBWzrCBIHMdRjFiWVVVV11KKbEYxr2pyNm04XuB5XpyELOIzmYzr+mEYCrKEMe72esAL67pMCGk129evXwdameOETqdTKBSq1elGowFw/9TUFHzKgADBwKSiKNvb24DrgMIWChaGYQzdWF/fnJqaATQBdF4XL148c+bMl7/85dnZ2TiOa7UabC5ZXV0FTAuwrvHYymazSULBANY0bYTQ0tISGOqk02nPd69du1YoFFiWrdfrPM+PRiOGYer1uqqqhJDhcAg6xFQqBfjq7OxsvV5HCI1GI9BwYk3r9/tR6IP+RBAE0zSDOJFlOZPJ9IajIAhUQ5+MXOwDkHvXJ8gzlheP+4F38+ZNXVEXFhYGg4E56ubzeaDmARyBTYnXrl2D6hQkPkAhIoSMtErI3sYxSGxg7gZyc8dxPNvjACjhedEwZHjLRqOR74eqqrIszzCwihC6VsQwXOhHlFIGs4gimiBCKUzThn4E6RXtV9aQ9CRRRAjQ14RhGMSwhJAoTliWZQQREeJTiqIImsMEY4bhv4u+7IcZhC6hFFOGQSxlecqyCUUkIUmEKEGUp5jhOZ5HDI4Z1o1oJl++ub5RLlZK+UKhVB4NBoVctZDLf2f88uKpo47jPPfcc8tLS+ff/FCn03nh2efy+fz2lUvj4SiXyzXr9ely2bXt6elpJ2ZGdiRpua/8w+NhEOi6vrZZ833iuNb09LTvRa7jj4bmsN9fWVm5cukKIwiBFxSL1WazoTZ73SvXS6VSwrKXrl8PEVnb2WQYhuXw4NowSRIlo8KHbdumruuGJkVRpIiI4xhNx5IkmOYgpaY4jsNMXCgYtdotU65WlpZrtVqW5Ub+iFFEXdd3dnY4jsvlclHs6hrX7/cX52cty+p6fXXq5Hg8jmLkuGF/sK2qaq8/DgKSL+jTM/NBQFOp1KVL1xHm2p3hPfcsl/LGhjloN3YRZRYWFk6dOJ5Op/P54vbuTqfb96M4ZujXv/UP73vfD9398Lnd3k5WVAlJWMRjgo+sHFlbW2U5rKtCNiMHvsJgt1SSZTlIkj4hvdXVF0msRn4UB3EYhnEQMgzIU8xuuwYkBE3iwPV0RRc5kec1XpQ4jvP39nCozXZ7bm5OlOWYkFQmE4ZhbzBA+3i7IIoiSiRMsMT3HTOJWdcLlg4d5nl+p9n1YzQc2wRhQRJTuuF4rj0eqxzruq4XBFA6chyH4sSx7Ewmk0llMWYZzJeK1TAMR6bDi0q1OkUpDYJAVlWEUKfXa66vb+7uMhybsAxFtNbvIoTCOOR0FSGUuFE6nVYEmYmIyolUVuM4tkdjYG4xQpqu7OGckBkB6wcABh1wfDoIS9LXTkJAEZvsOzLhA7Js+B4oMidfwfujSUDO4H0xDd2XrfLMa55iklTRa1mKyddlWZ6IeBiGoRjFcRwGcafTyWWzhmHU6/UkileWlqanpzutdhTFCKF2u/PAAw/OTE/duHEDlteCsAsg3NFoNDMz0263ESIcx+3u7pZKpWarHgVhKnX4O995MQoClmU7nfbM1HQURQgTVZUbjdpw2I8o1jTN9z0g/W3bjOJAURTTHCX7y8M933ddV5KkQiFHwphlsSAKsiIKgsCyDMOIkiSJEo8QyeVy6XQKqkGGRaIoRmGCKGNbriQqnhsUCoU4jkVBxhgH/t5ugl53IIkKy7ICL0H5BH0XxwkwkwFLOJrN5nA4hLmHQqGgaUYURZ1OR1LkdDaTxHRjazMMo51afX5+EZACmWOXl88Konjx4sV77r77U3/yx49+8MOdTocQkkqlbt26kUrrQeA///zz5+4+I8ui67rlUlVVVde1l5ZWEEKrt+qg8gWwHthUkFLMzMyAswHGOIoiMIPVjD2eSdf1dDqtKAqoT/HeykoPIQRN5h6CGLqpTJphmGazGUZJThCCIGi1Wmh/QgAAkCgOQMsFgQcVH3RuwP3AvNJEVUcphSlE2+ahHICUA1VkGIaO58IgIsGIZVkY2en3+yk9tbCwkMvlYCEHIKIw5k/2bSu+q9sGIBXvr1Iib3CO2Ss4Dyi86WuB0El4TNLg5D488oTln/wsVLMHgxBsoPABpdtr4vAAjQgPwnFcRBJCCIMxyzIUoyiKoigRRRE0fnCgdLtdSZBJnICkUJKklaXlmdnpb33rW51OZ2lpaTDoqYpUKhc4jknpGV2Xo8iI4xijxPO8x594NW2kaMIcPXboiSeeYBHO57P1Wl0UuG63LbCcKIqtVst1XVlP+763vb0JA3u8wI5GI9s2NU2jlMRxjBmaJCLLYlEU8/n8sDPSdBlaeYxpkrCSJBkprVKpdDqtyRuoaVoURcVCmVIKkkXbtqF6BHAYYABA7W/evLmzswNTGlCb7fXP+709GGfBABFM/c7MzDAMF8ex6w42NjZs266Up6ampk6dumt9c1NVdVlV0qnst598Yjy2Tp46dfny5aNHjz765h8YDvscxzSbbccZO45z/MRRSollVUvFShB6rmsLAg+UPUJ7py2QCjAMAQWqqqqrq6sYY4C1QBzDcdzCwsKVa9dBKANsIUJoz7MvSUCmDzpnVVVBN8LQvYEEhBjgMwBukSSJECS6bhQlDKCGLIJtaoDQwk9BEwHCVLCTDIIAEBfY1SNwDEheoygCHkIUxXQ6zZljWMFdKJcopbVaLQzDdDrdaDTS6TQEpO/74Get63q/3wfIKooiDmJvgpccTEEH895Bag4dWOv7xqw1yX7wU/H+GsOD7eLr8uTrfnbyLAx+fcjBC9kT1hAKAFKCKORIAgMFGMOg0PbWFou5w8srkiA2m03X9iqlciqd2dnZev/7fiiKoueeey6TyRSyGd/3eQFHQXj06Eq/3z+0vPTKK69wPBPFYRSxsiy5flCtiMdPHBkO++Vitlar6caMZfLjcT+K/SRCmCGKKooSJ6qqZVkJiUgYa5rGcVBoJAyDOZ6P7CCOIsMwJEnwPG88HqqaBAwSLJyK41jVRMPQUil9OOxLkjQej8MwIoSCMaksa47jVKszzWZnenp6a2urUqlYlitJqmk6sqyVSqVMJh9FkesGk7FXGFcfDsdwYQEWWigUkphOT0/funWrWCyGYQyCzJSRKeRLXuC32+2NjQ3X88rl6k5tt15rwkVpjseGob/88nd+8Affe/XlF+fn5zudVqvdmJqumKZpmuMTJ05Y1ng0Hhw5cmRra8tx3HJpCiaGAckAJg3ADCBICoWCoihwbiKEANj0fb9arcLFDSPae6OVUZROpwHlt20b1JSyLKfT6SR0tre3GYYTRNEwjChKgiDQVJ1ihDGOknSSJAzD7YnpRc5znAl9B04zEBuCIMCAL8wlA8zOcZwVeCBPg/9yXVdV1ampqVQm3Wg09mYgHafRaMmyCB65vu8DNi7LMsxeAJ4EhLnv+1xyYICQ7pN19A1q7O8ZhAcjCh/w6p5Up5Oget134v11vwelp1AYJ2EM/wsxt/dEdE9qAzKcPYkcgdM9Yfg9/yiQvQPLAnyApuhhGEZBmMvleFZot9t2y3vzm99crU5/7WtfiaNk4dBcp9MKI19TZKqI6YweeDbLUZZLWBbxCRYQxjh55KFzzWZzZrby9a989dixo9VqKZdNGbrU6/Wyad20RoQks3NVz/P8gJJYmqqUu91uIZ/p9/vzc7OKIq2treVLZUUSx+OxoasIoQFNKCGKIsqyrKqwi5fyPKMosixLu7u7lmXBpQZiJkIoy2KW4eOIiCl5FJs8Jwq8xHOiJCo8JzbqrWajvbKywrGCqujj8dixPVg6e+AUQ5IkZTKZq1evchzHsQIA+js7O7Kslstlc2wXSsVjx45du3ZtY2PzuRdeyGazsqz2er2nnnrq6NGjkqr0+31FlvO5nCxLS8sLhJBcPkNotLCwcOnSJZCh1uu7iqKwDE8JZhkxmykSgmDPJEQyNMOQzWCeExhtz/OGw+FDDz2UzWaffPLJytQ0HKmGYciybJrm9vb2aDQ66GEBVQBsNfYdFCc08t29/MYSjDFJiB8GlFJJEBiOgyGjJIp5nhdUFUga4NtgYA0OL7gBfIgxDoLAcRxEYphLBE1yQikMyHd63dnZWcdxVtfXhsNhNptOpVK2bS9OzeD9AUUgfvr9Pmg5AZjxfZ+baD4nMQOB8T1T4sHK8/uFKFSVk4RG91esvS5c0f44xYTtwG/Ie9+dzKB7gc3iPRRr0hdO2MiEkCQhFKMJY5lOpWRRkQXRGps8zzOIQQgVCiVFUR577DHbtqcq5bW1VUkWpirldqdVrU4FnqPpUrvdOHpkpd/v+76YYtVut5vP5wSB73Wap8+cdBzn1Mmj4/G4WMrSJNB1PYrdMAhkiQ98p1wpt9tt3VD8QJmdnRZFvlIpMQwSRb5SqcRJ2G4LkiTB+E82m0UkkCReEBmEEGYEhmEUVeJ53vP2WiNJkhiGY1m+WCwLgtBsdCnFo5GZTmeHw3GpVEkSks8X4zhOp7OQ7rrdPlzf5XIVPl4Yb6V0z7oSnDI2NjYWF5Z7vR7IhqrV6TiOR+ZYVtXx2NQ0/dTp09vb24IgbW1tzc7PHTl6iGGYbqtdKJeazfr995+nhJw5c/prX/saw+BcLscwjKIohw8fZhjGcbzp6dlOpyeKsqrqpmmXS9O1Wg3IGzhWgIGwLGs8HmuaVqvVZmdnT506BaPYkLFBZAs6FZiEgDxJCIFhImDhEELAuCaRDxew5wWZPEmn05lMptftA6tBKRWQhDFGiDAM4hisp9PAHwIpALOCwBTE+z6A7D79RgjJpFKUUhiCyefziGHa7fba2lpv0IcNNrBSBQTiPM+7vg98NScIcRyPTNPzPEGSEEIJpQQhijEHsMpE0gk9A7yIN8bM98VLDiRJdKDr20to+3cmPwhp8I2Pf7A0nXw/hONeikbfPQWYPbUHm1AaRRFFCGNGEAVBEHhOgEYIIZTJZGzTGo1GumrMzc1Vlme/9a3HZVm87577nn7mSde13v9D7+1229lsWpVFa9xPZ/TQ5yqVih/YLEdZOR1G7mjcO3H02Pb29r333n3l1Uuarlj2iGeYSqWMGSqJfCGf5TjOMLRyOR+GLsa0VCoUi3lVlSVJ9H0/k02xHHa9EM5soD0qlYrrdDmOYzBLKWVZDGq7hETFYjGKYkIIy/IIJXFEctkCxth1dwDq4nnBth2O4z3PSxLS7w8qlYphpDDGpmmBlnVqatrzbADPgMICkg0hDFMOMEoCLwk6GdcPJEVpdTqAkfh+yLJ8r9crlktHjhwBsqGYyzeN1Kg/uHLp4nz1raoqE0Isy+p223Dx9fv9fD7veb7juCzLBkEYBCEh1Bw77U57coGBJAtS1uLiIkKoWCwuLCyIothut0EDDM2FIAhQ+4FZ4OzsLOQrqF0RQmEYAlgyHI1Gls3zPGIZvG+8AMkKwsz3XSZkQt/HlAocN1kxAHwDNJzgAQNT/JNrG65hoAEhg8my3On1oP3TDH1nZ0fTtHQuKwgCkIRHjhyxBiPQS8DqX3hMMDqZpBAO7atV6D5bckDl/QbS77Xhhw6AMcxrNwpOMuf3S55wpOF9/hDt94fca2vXvRimrzkC9jId2ntJe6o6llUUFSAsBnOtVqtYKCiSCv4uiiRlMpmpqalv/P++8cADDxSL+e985zuzs7OVcmFjY2Pl0KI6Erq9JkJUlZWlhflWuxEEXhgGkhBpspLPZAWBO3r08LWrry4uLtTr9WIur6hSJpMZD0eGoR09egQUxmPTK+QyGONUKkWTiGdxEgUpXc0cO04pHfb6uqJOV6c4zLiuOzc9U28G8BHEEdk/nmLfT/K5IsaM4zj5fD4M4uFwGIbhcDhmMOe4Vqqc6XUHqqrWa02e54fDYafT1VQjiqJ8Pn/k8DHbtm/evNnvDcPIgcwAOw/hpIvjGGY7RqORoiitVmtxcVHTDN/3VUPN54sXLlyYm5sDQzRZlkF5t7a2Zprm/Pz8hVe+c/jwYUWVdnd3vvjFLwJvBnlDUZSdnZ0oinTduHLlSqVScWyPYZhSqXRndT2XKxQKBbiIofWChlDX9atXry4tLaXT6dXVVcuyNE0D2QogosVicWK+JkkSKJ+AexwOh81m0/M8QRCKxaIsy8Bk5rJ5gRfbrU4cJbIsFwoFsCYALCQMfUIIwyAoNwghE5UYnA6T9ZtJksDlBiprkFhDyMApwHFcOp12fU9RFIwxWGal0+lqtVoqlUgQEUJAb2AYRqFQGA6HMMQMyY9Syk2OyQlGAo3j5KKn+1bce4Xfa123mQMzfnsXUxwn390hwUymePEBZzS6b6t6MG73ytc4+u4/J3KcPWkfwhShfQAJMyzDMK7rKLqm63oUx1EU27ADIEyAWZZleWNjo9FovOdd7zq8cuTXfuVXT993zrIsjGmlUomjwLZtTdM827Ftu1QoDoZ9MP81x8NqpUII2W1bMJ7PMAxGKJVKpVIpEsWGYaiKxPN83bY1TYvCUJYkeKNqtVqlUikUChMVMshlgiDIZrOAts3MzDSbTdd1YcXsVHXGdd0giDiO6/UGmXQuiiKGYaMoMU2TJKjX67VanSRJ7r/vIXCaAhwCBuRv3759+vRpkDgPBoNcLifL8tTUFAwKViqVTCYzGo1qtcbhw4cFQbBtR5blbDbbbLRXV1dlWR4MBmEYp1IpNa15gZ8vFlieI2EwHo/7w0Gz3QIkc3F+IZPJnDpxkud5nuUW5uZfefn5EydOFAqFo0eP3rhxa2Fh4cUXX+Q4vt/vq4rOsQLLhjzPO443Gpm6nuJ4zvM8sCmo1Wqj0ejkyZOrq6scx2WzWfDC6Ha7hw4dAsxjt94AaX4URZZlgTdxv98HZrzf7zMMA6PS4M3F8lwqnQmCIEpi1/cUTQ2ikCAqy7JtWqqs8Cw3GAyWFheKxeJwONxY34aJwU6nw/N8pVIZDAYXL16emqpM+jIIEKgNAUEFNQtCSPI8mF3mRQFaTdt0QUQOczPrd+40m81isZjJZUHR1h8OEkpEWZqoPrmD2QwfgENfUw0eiJZJz8rsD0BABTXZWU8P+HbT15KKBzPkpFJ9Y2pFb7jBccWyLMdyUDZMXmShUPCj0HGcOEkkSVY0led5kiCMsSLLt27dknjh53/+523T/PVf//UHHnjAJYHneRy7t2zD9VyUxEkU6JpsWU6r2S7kM7MzM3R62rLGV69e5dVCFEW+68VhlMlkioXy3PRsxkj3e90wjEVRLuTyoW6AvVoURYIgwTaLCxculMtlx3EGg8GhQ4d0PdXpdFRVJwSZpk0ptW3XspyxOc5kcq7rc5yAMR+GYcrIQMWYzeYHg4FtueVymWX5MPSKxeLNmzeTJIFuCopA0IuEYWgYBvhwN5tNjuPAz+Lc4jmQXGezWUlSJjsFwPEJlD2gaEWIGY1GyRjZtr2wsLC5uanreqvVAvkYgzBYg3qeQ2mSzRZUVW42m7lcYXt7d3n5UBTFjuPs7tbS6Qyg9o7t9ft9WK6CELPfX6GJ0hI6uunp6UajEYbh1tYWyI9g4pFSCm9ppVLpdDrdbrdarYK4FOb34VxDCGUymYkquN3tybKKMTscDsHzBiHU6/V6ne7c/OxgMJBluVDMAz+0ubm5vLwMCieQlUHufeSRhzY3N+Eih1IWFqIwDAM6GFCTMwxjuy6UpnrKmHAhuVwO7PwAHYUxRXD6msxIgGo3n8/ncjluEn7Qp33PGDh4m5BOUEBCKsf7HOAkSU6+n933U5mgNXRftobeANVM7h98BLTvZcjsewTvFbqIYow9z4spYRiGZxiouVVV1VSD5/lbN28KnPhPf/pn4jD6+7//+2w6JwiCbbvmeBiFviQJlJLxYOj7rudr1ljgBZZnBUJw4Cc8z8YBiQOipSWRlyml3e4wDGOGop3txmDYy6YztmVxDKdpKUqJ53kkIiInekHY7/Y0RU2lUlEQirwgavLudm1+nuu2e7OzsyzmXNdlGCbyiTVyZSUl8MpwMK5WqzyHBv1xJpOJIrK9vc1xQqPewhjD0btnGToaRFEEck2QhmUyGZZldV1nGGaCrQPiBxUUDK1PGGRZlg0jxTBMr9cDYTpwGHFMNjc3WUXp9TqZfK7RbmU8z3MDeDrXdiilYehHURQFwXg4TKKIxFGpVL506VK/PyiVSo7jYsTxPI8RyzJ8KiVsbGyUSiWMcRQFkiRgTEejMcMwpmnW63Xo2ZrNJhR4pVJpdnaW4zjwdMrn88vLy5/7/Bfife8vKJ7B9wmsQ8DWudvtQo3tOI5p26qqIoZBDMdxgqqqQPQnUTwxblMlmedZz7ZK+Vyr0wfBAAwWg0g1k8koigLPC9tjwZ8OITTodSbkPqgOgNXA+6N/iGVABrSxsbG5uZlNpYH/oPuegFCntFotCCWWZV8ThAfz1esgTbRvYMFz3CTXTUpQdMAl8WAjCzf6P2DidjDe0GvzMEII0dc4ke6/Howxdl2XMhhOQUKoF/imafpeSAi59957Txw7+cILLziWffTo0bXV9U6no2ZVOCw4jsOEgg+ayEuj0bBULGSz+SiK+r2RpkgYceXSlEcYSVQ5jrFMW+JlhmGiKMGE4znZc/oMclVVRYgEbsiyQqlU3tzdIoTAmFy73YVf//btO/l8sVFv5bKFOI6HAzOTyWQy2Xy+kMqqg8HA9yPPCxmGCQMShbRYKK/d2RgNzSiKGIbrdHqwGqHRaFiWPR6Pg9Cr1Wrz8/OixJcrRYRQGIZh5AehJysiZqhuqJIkwUQvAOJhGEbRuFqtEkIYhh0Oh51ORxIVnucBAlFV3XVdFmNF0ba2dliW7w0HYG3GZtjl5eUkiSRJEnneskzf9xkGHzl0uFbvZDPFSxev/vAPL4uCyvPieDwEWoXn+f6gm82lM9lUq9USJd6yx45jr6ys3L59u9FonDx5stPpbGxsgGfk1tYWz/OKorTbbZD4yLJcKpVgTl9V1e985zuqqq6srHQ6HYQQqDTBkBKQSVEUp2bkKAhBM81xnOcFgFjKorRb20mn083arq7rqqpeuHDhrW95S73ZS6VSYDkHF61lWRsbG6COoJTClFyyv1MMIgda0Em5QSkdDofgUMzwHBAwMPWyubVlmibQZjzi/SicjHqGSdxot/qjITeJwEmCoq81R5uUlBBRYRRNIu3gOG+0//XJ33vTum+wEj4Ipb4xJv/vwt6zSY4syw5877lWoUVGSiQSKABV1VVd09NLLm2bazPkrPiD/BHD5bddcse4NNoOh5zp7mlZ1SUgMpEytHTt/sR+OBGOAFDDDSuDZWVGRkRG+H333nPPOXc/CN/9qwheIeabeGqqbemzSZ6hF7VtRzcNzrng6uDg4J//83/+y7//1fDhoVlvoM6ZzWbd4y5jxDZ02zAJVZZl6Yw26i3LsnRNJ0pKwT0/6HX7URTFUS5yahquZWiyJLwkjJE4TAnRNM1k1JCCFgUXRZkkBaNmLWgdH/NHjx5PJpOrq+t6vZ5lxXg8bjbaumbmeTGbzYVQk8lM181Wq31wcDhfzuKocJ36/d1E07RGo8W5VIoeHh4LIY6OTgzDAIXK85yrqzf1egtHMmMM6lIooeGf32w2MXCL4xiiHsNgtVpts9lYlvX27Q0E7JRS1Mm1QAZBkKYpAI96vb7Oi5OTY2wLFsJYhZtWvWEYhu/7eZ5mSbIpcimlKPl6uaJErpbp4/Onv/nNb8bj6fHx6dXVFdzzNE1LsxhOH4PBwdu3V4PBACYmEKGT3QJ2qIGOj4+vr6+TJAEzBjSM+XzeaLUfHh4wi68k7eAw4I8qyxLsE6T0lO/Sg2CEcGVZwDjCMJRSMrUt35I4ztLYMLQnT57AcBH5EP0nak62076ynQKBMea7NgJV7pw7gafEaYJfVIwiVtG04wiQUlbkUFgfDQYD8GTey4T7RebHubEKwn0Xtv3f+qCrpDsmhNpx1vaxzSrsP45D+v5MsvoXVx6yrr5zy0FYQnISxXEYRo7n9vv9Rr31+PHjf/Nv/s2nzz87PDz8w+9+H3ie67rddieNQkJIQmlZlrqhpXFSFMVisXIdC6cmEYQojSgtS0sldVNjZVbkiRyPZkRpusYUViApZtu+bdtE8LwsiNKklFlaHh+fYjEQISwMY8t0BgdHZcmjKLYsRwhVCxpxlMZR6tjlaDj7/uUPrVaj2+2+/OFS07TTk0ebzebm+r7d7sAKDRNexojjOPP5XAje63WbzaYQvCwLTWNhuJFS9vu9+Xzuug5jrNfr2rbtOPZ6vXry5DF2DD158kQIAYn98fGJ7/sAEjAGwKW5O+Otbrf33Xffdtrty8vX56dnjNFer2dZxu319fX12267c3xyuF6vb25u5jPe7w9OTx/94fff/OxnX/23//bfnj9/7jgWYyRO4DIqazXfsoz+QVupFqPmarWCdeJwOCyKAk0jIFPLsvI8H4/HWZY9e/as1Wq9vbl1HOfNmzeEkE6nszWeCYJmswl8FXJN1NW6rk+u7nr9jlJqOp1qlLXbbctyVqtVFG5++tOfLmfT09NTQ9cXi9mLFy/u7+/9em8+ny8WC0AvWP8MXQj6QDSEpmnuUhFFAoR0Tu2szDzPw7Fo2BbCcjQa3d7eVgbTkASCZ88YgzjT87xGo6Hvx8+7Cfs/XTSCZYfeD8cS6l1AumqP5oZgo7scu59OqzS7X9Zug20X+WrP/JeSvXHFDqqiCudlZjo23g4otaAF+du//dtPPvlkuVzOihJ/6tOnTzerdVFm5e7mB15ZltiedXgwKIqiWasnSZqlPE3KKIosU5eShWGqlCKEtdtd27TSLO73+2UhNWZoSouStOTCMKyiKDarMGjaV2/emqbZ7/am07ltWZ129/r6erMKPcdVgrSbHSXI7e1tHMab1WYynjYbrShMlWSlFLPZSimBq9DzHSF2K98MA7vchsNhq9XIssT3XSnlyckJpRQ80vv7W0qbZZl3u+2yLB3Hur6+evz4ERwA+v0+pm1gY+FqgAsBxuWaZkgpTdvCQR5F0dHh4cXFhW3blKnr6+ter7MlfNgO53yz2QzvH5LEXi7Ck5Ozv/u7v/v888891+ech2FRlnkYrYXgjJEwDJlGlFKHhwe2VXv9+jVUrW/evDFNs9PpYKgwn89RtmFXdq/XS9O01+udnJxcXl5Op9NWq4W1MHme53kOOAevBNt+arXa+cXjdrOVZnEcx1mSxnEsynKzWTm2VZblYrHgnJcajaKo2+1OxxNBLEoppgtkJ2rHEBLmVJjjQxcuhHDtLdcCjHZML9M0pRqD3jJo1G3bBpYLD3iYdHDOwVwDGIudGWDzvpcJ9/NVFRsffIFNRtVN29ud9EHNSd6HW/eDs+oVq8Rb/W7FTkD/VwUhbnSnvzQMQ5QcHMI0y8IwtB3ns88+O310dnd39+tf/eYv/uIvfv+7362Xm4tH55+9+DRcr7///vuzk1PHqYEuZJi67/tUESysB3WD5wUWGGqaFkcR8f3u4HATroqicBzP8wJR8ulkXhbi5PhQSpXnBerAVssjkuZ5OZnMfN+fTqd5noPIcnV1ZVmOZakgqK9WK6yCieOk2+3puu55dcb08XhaqzWKInvz5k2v15OSfPPNNxdPzsuylPKdA93x8bHrugcHB5hJ6rr+/PlzrEa5vr5eLBZPnz4FYDOdTrEUAcWP4zhJkgCTbDQaSIme5/V7A0rpdDrFBKLX6zlCWI4dh5HjOIZp/tVf/a9v37zWdDqdTk1TZ4w26w0gq2Ve9Hq9V6+Ws9ms1WoJIWaz2ePHj2/vbsJwbRiakKUQXNO08WQYx/H9/W273by/exNFEezlcRwsl0s4AhNC0MgBb4uiaLlcekGNMYadSrPZrN1uHx8fv3r1qpJrSilvbm5Wq5Vpmp7nbfL8YTSUXKAlBnmlXg/yLP3222+zOCry3HEs0zSzJD05OXl9PcRKXXhbVTJiLC+B3wKYt3BIytMY7DmkH3CPpJS+7zmOQymFpBs1s+d5wJ/Vzj20Cpkvv/wS7HnMxt91aHJPOaHtrF/IXn9ICNGZodSWUbYtO3dw5XbSKGHGxpQgQknNMqiUGmF0B5wKriSVmkbVbljJyFY6KKUUrrZT0BNFKN3iMqRRb6Bf94M604wwSiiljWYrz/M0589ffP7ll18+PDz8H3/971zb+d/+6n/57W9/65l297zlOPZ8MSWEtPpNachws4zjWCmla2o2Lcqy9AO71a7VW36UrheLhWmazKbrdGV7tuEbV5d/fPr0aRzzZt3UWS5IuViMGOOue7FaLe/v723bef78+cPDg1JqvV63eMM0vSgaep53eXl3dHS0WsetljUaTwGpfffDt57nFTwTqrRt+/io57mWFM7V1RUMLxazOS9Eu9m7fTvq9/tJnNu27Tudg+6jxWLRbvcuL6+fPn2KtHZ3N2SM9fuH63V0dmbf3Nw/fvx4s9nUavy3v/3D8fHxbLmazBdCiG+++/6TTz755rtvYZAzW85N01yGC8/zjh8dwVC03W7HcUgpdxpmr+WEy4fbt1YUhY7jXDx5dHV1tV5tbNt+9OSJ67qvLq/DMHZ9pWjy8vUfWm3vT9/+/he/+MXvfv+Pz58/H4/HR72Tu7u70XD5ySeftJvp/e3o4pyEm2Q6XazXa9j1Usvkko4m86wUpaLdwVFeluXdwzrNv7982263R6MJEO/1OrQsqyzFmzdXpmmHYbjZRJSOKaVK0SCoC6Fub+91x9hsNv1+P/Dt0WgUOL0g8K6vr03dKMuy1WrpjOGSzrIsSkulFMbu6OKAZAJHLcsSNpYIIaQBzlSeRkqn6yyOeU4ZpaZm6vYmDpM85ZwfHR2dnp6A+zadTmku83XChOJJbhOjzMrrHy4PDg5kUtrE0HSS57t12eTH5gQfNH67MpXu32H/nj/6nYqlUZEBKNm616gde1vt5UmAUVUyZFujDHX59u2Tx48ty7q5eksphb0vFOtffvmlZZr/9b/+1zAMP/nkE8eyv//++6IovJqDAaa7VwP4jgllDQabOC8x5WOMtVotVCB43xeLheOam3C5XC6Xy3m9Hriu22rVgsBbrqYlzylTSRqORsP7+zuUhZqp+v2+41pZniyWM9PSh6P7+WIahiFEVZZlFWXmerYiIssTSrTZbBLHcRiuKSW2bZZlrpRcLueu6/q+m+epEKVp6kkSTSajg6PPANyNx+N6vf6P//iPYRjCNRTGnoyxy8vLp0+f5nn+u9/97nmRYRlLvV5XShmGgcMb5RDZ8Y3QH87n8263PRqNgKm22+16vZmm+Xg8tiyr2+32ewfoIWezGWrgNMpRa8VxfH5+/vLly1qthkwSBEG1GhEjk8Vi4bouMg+KQMO2CCFQ/SA9sp2zPdwrw+Wq2+2apnl4eLhcLi8vL4+PjweDwc3NzXq9Bq4LA2w8Jnow6BVN00zTdDabZVlmBSZEEtbOVRATiG63ixeAtIlhia7rMDKEZAmFKH693qmz3VZpuqN5Sil/9rOfgVyOp4OXXKPRuPruDcIHbBsYGsHTiDEGexS9ipaqINwvSvf7wyqE9uOtusn3FUnVYwrYtwn5LpIJoZAj7TCb6oGUUlyW+79eVbjnZ2dXV1dCqfPjk1qtBu/08/PzTqcjpZxMJnB5qSp4WAzAuZkxhrUQx8fHpsYty1osFmBvwrgFvwVcDsYzSqlms9lqtYKamSQJ1STR+GI94arBVbEKZ6XIKaVZEa1W64KnsI0pimI0Kut13zBYUaRFkWZZnOdJHG9M06zVvDRNXdcSgjcagRAFjIxm8yEhxLRovV4/GLSn0ylT6vCou1qtuEgfX5wQQkajUZaHp2eD+/v7zWZDCAHxGrpYuBhiYZBt27e3t48ePcLU+9WrV5RSz/Ngb6OUSpKkXq8/e/YMC70wtQPgvlqt0jQGCbPRaOm6Ph6PHceBveejs3MU7cPhcD6fT6fTfr9fFJnnOUKo8/OzJ08e397etloNzstGo0cIcV3bsixCpGUZvu8mSeR5XczWtr5eGiOE5Hnu+h5MFl3fxyGoadpyubR1A+NQWGmt1+ujoyNYVKxWK1zxIOUBGsGkAaS8drvt2U69Xn/8+PF0PCl2y94RdSgvqWYAnYI/Nb7Wdf3s7Ay8cKxwYrBKSZKUp+CyAqGsxtevX7+GpxMGQnhti8XipHuEPITjHtQfpdRsNsMzGoah70fgx3G4D5kgzBhl++H3cQB/8MU2j4t3kw9V9Yq7e7L9lS/vr0yjuy0U0+m0Xq/DNwkd/OHh4cnJyWq1evPmTZ5lEKRtNpssSWG0yvMCHycMPxqNxsnJyXxyC9QLD4tGAkqTOI6hcENx32w2B4MB0xOp8v5BS5EiDOMoXpY8SRJeFInruiVP02xj2azSE+Z5GidrqQrT0jrdRrMVcNHGWOX45GCxWHielyRJr9fNsiyMlorkRRnV63XXa3a73VarESdLxhg4K5QK22GMMdthQqaO6y1WOWbBwN/wlq5Wq3q9jvXu0L+u12sp5enpKZeyLEvTsGzLWS5WvhfMpvPNOvz000/hHJMkSZpkRFGN6VEYF4WBOQekepPJ5Pnz50KI1XJNKR2Px+PxGKo/4HuiFEDCBoNBkiT9fh9eZsA20AXN53POOcAYvMIkScD1ofo2a5Vl2e/3cTICe4N70tnpWRRFeDQImpCmcGrgsgSVbLvvqeaCUApaQhJGSZKcnZ2B2qbrumUYSikEYZIkab71pEYFhLGBrus4sJCN8VCoqjRbp5QJIZUq1dagUIciCnvXwGjVdQPzWCiw8RTYeAFhJPI20ux7mbCqS6sESN8PCSkl0Bu2J3v/OIA/eMx9wKa6A2OM7IwS93kwTN8ybLZSQ7VNsMBgNE1Lk7Tdbl9cXFBKsfAM8HRF5VFKodw6PDhgjI3HY16Wp6eng8FgOp2uFgs8IBjAsFfFuGk0GsVxjIXGOLHSNFVsxUXWbDal7JRlzhhtNuuAxfzA0w1NN2i71XEcq1YL4jjudpumqXNeeJ5Tq/m2bSslkGSCwCuKzHFsTaOdTivLsjgOLVtvtmr1es0wjFrNMy2t3WlYluX59ld/9sVoNBqN7gkhjWZjuVz+/g+/se02qizTNL/++mus4xwOh0+fPp3NZm/fvr25ucHsC06HXhBg1TYGVo8ePfrDH/6wWCwAmuOUxMS52WzCQlfTtMVisVwu2+22bTmtZns8mjx79mwwGIBvBT2ulHKz2Xi2h9HC69ev8zw/Pz9XSnmeB94sGipgUfgaK4crv6Mt0E1pWZYgi8+XSxyOIMegigNfdDgcttvtbfRSCu5Yde1xzouiCFo1dMsYr2dpgimIbVqoM/chD1CoMUureJTQJSIU8VPbtuFsr5RahEs8EXKgZVnYWzidTkFbw0GD+lYpxbnwfb9izMPzW0p5fHxciX3fy4T7QfJB5FR9HaGyiqL9+3xwf/JBq/kRGQ5VKHl/aKGUEuTHVcWHh4fYtvX5i0+Pj483mw1WDiA9WqYJel4URUQqePvDgpYQ0mq1sCBtOp06pgKUXNHhweXnnNdqNSyvAniIQBVSosdwXAvFm67rQVCHSQznknNeMTmVUhcXF2AJ4ngmhOCJMMXCB2NZFhoSIQSeER85qql+v49xWb1ex3Cl+kRN0xRCgKHW6XTm8/njx4+BEyLMTNMcjUZYKA9e1WQyAf3ScRwEAwbfOPtxt16vB27XYDBAZwHk/dNPP6WUWpaFMx5ygVartVwukW9RvIVh+NOf/vS7777D4pdarYZJAKxx8GfiAgUiCoC92WzWajWqa8gGs9EckzcEuRACDhebzQbTlNlsBufSJEnw+VJKTdPMdzfM/W9ubur1OiEE4KSp6ajgHh4eoGNQQqCeRPAouiUMgDNAKcXRgLJos9lAYITGD68QoGhlgYEmCFQHQggeDW1qGIY1J5CUlFJkZZHzUhClG7pGaVrkcZqsozAMw3fAzH4m/CAg94NQEbE/fvg4YD6I4S13lLxTD6IF3C90q6dWO8UGqSaNO73SYrGAL60oym+++Qaek7AAQtmDiiWKIkPTYcuNhanHx8eB78NUE4d91TpiC5W2cx8PgoAQAhCGUroFaXSfc6mIrul2s91RSsVRKiRpd/pKqTRNNd0yLVdRRjVmux5jpmVpppmnaWrb1LIs163lec6YyTmxbR/igCQpGGN5LvK87Pf7i8XCNLdiv9PTWpJky+VS102laL8/WC6XWVYwpn/55VfTafTkyZPZbGYYxvn5Od5e7AbGZjz0eGhxDw8PuZTNZvPZs2fwAr++vp7P5+jKHMdZrVaoJEGb7Pf7cN02TVsIZRjWZrOZTGau67969QOuKgx4TNPs9jrT6TQJ46Dm+YH76WfPm83mN998o+n05vbedV2mkZLnuHaxRACnTFmWkC/5vh8mMfyUCCFXV1e+7wul0By+ffu21Wppiui6PhwOUbngOMOYju3tBcMoQtf1QpXgciRJslwuRVFCxGybFmaA6I/AKbVtO0qWbLeTE2HJGIPSBd0mwkzX9SrxIH/iCEBhqZQCI3S1WgF6QXWGF4w3DY8AHm+73R4Oh6iHi6L4kUz4cQTuJzelZJW4Pw6/DxAdpRRjKC/fEXEQhFJuTdLUBzf6jiVHCNG07QP+7Ks/Wy6Xb9++zeIExxgaCciu8yy7vb3dYdDaeDxGjY7VnCBegoaLfLXVzgqBK0DTNDjSwlqr1Wo9fvxY07TxeNzsOESVZSE1Znfa9bIsw/V9EpetphvHaRQWnHON2UlcBkHQbPjz2cZ1XaKMJN74HqnXAkN31+v1bLq2rcxxHKIM32smSeI6ruc2RsNpq9lN4tzQ7Tzj08miXmuNx+PZbHZ0eOrY2mw2u357V6vVNGZqzIThClySWq3WYrHA5Yj1w0VRnJ2dIc5x1pSlmM3GpmnHcex5wcXFU8fxxHYdHU2SjDHmOF6aput1WK83oyiJ49T3/STJlKKTyeyHH77DzobJZAJZY1mWrXbz4OBgs9lQSUDv/OKLL/Da8PIAuSGn9ft9IMO2bTOqAzuxLAv1Ht7/zz///PLy0nEc1/dxuk0mS8/zbN2AAzpQqLu7u0ajUZYlRAwY1kFXAeMMpW9Nn5BRDcdFCapRBpp7tNkgaHEZAHRBAsSyKogqdF3HX4qhPN+thxBcSUEo0XRdd2zPtlyNGVKoq5trrAPYNorMYFSnRENKL4oC+g+U8Shizd2KoXdBWMGbdNf1iT0DGBTulFKNvaOtbaNmj3Czn8QwlEzLnBBC1XtJdRvAO6iG7bFkdEOXUqJvVkrV/aDf77fbbRALkyTB0ILthFRSyvl8zssSLMqyLCUVkIRW6mTPdcFXZIyVZY6AhLESYO5vv/325OQEJGBs+cCFIoQQXCtynme5bdt5JotCdNoDwzDms8i27U570G53b29va0FnuVw2Gg3XcjRmM1qulvHhwImjQggRbjJe0rdX9+122zCMX//q951O5+TE63YOpzM5n22WiyiOijRNn1y8GA1Ho9GMEPLyh6sgCGazZZGrOCps2758c3t4eIhF8P1+v9FoYCvQxcXFs2fPvv/++1//+tfPnz/HdoSiKLAQt9/vYy53fX2NYtgwDCz6Mgzj5z//uRDij3/8IwyzdW24XC4p0TrtXriJ4zg+P7+Yz6etVgs7QDVNcz1HKQU8NlxtcBS+fv0aBx/nHP/2er3b21u0bZizTyaTPCsNw3j69Cnn/Pz8fPTLCVI3fDHKsuRSQkF/eNidTqd1z4/jmBDiOM5kMmm1Wu12+/7+/urqCppDuMWhiXh4eKi168irqGtEUTYajTiO18sVrnv0csvlEvW8ZVmz2ezRo0eYsoBJc3d3B12Y4zhIjABslFLAC+hOdYTeEjLI2Wzm+z5aCcTeer0mUjqOYzl2vdkwbYtqrNVux2mim0YYRxBkvQNm6Pvyv/3MRveat/3v/P/efjTB4sYYo3tVKNlR4fI4xRHVbrdrtZptmEIIbGvYWg9YdqVjlDunRrXTEFNKGWWU0na77ZgW6Py8LDGcNQwjixdgFcODAH0a8LrFYhEEASp7bE3Tdd20g+U64ZwbZlByuVqlrusdHB6G8ds0l0kSpbmcTNeEMM9vtdr9IkmTuIijnChdcKqkzLI8CjPBqeB0uQgPDw+LXNqWz0timZ6u2etVrDFrPlt1Oh3TcDvtAyW1JEke7ieWtXZd17EDJZXgVNdsYBJRFAFuAevi9evXp6eneZ5jbzF26y6Xy3q9LpTGGOt2u8PhsNFojUaTbrcbx/Mvv/zKtu3lcvny5et2u/3s2YskSf7tv/13n336xWq11jSdMe3+/l7XzE6nXREdTVOXUgrJlVKo08x2K4oiz3OlFPf3d6j0iiKXUobh5uCgH8dxrRas16s4juI4yjPOOUfSE0JUBvtAvwkh+KNAswQU1+l00GHGcRyGoRDC87zxeHx3dwfTGtiWojFDV4IMWRSFLDkOdMwe1+v14eGhUmoymaCLZroJ5o1t29fX1wBIa7Xa27dv8fJwVFW+UpbjGYal66XjuLVaw7bdOI5Xq81u/sEcZzvZLwqe52XgOWznsIoFYd1uF4U9qOdKKX2/qvwgwOieFp7u1vcQ9d6yiA+KUrWnCZb/XXUi0FHyUTmKjwRWJaZpllmOJVKmbpimqWmaZ291A8BFP0jgVbmLzAxqvLbL5HDIwk/BVkP3WA1kUaUAQEfyTC5vUNAzuuGch2FclnIymZWF4rwMN6kULE1z27Y5F0lSEM7RNYFyAXMNFCGcczwUcuxkMjFNM8/L9Xr9+eef397eP3nyyXodNptNIdTRkXN5eSmEME1b100URZgQ4l3FNhLMpkejUavVsm370aNHzWYTmm7UbOPpEo0QCj+M2nVdn81mlmV99tlnl5eX8/n89va21WodHx9DamxZDiFsPJ4MBgPX8RljlGq1Ws333SzLsjxVSrmuyxhxTAfjnzRNHx4eut2u3FlCZFmGRYX4mKAh7rT7Ukq0r1j/hFeFfSFFUYxGI4QN2vJ6vQ5vC4gScFifnJzc3d3NZrPNJnJd29/dgiC4G99jsy/+ZCK2rtbMMOH1cnhwgF1RWIxr2w6AFjDgwOFWSoFWRXb7nrePRohm2bVaDa8EPtG2bR8eHoL1ho9YKVXJaNnOJ0pKCZ+OWq0GgTI460mS6B8P2fcrzP0acssRVXsE638i46k90OWfCkIpJdkT4LMdmch2HcBTIA3FmxCzJkyBGGOWYeJNwbB1axb+EYaU57lkHNcuNYyqfA03S0xE8GahQcewFbDNdDqFcQiQxkLQZrPJmHZ9fS2EAFY5mUwM3cLHgJmkaZoYnTUDF0CRrmtRtAEYqJRwXUeI0rIMSlWjUQvDNT7mxXI6GAzCMHzx4sWf/vSnn/zkJ69evTo6OrJtu9fr4XLMsqzRaKDvD8MQi9rhHo8KEAEA6g98hNBH0d3Wx+FwqGkalntiDkEp/earxkMAADSASURBVPbbb3u9nq7r2P4nhDBN03ebgquyEIwxw7DKsry7u8uz0vMUUk1lDgRLVUIIIBagWbiIISMACo2cjKGfYRhxHPu+f3p6aprmw8MD/Lan06lQcr1e76jkGorGWq2GdYVYxgpd8u3tbZ7n/X5/OBw2GjUEKmBYlAl5nmPeyxjT6VaOFIahpmmY3MxmM6DNRVH4tQasa6o+DdaGMIACUKx2mnK8EsMw2u32zggrA0gDO2PEv9qtBDVNk0vpmqZNiGYYhDEu5WK1enN1BdnkaDJJ01QXe4a/+4BnlVcQb/LHVLn731E78LOKq+33K/bnu197LwgZY4puxU34Oa48/D1ESDi3F1m+fVW7lTI40fdfzPZJ5a64JXR7xey0+ZqmlVmEAMNprZRC3QJvvCAIqnkx7lOkWV5meZ5PZ1NKqW7qpSjjOGZMI4QkcUY1ommaYTUKnq43YZ7MMQSr1WrrzYwQQhn1fLPdbkiVe54XRotG07u9vcXBX5TJJly0O/XFcuG4xmw+0nSVpJtNuNhqi7I4juPFkjVUw3GNakaHTw2RVqvVxuNxJagROxfJPM8p0aQgUhDLNAO/XgsalmkrSV3PO+gf+l5tPJr6vn90eJKm6Tdff/uXf/G/C6Fub681TcPIYTi8DwK/KNws02zbrDyEgIjEcdjptHSdFUXW73dd1767u8MvYl5qmvr19egnP/lsNBo5jvVwPwU8OJ/PdV1fzFcnJyfdbrcUHFc/AIhqhIOFVpAgdzoddFzAxgkhGN8DyNlsNug+it3ePsaYVFtHIvwvIhkcNOhuAZkCjwGpqHLmrdAKXGk4u4VQaRqZpt1stlutDhakPTw81Ot1xnRCSkqZELwsha4rQhikmBiTwoe7LMuHhwesfMOcZhuEVbxV4cT2RL2oZHYX+4/vpt/vJ98LTvYu8PaDUO2USlW4boEfTQMAAxDJdk2EJQZxZLdRAHMFBKHaU/orpQACJUliMG07jTAMvttz2mvXtZ1BCIp+PHu73ea7ZWb4SPAeFWRSlLFlm59+9pQxbbPZxPFSERrUPF7KzWY5md4LIYQ8KnnGNJFkK8aYwRzN4KvN3Pd923FsWzNtZVhSM/h4PD48PCxF1Gg0TG74tQPGWJpt8iI6GPTRzcZxLJUgvBSCO65OqDmZ3sfJ6uTkZBMmhmFgLAZeVXVq4DQpiqLT6eBszrKMUl0IcX5+nqYpBsSGYQyHQ9u2sTsByvrZbOa6brPZvLx8m2XJ3d2DrrN6/VNoZ7vdTp7nnBeUKlyLZZmj1OecY/EtmjdcbUiMKOxRJ8NkLc9zKTSMT0H7xDr7R48eXV2/xbJUdGVoDWAJ43nexcUFsMRqhn57ezuZTNBcoDaGFBD7xfTdSm2MAaB+BrCEq//u7g7jB+zcBtiTpulkMoFfKJrSqnfA0YNhMnx6wG7FcwGIIoSgGMHpCdS60ajXajUQlbG1Al3Sy5cvqwnHNggRcvsZbD8mxc7QjlJKiUb+u64w1f9uUyi+uReE+Bq9m/roBmSJEILhtZQSosl2s7UFYNS7cWL1CuW+kzdlIHYJul23yPb+wHLnbwcYwNyt3QOYAbwL7y/OV2ZI29Db7eZgMBBCkHtRcsziqes6RennebFabUoe2w7z/EBTOt5PzzNn88KymWlRRYo4WVk2o4xruiS0bLZ837fxgp8/f/6f//N//pf/8l/+8pe//PLLL1erlWVrhDLPD8A7qTfcOFlF8ZKyAV75er0uyxKzKYyq0QOj5Ts+Pp7P5xhgWLYLClhV5KMoaLfbUsp//Md/rK57gGGj4Qy1XByn6OK63a7neUkaxXGm63qr1fA8LwwxjOAw8Ib+ixCS5zmWH4LFBgVdp9NBLcoYs+1cKfXdd9+9efOGc76Jt4UJnAKbzWaSZdh81mw25/O5pkiSJOijgAVEUfT27dvpdLrZhK1WixACpeVisRiNRjiesCt3vV6DtsY5bzWalNIKjcNvlWW5DuNarYYzGgw1jDH1nXB8H/ZTSimxbXaw4RSHHQgMEP6CIAr0wfM8QjggVvw6WpVyZ0OBzKxXhc3+nKBq0sjOkxsVCKbn5CPg9Edve0nuRzLhvlRK7Q1I5tMF6ml8ohkXwGmASaDKxxtEd/IrKWWl36/4N47jUKn274M/AQvu8OtorNEb1Ov1wWAALwkMADRNm0wmrmcFQWDb1mw+zrOCMdXtNoVQNzc3R0cn9UaglGKaajYb0MK4ZolBGQa8lqUxJrMsk9IAaH542HNdp1ZzkZkJYZwXf/EX//Pd3d2f/dlP/+Zv/uYv//IvX79+zTk/PT0VopxOJ91ut9GobTabNI2VssE+JYQ0m816vQ6EBmoA27b7/f7FxcUPP/zguu7bt29X6ywIgtVq5fs+xt/oyhBa8G5DtKB/xu7hZrN+d3cHpOH4+BDvHg5y0zQJ9bIswTdrtRr6Xsdx0EFgoHd2dvbNN9+AV+R53v39PYg+yJ/T6bRKC69evbJtu+Blu90+ODiYzudSykajAQu5Ikkx4J5Opxi1qZ2boOPYqGgw+gcFDNyAer0OQs9iOkN1KoTANP/t27fw9oVZVl5wQgj0n+v1utForFYrSikip6q6wcw2DCMtJQ7xwWDg+37VgsIAju/cij3POzg46PV6b9++3jKuHAf0LFBE0I1rMOemTN8mJUIxusN/QpIkTZBVFWFCEqUIZQwe2Eh2iKh35evu+5RSyii6JiJKQray3G1kEkIIKbiglFKNUko5kVwWSihCCAAYHNhVrGLMQLeuNu97CmNvzM4IhFIqiVJKKkqURhljSmNSZ8TUNSwJlaZhGGmqhMg0jRal0jTt+OTM8xw/cBljtmtacyPLEqF4t9+kVLeo7ZuB23Axk8RGsW6jX3frGByfDk5hoPTmzRu33YuiqEjWqzBpt09gsdVstDRNc23nzZs3FxcXUkqDabyM7h8eXvz0RZbzMkqTlM/mm6dPf6LrtfPzz/7jf/x/wg0pCm6a9eWCHB6e5+nEd08vr/84HN3863/9r//2//27NI0ajWCzWf7iF//Tcrn84x+/Nk0TC7fvbh9OT0//2f/wP/6f/+H/3oQrz/Nsx2x3mo5jnz8+e3h4MC19Mh09f/HJr3/9a8Mw6o3g/uHW853lctpoNOJ0ZNp5p+fEqa1I1u32nFj7Z//sZ5eXl9gV4Thev98PguCHb38Yj8effvrp7e3tfD4/PDy8vRk9efIkz6Tr1A/6x8PhkChD1xzfa87nc8t1er1eccUn81m73QZi2Wq1yrIM15vlfNFvd7KT0ySKV7P5oNu7ebh3a8EqCm3fe/32CkdzmqYHwcHJ+SPLcx3X1W3r8uZ6PB4vNuvBwdHt7W1ZiE6753u1rOCbKHn69GmSps1WW1BW7/byPJe6YXj+aac7HA7vRsOiKBaLxTrcSKJQPaJu1zSNKaJTZmq6bZimac7ml5Zech4nIfXsrpCyzLK6b1MpeMCKgjiObts+ISQJRw/5QuVespK6ro2miyjeaBrlqSBcdZsN13MYI0WRvdcTvldGSokyd39KQfesCj+oQquEtn9n+j49bf8L9f7aw+pHbO9lVJFeJerqR9XL0N7fpbGfVPefC4+gaRr2EKLWNQwLXU0cxwcHvSxPtN2ChIeHO6VUu90WYrso7u7ubj6fQ/NWlmWn0wEHEt2CZVmDweCzzz67vrtJkkxK4vs1TdOIkEopypjreLZtHwyODMNar9fKskzHffLs+Xq9Pjw8pjRnjCVxBl+J29vbp0+f+l4tiqKi4CjI0yxJ0hin+Hw+Pzo6Wq1Wd3d3jx8/uby8fPLkyaNHjzqdznfffXd/N9R1/c2bN2i3lssl1Iz4q0Evhgk0pq/41IBG9Pt9xhg8MxeLBdjtjx49+v3vf39/f48ar3o3Hj9+3Gg0xuPxaDQCmFmVu0hNQDLhVaHrOp4OboKMsYeHh88//xyvStd1rOZ9eHgYj8foxDabjb9bz0gprdoEYNHghQkh4FUD8ucf//jHR48eWZb1y1/+siiKVqslpfz6668/+eQTYDlgaGRZNhgMgA/h+9AlwmijXq+LkmNVliy3lA+UVM+ffwrIlBCi63oYhvi4PS+YTGY3N/dKkV6vDfg0TXNGnCjagO/JebFahWVZ1uv14XAY1Hxd17NsN6L4IAj3o6jCIZGIYLf6QfjtR291xbM9Pf5+MOzfuQqe/X/fv9uHs0dF2f5zsY/CuGoOqzgXuxvnnCtdiIKQjBBiGBqjmiACnI8sywxTw5wRHGsppRRMSbpZR1eX15PJxHXdTqfjed6/+Bf/YjgcrtdroqTGWBylD/ejPM9zWaRZTil1Pd82rSzLiqIwNM2wLKFUvd5I0nQ8mTqWbVnW+fl5PIlXyw1wAkpps9mO4zjLcl3XhVO6rmvbUtd1368pJQ4OequX96enp9PJHIzhly9fnp2dL5fLP/zhD0dHxy9fvuy0e+v1er1eP3nyZDwed/oH6NmwTQXgId5hXddh0wIUfgcJepvNptc7sCzn6uracZwwjO/uHrrd/tu3b4+OjuI4ffTocbO5hi0NlZxS+vDwMBgMANIWRfHw8IA+EMxJQJf9fr9er89Xy++++84wjPF4fHZ2tl6vj4+PUaweHR1JKV+/fg34VCkVRdFqter1erAtw+tHfdTr9Q4ODprNJoYEUkoQ2SmlQGtA3MMEC1u1q/oZzGFMRDabDcgxsLqRnKNotM2tHxyVquLicc4nb4bYn4GDAycyCs56vX50JBhj/X4fMtfNZjMdrymlYRhiS2xR5IoIz3POz88bjZrtWGka/4ioFze526akdtgjmlT2vgXbxwHw0Y8+TIDk/Zt6fyz5rmrdaymVUrB1opRKKqsuWe75cXxwHLCdpxuyN/AeTdOoCeZkXpalaeoVD/jhYRQEHqFyNBoLwXXdlFKORqNwU2Bv3nK5Ngzr5OTs/Pzcdd2XL19vNhuxE3THcRqG8WKx6J0c5aVSShoZL/LtkVkL7LuHyXqxDIIA1vS+KylNO92s0+nd3Nx8/fWfTk5OTNM0jEgIifYDJ3FRFIZhFEUShuvZbEIUOzk+u7y8nM1mL168mE7nd3d3R0dHWZaDqXz+6KLb7f72t79N0/T+/j5KM9u2W60WRqCAfB3HQT5J0xRYa61Wa7VarVaLUrVcLh3HGY/Hy+Xy8ePHaZoOh8Pj42NCCFw8oC2CfIxoOhzHB4MB8Eyc1MvlUt85iwF1NAyj2+3qun59fY3dwEdHR9DBgGwNdBpMSwCBuq4zZmiaBlkZ0hSG8tXyerClwQ2IoqjX6wFah8B/Pp8LIcDGVruJOXjVUBULIaDPIITYtq1RyjmPokhYHF9QqTRNA6mDEOK6frPZhABFCJEkWZrmUZTM50vf9weDI9xZKVoUvCi4rrNer6frdDIZapp2eHRAKc2yBFemrhmu6+v7AAluCDMUDFUaqbLKvmyPvF927sdnFRhVEqPve+xXd64eYYsg7U3w9x9N7qAXqlPMdvCeit0S0g8OBcQGngVBiKkDNzLUP4QQXTctyxSSa5pByNZHvCiKsixw6SyXy+kkAiul0Wg1m83T0zPLcjab6OXL14PB4PBwEMfxZDLhXPq+X683ozgNoyRN09lsgbQTBIEQ6ocfXt7d3MKpodtum7ZTFMU6jOxSZ9So1xu+HyRJslqtCSFgSyF5Z1mqaSxNk5JnRZli8fJgMBiPx0Ko4+Pjm5ubo6Njzvnf//3ff/GTn37//feDweD4+Pj+fnh0dPL9q5cHB/jgs/l8jkEiIWQ43J7osPfu9XrdbrfRaDw83KVpqpRChQnC12azubq6ajQat7e39XodyYoxNp/PfcdttVpwFgMh3nEcVBa46bp+cnICet319bXv+ycnJ9Pp9Oc///nbt2+/+uqrLMsODg4WiwVKfaVUURTT6RSzkEfnj2BAOhwOkyRtNhvNZrPb7YLqgLEH4BDkydlsBgtM1JbQvy8Wi263C6kkCn78yVBOr1YrcN+FEI1aDWgnL8qiKKIo0inDUhqcXJodBLWaECKPIiEkZRrT9KLkQeCbli0ViTbhYrkC9SrLMqr09WaRZrFuMMex2+22bZs4elB6UEp1bc/djOyVhXBWpJSCvVrFD/Lh/hVP90xE2UfOa1s54ftuolXcVk/HdoRvuWd8Wv06Ie/hsZj+KchYsIN893RVGYwivsrkqCiEELnU4NeANQCGoeV5zjSq6yzPM0II3prJZILTsdnuQPOCqaui7Pb+4f7+/umz52DrJlluWLbjeYZlx2k2WazBUUziGEms3SwFJ3kp80JEcdZ1g3qzk+e5UGw6X91ev/7iiy++/OJnQpR5NiSEuJ6NIieOI8uykiRqt5uaVnMcCz4rX3/99RdffGFZzuXlZa1Wq9Xqs9ns9vaWl9KyrCzL8rwkhJydndVqtQspCCGgYqH9gw/feDxGQQtaAiGk8sYsigLJQSn1m9/8BoM+KSWiC1wT4Jyz2Sx0Xdtz11F4dXONqToMrW3bprqW87Ioinqr6fieJR0cbZiqwTYC6Q5L2tBjdzod5BmkO13X4YVBCHFdB+a8GNxjBcV6vQagHQRBEATD4ZDueELAq1Eu4joHQwOnDxIsShV8Z71eY1hvGIbpGtvnpQRTRxBuJsNxHKdJkqxWK7iSel7gecHx8TGkLZpWSkmKgqdpmiSJZZh3dzemafZ6HU3ThCgtK8DT4TopiuKdoHj/WkfGQwrGT6uwqQZ0VetVpcHqd8lusKGUAk/74zjcj1X6/o3tbcWgO1gVH4xS22ExBoDV3aqCGeHH9gh71Y/gXxA49VazU6v7ux0MinNuMD3LCiEEY0RImec559uRzOnpaTWuhJsgLtYwDK+vr4uiqLT5YRje3t4mQuV5HkUJVvMFvgtvON+v9QaHtm03m23dsC6vrh3HSdJ8NlvEcRoE9eUyMQyrLPPAr0u5bDYbm81G11mWMdPSizIryiyMllGUcC6//fZ7vJM3N7dHR0cvX74scn5+fj6bzZ49e6Hr+tdff/306dOrq6s/+/mfAx2BIhmwBJrA2WzW7XZXqxVAdlTX9Xq93x8sl2vX9U3TnkwmcZzatlur1ZbLtW27nEuMCjabTavV0XWGQR/Ga47jAFdUSsF/5P7+Ho1co9EYDAb/8A//ALwEHSbAHlhoA2vB+I4xBifbyiQSgQ1b/jiOoYTG1QvcH4c4YwysQ9ScOHcajQZqXcijq9k69ltRStEWWpbFiwKUzlajicISlA8ACmma2pbrezVG9fUqLHJu6BI4X5HzshCM6rblCh4nWcZLaZnOQb/NeXF8fHx2djadTgkhvV5PKQU+g6YZWZa96wk/uGHgCDCtovBUmGTV++3DoWxvTZrYmawR8iNL7ck/AdLsp+L9tIkEhTtru2lEdf+9BnI7VK2IDghCsnNcZ4wFfuC6LlFss9kkSaKUYIz4vp9mSbfbNgxtPp/ned5sNoWorVZL23azLNtsInhDYQh7eHg8nc7AgW63u6ZpL5frh4eH+/uh3WpKJYnGYOkHVDCJYi4EnJdMx4aVxtHRkWmaF4+fZGk+HA7DMHRdO0kylCio5TzPUUToul6W+WazEqKMo/Lp06f/6T/9p6Ojo08+efbLX/5yMBgsF+snT55Mp1Pbds/OzoQQnU7v/n6ICQqiCxplAIkAaWDVA55KRUi6ubmBI3273ca2pru7O0JIlmWwloIrJFLrkydP1utlURR4h5GmhsMhSsTz83NMvdFcgVuLqMiy7MWLF8jb8N3BpY8PFIxcYCeW51awGWaA+m6zJ5b+Yh0qIgRJDyU3RrWYeEGSbxgGki02i0HQAONguLA6jsMcB50LWFmu61q6gak1MOosy+qtpuU6buCXZWk6NgwN0iKnlJqOLSnJ16tC8Have3p6moSLI3L4/MWzWq0Gyr5tm8PhOIqiNM23xAD2Tyh0q7lq9Zcg8KrSFLfq4q5iUu7WZf9oq7Zf+lYISjV8V7t9GBVBZ7++rQbxamc3UCVkRF1VGFdKQmNHWEPZY5omY3rFzzYMo1Zr+r5r26ams7u7u9lsYpo63EQsy3r06HyxXEKryjRNRzI1zZLz6WxWlmXJ+ZvLS1wBmqa1O52UKGAMvXYHezl1XYcIUEnZ7XYtyxoNR0dHRzjLeZE+PDycnB6jPoEUkGnEcWy4d2n6tlPFNFzXbaXUJ598outGkiTPnj0zdGswGPR6vWZzuw5+NJr4vo+MPR6PNU07Pj7mnB8eHqLRHY/HFxcXEA21Wq0gCO7u7hhjZ2dnYRh+++23X3311a9+9Stwu9FxgVr5i1/84r/8l/+CGq/dbj99+vQffvX3P7x6KYQ4OBzc3t4u16v+4GA4HD5+/Hi+XHz7/XcwtJdE3dzccCkqwAxLCBuNxtdffw1COUrlKIrg6YLrYbVaYWZQXWOO4ziO0+12R6MR0jigF+BMkF/gbrDrLori4OAABHfDMDDJABseFF/M8S3LWi6XkvPqaux0OrZtx5sQ8NXBwYHneS+vr4HxgIQAnhC2hYL2be6WTzw8PHiel4QL7DyE3x+OOayHkZLgk9Wrsm0fayG7RZxqbz1TFXXVpU/3tE4fcFCrUlDX31lC7d8qygvbGT0h0ioLtu2P6DYIcfjt/6LaOW1VQV7FIdmtcEPxjKkreuU0Te3dqleiWJIknBeGoUFU1u93fd9XSiyXy9l8slgsmOGnWbwFZhWXigpZckGYRhzDUkqlaQJALwgCz3eSTagRZdmWptGiyJSUghdECccGPT8URV4UmWVZjmMVRZGXSiohhLAsQ9cZITLPc6m4aRqEENe1a0EDiqeiKG3btq26aVr1egMDZSWpruvn5+dxnMKJdDKZIUv4vg/8EHDFeDx2XReNVr/fv7+/f/LkCdIFpdR1Xfg49XuDNMk77V6R80a9dXf7UBYCUn3H9r75+ttGvRVFUeDXKdHubh8ANn7//feAW3BSwEgbgQSSDQR7nHOdaVtfiSjK8/zy8tLzvDzPgyDYLzvhkorsJ3d0PHNnKC6lHA6HcGEDlQekeSklDPhQtgghcH/gLmzPnQxtBQorPEKe55vNRgkBRBqyj8ViUfeDFy9edDqd6XT6/fffp1JCuQJADj4jjuM8f/4cPCdMPtCkbDYbKqRju2EYhpvIHwRxHE8ms1arZZqmECUyjY46hO7tY8INLWyVFfczpNqDOvfjs4rJ/TaSvg+T7j8O28mXqiJWSkn2EiBjTGdbK0Q0gWoPVsXHY+jvrdPY/wJ1PNktkMHfqBlarVYLgiDP09lsRpk6PDzsdvtRtGk2m7quj8fD29trSPXare5wugR+QCkFXoLXg4sGY1/MiMCnSXgOL1NdI4LnmqbZlm5bupIlkazI4jTeaJT5Xt2xDcHzdrtZlnkUbRgjnHPTNKXihmE4jpskcRjGyOqr5cZxnIP+sWW5o9EI3iT1WhOoBucSAzHOpWVZ4/H45PjUMu0oipIixmu+uLiADljTtE8//fSHH36QUqLAhr5OKfXzn//8P/z7vwmC4Pr6+uTkZDabHRwcYFMSfnE4HGLzKex97+/vj04PwjBEhun3+5jZ4AM9ODjAe4L3DR+H57iAQFEn393dffHFF1jHjSUTuHzb7Taq0DLcgBzLGAPfAL9br9fR5UZRBLkTKjKgKZD8wamNcw6QtkIT0MVBSwEjRry8oigYIYwxz/NmsxmMZ0VRXl9fj0YjUAKn0ZrLMk4jRZVuaqUo1uGKUvry9Q+LxeLg4IBznpf505MnMKFsB+2rq2vP8zwvuLt7aLVaoAdjc6iUklLyDkKsqj7y0U3uqW/3I6qKq/1Wbf/R9mLwwyCs/pftyTXkznimyrraLgirmlnuiarU+zPG/adA74H7VHItTdMcxyWE5Hme5yWl1DQsnIWmaUqhcpEzxgaDI0pVHMej0aikWiFKYAA5L1Spcl6UZen7fpqlWZlLqryaDxXMcrM6OOxiKJfGCXzB6nXfcxwlcs/yAPB4ttPtNDRN40XCGNE0WpSJpmlZlqVZjEQNvB4qONM04zilVMuygjEjiiLH9uq1JhSx0+ncNNNut0uplqbpxcXFv/+//sPp6enDwwNyICEExAAU+dPpFPzm2WzW6XRev36dpulXX3318uXLn/3sZ9hDdnt7OxgMLi8vUaRh9LxYLD7//HNM4WazGei1myhah2G7210ul5999tnvfve7TqeTJEkpxJNaTQjBpVyu10VRCKUYY8vlErAKOrfDw0O4GIK9SfaafDB70Inx3RK4CqTAnQG9oItG1gUj6uTkBCxZpRREjNVcChAdzvTqmK44DLphQFjo2g6K4SLNMJxEVeV4VpIkcRzqus55IUSZprEQYrXa9Pvds7OT9XpNCGm3m2VZClESwqIo0TTDcTzDsGzb9TwvjmNwoTWN6jgP9svRqiLVdosH99OO2iN279/UbnXZfqzuEto7ff0HcYi7VUNIPAXbC6r9+Kx4HkwjVeZUO6Lcx48ssfltZyXEOQdIIIRYrdaaxjzPA2oSx/E334xqtdrZ2Umr3aSULhaz7Uei63GWCQGwDuXi9lxaLGa+73e77aIopORZlpimfnDQcx3DdUzDYJEs8jQqsrjI4jmlWZa16g1d1xkRTFN5FqORKHgphPB9H0pzsEySJLm9vYVSybIspYjjuFlaXl3eCCHKsgRhoCiKxWIVx3G3203THOif4BISHpRYEEys1+vf//73Uspnz55Np9M//elP8GI7PT29urqSUvZ6vT/84Q9//dd/Xa83wjAMgtpyuQqC2suXr0zTyrK80+k+PDw0Gs3FYkEpOzgYcC7a7c5o/sAYg6k7shN8lnADalJuV6b4EONiv5Jt22/evHn69Onr16/xV4MPTSlF8GCwCcEbgAnozvjOhx+5AT0hCmBkNnz6i8VivV6juEWWQ39eYaoIPLzCrRs3pYhMPN3Wlp9S2J/iTHS1gItSCJEX2Wq1WiwXSik/8A1T/+LLnxiGMRoPXdedziaz2SwIgvls9eyTT29urocP45/97GeT6ehv/uY/npwcpWlKqaKU6oam890u4v3g2W8FP7iy99PX/lhivw/c7y1xl4/jtoq6Kly3ma36YrcTuzoXEW8aofs6pqps3u8VKaUVQRQHKt2hpgCpHcfRNLbZRFJyTaemaaNdnE75cDicTEaaprVajU6ns7m/1TRq21ajUUdvqfa8DxDe1ahNCFHka1GmjHDLoLXAwTdFWWpUGCY1NGqZZuDbVAleprqmJGGe55imaVkGIbamab7vr9cb7EKIo1RoSgrSaraB7DHGCSFY/cMYm80WSZJMp/Plcn1/f1+vNW5v7uI4/u1vf1u5qkkpMXBP0xTerZBKdLvdy8tLmI7e3t4eHBy8evXq6PC0Xq/f3d1Bjlh13d1uN4qi4XAI1OHTTz/F2gnginCnHw6Hg8FAKeV5HkibWFmFwg+DuLrnNxqNXq8npYQrB8brKEaAl8IgGPQxw7ExpvM8D9TfsixBJ8IRg5as3W4TQvCeYFwppbQsCzxVDAP3QT6xkwgi9tATmaZJdzjCcrmEV28SRuCyttvto6OjSMZZlsAhBqsXESxxXGRZsl7nWZa0280sS1arxdHRIDC6lLI4zjgvZrM5ti9j3CpEKZXgvNCrUrOKjf2Scr/LYjsK2C66WDUZp7uWshoP0t0KKOxV2o9Dtev65O5W9Zn7ob6NaiU/yNJq74by9YOKVO3kF/tfsz32D3qDLEvX6zUhstvttlotXWdYcmJaxtOnTz3PWywW9/cPzVbdtHQwv+D9WhQFZQqe0GEY2natVqtRSqF/PRz0MBqp1zyn33EtG8+ra5rneWVZapShjISt7XSxwI4h09Q1TfM8Wq830zQ9OztjVC/ykWnaUsKOhwVBrdWqgRht23at1iCE4MxOkuTu7i5uJLBCiqKk0+mAbw0/MiBbr1696vf7x8fH4BALIU5PTzVN+/bbbx8/fnx6evr61dXJycloNOp2u5PJBLp1nDIQKDYajXq9Dihhs9ngcgfoP51Oge4sFgsArQB1N5sN+iVCyOvXr1ut1mq1siyr1Woxxo6Pjw3DuLq6QrQj9hD8mqYhSeD4wyxRSokDAkGodoRKCDJ0XUdaVkrBOA85E3g4alqMMQnZgpOgByL7Sc4p7BiZttls1ut1meUYGBZFMZ/P7Y5LGFWUaIberNWwgCQMwxeffSqEIIxePH3S6/UmkwnTtYKX3UH/V7/6VRyl9UZwfX3t+95PPv+y5HmjQUteFEUhRKmrvSZwP7lVViIfRKDck+3t94E4gcT71r2apknJP+4G9zPtByXlfqRJKSnZPqn2fhn8QVjul69qx06uCqFqyJnnucHMyWSilHJdeGyZnPO3b99KyZvNervdlkpMJhOUT48fPxaGBPcCcjgkZCjoGGNYU053JCld1y3LmM3WWKnVbjZZs2nbtqGzfr/HCF0ul5pGHceiSmlUBUEgKfU8z3EsZGNeSinJbDYbDA6VpIZhtVqtNM2jKJpN52XBV6sZDAsty1ou16vVqtlsUqoVRQEkxvO8yWTiuj6qvk6ng9IUVOl6vV6r1ZbL5U9+8pObm5uzs7PVaoXtOghXaO1fvHjBGPvzP//zt2/fws+bEALwSQhxcnIyHo/hcQZ+DMpLLFGB5ngymUDdZ9v2er0ej8fYREC46Ha7eK9OTk4g2AUbDgwkDKWra6bYbelCqKOVgk9hdZ6iV6w+esQ29O+YBoOOj6MEJylGi+ghAfYg0kRZSikNw1gvV2C9ebaDswAfcU3W6vX68fEh4C7Hsdrt5uHhwcXFxT/8wz/EcdhsNpUShEjXtYUor69vs6woioJR/fDwiBCZpqlusG63W5R5nqecFzoj78AVolS1kpIxRhQhUqndltzthIARxoihM01jlJKdA7LEGSaFYJQwDcinFFwwppSs0uzWk5BSvchzzrkopVKEMqoIQfwKUe7uLCmlbHcQGKYuJaRVOqWalIpSzbIMy9yipsjA1UvFdVNhsFXQztVCM5iu66lJJRdaqlFFiFKddotoWpJnBtN8r24aBo7M2+HIcWxDmb4VNBq1m5u3i8nE85xwOTdNkxIpi8IwDCaUo9u1Vi1M1GK5iSKv223rVmuxTDebKef8aCEoVWkad9ttL+Cz2SwMw4uL8/WsEJlp2y0iCSFEqnK1Xraa/X7v6OWb10meTRdzTdc55ylPf/PH3xz3HjebTV4aSZxYVhmGcRynus4opVmeUkrjWClF1+sw8Os31w/CzAkhPI5qtdomDLE4xTTN2XjyyflFFEUqLx3NiONsdvuA6mtYlFLKFy9eOKbVbjTLLBdCUKk0QmXJN8vV6P7h7Oxstlq/ffv24snjMisF5SUvDMls3aKCdJutgpc13725ueFKJkl8MOj+8PLber3++PTpfLECNfzw9Ozq6mqxCYlp6q4rDH2RxIs44qbBTDNK00WWbjZZu9VKBVmPp4zIZrfNebFYz4LA8wOPMb0sSyXKOAw5557j8FKu16HneZLQyWxONaZpmm7bm8WCJKlhWAYzsjxdTGdpnDBKbc2hnOlUI4LkccYY0zQ9S1JCiG2bcRzXW/X1ZmV5ZpSFTmAfN05N0zRNXZmiLAuRlkUeG6Y2vrqxOLOFxtf5fJktZ0uSsEW2SuwsTRbPPnlEKT0adF5fXWJJa5rxu7vher02bOs9G/wPUg39CDVVSmm69kHKIu/jqx/c5HvqPlkVjR/fk+4ATLKfgfe+L/eIcrhpO2M5+b4/AH1/Vdv+ayt4YRoGChK05kQqqsibN28Gg8HR4NC1bJD62Y45lWVZksRhtGaMRNFGKkEIwQ7XsizzvIiiaLMOy1JQSq/v5rA5gTGmKHPPc9vt9uvXr13X1nVGlZJSYCqdJFEey16v5/n+ZrOZzWZ5nhNGCSGz2YxSCvQlC0Nt56mz2Wy4KBaLhRClplEuSs9zOJeO43ieo+umFCrLCkIk0wjnfJ2sALe6rqvT7e4NSilXPNqEYRhmaco51y1TpyxNU6y2XywWIGSjkNN1/U9/+hMMPuDF5HkeiuowDFer1fPnz9EXzefz0WhUFNlP/+yrzWal6zolClgoHg0FC+d8Op0KotbrNYhEURTphYGGsBRC7LyYXMdJkiSM1qZptJt113XDsExTURQbnWmdTs+2bZ1qQqg4jrMsq9eabGe2r5Siu4uz1WrVg1qz3hBFORmP5/O5qRuNej3KC0IIVsphz4phGAAlgcNVHanc0ULyPF+vl3mRCsF1ndm2bVr67e1tkXPGGCGsLDlKMCHE/f09hkCe511eXo7Go/V6XavVNF0H0qPiSK8u2Y+jaL9Pq26apn8cXeTHVLy7R94vGj8Ecj74dUJI9fjboPqon9wHfiilSr3jzexHb1UY/+gNVSVThDFmaLppGKfHx41Gox7UiNgqXNHWu0wry5xSWpZFlke9Xq9/0PN9dz6fw1xos4miMM6yjHMZx3Gz2Tw9PY3j+Pr6Stf1i/OzIPDjOA58F4WoZRiEkHa73el0kiRq1dxGo0EYXa7KNIuLogBFcj6faqbhunaWJZtwBT69ptMo3mR5VJa5rjMuSl1nvu+GYShE6bq279eEQENueJ6nFA2oAyJ+u9EsyxJiBc/zDEODwQTwCeibZuMJDAthG0F3usosy+r1Okhb0CL88MMP6/X69PQ0Lyz4poEvJimZz+eWZaBN5ZxrpkHIdgkPBjloW2azWc7LrQu9xsqy5HIbeJQxfbevU0k9iqKSSwAn6HEcRzMMjRACyC0XinOJk/FwcFwIjh5V13XdNHCsO45T5sVisSBCSik9z9Moy7KMUJZlWavV8jwP86Fms1mWOfg0OGcJISAhQv+dJEkYrtMsJkRBDacbrCxLx/YsyypLAfQI12ezWU/TdDR66Pf76FaiaJNlie1aeZEWZSYJeaeikHvaPPpjZOvqKlfVhqb3udcfRCP5qHOrMuGPRu8O5Hx/pr/7AueKlFLf80fEUfcBZrOfqPcjEzfH8ZSQZSEIEZZu2Jbte55j2Y16Swo5ny+JkIwxx/ZM06REwwXturYQXEtou93EygF4eOMlwZU4z0shRK1Zm8/n0KQ/efLk5GiQpsnDw8PpydFms2o2641aLU0TsPLv729FTlar1SYKF4uFEMLzPKZpgM5BQFU7U0ac7ppmMEZqdQ8+vJZldLqtkueGodfrQbvV4Vyapm6atu8FRVFIs+k4DlOk3+8XRcHLMssypogeMEkUdPpJHNdqNThDj6fbZbpKKczicNmBsYCxPiZ7Sqmjo6PLqzcwaA2CoN1u2p776aefFkX26tUry9oaLidJjGbBdd3VbOuqzDnXiUKGzNJE13WhJCZD2m6Up2nachHZtu35jmkaZVnmeapptN/v27Ypyh28KYlSFNm+3W7PV0tQW7XdjVLqed48SZWQpqa7rlv3A0boerVq1htYCgScSQgB1CeOY6UEZBaKSJgjcs43m1jtRiZSCil5nudpxo+Ojjw3UErN50sEuev6pmkahgZyDzgPGlGw1apU/PhL33k9qT2zQ7mnJ9q/oNXeHELtaWc/ntTv56UPHqHSN+GLHccCheg/qf2tIrZ60h8Nsx99AdW/uq6XosAnVLXpQojb21vLsmpBUPcDx3E0uh0KB41GnuemaQjBhLQJYbPp/P7hDodFnpe6rteCOiFsPp9HUTKbzf70pz+ZpvlXf/WvPv/882izyrL0k08+MQ1ts1mBdbFabVf8LZfL5XRTlmWUxOBV27bNNA0Tv8V6ZVlWrVbbLj/NMkJIo1lHv+04FtOk73uNRhBFHmh3zVatKErOC00zdEPjgkZh6Fq2tUN316sVCDegpzRqddM0geADAe72DyGJwOZnOBTZtv369WssbPj+++8B8FiWNZ/PgyD46quv7u7uiqJYrVbpZMwYg3oY9bzjeXmelWUZx/FyudTY1qW3LEuLUThE5GWBTAjyJytLzJMQA4ZhGAw7DAspuWW5QRAQInWmGYbluq7BdIxJYQ8HcaOiW6ojHgrvj2PZiotRkuimddDrd9ptzXaw/oBSCm6NpmmrFbSgGSFktVoZpg7YJs/zohCQL2k6LcsiyxLOuSJC7NkimqZZrzVbrY5lWff3t6Zpnp+fg/h+d/0Af7rZbIZ0bQIWr/LeB6lp/ztV37VfuO5f3PvX/X4mrNo2pKvqV7YVKduOK8AoIoRIyX80m+E+lFJEDtkxCgR/xwncZ7Ttv7zq9VBK8zynioCJjbMt5lwJWfeDIAi63a5nO2VZlnmBzifw67oeoyINgsC2zSSJyrJUSt+y4DXD87yyFErRsiyHk9nTp08vLi5OTk5gvwXyVFnmURTd3ZVpHE8m4zzPYeFcUSsxSatgOtu26YZSSn3fN20Lf5RhGI3ANwxjE65KnmkaMQzNMLVa3WeMUaaUErpOLVsnhOg6sW29VA74N3e3t0EQVPF2dXWFckujjDEWJ8l8sciLoh7UFSHM0AVROS8Vo4Iow7YGx0eW60hKxEyto3ATR77vx1l6fDg4Pz9Hoo6izc3dLfDYVqcNnga86vI8x96IVs1HUk3TlGisMghkjIlSglEkd7Y3ZVlSaggh8lwqJT3Hct26bZu6ruV5qtFttlBU4SQFkTCKIs6547mWZUmiIO/gnM9mM40ynbIsy3rtTrvdXq9WScnBnmWMBUGA2nu3zaLcSrQMDZm/KAoiNSkl51wqUu78RXXD2mw2vJR0T91eOa+BhwBHvNF0QghZLBb4TMEl+JFNvep9+UKVx9ie8pB+dCM/dkPe2itK6Xs/2aKldL9sALxRvQz6PjIEEgww5eqVkL1JoNwZNLI9tu7+Lc9zyzAxscBRZJmmqRtYHhSGYbhaZ1lGFcGmWOygTrPYMLRefytTwN4VpEqN6egGcW1JKQ8PD1ut1u3tbRRFrm0qJVerlefaq9VyvSZ3NzeTydhxnMPDQzQJtm0pSgjBDpOMy7Isy3ojIBqBkGIdbhzHwqTLNs1ms0moKIrMD1yAPY5jWpYlpSKU27bjc1dKAtM+O/dbrdZ1koxGI2h2EBjQlUopDcuoNepCiIeHB875YhMppeB1DbMWMJIZY3d3d47jgJSH+rPZbN7d3ZmmuVgsIOFrNpuu615cXMyXC6UEpdSyLNu2MF7HS8LUIcuynJdZloVh6AU+AgaHI9/NnKWUGqOmaeIiIoRYlmUYuuCllNKyTEJIFEVMUV03cW0gfmzbBrM0ThMM2+bz+fX1tUZZs1a3TBO7D5bLZcpFxSVA9wiZhVKqLEs0GsCNQBAnAh6+GWVKKckYMQwDLw8jE8ZYWRZhGCpFXdetNwKpOBeFH7iWbfT73eVyKURpWb6UEhY2Otnr3HD8VHXmfrrbuyn60W0/sD6oJBnb92WqQrBafL/NYFVB+67Zw6Gye0AB26UdAKMq55vdC2B73HH5Y7todq+NUaoRwrIsL9JM13XP8VutNiEsjtMkSm3TdF3XsdADyDhPUBcZhmZbrlRcCGVbTpLGnHPBpdBFlmVRlOR5rjHj/Pz85ubm+vq63W7quj56uIvjSNd12zK63Xa3286SRNc1ZF2lxFTMCCEFL/Xd4vWCl2EYmqbZ7XZt28bmUwzWpJSM0UajLmUpZNnptCglJc8NUzs6HsRxbJmOZdlKqTTJGaO6rsWLzcHBQb1eL9LMcZwsTjCIOzo6wjkCSU4YhqPRKMuyVkeDDblSCqRwpRS0WoZhtFotCGHBWU/T9OjoCL5SUsp6PSiK4rvvvqvVfMKo41igXEdRiHIagcEYg+kLdPd5ntca9eoz0jQNkyV87kmcOo5j2UaSxGEYahr1PEdJURR5zQ9c18+yTJZC103GWDW+r1RskEowxsAp7Xd7zVo93GygQSnLkmk6ukHOueNaUso4jqXcajLozsZB7HbpaNVSRJ0ahg4DTugYqxCQUirJKaWe52V5hM2thBCsKpBSDgYD04YHQr5er/8/mJNilez1C2gAAAAASUVORK5CYII=\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "execution_count": 10
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "My5Z6p7pQ3UC"
+ },
+ "source": [
+ "### Support new dataset\n",
+ "\n",
+ "We have two methods to support a new dataset in MMClassification.\n",
+ "\n",
+ "The simplest method is to re-organize the new dataset as the format of a dataset supported officially (like ImageNet). And the other method is to create a new dataset class, and more details are in [the docs](https://mmclassification.readthedocs.io/en/latest/tutorials/new_dataset.html#an-example-of-customized-dataset).\n",
+ "\n",
+ "In this tutorial, for convenience, we have re-organized the cats & dogs dataset as the format of ImageNet."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "P335gKt9Q5U-"
+ },
+ "source": [
+ "Besides image files, it also includes the following files:\n",
+ "\n",
+ "1. A class list file, and every line is a class.\n",
+ " ```\n",
+ " cats\n",
+ " dogs\n",
+ " ```\n",
+ "2. Training / Validation / Test annotation files. And every line includes an file path and the corresponding label.\n",
+ "\n",
+ " ```\n",
+ " ...\n",
+ " cats/cat.3769.jpg 0\n",
+ " cats/cat.882.jpg 0\n",
+ " ...\n",
+ " dogs/dog.3881.jpg 1\n",
+ " dogs/dog.3377.jpg 1\n",
+ " ...\n",
+ " ```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "BafQ7ijBQ8N_"
+ },
+ "source": [
+ "## Train and test model with shell commands\n",
+ "\n",
+ "You can use shell commands provided by MMClassification to do the following task:\n",
+ "\n",
+ "1. Train a model\n",
+ "2. Fine-tune a model\n",
+ "3. Test a model\n",
+ "4. Inference with a model\n",
+ "\n",
+ "The procedure to train and fine-tune a model is almost the same. And we have introduced how to do these tasks with Python API. In the following, we will introduce how to do them with shell commands. More details are in [the docs](https://mmclassification.readthedocs.io/en/latest/getting_started.html)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "Aj5cGMihURrZ"
+ },
+ "source": [
+ "### Fine-tune a model\n",
+ "\n",
+ "The steps to fine-tune a model are as below:\n",
+ "\n",
+ "1. Prepare the custom dataset.\n",
+ "2. Create a new config file of the task.\n",
+ "3. Start training task by shell commands.\n",
+ "\n",
+ "We have finished the first step, and then we will introduce the next two steps.\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "WBBV3aG79ZH5"
+ },
+ "source": [
+ "#### Create a new config file\n",
+ "\n",
+ "To reuse the common parts of different config files, we support inheriting multiple base config files. For example, to fine-tune a MobileNetV2 model, the new config file can create the model's basic structure by inheriting `configs/_base_/models/mobilenet_v2_1x.py`.\n",
+ "\n",
+ "According to the common practice, we usually split whole configs into four parts: model, dataset, learning rate schedule, and runtime. Configs of each part are saved into one file in the `configs/_base_` folder. \n",
+ "\n",
+ "And then, when creating a new config file, we can select some parts to inherit and only override some different configs.\n",
+ "\n",
+ "The head of the final config file should look like:\n",
+ "\n",
+ "```python\n",
+ "_base_ = [\n",
+ " '../_base_/models/mobilenet_v2_1x.py',\n",
+ " '../_base_/schedules/imagenet_bs256_epochstep.py',\n",
+ " '../_base_/default_runtime.py'\n",
+ "]\n",
+ "```\n",
+ "\n",
+ "Here, because the dataset configs are almost brand new, we don't need to inherit any dataset config file.\n",
+ "\n",
+ "Of course, you can also create an entire config file without inheritance, like `configs/mnist/lenet5.py`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "_UV3oBhLRG8B"
+ },
+ "source": [
+ "After that, we only need to set the part of configs we want to modify, because the inherited configs will be merged to the final configs."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "8QfM4qBeWIQh",
+ "outputId": "a826f0cf-2633-4a9a-e49b-4be7eca5e3a0"
+ },
+ "source": [
+ "%%writefile configs/mobilenet_v2/mobilenet_v2_1x_cats_dogs.py\n",
+ "_base_ = [\n",
+ " '../_base_/models/mobilenet_v2_1x.py',\n",
+ " '../_base_/schedules/imagenet_bs256_epochstep.py',\n",
+ " '../_base_/default_runtime.py'\n",
+ "]\n",
+ "\n",
+ "# ---- Model configs ----\n",
+ "# Here we use init_cfg to load pre-trained model.\n",
+ "# In this way, only the weights of backbone will be loaded.\n",
+ "# And modify the num_classes to match our dataset.\n",
+ "\n",
+ "model = dict(\n",
+ " backbone=dict(\n",
+ " init_cfg = dict(\n",
+ " type='Pretrained', \n",
+ " checkpoint='https://download.openmmlab.com/mmclassification/v0/mobilenet_v2/mobilenet_v2_batch256_imagenet_20200708-3b2dc3af.pth', \n",
+ " prefix='backbone')\n",
+ " ),\n",
+ " head=dict(\n",
+ " num_classes=2,\n",
+ " topk = (1, )\n",
+ " ))\n",
+ "\n",
+ "# ---- Dataset configs ----\n",
+ "# We re-organized the dataset as ImageNet format.\n",
+ "dataset_type = 'ImageNet'\n",
+ "img_norm_cfg = dict(\n",
+ " mean=[124.508, 116.050, 106.438],\n",
+ " std=[58.577, 57.310, 57.437],\n",
+ " to_rgb=True)\n",
+ "train_pipeline = [\n",
+ " dict(type='LoadImageFromFile'),\n",
+ " dict(type='RandomResizedCrop', size=224, backend='pillow'),\n",
+ " dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),\n",
+ " dict(type='Normalize', **img_norm_cfg),\n",
+ " dict(type='ImageToTensor', keys=['img']),\n",
+ " dict(type='ToTensor', keys=['gt_label']),\n",
+ " dict(type='Collect', keys=['img', 'gt_label'])\n",
+ "]\n",
+ "test_pipeline = [\n",
+ " dict(type='LoadImageFromFile'),\n",
+ " dict(type='Resize', size=(256, -1), backend='pillow'),\n",
+ " dict(type='CenterCrop', crop_size=224),\n",
+ " dict(type='Normalize', **img_norm_cfg),\n",
+ " dict(type='ImageToTensor', keys=['img']),\n",
+ " dict(type='Collect', keys=['img'])\n",
+ "]\n",
+ "data = dict(\n",
+ " # Specify the batch size and number of workers in each GPU.\n",
+ " # Please configure it according to your hardware.\n",
+ " samples_per_gpu=32,\n",
+ " workers_per_gpu=2,\n",
+ " # Specify the training dataset type and path\n",
+ " train=dict(\n",
+ " type=dataset_type,\n",
+ " data_prefix='data/cats_dogs_dataset/training_set/training_set',\n",
+ " classes='data/cats_dogs_dataset/classes.txt',\n",
+ " pipeline=train_pipeline),\n",
+ " # Specify the validation dataset type and path\n",
+ " val=dict(\n",
+ " type=dataset_type,\n",
+ " data_prefix='data/cats_dogs_dataset/val_set/val_set',\n",
+ " ann_file='data/cats_dogs_dataset/val.txt',\n",
+ " classes='data/cats_dogs_dataset/classes.txt',\n",
+ " pipeline=test_pipeline),\n",
+ " # Specify the test dataset type and path\n",
+ " test=dict(\n",
+ " type=dataset_type,\n",
+ " data_prefix='data/cats_dogs_dataset/test_set/test_set',\n",
+ " ann_file='data/cats_dogs_dataset/test.txt',\n",
+ " classes='data/cats_dogs_dataset/classes.txt',\n",
+ " pipeline=test_pipeline))\n",
+ "\n",
+ "# Specify evaluation metric\n",
+ "evaluation = dict(metric='accuracy', metric_options={'topk': (1, )})\n",
+ "\n",
+ "# ---- Schedule configs ----\n",
+ "# Usually in fine-tuning, we need a smaller learning rate and less training epochs.\n",
+ "# Specify the learning rate\n",
+ "optimizer = dict(type='SGD', lr=0.005, momentum=0.9, weight_decay=0.0001)\n",
+ "optimizer_config = dict(grad_clip=None)\n",
+ "# Set the learning rate scheduler\n",
+ "lr_config = dict(policy='step', step=1, gamma=0.1)\n",
+ "runner = dict(type='EpochBasedRunner', max_epochs=2)\n",
+ "\n",
+ "# ---- Runtime configs ----\n",
+ "# Output training log every 10 iterations.\n",
+ "log_config = dict(interval=10)"
+ ],
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "Writing configs/mobilenet_v2/mobilenet_v2_1x_cats_dogs.py\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "chLX7bL3RP2F"
+ },
+ "source": [
+ "#### Use shell command to start fine-tuning\n",
+ "\n",
+ "We use `tools/train.py` to fine-tune a model:\n",
+ "\n",
+ "```shell\n",
+ "python tools/train.py ${CONFIG_FILE} [optional arguments]\n",
+ "```\n",
+ "\n",
+ "And if you want to specify another folder to save log files and checkpoints, use the argument `--work_dir ${YOUR_WORK_DIR}`.\n",
+ "\n",
+ "If you want to ensure reproducibility, use the argument `--seed ${SEED}` to set a random seed. And the argument `--deterministic` can enable the deterministic option in cuDNN to further ensure reproducibility, but it may reduce the training speed.\n",
+ "\n",
+ "Here we use the `MobileNetV2` model and cats & dogs dataset as an example:\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "gbFGR4SBRUYN",
+ "outputId": "3412752c-433f-43c5-82a9-3495d1cd797a"
+ },
+ "source": [
+ "!python tools/train.py \\\n",
+ " configs/mobilenet_v2/mobilenet_v2_1x_cats_dogs.py \\\n",
+ " --work-dir work_dirs/mobilenet_v2_1x_cats_dogs \\\n",
+ " --seed 0 \\\n",
+ " --deterministic"
+ ],
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "/usr/local/lib/python3.7/dist-packages/mmcv/cnn/bricks/transformer.py:28: UserWarning: Fail to import ``MultiScaleDeformableAttention`` from ``mmcv.ops.multi_scale_deform_attn``, You should install ``mmcv-full`` if you need this module. \n",
+ " warnings.warn('Fail to import ``MultiScaleDeformableAttention`` from '\n",
+ "/usr/lib/python3.7/importlib/_bootstrap.py:219: RuntimeWarning: numpy.ufunc size changed, may indicate binary incompatibility. Expected 192 from C header, got 216 from PyObject\n",
+ " return f(*args, **kwds)\n",
+ "/usr/lib/python3.7/importlib/_bootstrap.py:219: RuntimeWarning: numpy.ufunc size changed, may indicate binary incompatibility. Expected 192 from C header, got 216 from PyObject\n",
+ " return f(*args, **kwds)\n",
+ "/usr/lib/python3.7/importlib/_bootstrap.py:219: RuntimeWarning: numpy.ufunc size changed, may indicate binary incompatibility. Expected 192 from C header, got 216 from PyObject\n",
+ " return f(*args, **kwds)\n",
+ "/usr/local/lib/python3.7/dist-packages/yaml/constructor.py:126: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3,and in 3.9 it will stop working\n",
+ " if not isinstance(key, collections.Hashable):\n",
+ "2021-10-21 02:48:20,030 - mmcls - INFO - Environment info:\n",
+ "------------------------------------------------------------\n",
+ "sys.platform: linux\n",
+ "Python: 3.7.12 (default, Sep 10 2021, 00:21:48) [GCC 7.5.0]\n",
+ "CUDA available: True\n",
+ "GPU 0: Tesla K80\n",
+ "CUDA_HOME: /usr/local/cuda\n",
+ "NVCC: Build cuda_11.1.TC455_06.29190527_0\n",
+ "GCC: gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0\n",
+ "PyTorch: 1.9.0+cu111\n",
+ "PyTorch compiling details: PyTorch built with:\n",
+ " - GCC 7.3\n",
+ " - C++ Version: 201402\n",
+ " - Intel(R) Math Kernel Library Version 2020.0.0 Product Build 20191122 for Intel(R) 64 architecture applications\n",
+ " - Intel(R) MKL-DNN v2.1.2 (Git Hash 98be7e8afa711dc9b66c8ff3504129cb82013cdb)\n",
+ " - OpenMP 201511 (a.k.a. OpenMP 4.5)\n",
+ " - NNPACK is enabled\n",
+ " - CPU capability usage: AVX2\n",
+ " - CUDA Runtime 11.1\n",
+ " - NVCC architecture flags: -gencode;arch=compute_37,code=sm_37;-gencode;arch=compute_50,code=sm_50;-gencode;arch=compute_60,code=sm_60;-gencode;arch=compute_70,code=sm_70;-gencode;arch=compute_75,code=sm_75;-gencode;arch=compute_80,code=sm_80;-gencode;arch=compute_86,code=sm_86\n",
+ " - CuDNN 8.0.5\n",
+ " - Magma 2.5.2\n",
+ " - Build settings: BLAS_INFO=mkl, BUILD_TYPE=Release, CUDA_VERSION=11.1, CUDNN_VERSION=8.0.5, CXX_COMPILER=/opt/rh/devtoolset-7/root/usr/bin/c++, CXX_FLAGS= -Wno-deprecated -fvisibility-inlines-hidden -DUSE_PTHREADPOOL -fopenmp -DNDEBUG -DUSE_KINETO -DUSE_FBGEMM -DUSE_QNNPACK -DUSE_PYTORCH_QNNPACK -DUSE_XNNPACK -DSYMBOLICATE_MOBILE_DEBUG_HANDLE -O2 -fPIC -Wno-narrowing -Wall -Wextra -Werror=return-type -Wno-missing-field-initializers -Wno-type-limits -Wno-array-bounds -Wno-unknown-pragmas -Wno-sign-compare -Wno-unused-parameter -Wno-unused-variable -Wno-unused-function -Wno-unused-result -Wno-unused-local-typedefs -Wno-strict-overflow -Wno-strict-aliasing -Wno-error=deprecated-declarations -Wno-stringop-overflow -Wno-psabi -Wno-error=pedantic -Wno-error=redundant-decls -Wno-error=old-style-cast -fdiagnostics-color=always -faligned-new -Wno-unused-but-set-variable -Wno-maybe-uninitialized -fno-math-errno -fno-trapping-math -Werror=format -Wno-stringop-overflow, LAPACK_INFO=mkl, PERF_WITH_AVX=1, PERF_WITH_AVX2=1, PERF_WITH_AVX512=1, TORCH_VERSION=1.9.0, USE_CUDA=ON, USE_CUDNN=ON, USE_EXCEPTION_PTR=1, USE_GFLAGS=OFF, USE_GLOG=OFF, USE_MKL=ON, USE_MKLDNN=ON, USE_MPI=OFF, USE_NCCL=ON, USE_NNPACK=ON, USE_OPENMP=ON, \n",
+ "\n",
+ "TorchVision: 0.10.0+cu111\n",
+ "OpenCV: 4.1.2\n",
+ "MMCV: 1.3.15\n",
+ "MMCV Compiler: n/a\n",
+ "MMCV CUDA Compiler: n/a\n",
+ "MMClassification: 0.16.0+77a3834\n",
+ "------------------------------------------------------------\n",
+ "\n",
+ "2021-10-21 02:48:20,030 - mmcls - INFO - Distributed training: False\n",
+ "2021-10-21 02:48:20,688 - mmcls - INFO - Config:\n",
+ "model = dict(\n",
+ " type='ImageClassifier',\n",
+ " backbone=dict(\n",
+ " type='MobileNetV2',\n",
+ " widen_factor=1.0,\n",
+ " init_cfg=dict(\n",
+ " type='Pretrained',\n",
+ " checkpoint=\n",
+ " 'https://download.openmmlab.com/mmclassification/v0/mobilenet_v2/mobilenet_v2_batch256_imagenet_20200708-3b2dc3af.pth',\n",
+ " prefix='backbone')),\n",
+ " neck=dict(type='GlobalAveragePooling'),\n",
+ " head=dict(\n",
+ " type='LinearClsHead',\n",
+ " num_classes=2,\n",
+ " in_channels=1280,\n",
+ " loss=dict(type='CrossEntropyLoss', loss_weight=1.0),\n",
+ " topk=(1, )))\n",
+ "optimizer = dict(type='SGD', lr=0.005, momentum=0.9, weight_decay=0.0001)\n",
+ "optimizer_config = dict(grad_clip=None)\n",
+ "lr_config = dict(policy='step', gamma=0.1, step=1)\n",
+ "runner = dict(type='EpochBasedRunner', max_epochs=2)\n",
+ "checkpoint_config = dict(interval=1)\n",
+ "log_config = dict(interval=10, hooks=[dict(type='TextLoggerHook')])\n",
+ "dist_params = dict(backend='nccl')\n",
+ "log_level = 'INFO'\n",
+ "load_from = None\n",
+ "resume_from = None\n",
+ "workflow = [('train', 1)]\n",
+ "dataset_type = 'ImageNet'\n",
+ "img_norm_cfg = dict(\n",
+ " mean=[124.508, 116.05, 106.438], std=[58.577, 57.31, 57.437], to_rgb=True)\n",
+ "train_pipeline = [\n",
+ " dict(type='LoadImageFromFile'),\n",
+ " dict(type='RandomResizedCrop', size=224, backend='pillow'),\n",
+ " dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),\n",
+ " dict(\n",
+ " type='Normalize',\n",
+ " mean=[124.508, 116.05, 106.438],\n",
+ " std=[58.577, 57.31, 57.437],\n",
+ " to_rgb=True),\n",
+ " dict(type='ImageToTensor', keys=['img']),\n",
+ " dict(type='ToTensor', keys=['gt_label']),\n",
+ " dict(type='Collect', keys=['img', 'gt_label'])\n",
+ "]\n",
+ "test_pipeline = [\n",
+ " dict(type='LoadImageFromFile'),\n",
+ " dict(type='Resize', size=(256, -1), backend='pillow'),\n",
+ " dict(type='CenterCrop', crop_size=224),\n",
+ " dict(\n",
+ " type='Normalize',\n",
+ " mean=[124.508, 116.05, 106.438],\n",
+ " std=[58.577, 57.31, 57.437],\n",
+ " to_rgb=True),\n",
+ " dict(type='ImageToTensor', keys=['img']),\n",
+ " dict(type='Collect', keys=['img'])\n",
+ "]\n",
+ "data = dict(\n",
+ " samples_per_gpu=32,\n",
+ " workers_per_gpu=2,\n",
+ " train=dict(\n",
+ " type='ImageNet',\n",
+ " data_prefix='data/cats_dogs_dataset/training_set/training_set',\n",
+ " classes='data/cats_dogs_dataset/classes.txt',\n",
+ " pipeline=[\n",
+ " dict(type='LoadImageFromFile'),\n",
+ " dict(type='RandomResizedCrop', size=224, backend='pillow'),\n",
+ " dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),\n",
+ " dict(\n",
+ " type='Normalize',\n",
+ " mean=[124.508, 116.05, 106.438],\n",
+ " std=[58.577, 57.31, 57.437],\n",
+ " to_rgb=True),\n",
+ " dict(type='ImageToTensor', keys=['img']),\n",
+ " dict(type='ToTensor', keys=['gt_label']),\n",
+ " dict(type='Collect', keys=['img', 'gt_label'])\n",
+ " ]),\n",
+ " val=dict(\n",
+ " type='ImageNet',\n",
+ " data_prefix='data/cats_dogs_dataset/val_set/val_set',\n",
+ " ann_file='data/cats_dogs_dataset/val.txt',\n",
+ " classes='data/cats_dogs_dataset/classes.txt',\n",
+ " pipeline=[\n",
+ " dict(type='LoadImageFromFile'),\n",
+ " dict(type='Resize', size=(256, -1), backend='pillow'),\n",
+ " dict(type='CenterCrop', crop_size=224),\n",
+ " dict(\n",
+ " type='Normalize',\n",
+ " mean=[124.508, 116.05, 106.438],\n",
+ " std=[58.577, 57.31, 57.437],\n",
+ " to_rgb=True),\n",
+ " dict(type='ImageToTensor', keys=['img']),\n",
+ " dict(type='Collect', keys=['img'])\n",
+ " ]),\n",
+ " test=dict(\n",
+ " type='ImageNet',\n",
+ " data_prefix='data/cats_dogs_dataset/test_set/test_set',\n",
+ " ann_file='data/cats_dogs_dataset/test.txt',\n",
+ " classes='data/cats_dogs_dataset/classes.txt',\n",
+ " pipeline=[\n",
+ " dict(type='LoadImageFromFile'),\n",
+ " dict(type='Resize', size=(256, -1), backend='pillow'),\n",
+ " dict(type='CenterCrop', crop_size=224),\n",
+ " dict(\n",
+ " type='Normalize',\n",
+ " mean=[124.508, 116.05, 106.438],\n",
+ " std=[58.577, 57.31, 57.437],\n",
+ " to_rgb=True),\n",
+ " dict(type='ImageToTensor', keys=['img']),\n",
+ " dict(type='Collect', keys=['img'])\n",
+ " ]))\n",
+ "evaluation = dict(metric='accuracy', metric_options=dict(topk=(1, )))\n",
+ "work_dir = 'work_dirs/mobilenet_v2_1x_cats_dogs'\n",
+ "gpu_ids = range(0, 1)\n",
+ "\n",
+ "2021-10-21 02:48:20,689 - mmcls - INFO - Set random seed to 0, deterministic: True\n",
+ "2021-10-21 02:48:20,854 - mmcls - INFO - initialize MobileNetV2 with init_cfg {'type': 'Pretrained', 'checkpoint': 'https://download.openmmlab.com/mmclassification/v0/mobilenet_v2/mobilenet_v2_batch256_imagenet_20200708-3b2dc3af.pth', 'prefix': 'backbone'}\n",
+ "2021-10-21 02:48:20,855 - mmcv - INFO - load backbone in model from: https://download.openmmlab.com/mmclassification/v0/mobilenet_v2/mobilenet_v2_batch256_imagenet_20200708-3b2dc3af.pth\n",
+ "Use load_from_http loader\n",
+ "Downloading: \"https://download.openmmlab.com/mmclassification/v0/mobilenet_v2/mobilenet_v2_batch256_imagenet_20200708-3b2dc3af.pth\" to /root/.cache/torch/hub/checkpoints/mobilenet_v2_batch256_imagenet_20200708-3b2dc3af.pth\n",
+ "100% 13.5M/13.5M [00:01<00:00, 9.54MB/s]\n",
+ "2021-10-21 02:48:23,564 - mmcls - INFO - initialize LinearClsHead with init_cfg {'type': 'Normal', 'layer': 'Linear', 'std': 0.01}\n",
+ "2021-10-21 02:48:38,767 - mmcls - INFO - Start running, host: root@992cc7e7be60, work_dir: /content/mmclassification/work_dirs/mobilenet_v2_1x_cats_dogs\n",
+ "2021-10-21 02:48:38,767 - mmcls - INFO - Hooks will be executed in the following order:\n",
+ "before_run:\n",
+ "(VERY_HIGH ) StepLrUpdaterHook \n",
+ "(NORMAL ) CheckpointHook \n",
+ "(LOW ) EvalHook \n",
+ "(VERY_LOW ) TextLoggerHook \n",
+ " -------------------- \n",
+ "before_train_epoch:\n",
+ "(VERY_HIGH ) StepLrUpdaterHook \n",
+ "(LOW ) IterTimerHook \n",
+ "(LOW ) EvalHook \n",
+ "(VERY_LOW ) TextLoggerHook \n",
+ " -------------------- \n",
+ "before_train_iter:\n",
+ "(VERY_HIGH ) StepLrUpdaterHook \n",
+ "(LOW ) IterTimerHook \n",
+ "(LOW ) EvalHook \n",
+ " -------------------- \n",
+ "after_train_iter:\n",
+ "(ABOVE_NORMAL) OptimizerHook \n",
+ "(NORMAL ) CheckpointHook \n",
+ "(LOW ) IterTimerHook \n",
+ "(LOW ) EvalHook \n",
+ "(VERY_LOW ) TextLoggerHook \n",
+ " -------------------- \n",
+ "after_train_epoch:\n",
+ "(NORMAL ) CheckpointHook \n",
+ "(LOW ) EvalHook \n",
+ "(VERY_LOW ) TextLoggerHook \n",
+ " -------------------- \n",
+ "before_val_epoch:\n",
+ "(LOW ) IterTimerHook \n",
+ "(VERY_LOW ) TextLoggerHook \n",
+ " -------------------- \n",
+ "before_val_iter:\n",
+ "(LOW ) IterTimerHook \n",
+ " -------------------- \n",
+ "after_val_iter:\n",
+ "(LOW ) IterTimerHook \n",
+ " -------------------- \n",
+ "after_val_epoch:\n",
+ "(VERY_LOW ) TextLoggerHook \n",
+ " -------------------- \n",
+ "2021-10-21 02:48:38,768 - mmcls - INFO - workflow: [('train', 1)], max: 2 epochs\n",
+ "2021-10-21 02:48:44,261 - mmcls - INFO - Epoch [1][10/201]\tlr: 5.000e-03, eta: 0:03:29, time: 0.533, data_time: 0.257, memory: 1709, loss: 0.3917\n",
+ "2021-10-21 02:48:46,950 - mmcls - INFO - Epoch [1][20/201]\tlr: 5.000e-03, eta: 0:02:33, time: 0.269, data_time: 0.019, memory: 1709, loss: 0.3508\n",
+ "2021-10-21 02:48:49,618 - mmcls - INFO - Epoch [1][30/201]\tlr: 5.000e-03, eta: 0:02:12, time: 0.266, data_time: 0.021, memory: 1709, loss: 0.3955\n",
+ "2021-10-21 02:48:52,271 - mmcls - INFO - Epoch [1][40/201]\tlr: 5.000e-03, eta: 0:02:00, time: 0.266, data_time: 0.018, memory: 1709, loss: 0.2485\n",
+ "2021-10-21 02:48:54,984 - mmcls - INFO - Epoch [1][50/201]\tlr: 5.000e-03, eta: 0:01:53, time: 0.272, data_time: 0.019, memory: 1709, loss: 0.4196\n",
+ "2021-10-21 02:48:57,661 - mmcls - INFO - Epoch [1][60/201]\tlr: 5.000e-03, eta: 0:01:46, time: 0.266, data_time: 0.019, memory: 1709, loss: 0.4994\n",
+ "2021-10-21 02:49:00,341 - mmcls - INFO - Epoch [1][70/201]\tlr: 5.000e-03, eta: 0:01:41, time: 0.268, data_time: 0.018, memory: 1709, loss: 0.4372\n",
+ "2021-10-21 02:49:03,035 - mmcls - INFO - Epoch [1][80/201]\tlr: 5.000e-03, eta: 0:01:37, time: 0.270, data_time: 0.019, memory: 1709, loss: 0.3179\n",
+ "2021-10-21 02:49:05,731 - mmcls - INFO - Epoch [1][90/201]\tlr: 5.000e-03, eta: 0:01:32, time: 0.269, data_time: 0.020, memory: 1709, loss: 0.3175\n",
+ "2021-10-21 02:49:08,404 - mmcls - INFO - Epoch [1][100/201]\tlr: 5.000e-03, eta: 0:01:29, time: 0.268, data_time: 0.019, memory: 1709, loss: 0.3412\n",
+ "2021-10-21 02:49:11,106 - mmcls - INFO - Epoch [1][110/201]\tlr: 5.000e-03, eta: 0:01:25, time: 0.270, data_time: 0.016, memory: 1709, loss: 0.2985\n",
+ "2021-10-21 02:49:13,776 - mmcls - INFO - Epoch [1][120/201]\tlr: 5.000e-03, eta: 0:01:21, time: 0.267, data_time: 0.018, memory: 1709, loss: 0.2778\n",
+ "2021-10-21 02:49:16,478 - mmcls - INFO - Epoch [1][130/201]\tlr: 5.000e-03, eta: 0:01:18, time: 0.270, data_time: 0.021, memory: 1709, loss: 0.2229\n",
+ "2021-10-21 02:49:19,130 - mmcls - INFO - Epoch [1][140/201]\tlr: 5.000e-03, eta: 0:01:15, time: 0.266, data_time: 0.018, memory: 1709, loss: 0.2318\n",
+ "2021-10-21 02:49:21,812 - mmcls - INFO - Epoch [1][150/201]\tlr: 5.000e-03, eta: 0:01:12, time: 0.268, data_time: 0.019, memory: 1709, loss: 0.2333\n",
+ "2021-10-21 02:49:24,514 - mmcls - INFO - Epoch [1][160/201]\tlr: 5.000e-03, eta: 0:01:08, time: 0.270, data_time: 0.017, memory: 1709, loss: 0.2783\n",
+ "2021-10-21 02:49:27,184 - mmcls - INFO - Epoch [1][170/201]\tlr: 5.000e-03, eta: 0:01:05, time: 0.267, data_time: 0.017, memory: 1709, loss: 0.2132\n",
+ "2021-10-21 02:49:29,875 - mmcls - INFO - Epoch [1][180/201]\tlr: 5.000e-03, eta: 0:01:02, time: 0.269, data_time: 0.021, memory: 1709, loss: 0.2096\n",
+ "2021-10-21 02:49:32,546 - mmcls - INFO - Epoch [1][190/201]\tlr: 5.000e-03, eta: 0:00:59, time: 0.267, data_time: 0.019, memory: 1709, loss: 0.1729\n",
+ "2021-10-21 02:49:35,200 - mmcls - INFO - Epoch [1][200/201]\tlr: 5.000e-03, eta: 0:00:56, time: 0.265, data_time: 0.017, memory: 1709, loss: 0.1969\n",
+ "2021-10-21 02:49:35,247 - mmcls - INFO - Saving checkpoint at 1 epochs\n",
+ "[ ] 0/1601, elapsed: 0s, ETA:[W pthreadpool-cpp.cc:90] Warning: Leaking Caffe2 thread-pool after fork. (function pthreadpool)\n",
+ "[W pthreadpool-cpp.cc:90] Warning: Leaking Caffe2 thread-pool after fork. (function pthreadpool)\n",
+ "[>>] 1601/1601, 173.2 task/s, elapsed: 9s, ETA: 0s2021-10-21 02:49:44,587 - mmcls - INFO - Epoch(val) [1][51]\taccuracy_top-1: 95.6277\n",
+ "2021-10-21 02:49:49,625 - mmcls - INFO - Epoch [2][10/201]\tlr: 5.000e-04, eta: 0:00:55, time: 0.488, data_time: 0.237, memory: 1709, loss: 0.1764\n",
+ "2021-10-21 02:49:52,305 - mmcls - INFO - Epoch [2][20/201]\tlr: 5.000e-04, eta: 0:00:52, time: 0.270, data_time: 0.018, memory: 1709, loss: 0.1514\n",
+ "2021-10-21 02:49:55,060 - mmcls - INFO - Epoch [2][30/201]\tlr: 5.000e-04, eta: 0:00:49, time: 0.275, data_time: 0.016, memory: 1709, loss: 0.1395\n",
+ "2021-10-21 02:49:57,696 - mmcls - INFO - Epoch [2][40/201]\tlr: 5.000e-04, eta: 0:00:46, time: 0.262, data_time: 0.016, memory: 1709, loss: 0.1508\n",
+ "2021-10-21 02:50:00,430 - mmcls - INFO - Epoch [2][50/201]\tlr: 5.000e-04, eta: 0:00:43, time: 0.273, data_time: 0.018, memory: 1709, loss: 0.1771\n",
+ "2021-10-21 02:50:03,099 - mmcls - INFO - Epoch [2][60/201]\tlr: 5.000e-04, eta: 0:00:40, time: 0.268, data_time: 0.020, memory: 1709, loss: 0.1438\n",
+ "2021-10-21 02:50:05,745 - mmcls - INFO - Epoch [2][70/201]\tlr: 5.000e-04, eta: 0:00:37, time: 0.264, data_time: 0.018, memory: 1709, loss: 0.1321\n",
+ "2021-10-21 02:50:08,385 - mmcls - INFO - Epoch [2][80/201]\tlr: 5.000e-04, eta: 0:00:34, time: 0.264, data_time: 0.020, memory: 1709, loss: 0.1629\n",
+ "2021-10-21 02:50:11,025 - mmcls - INFO - Epoch [2][90/201]\tlr: 5.000e-04, eta: 0:00:31, time: 0.264, data_time: 0.019, memory: 1709, loss: 0.1574\n",
+ "2021-10-21 02:50:13,685 - mmcls - INFO - Epoch [2][100/201]\tlr: 5.000e-04, eta: 0:00:28, time: 0.266, data_time: 0.019, memory: 1709, loss: 0.1220\n",
+ "2021-10-21 02:50:16,329 - mmcls - INFO - Epoch [2][110/201]\tlr: 5.000e-04, eta: 0:00:25, time: 0.264, data_time: 0.021, memory: 1709, loss: 0.2550\n",
+ "2021-10-21 02:50:19,007 - mmcls - INFO - Epoch [2][120/201]\tlr: 5.000e-04, eta: 0:00:22, time: 0.268, data_time: 0.020, memory: 1709, loss: 0.1528\n",
+ "2021-10-21 02:50:21,750 - mmcls - INFO - Epoch [2][130/201]\tlr: 5.000e-04, eta: 0:00:20, time: 0.275, data_time: 0.021, memory: 1709, loss: 0.1223\n",
+ "2021-10-21 02:50:24,392 - mmcls - INFO - Epoch [2][140/201]\tlr: 5.000e-04, eta: 0:00:17, time: 0.264, data_time: 0.017, memory: 1709, loss: 0.1734\n",
+ "2021-10-21 02:50:27,049 - mmcls - INFO - Epoch [2][150/201]\tlr: 5.000e-04, eta: 0:00:14, time: 0.265, data_time: 0.020, memory: 1709, loss: 0.1527\n",
+ "2021-10-21 02:50:29,681 - mmcls - INFO - Epoch [2][160/201]\tlr: 5.000e-04, eta: 0:00:11, time: 0.265, data_time: 0.019, memory: 1709, loss: 0.1910\n",
+ "2021-10-21 02:50:32,318 - mmcls - INFO - Epoch [2][170/201]\tlr: 5.000e-04, eta: 0:00:08, time: 0.262, data_time: 0.017, memory: 1709, loss: 0.1922\n",
+ "2021-10-21 02:50:34,955 - mmcls - INFO - Epoch [2][180/201]\tlr: 5.000e-04, eta: 0:00:05, time: 0.264, data_time: 0.021, memory: 1709, loss: 0.1760\n",
+ "2021-10-21 02:50:37,681 - mmcls - INFO - Epoch [2][190/201]\tlr: 5.000e-04, eta: 0:00:03, time: 0.273, data_time: 0.019, memory: 1709, loss: 0.1739\n",
+ "2021-10-21 02:50:40,408 - mmcls - INFO - Epoch [2][200/201]\tlr: 5.000e-04, eta: 0:00:00, time: 0.272, data_time: 0.018, memory: 1709, loss: 0.1654\n",
+ "2021-10-21 02:50:40,443 - mmcls - INFO - Saving checkpoint at 2 epochs\n",
+ "[>>] 1601/1601, 170.9 task/s, elapsed: 9s, ETA: 0s2021-10-21 02:50:49,905 - mmcls - INFO - Epoch(val) [2][51]\taccuracy_top-1: 97.5016\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "m_ZSkwB5Rflb"
+ },
+ "source": [
+ "### Test a model\n",
+ "\n",
+ "We use `tools/test.py` to test a model:\n",
+ "\n",
+ "```\n",
+ "python tools/test.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [optional arguments]\n",
+ "```\n",
+ "\n",
+ "Here are some optional arguments:\n",
+ "\n",
+ "- `--metrics`: The evaluation metrics. The available choices are defined in the dataset class. Usually, you can specify \"accuracy\" to metric a single-label classification task.\n",
+ "- `--metric-options`: The extra options passed to metrics. For example, by specifying \"topk=1\", the \"accuracy\" metric will calculate top-1 accuracy.\n",
+ "\n",
+ "More details are in the help docs of `tools/test.py`.\n",
+ "\n",
+ "Here we still use the `MobileNetV2` model we fine-tuned."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "Zd4EM00QRtyc",
+ "outputId": "8788264f-83df-4419-9748-822c20538aa7"
+ },
+ "source": [
+ "!python tools/test.py configs/mobilenet_v2/mobilenet_v2_1x_cats_dogs.py work_dirs/mobilenet_v2_1x_cats_dogs/latest.pth --metrics accuracy --metric-options topk=1"
+ ],
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "/usr/local/lib/python3.7/dist-packages/mmcv/cnn/bricks/transformer.py:28: UserWarning: Fail to import ``MultiScaleDeformableAttention`` from ``mmcv.ops.multi_scale_deform_attn``, You should install ``mmcv-full`` if you need this module. \n",
+ " warnings.warn('Fail to import ``MultiScaleDeformableAttention`` from '\n",
+ "/usr/lib/python3.7/importlib/_bootstrap.py:219: RuntimeWarning: numpy.ufunc size changed, may indicate binary incompatibility. Expected 192 from C header, got 216 from PyObject\n",
+ " return f(*args, **kwds)\n",
+ "/usr/lib/python3.7/importlib/_bootstrap.py:219: RuntimeWarning: numpy.ufunc size changed, may indicate binary incompatibility. Expected 192 from C header, got 216 from PyObject\n",
+ " return f(*args, **kwds)\n",
+ "/usr/lib/python3.7/importlib/_bootstrap.py:219: RuntimeWarning: numpy.ufunc size changed, may indicate binary incompatibility. Expected 192 from C header, got 216 from PyObject\n",
+ " return f(*args, **kwds)\n",
+ "/usr/local/lib/python3.7/dist-packages/yaml/constructor.py:126: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3,and in 3.9 it will stop working\n",
+ " if not isinstance(key, collections.Hashable):\n",
+ "Use load_from_local loader\n",
+ "[>>] 2023/2023, 168.4 task/s, elapsed: 12s, ETA: 0s\n",
+ "accuracy : 97.38\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "IwThQkjaRwF7"
+ },
+ "source": [
+ "### Inference with a model\n",
+ "\n",
+ "Sometimes we want to save the inference results on a dataset, just use the command below.\n",
+ "\n",
+ "```shell\n",
+ "python tools/test.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--out ${RESULT_FILE}]\n",
+ "```\n",
+ "\n",
+ "Arguments:\n",
+ "\n",
+ "- `--out`: The output filename. If not specified, the inference results won't be saved. It supports json, pkl and yml.\n",
+ "- `--out-items`: What items will be saved. You can choose some of \"class_scores\", \"pred_score\", \"pred_label\" and \"pred_class\", or use \"all\" to select all of them.\n",
+ "\n",
+ "These items mean:\n",
+ "- `class_scores`: The score of every class for each sample.\n",
+ "- `pred_score`: The score of predict class for each sample.\n",
+ "- `pred_label`: The label of predict class for each sample. It will read the label string of each class from the model, if the label strings are not saved, it will use ImageNet labels.\n",
+ "- `pred_class`: The id of predict class for each sample. It's a group of integers. \n",
+ "- `all`: Save all items above.\n",
+ "- `none`: Don't save any items above. Because the output file will save the metric besides inference results. If you want to save only metrics, you can use this option to reduce the output file size.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/"
+ },
+ "id": "6GVKloPHR0Fn",
+ "outputId": "4f4cd414-1be6-4e17-985f-6449b8a3d9e8"
+ },
+ "source": [
+ "!python tools/test.py configs/mobilenet_v2/mobilenet_v2_1x_cats_dogs.py work_dirs/mobilenet_v2_1x_cats_dogs/latest.pth --out results.json --out-items all"
+ ],
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "/usr/local/lib/python3.7/dist-packages/mmcv/cnn/bricks/transformer.py:28: UserWarning: Fail to import ``MultiScaleDeformableAttention`` from ``mmcv.ops.multi_scale_deform_attn``, You should install ``mmcv-full`` if you need this module. \n",
+ " warnings.warn('Fail to import ``MultiScaleDeformableAttention`` from '\n",
+ "/usr/lib/python3.7/importlib/_bootstrap.py:219: RuntimeWarning: numpy.ufunc size changed, may indicate binary incompatibility. Expected 192 from C header, got 216 from PyObject\n",
+ " return f(*args, **kwds)\n",
+ "/usr/lib/python3.7/importlib/_bootstrap.py:219: RuntimeWarning: numpy.ufunc size changed, may indicate binary incompatibility. Expected 192 from C header, got 216 from PyObject\n",
+ " return f(*args, **kwds)\n",
+ "/usr/lib/python3.7/importlib/_bootstrap.py:219: RuntimeWarning: numpy.ufunc size changed, may indicate binary incompatibility. Expected 192 from C header, got 216 from PyObject\n",
+ " return f(*args, **kwds)\n",
+ "/usr/local/lib/python3.7/dist-packages/yaml/constructor.py:126: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3,and in 3.9 it will stop working\n",
+ " if not isinstance(key, collections.Hashable):\n",
+ "Use load_from_local loader\n",
+ "[>>] 2023/2023, 170.6 task/s, elapsed: 12s, ETA: 0s\n",
+ "dumping results to results.json\n"
+ ]
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "G0NJI1s6e3FD"
+ },
+ "source": [
+ "All inference results are saved in the output json file, and you can read it."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 370
+ },
+ "id": "HJdJeLUafFhX",
+ "outputId": "7614d546-7c2f-4bfd-ce63-4c2a5228620f"
+ },
+ "source": [
+ "import json\n",
+ "\n",
+ "with open(\"./results.json\", 'r') as f:\n",
+ " results = json.load(f)\n",
+ "\n",
+ "# Show the inference result of the first image.\n",
+ "print('class_scores:', results['class_scores'][0])\n",
+ "print('pred_class:', results['pred_class'][0])\n",
+ "print('pred_label:', results['pred_label'][0])\n",
+ "print('pred_score:', results['pred_score'][0])\n",
+ "Image.open('data/cats_dogs_dataset/training_set/training_set/cats/cat.1.jpg')"
+ ],
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "name": "stdout",
+ "text": [
+ "class_scores: [1.0, 5.184615757547473e-13]\n",
+ "pred_class: cats\n",
+ "pred_label: 0\n",
+ "pred_score: 1.0\n"
+ ]
+ },
+ {
+ "output_type": "execute_result",
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAASwAAAEYCAIAAABp9FyZAAEAAElEQVR4nJT96ZMkWXIfCKrqe88uv+KOvCuzju7qrmo00A00QZAcoZAznPmwIkP+p/NhZWVnZTAHSBALAmig+u6ursrKMzIyDj/teofqflB3S89qckTWpCTKM8Lc3ezZ0+unP1XF737+KQAgGEQDQCAoIgD04MGDul7f3Nx0fTsalbPZJCa/XC5dOQ4hGGMAoGka55y1tu/7LMu89865zWbz+PHj+Xy+2WxGo1G3rokIAJgZEa3N9L3C4Jwzxngfu64TkbIsq6pqu40xJqXU922MkYiMMSKS56Uh61zubGmtM5QjGkTMDZRl2fd9CKGoSkTx3gthSgkRBCGllFIKnAAAEbGXPM/1q7vOM3OM0ZBLKcWYUkrM/PHH33n58mWe5yklnlW3t7e5M6fHJ7PRaLOY3769zAxNsvz06PDJwwe5ofnV1eNHD2+uru7du3O7aeu6Nha97/7n//l/fvHimQ8dIi4Wi/V6vVk34/H0+voa0Xzy8XdfvXrVtt39+/c//vjjt2/fPn32TZ7nDBKZY4x37997+/btn/zpj9++fftX//E//vCHP1yv1yWS98FaK2i+//3vv3nztutDluWcQMjEwNfXN/cfPHrz5o0IVlVlQvfpp5+mlH72s5+NZ9MY48nZ6Xy5XK1Wn37/e3fu3Hnx6uVvf/vbPoQ7d+6cn5/317ezg8lqteq6phqVV1eXm83q0aNHTdOklFKSlASBrM2yLHM2ny9eiAgB5nleFBWhjZG7kMAUybibTXdxs1yHaMspOtf7aAiMMdZaRERhRHTOOUNd13FKIfQxRokJUay1xpibxTzGKCLWWiJCkizL8jy/vb1JKT169Kiu133fHx8f397etm07nU67rqubdex9nueTycQY5JjeXF4cTmfHx8fG4Pz2drVaTSaTO3furNb1YrHw3mdZllKq6xoRx+NxWZbM7JyLMW42G+dcVVUppc1mlee5916/6/T0tGmaZ8+e3bt3DxHLsgSAvu+dcyIyn8/J8PHxIVFs13Mi/z/+9/9qOrWzcX7n7lEKTQi9QLKICAAg8K2j7/uUEiKKSAih67rEIYRQ94uiKEII0+n06urq5OQkhAAAMUa9iJQSAFRVZa1dr9ciwszGGN36RDal5L1HIGYGABHRaxARXe5vXwrAVoaBmVl2B4CIiE/RGOO9DyGQNYMQhhCICA3q240xiGj0f4jW2rqui6Lquq4sS9/HoiiOjo5fvXo1mUx+/OMf379//2/+5m8ePnz4y9fPfvSjH/3t3/y1M/aPP//8F/+0qKpqeXuTCSBi0zTZeDSbzbquy/P86uqqmB5WVeUyU9dS1/XV1VVMvqqqs7Oz5XK5XC6rajydTlerze3tbUppPB4bY4qi0KUmIu9749xqtdKlOz8/J6I/+7M/CyE450Lb6foj4dOnT7OsCCGEEIu8stYSwsHBASBba5m5qoo337yq6/WdO3em0/Hd+3frun79+uXZ2Vni8PTpVwcH0zx3JydHTdN88+zrsspzgL7vjTHT6bQo87atQ+ibpgkhWGuzLBPBGFIIqWkakM7lGcfEzMzsvUdIPiYfOJ9UQGSMIWsNCxElkARCAqr4AACFRcR7j8LMzCnF6FNKFinLbFEUWZYdnZ40TdN1XQghhOBD1/e9bjYVifV6HUIYjUZElBeubVuBlGWZI6OLEH3o+x4AyrIUkfl8EUI4Pj4moouLCxa01qrCreu6russy0RkPB7Xda17O8syIkopxRiZue97773esu7YsixVjJ1zRDRsY2stS4wxWgPWWmt1JwMiJh9i4pQSS6Rhl8v7h/ceEVVy9r9YHwYijkYjABiNRnoPw14XEbWBjx8/zvNcf6NXP0ia3ttwV2ru9CnuiyUi6tbUW8L3D/3GuDtCivsHMydhERARRgAAIkLEBNLH4FNcbtYhJUHMigKtuV0uNm2zbuou+Murq+9//rnNsuvb2zzPz8/PP//88+l0aoxxzumNq6LRVTo6Ouq67ujoaL1e3717dzwep5SyLBuNRrPZjJlXq9VqtXLO6T7I81y1qd71crns+17dAedc13UoYoy5vLw8OTl5/vz5D37wg/Ozs6PDwzzPmVkfZmbp5cuXKSUU0MtAxLars8xJ4uOTQ/VN7t69+0//9E+67Z49e6ZKJ8Z4cHDw4ZMnP//5z5tNrdvx0f0Hq/miqde31zd92xhCjoEADdJ6ueqaNoVokMo8K/LMEkYfurZGREEQER9DCMHHwMyM0HVt27ad72OMMaXOt13X6T3GGL33IXh1Urz3XddlWWYt7R7rdqsw87Nnz54/f/7ixYu3b99u6lVKyVqbZS7LsizLdFPpNiADRJQ4OOemo/F0Oi2KAoD188fjMQC0bRtDyLJMDd1yuby+vo4xZllmjCGi8Xh8dnZ2584dAPDe13UdY9RtrIrDWqsbTLdTCAERp9Op6ik9Qfe8un4JpO9DCMHaLM9LvVRh8N7HGEUQwZCezcxpdwwibq0djUZlWaoa0C/QbeScCyGMx2MRybJMnQoR6ft+vV5fXV0h4ocffnh6ejoajVSRqIx1Xafr+L6PalWwU0qDJRzEbBBLRCQilVh98U4g3700QoiI+pCstcZYEBSGFDlF7r0XgN57Y20fvMszm7nRZFxUZe/94dFRluf/5e//7uPvfPKTP/9n63pzfHz8/Pnzf/2v//WDBw9+9atf6dqpsiyKIqWkznnTNKenp9PpVNVn0zSbzeby8lK90BDCb37zG2vt2dlZ3/d1XRdFoU+rbdurq6vlcpmEp9Pp0dGRcw4AiqLo2857//vf/q5erfURTMbjsiy7rh3WIYTgMiPCSGJAdspbPnz85OT0qOub2XTcdvXzF98kDin0PnQI7H2XGTo7OyGU2/n1Yn5jCI4OZ02z6ft+uVyq8VksFl3XuMxYR71v62a92azUThZFUZalc86nkFIMKaaUYkqIaDNXlCUQCoIxJitcVhY2z4xzLs8QkZm93+p0IrKWdD/oHTnnNMbRM+/cuXN0dKSO1aCRdds4ZxCxKAqXGR+6EEJd11VejMtK/UlriQCNMS6zR7ODvu/bth6NRqPRqGmaul7nuauqSpcxxjgej+/fv3/37t3pdHp9fb1er9u2TSk553RzWmuLotDtp0ZPhVDV7mCo9F7UzAij97Hvg6AGVsAhMbP3UZIYpNwVpHel4qcirnKoFl9XRKVFhTDP89VqhYjz+Xwymcznc0TUB6OGmJnrum6aRgVYTZBzTkVCpUtvYJCbQa50lQch3JdD3h3f0hr6sIczhRCAEI2xmTHGGAN7QktEYMgVeQKZHh4kEJtnPsWsKNQe5lXJCJfXV6t644ocDLVtu1gs1Dm/vr6u63qz2YiIOkjOOdU7RHRwcPDJJ58Q0ZMnTz755JOyLL/88stvvvkGAL773e/OZjO9AFVn6l8YY5yzIty2zXg8un//3tnZ6cHBjAhR+PDo4JunX2eZ+9//978cFcXLZ8/KLJtOp23b6uecHh2v18uUUgpxvV7H5HNrEofet5PJ+N6dO9Px+M2b13/ywz/65puvr64u//zPf3JyeHB5eVGV+Xqz/Onf/5c//qPPLSGBfP797/3iZ19Yg9aStSTAXdfU9TrGOBqNzs/PsyyLMa5Wq+Vy7n3nMluWeVnmiCiEYBAIhRhIyFrnXDmqqqoqRuVkNj04OJhOp7OD6eHxgbWGCNWl6vs+hF631mJ5u9lsvPcAbK3VHQIAR0dHBwcHk+moKDPdIQCit8/MbdsmDjHGuq5VIZZlqVYhRh99UIMxLisAcM6URWGtTSl436FAmRcff/zxeDxWpVMUxWQySSldXl7e3Nzoe3UXqQEnIjUY+vtB8FRpMrPamGG3ExGA+rEphMBJhJETxMgxcIoCQIho33dHh+BQ+r4vikz3SkoJgMkYRHRZpjAMEZ2cnCyXyyzLxuNxjHEw6HVdLxYL9bJ0rVXwjDEpSQhBRPKsGELBwdskIpb0LXdUzwkhgKAxKaWESAxRBEXEUhRJzFFEEggyi0hiFo0JrUE0REQWt2qCpe07RCRryqpq2lZEYpLFallVo7quRaQoy//rr/7qiy++QKLb29vvfOc7f//3f99s6ocPH8auXc9vl32neuqDDx719abv+7OzMyL66KOPlq2/d+/exZtXb968bttWndLxePxv/s2/+cd//Mem3pycnIzHY2a4vVmUZbl14Jv65OREg73cZcvlEgBIoCzL4L3v+8s3bzil9WpV2swgoUDXdbPZbLFYdFknkDarFRG5zPjer7vm9cXLsiwn01Gql865er3Jsmw+nxPR2fGJc261Wi2Xy6urq+l4TACb1SqFUGU5JHCZTSm1bcucEocYXVHMRqNqs9m0bde2YG2mqJgxxpmMUiINNwQCJ0keAGKSNvVNkN5zx+LFJiRBHFRvSomjD4GMMYaAmSUxAItYA5g46Mbb1Ku2bdu2Vf1rDDkqHRm2FCO1bZ1lGSQGxEk1MiDq76UUuqbt+56IMmvAmKbZHB4cENF8fpNCrKoKcthsNhoX6Is8zxFxtVpdXl4y83Q6HZS7ao0YI2AkA9aRCIfYAwBLBOTRuGyaJnFIHIwxxiKSLas8Nd6iYW77nr2DIfISZiGQJAn4nQnat0tqZwerqLpqeL96I0+ePDk+Pp5Op6PR6IMPPhAR3U+j0aiqqsVicXt7KyJnZ2dFUXjv+77Xc4hoELwhStRYVj/hW+7oPlTzh2Hh8JOR9YUAIKL33qeYkjADoDHkrMmczV2eXd/ekDVN137w5HHne5u5pmur8ajre2MtGVONR19+9fvJbDo7PCiKQt3OqqpGo9Gf//mfP3z4UL0mZn78+PHx8XGM8fT09MWLFymlm5ubzWYzn8/VCy2KYrFYfPHFF8fHx/pPdXjOz8+n0ykR5WUxnk4Wi0XbtqvV6sWLF8wceq+e6t2z83q1PpzOXnzz7PToeDVfKKwXY4g+1HVdliVymk6nHzx+GPo+BC+QMkvPn399c3PVdc3sYHzx5uXJ6WGemf/8N/+xblZHx7PQN5NR8dGTR7/77a+Cbwn59avnTx4/9H3TtQ0Bhr7zfZtnVlK6vb5ZLm4NQe6yIssIxfdtW29C7xHEWLSOjLVElERSSiHGEEJdr9fr9XK5nK/m8/n8dn59e3t7u1wMDpuxuIsPO0U1jNmhgH2j/vxqtQKALMum0+l0Os3zTLXDpl4horWkf82yDFGcM9bavm36tvFdrxGawW1smTnHzE2z8V2v77KOBNLbt2/X67VzTjezWjPd5JPJJMuyrus05NZ4SiN5NbaDX9Z1nbqsek7btgMU5JxzRW7IKXZojHMuR0ZDzhpjAFFkcNNokMB9+6PH4DFaa7uuUxX44MEDNd/OuTt37uhbFJO01up1VFX1ve99786dOxrOqgevTrmKlt6DSvtgAP/wUIR6ODRm0AMM6X/qNuDO+UyAaipZUASZgRlEsKiqdV0XVeVj/Og7n9g8Ozo9sXk2OZjdLhfj2TQri6vbm1W9+dN/9hO05uTk5NWrV9/5znfOz88vLi5++MMfnpyc6FV57w8PD4uiUP/wt7/97cuXL//xH//x9va2KIrZbKaPqmma29vb3/72t4vFYjabVVUFAOPxWCMTl5lqVLRd3fXNerO8ur5EEmMRgcsia5vN4cF0ubh1lgyBcJzP5xqBiPBmtT44nOaFOz06/MlP/jQvXAy9c/b09CT6vl4v1svbm5urosgeP35EBASSO/v0q99H3x/OpkXmzk+OFzfX7Wb9wYP7yffL25sQeiIQSEQwHo+so029WixuQ+jJQF44a01KofdtjB4GvBoSAACKSNI9swvIt3uJmVmiCLddHZMnAxrpEMHOSw9bwCb2KqXOuTzPVZdZa8qyqKoqz3PryFrbdY2i9+r3xRjbtl0ul7rXU0pkMLPbXAincHBwsFotrt9eEdF4UnH0vu0Kl63Xa0Q8OTm5d+/eeDweHDcAUIezaRqVKI3xUkoqhAPerkZF10FxpqZpFNExxhhnsywja3ST6+5VxzAz1lpryWzFTz9XDaC6ixqeWWvH43Ge53pxAKB46Xw+1/iwqqrXr1+/ePFCjaR+AgAcHx//5je/2Ww2RVE45/RD6rper9d93zdNo8pG10ghx6Io9BtVC2RZpiC1Yll6q3onapb1nxpDF0VRVVVZVU3TZFmWlZVzzthMAej5fEHGhpgOj45Diud375zdOe98/3/8X//ng0cPL6/edn0vAOWoCilOZtO79++9fP1qNBk/evzBZ599Nh6Pf/Ob30wmkxcvXnzzzTfq1Olz+sUvfqH48KtXr87Pz3//+99/9NFHZVl++umnZ2dnuhSImOf5V199dXx8/Mtf/hIATk5OVqvVJ598olFQ3/ez2Wy9XnddN5lM1DXS9wLAcrlURHS5XFprD2cH6+UKRRWT2azWfdt99fWX8+ubTz75KMssp74s86LIvv76933fssS2q1+9fhGTn87GL14+G0+q3rdX15fXN2/bzVpiiH3XNzUJHx/M2ratmzUixhgXiwURHR0dKDaY0hb922LxBgZrYMlYS5mxItJ3zaZeT6fTssrzLBuX1fHJ0Z07Z4eHh7l1VVXps3PO5blTxA6AQwjWUVFuN7e1xhgS4aoo+rZt64Zjij6AJGcssBAgsFRF3tZN9CF3WdvU41FlLLEkH3oOERGJIEXfNM3Fxaury7cicnJ6NCrK29vb+Xyu9uPo6EizoLe3t8+ePVPtUJbl8+fPnXP3799X4+acSykVRQbAKQVEEUld1zTNxvuu79s8dyH08/lNCH1KIUY/m02Yeb1eA7CGuCIphP7wcDYZV+PxuMwLYwwNDqe8n6AbPEZFhBQUUo9xOp2GEN68eXNxcaEJrsvLS5UuPV/zM+pfPX369ObmRmGMIRuxb131ngeveECi1B/eoRdOA9+iKPq+L4qirmt9Wl3X9n2fhPvgF8vbLMu64K21LitijHXdANJ0dti2PaLp+/CTn/zk5ORkvV5XVaUZBSIajUbX19eHh4eLxaJpGiIqiuJv//Zv792790d/9EfMfHNzs1gsUkp/+Zd/qeqQiMqyfP36dYzx+Ph4sVhsNpu+71erVQjh6dOnimDleX5wcFCWZdd1mqW4uLjQ/G9d18457/2ACetT2OpXQGCRxJIYWHTbWTIaKRFRkeW5y1KKIXiO6eunv0+hn89v8txNJ2VV5p9+7zsHh9OUYowhxpBSJAACkJRSCNH7FAJw4hii70PfpeCB08F07AhD16bgncFxWYyrYlTmKAklISfgCJwgReSEkjgGjoFDhMQAkFlbuKxwWb1ZJR+MMWWRFVnujEUBBN45WSmloNspy22e58agKmUVv8E7G2DzEPvEYReA6OfwLkoRpG1g0tVN6HqJiZlFEiE658oy36zWVVWdnh1PR+Msy0ajMsts1zUhhNlsNh6P5/P5L3/5y+l0ul6vDw4OFK3RdMAQWCkmR0RqG3Qn617VZ0dEapk0mdQ0jXPGWHSZzXNnLaWUhKOztm3btt40m3W9WtKQlhjQkSESVbOjO7Isy0G01Ka9ePHi9evXCl71fT9cxAByKtz6/PlzTdnroQs9JItUzDTqHdB/VbT6NFTX6mld16niVJtZluVoNGq6thyVWZbNZjMRKUYVACmQBYAhxjwv79+/H0JC6y6vb44ODo8PjzimB/furxbL3GUoIMxnp6dd007Hk+jDqxcv7925+4uf/fzDx0/UNKl38fjx46dPn+olaf7m+vo6hPD48WO1FXme39zcvHnzRm9EobajoyOVHMXfXr9+rVn7V69eGWM630YOAIpJJJFEBJk1qm45eP1PYkBOFoEAUMQ5d3BwMJvNcpdZa0ej6sWL519//dXHHz55ffHq9cWrH/7x5yAxc+QyQwYA2RAYi2SAJYa+TaFHSQRCIChMIJk1ZZ4hCiICSgh9Xa839SqEYC2Nx1VVVdYRAHvfNW29Xq+Xy7mkFL3vu8b3vXAkAINkkKIPwGxAJHHsve86jh5YmCNzHJQsIA/wIzOL8ADux+RZorUWkGPyQ8CieQqVQGYW4P19q/Kw3cJbdQm5y4iorHIDeH19/ebNa9/3usOIqGmau3fv/sM//IPqSn2UxpjDw8MQgtJllOalSXl1d9VfVauufqb6hppn0qxy3/dZZgUZgF1mjaXEIQTPHB0hGQRg4bgVwn2kZIgJY4xd130LUGmaRhHh29vbm5sbvRoF69WHUYd20O6avdAIFXYZc13NQc4HRYCIm81G8564lz9UKUVEBWY1Lq3rWgHihw8fZpl7/Pjx4fGxc246nW7qtu/7LM+Losrz8vT0vKjGs+nhcrH+zW9+o67vvXv3UkqvXr2azWZ1XSuGmef5d7/7Xc2YP3r06OnTp4vFQnXe27dvz87OTk9P27a11qqKQcTb29uDg4O7d++KSFVVRVG8fPny/Px8Pp+rlFZVNZ1O9QllWdb3/cXFRdd1mr1Q/bKfZbFksiwDAEgcYxRmYU4xgojR3AVHg5LlNi+cdZQ5MxqXhHJ9/db7DlJcr5fX129fv37ZdU1RZFlmM2s092AMIqcYve/bGHqWSMAEbFAyS6M829RrlpRl1lrbNM3bt29vb68VnFQIepcqiokjS0IBSZxCTMGLjxxTip6jz52xRBxiW9eL+c1yfts1LSRWy7/NMO0g0BB67733vUqmYjPqPaEkSIwsJGCRLAIkTqFXNS8pShxOYORkLWWZzTNLiIr6pBBFJMstsrRtu1ot6s1Gcwm6/k3TXF9f931/fn6uN6jpQWVfXlxcPHr0SDfAkydPUgptW282q75vEUUZkESwg+gTEThnjEHm2PctOWKOPnTMUdWLD13bbVyGuTWZM84Z2reBg/iptCgSpYGpopcas+nmUxF9/fr17e2tAkqDN6Uepoh47zUS1bSP7NKsKpYauw/uqMZ+aiSVQaIGTe07Ik6n077vJ5PJcrmsqmo+n6uLeHx8vKo3eVmWZbnZbE5OT0XE+1CWozzP27btfDw+Pn748IOTk7OLV68OZzMCCH1fuOzy9cX3v/tpmeVvXr3+3ne+29XNw3v3/+xHP754+eqjx0/+z7/839+8eXP37t2yLH/5y1/e3t5+97vfnc/neZ5r0uz09FRd8fPzc9XB0+l0uVxqQL9arbqu22w29+7dIyIlH45Go4uLCyLSuPpb6m/AyQqXGWMMbQEni2QAM2N1GUMIoetVB6nWK8vy5Pj4//u3//nxk0enx4f/7//X/7Oqiq5rUgocg+4PADYoZMAQeN8l33PwMfQx9MF3vm+7trZkJDEKVGV+eDCdjCoQburNzfXVYn7bbNYpemuwKsrpeDKbTDNrCmty6xwSsEBMEhPHxDFJYkicQuA+BO+TD7zTXMbgAMkwb32iAZBPvHWLBiWle8aYrW3w3ouoalJ2VFRASFFQ986HikqpA2SLFGME4MODg5OTk7IsU4p93y2Xy/F4/Pd///d/8id/ogp3vV4fHh6ORiNjjKbEx+OxhkWz2Uz5cX3fa/6jKArl0zRNo3jMZrNZr9dqS7quMxYBJKXA4q2DIndE0Pdt7zsfuph64R13VB//gMfssgUppcSSiIAIALd025TSeDy21k4mk/V6rRen1qksy4ODA2WNrlarGCNmOHgIuOPEqEwOmY99yVe4YrFYNM1GBVt3YUrddFKuVpuiKEKIjx49IrLe+zos1019fX395s2bpmnmy8VHWVZV1XK1NsYgmtvFyl1cHB+fnp+fA8DbN78rsnyzWndNm1kXen/3/M6ds/Pr6+u2ae6cn//i5z8/P7t7dnL6i5/93JL52c9+dufOnclkotv90aNHX//ut6EzKlH3799/8fTr29vbs5PjLMvatu2bPqX0+9///uOPP95sNnnuXr16dXh4qNB23/cnJ7OLi4vRaKJ2nnbMISAgwMFByLIMAaJzFqlwmbr6lkxZZoMHgbx1zrqmjpyOjo7unt9Rnsfx8eHRwfTNmzdNu2XbEpElEiKDRFlGAsYYQjTGAAsKhN4H8LPZtG3brm8Oq8N79+50XfPm8kKNkjHGbqM1gyTqP1siY50lI1HUxAEZFOPbLoJDkCLLghj2sWNIwYNRdU8iggCIAkAxBjIIrNe5TVYrQKraX3dgSluUVcVywCQHDHbA22XHQ1a7YkBZ/q1qAQBAhMzYLMvmu/S6eqF1XaszCQBv375FxPPz89VqxcwPHjy4vb2dzWa6jXU/a3ilnBtVqcqXUt2R5zlzsg5ALKRABGWZIzuQFPoOLSBHRKFB++qLAZgZMgHqDQ8KW/Njmp0fjUaKT04mk4GHpXmL6XSqkOY2Zef9AMmoXA3wxi7Pg+rgTafT8Xisfp0ybJQ+PgikgoeHh4f379+fTCaj0ej169dEdHFxoV7TYrHQ98YYwRAiXl/fImII4YPHTxDx+vq667r1eq3a9+uvvy7L8vz8/He/+50CZUpMf/v27dHR0fPnzxeLhTHm6OjIe399fX16eppS0rsry1KBzaurq4ODA72A8Xj8q1/96t69ez/84Q+rqmLmZ8+e6bPR3aO+9MHBQYxR3csQ+hi9ujQx+tC3gKzhk+71FH3wXYg9wZb2oKsxLqsss+o7LJfLzz///uXlxYsXz773ve9tF00iICMJS4zRh9CrXySQhKNIMghZZrPMWgLgqMrxnXkB9W4kz12e51nunDOIol7cer1u6ybGaACJiABRwAA65wgQZWsVt+moFHkXCirYrlDC4IKJvMtwqBnIc6ffrn/VN+6CoyGdtkUitgQpgF3A6bfMctmGPGqylsvl7e0NJFbc3lo7n88//vjjL7/8Urfuo0ePbm5u6rpWL0xdQt2lr1+/HtIn+gjirsJDYYI8z/W1ZjJGo1Fdr40xZZkLQggeCVxmicAYdNZkWVbm7r+ZIRygywEgUeHUr8mybLPZxBjX67X3frVaDUkVBQkHVTF4XCrVGsUOf8rzXNOGKjZE9PTp04uLi9VqS9VVudUiKb2qt2/f6jemlBaLxQBsKmxTluXNzY3q0YH+qr7N9c1tlmVv31w+/+aZJVNkue/7tmn+y9/+re/6zWqdQnz14uX/8G//+8l4/PLFiz/70z99+eLFwcGBItQi8uzZs7/5m79R1RO3Kem6LMumaV69ejUajbqu+/DDD8/Ozl6+fDmfzx8/frxcLu/cuaPMJg2PVe8qPNu2rZq+fXhsx2/eHvpdmsno+36z2SinVF0yDd5ijFVVdV33/Pnz8/Pzo6OjL7/8crPZqE4cXKZ3JlREKwbatt1ua9waYV1YY4z3/vLy8uXLl8vlUlXk4M4MWhsAmvXGt12MUdKOSAhgCcejkTNWVU+7qX3bcYiay+77XsOcATMfLNiA1WvegraS/c5BUIB0gFDxD+jEg2cxwA0q8FdXV87aw8PDqqo0A6EKHXcpQe/9YrH4i7/4C2vts2fPLi4u7t27Z629vb1VRsrt7a3m2BSe2HeMrbUDCUF/PyiL9WYJwM455qgR1iBuxqB15JwhMVkQamPsUoiYxAlbjuSpgOqgooK8BDboRRabdr5ummYlEvq+nc0mGpuqsW7bNkWxJntz8fbe3YfrVdPUPYKtu5SVU3JV6yWKZbStT00f123XxXS7Wv/5v/wX1XSSEPoUPafFKgiVqzoKlU0PJhv1EUfT4/HsKAh5Rsoyz/Ly8uLt7Q1YO6tm/+Zf/ZsMs9RHi1lh8na9aTZ1ledXby6OpiNH8cGd48cPzn/9i39Y3l7c//BhE/2ybT2Qq6bgRid3PnpzU883sfZ47/GHXz57Fk367/6Hf/XXf/9Xk9MiXne0kcrOTk4fZrOztbjrjt3ouKyOTmYn9fUybVqHPJ1k8/XbYmp/9et/StyenR39zd/8ddNsfvSjH725eHtyfJZn5aiajMfjEPqiyMbjIsR6dlCaLhYJ8wjY+LhusAuZYIamcnmZ5dPR2DnXtm1iNtb23i831ybj6awwNq3W14vltaR+XGXs20mZZYSxaTAEJ2KZnYgVxMjiI4SkJREi0ocwnk5NniWEgOKB6xRaSdFS4l4gWEshhOVyzQmLfMTJ5NnYmlyYmJXDxGQYKdE485br1NXcBytSuGig4fD87cVNvWlBAoInis5GlwUwI8s5+ALDNMdRBlYCpd5RwuQJIgGjJOaYUlBvxSdfjArKsIstYDKGUvIxdCCJJBpIBsRwgNClrvb1qmtDDGLAVcUkyyqJEtoUfTo5OkXG2MfC5IXJmk2zXqySj8eZbS4v4vzmBx9+8IMPP7h5+c2r3//mbDbC2D7/6jeWosFAGA4Pqq5dFjm2c5hmp7P8rJ1zO+fj0b2HZx9O8+OSJqnB1CB6wy1QsOjN+ro+KU6y4Py8cR6PyknOGNdNJuBAJETugwS2A2VMHVy1IUR0e3ur8WiMsSzLoihU38TYISLAFkce3Nc8zxG2/rEa4sGNfN/f2Lq1CqLc3Ny8evXq/v37P/jBD7744ouvv/7amhIARqNR33eIqKFmCOHg4ECkSUkyl2/LF3xKKV1dXX322We///3v9bSu6+7du/f02YuBF/bJJ588f/ZSWazffPPNarUqyxKA6roGgNlsVlWFiKzX6yyzbduen59+9PHj29vru3fvapS7XC6b6GcnR6enp/Pb67quHz9+3Ly90uWKMcQUWZBRdB1ub2/v3bv3u9/97ptvvlkul8aYuq5VTW6x0B1BbyC+D5CVrs8+WqZRB+8IGQMdAnaFY/qZqvXVYvCW7gsiYmkbTQwxhT4LDcsHaFofunNOJO7jc8O71BPZ2aitdTLGgGEAQAEEQjSIhGCI7EcffXSzap1P0+MiUN4LMJksL5//7p9gR8dXbE+vX9NO+5tEz9EsubFb+5ZSMgadtYPjoC7sYELRGBCWrX8RgBkAGMUYs80qEjKIOmtE1PZdSNFm7ujoqKjK1Wq1rmtF4Bnh5OSERbZMtOBDiqFfbWpnjMkLhwgCse+7ELo8dyGEmDoyGSKyBBZBsogGjLHGQbJExCAAQGQSgzAjRBHZAjODd7vLsbxb/WF/6J/UNVVGGO6x24hIQzje5ihdlmVN02h+dvAThvM15z6dTp8/f350dHR0dNT3fZ7nIEbLL66uLvXzFa/fppUAlElDZBEiIq1WN2/fvh3SNVojOx6Pl8vl4eHR119//fjx46Zpnj9/PhqN3r5969lnWUZkEXpni5RSXdd3794PIfR9cXt7O5tNHj38YLmc3717d7Vaee99DHXoJ0cHZVm+DWE+nz84Poq5HYprYgwCQM4YY8hkSjguiuKnP/3pv/t3/+7ly5dd16l2U19F10oXITdGwzCidxUtvKtiGVZVAa2UEjrUDbTrS+B1M/GuUlbPHNBsZ+ywWYf119M0IlA3VU/Q/Bj8QR2Zao0BAJe9hPCe2LBBQdziS5vN5vXry8v5WrLCk1s2XUI7Gk8fnZ0MW04dRfW9YVfXpqpBP1m5oAMwkbYLYlRiCWSLJsp+ols0fbiVSRHljTNICGn7+YJtUMl3LDiZHliXL5bruFiGEIzLsqK8Xa271h8cHgPAYrUUkbIcgbHzNzd1tymKLK8sInahafyq67p8lPt1E2IkAUMUJQBLgozQEJIxVtiBoRQ5cBIN01iAhVOyWhk5RCOqQTUStdaqa6vPVTl1k0kJu+rgfb3lvdfiWY0lFIQYlPc+8DNsCAV8r6+vLy8v27Z9+vTpyclJvfFaLFtV1XK5HI8rxRWbpjHGGrPVx0WRgVDf+9PT01//+tdKBEPEk5OTb7755t6DR+v1ej6fHx4efvHFF/P54t69e7//4vc//vGPw3rjW49opqMxkX3+/OXyZvnR4yeTquybenk7f2Xo8vVFmZUWLYqs1+vJbCrONE1jcqsG/M2bN2OirUEgIiEAJgJj3HrTKOnx7Ozspz/96TfffFNVlYhoQkLdBNxh0SIylBUMcRHs2hToybQrXdumUolUCDWEVuMwRE2D/Mgu9k4hiAiIbEHHnVfSd53G/EPFatu2XddV5RgRQQB3ZWXMggASE4hovg6UzSOJkRModiPILGCMSSAIQOf3HyRy2Xi16tO6iz1DBIMkX3311QBAyC4OFJGDg4Mh4Bx0BO5VvRljtP59wEJpKAZIMmgHRAEQFXNEERJNhrR9TwAMeZ7nQEaQ0JDN8rprZ7OZyTOtUytHo7wsg/Cde3cjp01Ti8h8tayqajYeReG8IsaeEfMiF+G2W3ddl1KoxidoBCSSY2MpQWJIUQxGQyTWIBExQxd870NIHJPWuKaU0rtSpm8dbdsOyk+XRsml+td9G4i4LTVC2C7K27dvNbi31qb4zhHaX2U1fUqUcc5tdvnTtm1VkouiePPmzWQy0hJYpdID0Gq5Xq/X1mYx8nq9/vjJ9xSeKctSAIeLF5G/+Iu/+Puf/gMzf/rpp0+fPj06Onr58uVkWoTQp+Q9hXv3Hty9e/err756+fIlACCJiFxdXf3DP/zD5z/4bL1et02vd0E2C6AsmfL27eXr1+nju3eVTumIDCBuZVJ+9KMf/fKXv1Sq0Gefffazn/3sRz/60d/93d9pC5NBcoYQQP5rbEG1ErqxhjTGcAoIg4BwEhAQJgRD6KzZuSrvaboYw/DI9qtljCHlsgFIWZZa5qdyKvLeT70MVcGD+hiEB40dPh9xdy8sX331e6E8pbRY3C7qPtkMbVbX4Xg82jv/3aG0Kg15UkoxBACQZIuigF1hgKokDRcRty1qmFlSGKIqJ1vpFQOAuNOTGAMzAHJCTgmBMsfMEaT1fRY8WDM7PDR51jQNgwjCJx8+eXnxet3UzJyYBaBp26vr60nlUkpJApADkQSeMZDDBN5kAMaQBTRinGGJfWwFMhRGJGROzD6ktvNtn+bLlSTPKcQY7a7MXoabBAC1PIOVG57/sGm0bcT+7jHGcAIi0mhQ/as8z5vYyc6hVX2mLzabzWazSSlpNkKTE4N3VBRFSkYLYfWXTdOMx1Nrs5RSSn0IwZpMKX8ff/zxixcvrLU+pM1m8yd/8ie//fKrzWajqY6UkoLOeVn87ne/m84ejcdT7/311c3HH1ff/e53U0qvXr84PDxMKRVF1nXd119//cHjRylJ13WT0WHTbKjIEkEm+Xg8Xt5c13Wd5zkipJSM0VgupQQMcOfs5Ory6Je//GVVVZNRObc0KvOzkyNmRpTQt2qXfBdUnwuqchIkQRAkAREkAWQWiUkTzRYQkIQAkohyjwaO0YAhx105GO6BmcLvMTEGVFOLPwa+oTo+RVFs6yH2NYIwAIS4rY9BJERAEBBmThYdAFi0iIigKDomgOhDORtVRG5hyfSuyMjl0IeUwhC1yh5Tcthp72ucXTG3JGOMEsS998H7PM+1TGlwH7YheuqNUdwR0zb1bZAImYksEIYUBSjLyz6GNsTj05OmaTZNff/hg2lmF6vlKM8Ojo+avgshtG1LxoxGozzPlbJiTMbMiUNM26xbnmvTsFYh28RBIBlrkE0IwWAQjiZYSwKEzBAj+5CQQTiyMCSm4c6H4qDBBRpeD5pvoHoO1n9YO+34gIiHh4dKUsM9LFt2xfuDy6QtrtRgdl23XC5ns1kIQX3R4+NjRPzoo49SSrPZTJdAO7jpJlND/eDBg7dv3ypTnJm7rqvr+uTk5Pj4+OTk5H/9X//XP/7jP14ul7/5zW/+/b//903TPH78uO9DURTnZ3estX3fzmaz4+NDIlByCRE5Z+q6/s2vf3t0dORcruvgnFssFpt69fHHH3/22Wd5kZ2eHk8mEzVTuyAnppT++q//ejabnZ+ff/LJJ1999ZX3/vnz5//8n//zDz74QNnnGrnxjoe97yAM/KH9/g6Kd6f0LoGm7cZSSpp93SviBtiVfeKOG2h2x951bg/nXFVV+uBUF9PgY79fUzYo3OHRDxec4raHDxFtjakIS5zOxkSEzFlmtVhWhK0j/aLhdoYb3F8Hs3foJoGdgz1ETMPGG96orrX3XYxRgJFgl0vcfhEaAIAueCGsJmNN4drMuTyr20aJX8WoOjg+MsY8e/ZMCZsiorlxETk9PdXLAyEtjSciLXHue2+MtdZ1ra83rTAacpwASZJsOdjO5WQzQStC5ArryiyvsmL0Tvb22Rv6gIfFGvaHBpDfEr/BEurJVVUpJU1XbT/a2cfWtBJPmT6qj6uqurq6stZqn6imac7OzpqmUS7bkJ3Teo7VaqUdNKy1T58+VR9PE18vXrw4OTnJ8/zTTz/96U9/enp6ent7+/Lly//wH/4DIq4Wq67pZtPpdDKZ394+++br9XJ1MJ2hAMcUfKfX+ptf//r0+HRcjYno9PT09PR0tVp0XffDH/7gz//8J7PZTDvKqBYkA7Rl8XNK6eLiou/7L7/88u7du2dnZ1dXV7/+9a8fPnw4Go1UDQ3aTUEX2KGgeuw/jkF5DSoMOeXWZIaQEwdvQHJrHKGBLXnSgOjrLZfSoP5HKIQCkkCScOza2hBMJ6OD2STPLKfg+zb4ThPuKECA+lp/WnpX2KEMG2ds7jKOkRXy3WX4tK9E37Rv31y8fv16tVr4rms2q3qzijH0bRN9r5WNVZGPyqIq8qrIo++j71PwWpYhKep/cddeSaEs9bC0h1W/O/b3qsYIzPE9cw5pvz8akozGpXaXefbsaZbZqirqep1SuHPnbDodf/PN13W9zjJbljkC+75dLee+bw8PptbkzhbW5JwwJRE2nMj3MQYgskS272PbemYxxgKgMQYFNOfpnHMuI7KAzlBGrnC2zPKRVX9mXzzMrtRotVqNx2Mt7jg+Pv7oo4+ePn2q4ZbITvkZY4zDHYxJRH3fi4AGeOPxWBibpjG7ekWtu48xLpdLEdFWUSrDNzc3GigulvPb21vNvE8mE2PMYrE4Pj72PhJR13XWZsfHx1mW/frXvy6cVVWq1ZnW2ouLC6CrO3fuNE2j4UTTNIvFIqT45MkTjun2ZlEUb/6n/+l/+l/+l//l6dOnxuJqvsrzvKzy1Wo1Go20edazb1589zvfe/P8ddNubt6+Oj09Lcry6dOnn3/63R//+E/++q//+l/86Iez2axvlr0PKXljzXK9zh12bWMIq7LYrFciQgiXby4OZtOubTJnASDFkBTJjASipQNGy8ri1usz3nsRVl2uh4hyuaXve+U5DICKcgn1KWgdwNZMpRT3ukjum46Ukmp6RT4066Nu4uD+hBCHOFOZDztojfWTVDWDVk4Y48gxgomCDLPJgQdadp4AkIQEwaAlODg7GxzjfUs7pLv0RoawBRGLokACZlbZ8z7kRdF1nbW77MsuctZABgBiZMRIWu+fOMY4nY1jYEA+mIyrqoLEHH2RWeB8vV4agylxjD50bbNeFc5uNhv2/aiqjHDXNrlz4/FYgp9fLZ1zWW4NmeQ1IpDM5FmVYzQppdKOwDGxjR1DpOX81jqDPhUWDdq27b//vc/HVebbNqaOY0B6v8fMoJX1cZ6enmrZnppjbb+3c+Jxf/kQseu6ELbdINOOLN/3PcC2y5PZFoamAePBHe2Ad2VQzrnRwUHT1tqv7dmzZ1o66JwLIXgfjo9PM5cb4xaLFaF1zhWF0dimKAqXFQDQtq2PbK3Nsvz29vbTTz/drJv/8l/+y2c/+DzGeH529vsvv7x7544z9uH9By9evDg8PDCIoe+vr68//PCj5XKJAqfHZ4v5/L/7V/+qX7c8F9c1Mcj19dt//Md/JE5N0zx58sFms7m8eTut8sm0jLFHIwcHBxzfsfz2j9vbW6VfDjHMgBVraPeH79rT5O8dg084OBf7zuq+J4mIKcRviZ++yF0GAByTkLHOWWsiYOAgIPp0RYR2SIs2z3r31He/BO39wxIALFKwmTGGgQBwuZyvN+uurb2PLARkARmAm2az20Lv/czzbTcjZhZR/U5E1Ie4vw78fo5kf/du/4RGTURktjsKCwFGH5gZEgSQThgAfNdx6KzRloPCMabQ+Q5TSpJ8ZtECE8eMgDJrrbHAEvrC5tZYh4aQxFhGZo4ShQyEtheR3NgYY7tqnHOjvBofFQAMHKvMlWU5LsfT0WwyLuf+GgAEDHN8J4S8l4RVRTiZTHTrpF1nYg0bREQJuMO7hueiSlQ16DaGTDDkKrTtpHL2dDvKLv8xCOHp6en1DTRNE6NXOyYimizRMOb4+DiENJ8vu64zxmo0qBWPdbMlxT969Oji4uLJkw8/++yzGKP6t1p+VdhiOjm4urr6T//pP//Zn/345ub6+vr67PxE/V7FWqzNmHkxX11f3T558mTT1G3bUm6stW8uX//TF6m+vvl//Ot/nXOKm+UWXiFBRAEevKZ9SQOAq6urIevDu654zAx/IIT7Dv8AaA1SJxw5SQIWEU5JRPSfnKIwIwhoty4RBCAUNIql6ecP2hY1uZxS8J4RxRgTow/BE2X7KmAQ6W9FicPWz2zOW27A1jkiskAm9pF2smQAkwinlCjwnr7YnU9q7lJKnJJoxk+2tObth7MwM/KWPmr3QlzFZId1I+N0WzFLSgyAJICEMUYEABYW6aMXkRgjpoQYSEBEiAP7GJERsSRASalv+uQJ0REZgdRFn5ITR0lrhQ0RJhGfJCbvKO99MERllvcM63ppUirK8vryjXOuKnN0Lvp0efF2M58jp5gCSmCOKUUre3Ed77IRiKgFjog4VDG+efNmUD/b9i3bnbEtjLDO7ZoFy1A9mOK2eGxnJLc0drNr9pjeb1cBAN772Wz2/Pk35+fnV1eX2iKp7/s8L+q6fvjgUdsu7t27d311G0I4mEzU7RQRbZwxnU5PTk7UT/vTn/zZf/7P/znG+Omnn15cvhGR5XJ9enq6Xq+/+OKL73zn45OTE+0sjoiPPnjw4vkrAJzNRqvlpiiKv/u7f/jxDz8PoTcGq9HI5FlTr+fz+fLq8vnz53/83Y+rR49Wt5dt2wqIIbPZbCq7SwCqAdmlBdbrJjOEYCQGFMmc1bWFbUF9Gnb5nrliFRnmbTKDmWVPYvchCt7rjcB7CcPBr5FdRmT4Pe11lYYdFlAUTvEXAEAUIv18VkxH3qv8Vli1ZGaJCREtGTKGyCSk6WzsAVph6byEGCNHjhykIDNc8L54D1TPwX9O24p1YWZAdbi22MyWijCkWHeOq4gAGr1XSQIgJAIGDRICATIgCAeJzBwNoiVsfEAkQ6SNok3iLMuwqqxq0r5HJJvnRi/ABxOFiAwmZ8GREySK3Idkk1datqVEhtkRJ9+vF2KjBVtYVxWlA1ovlldN3debyXRkichAkmhlh3QNClhFsaqq29tbhfiJqKqqi4uLg4OD21st5YJhB6gKxL3ksnqwupTaUk6ldyiY0EUcdsCAqqeULi8vQwhaJzHgE0P6/vr6On2cmqb5sz/7Z79Mv765uf3kk0++/vrr6+trtYez2SzG+PLly3v37n3xxc8Ojg5DCKenp/rkZrMZFDCfz++cnR3OJv/H//aX53fOPvv0e8+ePd3Um+ODw8PZbL3acIiT0cjZvN3UP/3pT0eT8cnJiVjazOsQwmw0Oj8///1XX96ZjWd5posQUyT3bhm/pdcQceDjD+yF4a+yo5IN0jJsTXmfljR4lcPD0m9Je/zB4YTtY43v3LnhJwAoOc1pjLBzN/KiUCuq56g7qo2ctsqXRa8SEYUFQGKMqG6gAANLEC8xCIyzDAA0sDOgrSgQgEXe9ZseLph2hD7YFbKKOroANq+stYACAGYXIOsWSgj7cK6+TmAFBCAKCidmYENEgAJMIAiJOUpMBtgQGTJgyRkgAuNIEjvk3EBmTWXHSpQ3iBmBIUAkJudRjAHnqCxNnjsA8Bl7TyGEvNhG6ZVz03K6Xq8Xi+uDR/dFUgo+9H1wOTprwYgtQ8eQYUbWorHqB6oIfUsVad8hremoqkpLrXY8xndjJBDf8dZ3rd23n5lSgoEastNVKg/69oFBMnhcl5eXxydHbds+fPjw7du3h4eHfd8rUmJ2pZMppcPDw4ODg6Zp5/M5ACgpHtAolKqklrIsf/WrXznn8ry8vr7+5JNPnj17Bv0W0z88vHNx8WqxuL1z5+QnP/nJX/3VX3Vd8+d//pMv/ukXFxeX9+8/vLmeHx4eC3ej0Wher7VqhAhTCr7rzo6PF4tFw+FoOqqqar6ouy5ZS5hwX2x2QY4ob+adbOyA/uFM2mOBKYI6iOX+MUSS+4L6LYu37wkP5++bQdkVpuj66++13qLetPsfPlwS7eo/9w+VJWSBrQuscs6SZLFYLNfr9bpuQgxAyIAIZCzBUE84FJGqMZOBCQKACkGJiPY6ARQV5gFD7vte5B11ZDiECZCRHKQEAMwCCdACsBAkbeQBJAaBCCxBUY220puMehkZgTNorTMpOmBEtCha5GWdHbltC2wlkJCBlHS8Sqc9rELoVVPM55ZS+/bqrYiMq2rscimZkdknDly3tbGoUNe2JTbtVfQOUO/Z2VlZltrNQWuC6rq228r6fRSHiCiEwCnsdNK29CmEEGHbutvspk0MmeU8z4cpNgNRq+887AiQfd9rq3OteAohFEWxWq2I6NWrVwo6f/nll7PZbDKZaAcATbIx0OvXr4+PTwRB/dKUUlmWIQRfx8PD2e3tbQj9/Qf3NpvVN9988/3PPj05PWqb/uDg4OGjB5eXV1ptmFK6f+8ug6zfrNvYlVVpDfV9v1ksvvvggXMOeu1Mp2wpZGbkd1zZfediu18RtQWQuvqKaorsLyZ+a2N9S9gMwZ7carsVARAEJNWGwsJRREAIwRhjB29lPyZEVLsESrhDBJGkcfhwPYOd+dY10C73KyKOTJJ3aXdjjDXOAc3XdfTBex9iCkBREMiISSgWAREJcPdFAsJsrdsqd5ZtgoS22c7hfvn9lRzUFsi7SqiIQEgGELYXGEVEkmTGkggIWyDrjDO0raatCjXjAA4AaIf0SkyFNTnliJhCDCEQ2sJmZNg5Kgp0jo0JiJg5wMxgNTZW21Jti2AlZvb+yepiISJVUU4mk+lkQgJtEsZY5hVLTCFEZDssK+3R+UQkz/OPP/5Yy7TVvTw4OLi4uChLx8wAabBvtBuKMFg55959GoBkWab9KbSoQs/vum40Gk0mE96lJTWNIZXVFoBv374tiuLm5kr7fFZVdX19qz2wqmr8u9/9jhNog0O1kMvl0mXF0dFRjPHi8mo8Hj979uzj73wiIutVfXFx8bOf/ezw8NCM3eXlZe9bY1AgqgH/j//xP5ZlaUz86quvjo5ODg4OLi4uHz54slwuq9JkRU5E4/EYne3axhlzenr6zTff3PvjH47H465rfEjOubwq1vVKb0Rxf7VmqlO08GxoM6OnDUOs9j3YP3RQh9/DLnje/82eS/Jt1rXswuxv+aIiot3r0l57Ic25OVsOH7j/Ff+tQ3YJdxQYTKs1bjSCJsa8D530feSUYmIBlC7G/c8c9JROaOFdUy/ctfTcpvKBY4wkoDxH2v11q+n2yhHFCIAB0HZViKyOGGSZwSQgkBvKHVmDBCiSIhILE24vHgWIyJHpu84ao01Bfds10lhriyzvwwIBQEgYtEPX1psz0LXBOVdVZUqp3iyD7yfj8tGjab3eSExd067sKicLzLnLXWV737ZtijFa4h5iFDHIbCRaSAwMSSZFUS9WVVFMy9F6XberJrYhQ+ebNoQQ+sgpGSJrjTOOiGxeKo5NgBjZCFQuL4zrYtJ844MHD6y1V1dXcdcFeLVaaS3z1dXV8fHx69evi6KgSblpm/rVsyKzjrBw1K1uoa8f3H/ULRZN2374/c+7yE+/eTk7OBTnkPnm5mYymdy9ezcGdsZm1pUum1YjOpb55VWe5xnRw/O70IV2vuq7W0tQzAprsGmS75OzJFDkxcGL118FeUtFNjsfz9u38+7F5Hy26dvYbLRCP8VYWMddl3p/UFTduq4mJSFaaxPKstmgpcwCIhNEFEZAAnZGDIL2ESOIvvMpxtwhEUTfZNbsQBwBiAhoDGbWisRdCzbRdg+KHLJx+4KmLxCBCDVGQARrDcC2GALJ6LnMPETvCNArpxS3BEvZ/gPEcoqRWQNCIrSMLkHsO6/uInOPws4Zm5El8cEzsikwyzJjsWmXb19dLxaLruusy9u2596v3t6mKE+efGTYvKHDIs9VuhAxt5aZ+75f1w0RIRnjUBR3QQIylFKGWFWTvu/X66UBmY7GRNuuakhiHSWyMfrESUASrhMPlcfIiAKYQFJiC2gFYwhtH4iZhBHFWnLOCVHfbNsjiUid0ng8rpt6E4JW7UwORjHGNjYA0nRtH/zQhFtRfe/9eDxmz7fzt8aYyWRSVrOmae5Wo6uu71LKDCDyslutNss+Bj0HTVqsVnZQZoNl0ye8XC6JrHY3bNtewRJEbLtaHU6zK7cfyBzGuK1Fhb1OaizGmPl8/oMf/EBd0GfPng2emJIwlTiiI7smh0dlnqXgtcslAgAnrTGv26btUxJWVpcirjqobOjemVJyzo3H4w8++GC1Wi2XKy0eTykxWwCwWY6IeVYaY0JiFyIYUldA/VVt02StjTH0TQtBABEzq/xnIlK0feuw7aRh61wJ7E1b2AHoIog6tBQHt21A2AdD9IcWZvjTkOfAve4m8H4stO/9Dif/V79C3s8lfuvYdZt+l4IzFomydlUfHEzzPNvUi7bbiKQYIcQ+chyPxyL8zdMvv3r6+67r7tw9v//o4ZuLy4PD8b37d8ajg5ub29cXl9533zx7WTz5M82Xpr3iD94l9IYrl11kKwmGdpiyQ01T2kISsHM6hze+t4AAoD63sO/7wmrOI6a+hRgdaeqLNfWl66yyrdMptNbxWx0hQPodB80Nj1Ur67Ub/T4B0Hs/Pbq/Wq0SyGiigyizyJULYbVa9FtbDnZ42O9DxsjM2h9aZFe1hViWZdstjTGGnOwBANbapmkQeZf94303Q9u/P3v27Ec/+tEXX3xxeHg4FEbUda39aYhIh54Cc5ZZJuzbOnhvAKwxArhcr4EskVzd3BblpCgrtK7btKcPzxTI1baLIfbW2rt3z3VMD/OWf2etJQLN5xhjjMvQGPQRjGWGvu+bts+ybLNZ31xd51VWlqXWEkIAmzmLxhhjEcHaRJR2vd9FRDNy260g/xXiJe4Ym/sCM/iHw7J/SzZ0a8oOsBlcL+97+IOIEfcqgPSj9uKL9wR7P7T7rx5DvICImnwi2gLUyuTUcorRaGQMth0eHJy+vHj98uXzlNInn3xwfHzo8izG+IP/8b8TxvW6RrJFaY+OZtZm9+6efHH17ZpV3UL76zMIp4gQGM1VKLPXbJt0JGMMIO/QnS3KJdtSJlDhZBESYAEBDSMtEYEgx5RCT9Y4Mpu2Tikpdq3fq/kw7YKxTx4EAGutvCPEvbuFIcdjjKmqSvt3aCBwc33ddZ11LoVoEI0ydV0PMDUWLZkin1nciwNhANNYk6eiXM0sM8ysA4ZCnCBiirIbcGGKwunAakTULwZB3BFqxVgFBl+8ePGDH/xAu6NqCTwRDYMju67TvvHL5XwyGjuDHFPf92WWj0ajqhwtFqvxZOayeHl1nRfd9ODYutzaTmlK67VhjsrzFkiz2eznP/+5cy4lSSkaQ3meAWDTNJ4zS2AASIQFEzOHVDddiFdFmanqcdksd5khAkmKyLldffpWGFAAeAdOsQgCAgnIH+TlcC9sG3wN2iHGgxb/Q6nYV/D6pX9owYb34q4rwrcO3BUZ/qEc/rcOZ60h7YUHacsyT8aYMs/apu66xloajSfWbGdUfPX110gwnRQHB9Pj48NyPAKAkOLt9YVzOQtOqrwsRmFSJIbrGxos3rDpdavoNhgiveFqdzIQAECjrxD6lHCr9N5fPcR31VQqhCAALAicOWcI1e8mIiBi5hCCoq8DRE97fN1BHIY5MESEe43S95WpiGjhkfakh53aTYElMViRmDiGzOaZM0i2Kg+bpo7JF1n+bvDi8LmIyABEpKCl5up41yTb7Goq425ksYqic455V/8uqIOmiqJ4/fbq4cOHX3755QcffPD3f//3n3/++c9//vM8z3Wmmq6+RgUHBwchhL5ryiLLbEEGRCSBlNXo9OxO04YuRLCYfIwsPgbK8icff7RcvlXKpQhro38AbtqNy4xzhqLE3TRibQhPMZGxrGNDBQQpcep8X9f1eXaa57lOJmFRgJsdkUFi5tB1AIIAkN7LcaEAIpIAoiC82z3f2tw0tDbcQxe/dfL/jYF693XvS/Xwe2XqDP/8loTvn/x/L4ckoN3yQUkCwigKmsfk+8zSZDIS4VcvXlxcvGrbdnSYHx8fn54eF1UeQttcL40xNssuLy8ns1melVVhgSwSoDCIR3S8x80aFoTfJ2zpb5iZU0xp4M/B0PcJcJtyHIbS7BZHbzltH44IQCIRp+MEQzQpkEFkE30ffffg0aOhKe4gVzFGnTlJu+JPHMpcCAlQPdhB8Wl6sO/7tm6AJcsyZyxTSill1gJnhMQpcYwAzlpCsoDYdnW9XhVlZoe7hf2c0p47FEJANPpivV4nbq21hrbtfZWkol4l7Lx54XdNfjWc03Y1Oo+BiNq21czeeDwe2hlqIgESh67tYevUNk2z2jTTg1hNptcvXorgdHZcVKNN3Qv2f/qnH//8P33JzCjJEqTQaVP3xe3VpCpTSol70NGFQRCxzE2P6JAYQQQYAY0zwAAQOaWU8rzQftAsgUC8D5kTZubAffICkBEyR0hsUVEodUC1heYWFv/WXtefQ58Y3qOzDHI17KFvOZmyxw3U9xr4tvRuf2rlu+wq3wUgsbDA+wTL4ev+W3Ko+SlELQ7czuchQiLI85wlrtfrm5vrq+tLY+jBgwc//LPvzec38/m8blaj0ags8xhjs1l9+NFjBLNYLK+uLkVwMpkdHZ7cu3Mmr1aaYgCwzMi7kkjtlyiS5H2Xvu+3TEnmLb0OAJCE+Z2wicjW+3r/nkh4C0Vtqw3FMFprM2PR2YjoA2SWrEFEUJDVEokAGpyOK93AzGy3mUNbVZWzOEwC1bAQ9iIy9e/yPB+Px+qU+s6AdUja8D4hgCUSIESxRpBYYrT7fs47Db2rbBhyMgqE9H0PGEW2LfJUVaj16/vemG0VXIoMAErS12TD8fHxer2+f/++pvWU4anW7/Ly0lqroAgAOEtt0yQdI+5svWqur6+tzWyWA4B1+RaPstTVm2a9Ikwh9taAiHRda60tclOvV7vJctGQqrdEaLIia2JKwpET8DYHjYCAnFnXdZ11VebcZtOSkVEx4hA5Re3EM3h9aAwbRERLGomhJsWRUQnO+9H18EZr3/XUgvexlj8UiWEXDqF1Gub27DCh4S24o33ty/AQYpBz/39ZQoO0deO2zxdEmFOKAgjSbNZvry7btj2cHdy7d+fw8PD1ywvmCIy99zqnZHowOTo6atYNWRcjg5Ag9l24vr559uyZMSeD4dqJFoqIpk/3HezhancrEPSFy4wj1/e9SNrl6/c8CFQlhMiyKzVAQokxGkg6OqogQINioIzZarUaRrvAziZr5/jh6WhKSTEYzswwpFnhHL1arfIbCNLj8Vgly5GxGQCRIwRgiSFG7kOXldloNLLWoIT3aBm4A9mIcGjppc9eCym895NpzrueQrJjXROR9z7PjZJRIiWN+rz3J3fuzufz+Xx+fHw8n8/VuI1GI21We+fOnYEMoFWFRZGv15sQwmg0Go+mXes7H1abGqEdjSZZll3fzIloOpldX19//btfZyhd6Lf+QL1y42lGxWqzzMwUUp9brMoxETVNE2OPmGLElGwKKIIpBZRtTwxjsOu6IrdIkFLKXVYUed+3yYslAmssMBFkWSbMYq0ytpGEGBGBEAnf8dr3PUN9kKrRBhH91rIPz/IPj8F4bsk3u5Kl/ZgKERWyG97yTgj/W9L23zi0DUeMmiAB2XUequt1URRNW7dtOx5XT548Kcvy9nb+9MWLoijGk6qqptZa50zmcmGLIJzI9zoP1BgqQCgGGjwCtWm81zzlW8W+sq0eBO2Zv9VWJDvGTLv1muUdTxARaadqgDR3AwhCIpvVqspcljtAjswkbIlcTovVUvGOYQUUmJnNZvtCMQSrOo8gCUBioB3VHmDdtHmeI0vyIZKhEVhjc+tYCGg7sx0FQgh99E1Xu2SsNUWRhSjbTl4AYK1NiZumQcSyGGVZttk0tCtLSynpqmm3D2bRFzokNaWkkyQ0Ka+sF4Uly7LUpL/S35RWNjCttaC+bdt3A1CNVFUZQqzrOs/K4+Pjum4vLy+Pjo5EBFiOZtPMke/WBOm3v/5Fe/nryWSiTdYcudRvbppl2/bEARGzrODQJBGIyYgYwYPJRFhS6JgBGAxuya5d22ora4NSZnkIvu87YxwYk1ICBIvAAL5vIXiDpIN+OCbmmBkQ3A7rMiZLu8YT+7EN7MovB6EaktG0O4YQYDhZa754x4D/Nq6o+1VEH98OVNum4AmRjAnbru/vObrD3sJd8b7sKITMuFrXZGA2mzHzfH7jnLtz53z+5c1vf/7bxEFHL15dX79584aZZ8dHxhgRC5xbk1nnCK0w5WUlDOORrZtOUuZ7WS4XvhdEyTILAEQ6FVN3V+z7ranfqRJgTlqbO1ybD526XTrTN6XIvJUQ3LWqMwZj3HZ8tGQAEZhjilmW5YUziH3fBo5Vnim9qCoLAOjaRvNnVVU5awihqTdbxhyANQTOikiKQex2Wq7Z9YLhHbklyzIVCgVKtr9hA8Axhab1NrfFuEBLXeo361ooaXNxq7tfRdEYN51OU0q+D1VVFcUWrQ4hKdUQERWG4fROcxuznRmg4R+9T2LW7nQqk4PhNsZoenD3CWbgyoHwsEcnk8l4PO37fj5fAkvTNBG76XjifbdY3EYfRlX1Z//inw1CKIJauLTZNNbaruu6tu/qOoRAZEej0XRatn1iYWBwZMlaYQzBhzYaY5BFUmLablDVsgQsKQoy4xadEyLZoqMMYLboKChSAPtCsi8z74MH2xMGs7lvOQcFvJ+i2LdUQ6C472fyXqpDD33XPmq6b6WH7T6YHdzt/dPTUx20WFXF/fv33rx587/95f+n7/t79+7duXNH1ejR0RERXV9fI2TWZC4rsjzPstxZZzJjjPE+JuGUDEKBJgdyLF1i2/luX8vAu+qNAne1vO/fguCOcWB4O6s0pW0/KwAQSCwMu4o5JJKUgBnRAACBUpsNCKMAkhChAUJJzNtWUYNKgj0XRvYycPuqc3go+6uq8cLQZ0Sb02iCMfap8z5KLPMqQZwvbiNIgkTWCCq1Ba2GmLDthyk6I7betJvNJoSkGTzEd22aWLRv+buGJeoVqBCqM73Ph3z69KmurCI3tIOGBwap3r+6u2ppNUvp++1IDSKq15uubogTSAgNO0PHk3w2Pjk6OPzux8daPyU67rzzmeGM5IMPPggh9H1Yr9c3Nzc3Nzf16qbdzO3hnZQSImUG87wAwYa4TRFAOMUArNMqCS0gJwm4I50DonVkjAXC+C7bzpoVlsQi2u3vnRgMUoe7STiwxzuT9ysJ9yVQdvn9ISAf7NguE/seYAN7Xu6+SA/vkvePQS98S1mIyGx2dDu/9t5XVdHF8Ox3v7m8vOhif3R6dHLntBiXN7e3bduPRiMim1Uj68q8KMvRqCxzZa2RRWNMv+kDp95DAiIwiclH6QN/ywnXK/wWODl4BIgIyID7Dvz2T9YqLLHN16ekk8kEaRh0lVAMEBMSQRoSSoaIIAGApJQ4kXtXSER7lMBBx+n6w676PMWIAIa2FF7Q2hDm4L3oOBaiPsa+68qicNYai6kNgkyORGRTNz1Hl1ubOQYEBkTZNurCXTSo3Ss0hRDj1iJpabxKl3K+hXEQJ5EteWfQavsR9sXFxXg81lntuBuolnblhd573PEAt7cagoYHbeeX65W1mfbCsCCzUSEhhHYzPTr8/qefnB4eeO8dhNj6br0drxVjCiEQx8XVhXOuKkeT08ODUX40KZumAaDny42RXdUZApDNiEzp2s4z67RZq85/gsSccmcjs7AXABFLIAwASaEZ1PZnIEPi/tv5Bt7V76Y9QGU/bBu07H4QOMjGALeYXU9eiTu2pwiKwBYOBQRE0Yp4UIDIkjFkmvRecnn4in1XindV14jYB399c5NSQnPw+vWLX/zyZwcH07/4iz/XgbUvXj1nBmbo5rd5Vo7HY6HcFkVRlnlRkAElsANgAI5JkjALJAHfh7rp6qYz06kumAZ7qGE1Q+JgjAEEEY4p6MUMgpG2JRFxZ7KEaNtqUrYlB2lno5JaCEQUSMg0GDqRBEy715xS5JicMyxsQAiBhCUGANAOPZAipN0kKkOQIHCKvI0jhhhhsIQxRrNrESQiurfROJvbJJxSMmhdnvmem7ZHvSMAALDaOcIYUxSF99si9Lt37x4eHs7nyy1DD2XwgHc7YEi+JZGtKTPmXcf14cyjoyO1hIr06HUrFroDALYToLYZSCKXZS4rEm/6PszncxTg6F2eEbKzcng4efLg7P7pzIH0i3lEUTdYRFDIgpCRjGy7mW9SmgMNnsPBuCzL8rrZIBgRDLFrVm0SYygzrsAtSxM5JjZGSPW0yYwRFuMxgkjihNpoNhKhQVXDiRlFy7Pl27jLvmYdfrm/PrjjbeFewn3fZH1LRFN8Vzo4/HL4/H2TqDtYOxjum53h7UPlmiIiehmXt2tErJv66Yuvmqa+++DukycfFONysVxe3rxNkc/Pz7OsWC7WImjzLDAKgRgBC2CRwGhzKOscYEwExCQgXeia0HapD3uTpb8VVg3XP+xvVSUi4n033Jpzdmc/cQfgx2FBCDQjvzMJIAKIBIYIOSUI1hgSBE779h92ePXgsOCeo067ahLZVZlpMK9SN6yqloYT0dArNYTgysrlLgXfdI04Gh1MI+HN25X0yCDCKAq+D1w4haacc9qX/vnzl5eXlwrIqkpWl09ECN+NTCMy6tDuXfe7/TFYAJ27pC6rao7Bs1KLui2rZ0+0lcmUYtd1lrDIHIqs57cns9H3Pvn4g3tn3Dfr1cLElPrAkR1RURZqq30fUkqz0VTHNbZ+O+RIRKy1ZydnxjhhU7d+uWrqto2QmJnIERBvnU/GbaWkRUSLFIh01ntCIP52Da6IZvATsRnEabBp+1L3LaM0/NR12A//BudC9oKT/bfsX8OwzvJ+Q+FBsId1HmRbt4iujO4nVYvz2qeUFsvbvq8fffDws8+/m2X2xcuXV9eXzuZFUWyamjd1ilKWo8RCFsGkJFEw2iwnIgCOwlnlTDSUOEXxPvbcBe5xx5L8Q4gY9jzAQSSYmSzIXpf7YfhSiD2JttV8j7gn70Zq75hrBlGIDEpSi7ol+oqIMpf2JW3AqAaZVCLx8ASHWHowIYqVKOqrYqmx1bZxhMGyLAW57RvIbCkTMBRYfO8ZMKUUGWzadddSdajPrGmaoigGyZa9Y/tE4V3DBUV41CXY3oy8W5R10wxokgbfQ1cL1Rm4NzdbRFigaZo8z53Ls4x81yeBPHPsO2fkYDI+Ppw65Pnqln1/dDhbxcgpEYo14iwAAyEDSdusfd8agumkJBq1bbtZN33fQ+iJKMttnlWFy5Z1X7fBR06SCBHQ6HQRECFrDRnmLX0J49ZWGMQhSwuwNYHvjBez2auy2d9YfyiHg1wNdmz/YQ+PU4VE/Zw/tKWw52TCnuUcXgyfMHQqYWZtf64/dT/pc1lHXC4XZZV/+tn3P3j8IEl6c3XZ9U1WFEVRANBivQo+jUaTSZ7ZPCsnFREah2TBWkKLKYGEBJTQgjEklELX+9BG9mjA7LVRU6ALd3nOwTXY4bS8j2ztqycRUVRZZ/furSeEMDQuEI3XEC0RWKLILJIYAczA9SPZC4kHoBjfr3G39h27E1i0GSTHFH0wSGjBkhFjRUQSi0YGzNonUiRFiUREzvoUV5t1BKiqql+tE0NkiilZZtZEn6a2EVGHSVxcXNR1S7s04LB22/IF2AJrMUbmbVofBwWDZlBmx8fHOuTg9vZWa9jUqOr4F8VUNDjU3TAuy9b3jJDnuYikECVFAhGC89Oz87OTvqlf31zGZjmucodyOJ1sNpsQAqQoSCgps0SU5c4RcN9tk6qOCFksweWbl1U5ns6Oq/FsMq6AsiRN2HTAghYNQhLYGnMjiBhDxAGIE0ER5ZHQtiRINdI2Rh+YxIN07RulQU5x79jfQ/siKrt2r2mv5/Jwzh/+1EzXvrnbZjXUgoegY2q6rtNRFnE3qTPuzfELIdw04ejo8PMffP+zz74rEJ8//6bruvF4rOSHGPn4+DhzRZYVo9FkPJrkoyKlgIjkCJ0gQgyhD10IXsiAEHMKyXe+jckj8f4iDC9g17eW9+jjaa9nx55DuAUdYorGGOZ35cUADLCNvfUz6V2967YrsbCS3QBA64Zp/xpgzxoPOnQf4BDZUaMAZFf+PiiU4QENiJpzLlgQETJYVVWfuK5rdG40Hq/qRhBYkAQsGmRgESFril242dY1CkBMAmAI8zyjCBDT1JXXkBAzQIwA6CjLjDD3HIpi20MlxpRlWOYFMzdN0/k1GnDW9mHjY304neWZvb6+nlQlokhsY2gBwIoYEkHYdGvnLJi07JeJGY1khiRxFvCDo/sHWLroesB1CMYdzu2Me4/ZyBSEBAlALKNI4pgwJPKRWEBSClGSLbPZqDg/P3v58uXtm2/idDaZHpyOZgelu856L3C7Xq67hK40tgiMEcRY22IGkgCIrKEo0ncpJeAYebxsVliVxjruehAuyKFAKqMzCJB8vy1pQWs0lRclAYDNHOzCG2utj7hcb2KMVVWhlb7vACDPs6ZpEnCm7eUdEEkS38VQGmTmwDFFVtsYY2z6zqTtwOembQGoKArLKbUNJdpsNiFJjHFdbzwnBlm3Xd21XfA+BCYDhIHTum/btv3OlH78Rw8+/6MPEndfP3t+fXNlrAsR13UDQNFzntOonFow/YYNBxqfkhMEhsjcobMmiz71YtHVdW+yPImpN2HVpkBFdE5iop27DtpoUGTrAao9T0kArHNIlEIQ7t/BM3HLq0YAwyA+KkxigABsTJB8mhRjBawTSBTmxH1sNpKmVYEZYuA21Chm7BywD21D+di5HLaYolhyAACMEsWAscZmYCmCCJuEObqeODA4Z/JqlAATi48hz3NIrOADQSLaNUKLMWbImKx1gMYaIUDGKLGd5M772MeYhO0gygCIhCKoVfMuy5iBmWNKFAICEZFB3OIPahZY0q5HXdt3uSusddaStRYIY0wxRi9bzaR6XafGxhhDrw2jWEmAg/YKIQiCMUS7Rg64y0keHByMrK3Xi8jh4OBgMpm0bTseF9sP3zY810dgRCwBZ1kWow/9dviWMcbm2Wg6cb7YMveB8moym01uFptRVYLl1kvnvRBLhLbeGAskQCgICCDaBQCjz3KbZZnNnEmJOXFSshc3bZsza4SQQAREQtAaSGZOOl1ExKctIrVarNfrZUpp1PdK3UKS3Od932VZlnNuzHvueqJ3LBZjjMnylFLf+5Ral2UiqMWfQzHO7XJRb1pmjpzW9SYyC0LdtDbPYtsg4nhU1k2nI8EfP3z0b378nclkcnl5efn2mpHu3r1XN82zZ89Oz+7ArkWA9z4rq9FofHJysgF9vqAt5y1u5+uGtM2zxyB9CNtG/vzOO9j3CGhHWhgcbLMb2MZ7qbx952I/lh72j5qBbXkTbkNiBCQgUTKmvDtwz6Xfd0n0i1RLGsC0ayKRUpLELnfMyfvtQGwkAOUMAgCAQRTZYxQKpJTAkCFnMpdYQuKQxIe0c0AkJbHDRRAZ7e6onm1pDFIS5iTsUyQgi1aHrWl3Ld6dLIwikFgSJSKKBBCYk7qHVnZjD9XR1yY54/F4GRbqFEncBl1DJCA78ioRgTAkBgH1iFCEiDKblWWW5a5PnhkQAVGSiAFBJCVmx5gyV2RZlpKO9UD12ep1jUhlWbLgarXa1O0osHVFVRQ5ZbaLPqyT74Qy6wCMNeTIgEFjkAVsTFGAWSSl5LeCseOhKqpnMAoIS2TxIWnHjz74siy74AeI2Mft2Oe2hrquvfebpiUi7zvF38oyz7IsLzLFuzWoY47ljuafUjLbelNUfkbb+T74EIIhR8Y1bQ9t33a+7Tt1SjvvGUQIIwe/CZAYCNtN3XbtqMjvPbj/0UcfffDBvYuLi5vbhTGGBd6+fQtoHjz8wLncGNP3Xu+1Cz4LQfcmOGMMWUJryRpKbKy1ne91k4SQutb7PjLS4G7vQ01q6OKO2bPvYyMip3ftZAZHXXasN9nLtQ54iWYoYPcuQjIoKXoSpm0XDEhE2p5+P0AgQG3tiEMCc1eerhK19Wt2v7FkdH5OCtG590rJEBEFgFADycxmZVm1IUJMSXwInYYA6u5aEUFBAiJAwXcKxseYQIRQCBmBgQEZQXLrtpC6XqsQE4sIIXICzzrgKVhrHdk8L8TIUGisSUIiUshHVzC9j0flmSXlRyRmBNqS5UUbrgXm6WSSFa5uVyGEsix3HTs1ZjDacAnRel9bS7k1iJhlKcaYQkwp9T7GJLm1jsjarO26erUMsrr/8EkfORiZFC543/QNEY4q5zlSQiJRIM2KCBm2KYTgU98G4xAERNEoFiRbMFDwqW37rut8itq7JSua7dQEYQ3ANGbg6JS1p3bAh06Xazodq85S9G8ITgraRh0pimJduoPvPXywWS5Xq5W19vBw5IpC5+TkWdmnXYNJTiLCvB22Xo1GrQ8319d5UXz3O9/58MMPR6PR119/s1qvjTHVdFY3jV+vJ9ODR48evXp1kecFkVHPpm1b9uycy04mFo2z5Cw5g0SgfVmEEYA4Qdv7vg8xCjqjlSZDlMW7JOqQ4pIdf2OwbHGv6H4/iv6vGsb9SHKQQ8JtRgK3USEnkESEtGXADJI/fObWt8KdVLOICOmov/e+MXFiEbFbT1rj0l2zEKOqwSCDc3lRVH2oEdnaDLhBNERICGCSJdgHvklAe9NhHzwzEBE4BDTMnBCIhBT90ZQMKKEBRYi23GXU1STasr+MmAE9DyFo4aP2UPiWR6EvxmUVhQNLSL0IEqBhVA5NSokkIlZ57pIUZG2C5FyOiFuaC2k9H8vO/ds0Xehb5TqJCAc+ODndLFcA4JybzqgoyxBSaFqOXd94g+b8aFoV+eX13KfOcZ6SERHRShdOBIzOkMnAAAAETglYUiQAnV4SIjNz23XDxMU+buswO9+HEBi2WXjded0GYowC7yZAAIAALFeN7LqYkXnXiWujpaIpKdWpTEnZQk3b+RDJWJflAti0bV3XddMEgbpvVYabrmVmIGzbNssyTglSPJiM7969+8mTx7PppGmay1dvjk6OyZrb29uirD77/g+S8IsXL9br2vtARAQmcwUAiNliUWTYOnSWLKlHshUDYfACfR98zwzWYf4H5Lx3x+AH4Q7O1Rf7juK+qHzrGM4hIiCEXWEFM4NyDBHhHXosKalfL8wRUQxZRGSdTrd9Aru6YU4sUZSSuOvUaMkgiffsvbdkjMtEBCSJCAoIyja+Q7QmY+YsKzJXxLhOgbMsI7TWEkAERqZt3lMLzQd9YBAlCoM2ijQGtn3VGQg5RLXX246uAsIiSRgAALPMTSaTIq+0ikIzs1vvGVFEdDvGGM22AGg7OHowjBllMXGKPih7BW0SEpa+rauqsJzqek1WxtMx5fbN1ZtqZIkIGBFZBLeeoSQBYo5d55umNSg6BdEY44ppSNzWTRQgorIsi5wRcT2f+5iq0exgXExHFUm8vpmHzU1enQKqk8CIWrwkBq33fiPcdW2MPoaemTnEGL1PkFLquq5uGpW0JBxCsNb2MWyZH7vUjmPqg+ogirBt4AkAkIIxJsawTVvZd6rNsFL/gBlBKCeHrjBEN4ultbaaTK21rQ+NfrvLWt+3fjviS31gFNQs1nK5nExG3//edx8/+iDLMt82BuTw+CjLchbJs8K5vPN914e27cvxyJBNIQBSlmXO2jzPq2kFJMZQbo2zhMASZeetUeTUe982wUdBsEg60utdr/EhGQh7Gc6BeqL4pNnmG95J4JDVwPdR6MGxlD2JBQAWBmCrBBsmQB3XyYzbTomwI83hNtcPAMAh6mVt/T5tJ8OCZQksQqwpAI4pGQbIUJJoIIcCu17JBtCgBeIir7Ks8H3wMbisRERLBgjBqkiTfd8iEaKgNRnaJIwEIsKg8TEBCSniZwjRCFIU7ll0k6bEkNiaLM9zRNP3ISUv8G7teI8Cq5lGay3gO8J3Sin0fYghxsAsYChBSIw+bqdYgt+RPCRlxtrMKUmOSJiREEWSSEKRtu2cswp1MEdD23GLddf1IbEgpBT6AMDOWmtwuV7neY7Jt6t5WY3Pj6YQ+1evLrL8YFC3hKzWKUq6rZfGGBHu+ib2fpeKi8wEu1Q47wrefQxlWcbIUcQYIyBBGAGEBY01O8Jk3JXMMcc8p5BSVOeKDTEAQmJOQAYBkISQyZB1Li+std53USAmMdvCbQMoaLDrW5+2Dp4YskhElKXgvS/z7O75nQ/uPRiX1Xxxq1hOVY3Wm41z+fHx6aZrv/rqqQCc3rlbFAUn6TpfuOzwaOZsDlsoQawR68gQYsLA2+SHiMTAbeubrg9BwGQINkWvvsqQcoC9rADuEV+HWJE4DUIoeyTPtGtkTjteGw8jHxEGUQQt6dUWISBIxLtIklHofWhHY0IRIYG4az+s7zWAIJA4lZkJTc+BVZA0USkpCYEkHUIvoA0KjEo5EphRURV5mVLyPpbMzO/lriyhRe08AGrhtkuQZVnkxMwC2whSa2UMiEGwRMbYKGwEQSRItNbVofNd37dt7pzmP40xWnugYPqg81QIt0uD24SMon++awNHFRNCHUnLEMPR0VH0PrTNqMizzK7X64p4Oh13rRJNlPEgKCiCIknb5ufjsXNuuVwu1qubRUop2XLW9T5zziK2bRv7jp2TxIagLLKU/M3lcnZweHb3XpyMLyH6zRJ2BCsRSaxMg+BjIMKUUtc1uudYYkgJowEABUKMMcAcUup7b4siioA+ZtAAUoC5770GfiICZDSGIXFdCImFAXXbJUSFVS2RJpcAUBCZCKwlayfl4XK5XK5XU5xOp9OsyNfLVdM0jQ99H8hs8cZk0JAg4ng8fnD/3uOHj6wzby/fzOc3ItLXG4MgCIK8WteBU1mMQorL5VILtZnBTfLJdGrJaS+jHMkSGQTa9uDdTYNNECP7PvZdjImNNQyUmIDSt3xIeR9cUZFTJiMisu/3fdFBPtOuB8zwOe9SqSqE9K26LVQhlL2ErQgIJBFEMQSoHWkIdKC9oqJskZzVMd0ppe3An+iDM9uaJo4xRp9Zt20nxYKonTc027ntiZhlGQlxTKLde0CYBVlEwPKWhC1qKBInffdms6mqKsvyzvcxBCIS5q5tpzGVWZ65LMbku9b76EPsvC+KKiND1qbgu6ZFQwbQIkUkLTrue51jTDtmzDbrqvxj2SF+ZW4cGBbxiX1sDdoiyybF1FkKoS+yPEbfNDw6mCCi9z4mgpSIyBp0zjhnsyyzBvM8P5xNYozz+fzt22uRdHp6enh4+PPfPs1yW5aj8biajMfJ98JRUry9ZkvALM5SDL6v10cH43/24z/+T3/3m6qq8sxpqts5h4Rd15I1b99etb4vy5xBdDjUaDSCd8OPrSBFQAY0LgM048mImVf1RuuyAaHpehKnII1WxO58LY4xWpMxKTEqL8ttr84UkYwBgKLMTo6OFcxwed40tTEGskKT8syszXK0brMsy2dPvzLGTEbjFy9eHB0clmXxL//lv3Rk/tNf/V/GmO9+8slicfuLX/zi7OyOdh5JSZq28zFMDw/OTs+/+PnPsix7/OjJZ599dnxwuFwudd9nFlMK3ps8c87aFCMz+z72PgHA9e18uawnk6MmyGq1mc6O27CQPZIK7UYwDCJEuyrHbVlcfEdAlx2cM+xYld6dD6LMUkfWZFlm8wwQvfdNvW7aBnNnEZx6IhFjjAbRWScMW4OlwpkSKCMlRBFBFsZtAE+AlozvWo5hQMskBREmsmQQmEhAKGk0qMQag7Sp6+Vi8ejJY20lUde1mkoAYSIRtrtoEAC0/eY2SwNaIBeTA0BjAMABOGNOp+PpdJqVRdf5xZI22GSElXNgbIicUpIQJQuIGUhKHBjfURBU9WxVEG51vArhAN4k30cWhm1HdMaoEK4iaXnhCKzNyTkDBMCQZYX+NTFDRCJOiRHN3bv3r64uL169atv6yYcfTqfTzXr9+vUbk7nlYlUVpUi5WCxKZ8ej8vrt5ePHjxeLRXc97/tWhyWdnZnDw6NPP/7wzZs3r14+Ozk5+eTjJ6/fXNzc3Nx/9PDizRu0iEGSsHG2Go9CSmLICDGzziVDQ1u/SMtHgldhE9z2ZcuKHMM2JNaqU9qWhPY2c1mWqTSKSOTkDNnM+eBB8OTkJPownk6Wy+XBdKLEmsy5osySD13XpBQz58aj0fV6vVgstMckIjZNM5vN7tw5/7f/9t+G0H/51ddNuymK4sWLZ1VV/It/8c/rTRhNJ3meb7ouAnDXtk33+s3FRx995H2s2+bnP//5wcHB0exgPBk55wLXzlhCJEFh5iTIW1b6el2v12sfxIKklHxIre/fG3G4J10Dc1r2yJy0BcphL1x6j2/9LfO4r82l70TNY0rGmBACEFoSQ6RET2vRWpti0GopAgbgpKMBmELotaaGiBBQXRTQ4lGl+CBqfIuIhMDMkFhprjo8BhABIYRQ5oX3vsxyBFbGUt+1LssQwZDANnggFBbeVnMYInDWFVnuuy7FkDtXWBdjNGRGeXZcFAcH07IsV5s69R1EF41jIgbs+tCGKJyi95ZFOGFkcPsJUmEZOjRu6020cxHvampC9FFYkNTLkJiYEjnWTGNRFMagdWgzl4iBPVGmFBZmFmHEhBhE+KuvnlZVcXx6upzb5XLVtt1oVD14+LDY9KOPPmrWm03dnpycBN8tl8vDw+NN07RNj9YcHBwpivv27VViOTs76bpG5yePqqIsMuZYVdXp6anJ3PXtTd02ITIYQoS+D9mu1dU2ttnB630MUTgrcpdnGEk7RGZloelWJDF2W9KlQC4ROWeIHDN3fZOYRMQ5V4zGOpo3xqjjeqy1y/V6Mpls1svlqrEIVVWNR2XbtvP5zXaydNMeHBxs1kvvuz/6/PNPPvpouVy+vbxoNquPv/PJ8eHB1dVVu6lD9F8/e+6cK8pyNJ2cn58fnRwbl4cU58tllmWZDskqysJl683m9ubmk+88BmBOECFK4r5pvdeaDFiu1pvNRkwFAFE4CseUjPmvVE4O0jXAE0Og6Oy7+knYA2D2iemDWCqWo50afYqsWWVnMudSHwGQiAwYEmtSwl37Q9y6hTt5VlKbgDquQ05/QDRo+1h3k60JcVtilkREYRuRXXFCiOPDg/V6rbig9x3H0PftQKHVwVAW2SFHEBIRBDRgCOBoOruNIXCqbGYM9iKGqDTOSjTRYyDLMUeIBiMBI4J1zpALzidOwftdh4XB+x9+7uszRER493u9T2RBw4Ysx5g47vSNNnsnY4CMIYcMhBG3DwuRyOqixSQisayqEMJ6vVpvNpnNRqORMabr+izLvvrq6fHBzFr7xRc/m00n9++cP3vxLPTeWjsaTfKi6PvQ+dVytal7f36Gp2dHH3/y5Pmrl7/5za+q8ejo+ODq6vLo5BitScKd77vWW8qMMUmYZNvHdptU2AlhTFvKi7ZP7/s+gZTWtrFGRBRKIfodO8Qa1M4sAEwoBncfxDw7nHVdN5/PtRxsPB5772+vr/K7513XNetVkbuqzEGga5vlYk7FdFxW9WYTQ+i6bjaZ/OAHPzg7OfmnL34aQg9Em83KOSpHJRlcN/WP/uRP1+v1arNumu5Xv/rNum0EIc/z73z66e3tbd/3uXNnJ6d37tw5PT09Pz+X5GOMiZOghBCaTdM0nfeRWbTlkSscIDKzMegyk+I7IRxEbvBC6Q9my+n+GcTyWwL8h0IYY1TMTwOo4UOMMdYaa8GqwVO6LIu1dnDWAEAjQ2TZVpnv1/gmrdwHRNEWHCJiCEXIAMYdOgrIoIkGFkRABGvNcj4HlKoqueEMnettSkpAN4RgrXUppaD9a4UEOKVEKFVR9nnRM5TOWjIUGQUyQG5rT8i+jyFYiaUlz+JZMmOKLK8A2t43bd+FiIjWZmk3RWgLKMN7oqgrtL+y2VBkbLSrR9RhD9p+N6WECMYZIiJIiBhjQkTnMiI0qN4fg8jp6fF8frtaLazNxtPxaDxeL5dv3rx5dnnz8OHDzWbTtfWd+/fY+5cvX84Ojha38/F4bG1WN33XdSbLKaMY4/X88v69h5999r0udL/+9W8fPnp0en7+1dOnaKgcjU5OTgRhvaoZQS8mttsGWZr4gl2ePQrrMx7kE0ALoOKOrxxSAmN1RNmYSIOfRISjUaXNZrQWwlrquu7u3fPr6+vj48N1V1tLF69fl5k7OT4kQt93m3UnMR1OJ1ervigK7z2HOB2NP//8s/Pz85fPn4vIZDKJyS+Xi7przk6OjDXz5XJx82VIsSgKk2e9X61Wm/O7Z9/77LOrqysdv3P3/Pzk7DTPc2PMaDRq1qsEzJxEMPnUdb7v+5TE+xhCIGvzPI8cIkfjLFmM4V1ufZClQVT2LR7sWOzv9PUfvEv+4AghoCFjjDNkt9Are+8tvPuW3Wdtn8XWZDHT3hB49T4HydxOQBZJuzJrEQEZulduc/RqUrd1ppiYJcsLSaHrm75tjg6mQOJTJILOt7oNLJEV3BYeASECYhIB4SgQU06WXGYEjUBBhgBsEkoeghEQEsgBjLOGxbAAgXMWXaaFEa7tAydEQmTaNTnfFzbeVZQleOeKEJElDJQUUVZo2FpblaVzjhBTCsZYYzHLMuCACJo32nUsRxRhjszx+vp6PB4/+fDjxfxmPp+/fv3aew9kHz165L3/+MMPJ+Pq+bOno+Pj6WTy9PdfHhwdTyYTYfC8Ikbn8igc6hYxzRdXjz/8+NNPv/P6zcWmXk26KQDU9Xo0mZweH00mk+ub+XK53LRtSh609MkaIiAC1p6vMRoEMATIMXoR2TU4ClohSqTwOGZZNhqVZVl67/u+875PKWW5LcpMRBKH1WpljHEuOzk5+fJ3v5mORwbw/8fafwVJlqXpgdj/n3OudC1D60gtK0tXV1frnumeQcNmsRhgbWcNtnhZmHEfCBiMLyQf8cAXgHzh8oGEGWwAcgcYLGZmZ3pqplpWl+ysqqzUkZkRGTrCPVyrK4/gw3H39IysbgIkr4WFRXi4X79+4/znV9//fdlsttNuUoaUEkbRQMOgikexEIIRIuKYKHBc5/Kli9evXgs8b2Njw7SYTzGXy8zOz/mDfhBFhGp2Quz3+z1vUCyVSqWSm0path1F/JVXXjk4ONBCHf1+H6TKZDLT09P7QSxjKQiCEqCIrosiEk10QBnCkF5IMNPUn/2URelHdG9wPGI6dk1jB3jKPeoawfhP43QGxvPTbCipHUdxFIcWRQpKIJEg6XClMYNQQlAIDSuVGjZGlMZmiDF2kqKuFaGUOIhjwzC0eKoc4UuHmwVwlDqJAqWEjkhNk4Wh7zhWGPn5fDaIA8KJ7bD+gOJIt4TFeqoa1Gg2EkEpVGowGICUBhDgAlAZenhSKAPBZtS0TA0NUUJalDJGQiERpEHRchKu63pB2B/4YRgOSVieDSKOlCWfL9GMjRBAaLAoGKBvgWmarmtreigdVziO4ziOisaOdDj8MYw+pFBKuk4iDEPPG/iep4UQe72O53nJjDs9PR0EQafdTCQSDEmtVqOm5Vq2BARC3EQKjTgK4yiKJKBpms1OO9k4KU+VX3nlxubWdrvdpmyYfhBA23IdJ+j1+iCezTExypg5FAzR4OVxkyOOIsqYZVkaDG0aVEqpJDcMw3Vd17W1k4mjEJRSkmuGIgIolQApGDN5GNkZBxEtyxKSI6pBvzc7Xfb6/TDwJBLbMU2k/qDf7XZNlh0MBulkanZ2dmVlBRGfPNkElIVC7uSk0u1iOpPqewMp+dTU1NT09KDBAGm91Ww0Wplc2jRNz/Pu3btXq9UMgxYKhdLUVC6TYYQGQbC7t82kCUBAciSoFAiuhBBAqQClhhAKLoSyLIuYpucHDJ+TKz0VWI7tc2xg/Hlpt0lXeeoMurbnOE7E9Qy6bqRRSankE20MkCglGXfnCSGj9s/IrSnQ/Ktk+MNkVzOOY0YoMJCjUWOdBTJCNY3NMCIdKdVoUuDpuVkh4nQmeXyilIiTmbTWltRnYJrbYIxYRaQAAhH8/sBilKJSsRAomWEwJFJKXf13HcsLIi+UQnBC0KBmzCPFBUhlMiNp26Yd69hAied2tbF2I6gRqQE8SxoJIYJHQojx1BchRGO+dTNNt1y0pmwkY333uRBSoi70AyiiZUlTjPNI4zZt29Tk0I7jSGY2Go3I98LAAyXmpmcymUwQBG4q2W51+p6PQCMhB56vgCSSSaH6cRydnJwUisULFy54fvjw4UMBKITo9XoAhJkGj2JCiG3bjuOEg6Gco2EYSMmIaIREUURHlESUUk3vGYYhIZpBTHNGWslkEhG5iA3DkJJzbgkhGCOUIqWGlDKVyB3t7wnhDvrdbDZLKY2i8ODgYKpUSCQSBiOB12/WG74/EFGsQColAs8vFmZLxSIhZHt7+8mTJ+tnVjX4wTCZrtZyKSIeD3zPdacvXbrU972Do8OB7znJRKFYFKCYaSSTrms7mog9k0pblqH3PsGVlEAVSCkF53Eck2cUjMM6p+VYwMxOt0ufzUM/d5ARgh8ngLIAwONo8mmT/nDCbGDcZ04kEuB7Y4C7/l+A5AaB4TYohBACheCgkCiDWc9dhgLdzbdMc7g+1dDxDucNxHPjhUSBntMnhIAiUmoU57MrJCA5jwqFnN40OedRFDI2HNceRkNggGEYCFKKKOQxRbANajBK0ANEStBMAiqJyhM8lko6juPYaFlGECsujTBmiC5jqUHkZdL5dD4nQAipyqVSKpkJw0fFLGk0Wopzkxp6rsIwzCgIkSAIqUBSRgjKOI6E4AAykozZyVhBJJAL5ftxwo8JtSSSMBIGEgIUJPJQEMR0OjNoj/lFqByOnQAimlai2eq46aykuL3ztBjmC6X8Xu0AY5pIJLKZDGazeuqq1R0MwvCzn/40m0rNzc0pEMsL0wcHB77v9Zq1lDmdYSXRj092TtbWV65fOCfDwaNHj1yWQNELej6CEQlJubARFChuYq1WLRaLCpVBTcMy6s2Gm0qGPK4326Wp8szslO+HQRQRYgAxJaURjyWQGNVhrXZ1ZqbTagSeBwAEoVTInZycpJNJilAsFsMw7HfYfHEBQA5aPQDZjTxC5OrakuWaALLrd4hrd1oNK5Hsx23GLOTe8tqcZVnpbOLoYD+KIsdyMMbNp09npqeZYvWDtm2n1ubOhmG493DPnBLJKKmUAgsybrZcLudyOYL4k/d+lnCcbDY7Pz/v5u36cdM0zbm5OcP0wshLJ5M7OzuO5QouAahrpsNwAMLkgW8Y1KZG6EUCedKyMAp0vYoorXqiuJJKSlBKU8XHnKNWzlMyiiKLTjAbAZCJFjyA5GJoopQhZQwAvD4KaTJCJUoAJRQoUMSgIoqEjONIMURLMgMojSRR0BFd0zQN05RSxoorVJGIwjBMpVL6TRGRoF50SlJUxI4NhpRQpJKgjoYIxZ7gqAAIEUopgaAkBSSIQRiFlAnLvPd0q+N1uypyStmuCjiNJEoBsULBlAAkChUSZIxRg5GEZTkGIVIYCqgWu5QghUCJKDGKRSwk4TLkIop4LMEmjBpGrpB3k0nbtoGCk3AWFuYME4ul/Kdbt30/9DxPSR0wEFTPxqhhJF2hYRYAkiqqw1ZtS2QELxzVdSiORxPRBUrEcFRcMcYIVTxWSilK6WAwqFardsKemi5Kudjvd9utlmO76VwmCILBYKCU0nt5s944qVWuX7+eTiRs2z46Omq1Wp7nMcay2WzUE4VCYTDo1ev1bC49uzA9MzOzvb2tlDIYSyRTBnMGQTiAgFIjkUxS0xiqiIVcckG0MoHjMsa8QaCENE0zCKJBr0cpNSjV8xPT09PddkepeH9nlzIUMUeEhGsrpISQbrebSaU1JYxUaNu2aTJAPhj0giCwLEop7XQ6ALLf71NK41gQEmt/4vf9qamp9fX1brdrMqNWq6XTaSnlwsKCVo9Lp9PNZvOjjz4ql8uXL1++tbMlpdQBfxRFR0dHnXY7mUx+7e03DWLoRlEcx5zHpmkAqFQq5TiONwhs2263O4yxQbOF1NI54RALBUIIEKiEksZwxEfXu4c4ax0L6aoaGfYIhkUsJb8iIXyusPf8EfNQSikUBwqIlBCChIGCKI4kgAQUSsZKEY20BkUNkxomZYyMY11CFRI55NNHDbIBAAlKapQSoNDsUWQ444IgRcyJFjgBlFr1VaFSGIahYVuI9Lhy1Op2qEGYZXQa3Vw+g6h0h44RhShAZx2IaAAzgBpg2BZBwYFTGYeCc8WFkkoJqVwWxahQBCEPhQA0kVFmmsx2mGmYtl2aKi6vLK2tLXERJRLORn2n3/dEFEeR5rEbMl8ITRen2zI4zq2FpsyTSkqgSIY8RRoIrltq2okDAGPMMRyZJDGP4jhGNBCJUpFSSCnd3NxcWFhYXl7sDrrHx8dRxA3DSDjJhw8fzszMzExPR1G0u7t9cnKScNy1tbV6vR75PiGk0WiUivl0Og0AWmAUUWmS/Hq9nswk0+nMxYsXP//yFqEskfJcJ+lHca/bt22XGYZFiRJy4PmaYdEwTQLACHVMK+m4ANButnq9HiqwDDPiscGsOI6z2Wy33bFtt9frJZPJOIp0wc515fTUbKfTieP45KSulEo4icFg0G4HhomWZSSTySjyarVaOp3EkTSdaWr4LjJmFtKF8+fPr62tPXnyRA9/6c5hrVZLJBJWKkUpLZfLmUym22p/+OGH2ZXFRCJhGUYYhqZpJnNpg7Eoio6Pj9OJpGVZmVS2XC5rNcwgCAKLaeYLABgMBvl8gdbbBGkQRVIpRKqGfmz4X4bni5y6SgkAZGJGaSxnT5GMZzImLfC3GKFSQik54vBHpYYrmzJGQKEY9hC4BKQKFYQ8VERx9SwnFIoIoCIetQFHLBhcgBAqVoRzGcnYZGpYVAPJJUpigC7voAQKAFSTOiqFUgIgNQyLIOMxD4Mojnm301cwZGpkSdNWSikhCSiGYFFqokEVsZmlFAqpdzBUiqBCQhiyRCRp4Md+LKnhWGbCTqUN2wJGy9PTFy5dnJ2dNh2mEAaeF/GYkSFoeHjrhwPBkiGJR4mxbuzAqMxFCEopJShKh0odQ5E2BZLKOI6jMGZRxKiBjCYd2+uRiIBuJCJIQMIYOXdmPZvPBUHQajTS6XS5XGy320+fbv7gBz84OTlpt1pKqWKx6DhO4Pn9fp8QMjc3p2El3W5bCKEzTw/CZrOplHBd1/f9jY2NQin/+uuvH59Um612v98HRYhhas1TAGBIMslUwIyE7QAQXS2zTavT6+ZzGaWwWasLIc6cOZNMph8/fpzO57qdTuhHOo20UmkAKTifmZlq1Oq+H87PzLdanV7fC8MwnUoN5MDz+mHom5wZRtJ2TCFCz4+DIEA9jKbQdV3TNB0nQSlN2YlCoVCv103TrFaruVyu2WzqLcx13W6v19rdXVlZef3116MounXrllMobGxsDAaD5eXlYrHY67ZN05yamrp47vxgMDg+Pq7VqtlsemqqpP8v3W5Nc4L1BgNEFAqR0SAKIy4kEsKQUoODIkgQqe6gDf/XI0scVwSelekmquV6avZUGea3HIxIQRQqlKik5FIolIhKSlBEylAoTRLMpGQKKWLPC41YjqeohsWz+Jm6DpWoWfc5V5xLrlApCSAYU6aJBtVeIR7SAYNSQkmJAEAZUARBoNfqxDGfm11wEqmtvad+GCWT6YHf1+UkUIo51FBKEQYGoZbJEqbpGMzSwH6FwgKBRHGToDIoEkBFjTCGMOaxok4ykcwUrERaUQMYXVhaPHv+HGO48ejB4eFuFAdRFHmep5mFUI8x6lm4KNbJLoJUBJXmvudCFxKUrtngEFWolAqCQHAFQ2VEzfYtFCMI0mJoWsyKKUUQSr8LJYTMzs42Ws1er0eQ2YbV7w84l/Pzi/V6vdvt6mE/3x9wzpNuolgsZjIZxzQPDw+bzSajWCqV9DRWKpXs9/tRFCTTSQDodDq2a9m2c+XK1Y1Hj/YPj+I4zqTSqWRaSpCKM8B8OhMlEoSQKIq6nb4fBq1+3TQtygwASDquk3BXV1Yopft7O8lEQgoRhmEqlRl0e+l8odfrJRKJ2dl5iqzbbXf7g06nWyoVCwVLSlk9qudyuUIhF8WB5/XDyLdtpgNmXR7QwNQo4oaBSqluv1c5qQaeH0VRo9HQaFLXdeeXFuMgjKIomUrpcDSfz8/OztYD7/z58/1+v9vt9ru95eVlx3H6ne7BwV46nZ6bnXYcZ25+xrKN6nHl5ORkupQMw9CyjV5vwEyzXq+3O71u3yeGLaRCyhRBJbQPHJU0dLCmRwdx7P1QKQVCAR3NhIIidEhCObZA9Tyn44uHVLEeNwcEBVKzAgLBOJKcc+CCcE6FYqCYBnYKZSiFOCY71/YGenwPh6OquqKmhADK2BBfDcCJpIJIqYcohhAcJaRUHACYZJSiohDGXCpkpmOYvpIoOCogrpMcMosrxUTgEUIMZjgGS9pmyrFtw2REqShCYiqCymAUkFE0mUEpPWkNhJIcKDJqOik7mWG2LQGtRPK4Wvnww1/Fkh8e7XZbTSHjMAy6YXc42zacJgHFh3PlUghERQjgsN4rpRIMNWuY0HUmHbjqMyhiUkoZM7UlA0cEajCwLENKHkcKhKQUGaOUkP39/WQyOT8/32zWd3arOu9ijP3VX/w555xRmkqlUqkUIoqY1+t1IUQcBJq0f252emZmplqt9vv9lGsDyFYrHAwG6Uwym80KoR4/3rQtN5XKWFYjjngQBGiTOI6DKFRh7Lou59yyXRAyDn0Z8X63Vy47rU7HsqxyMZ/JFWzTEkKkkynGWNJxlVKmabYbTSEUj+JEIvHk8VYi6czMzPm+n87mL1+9ls/nNx486LXDZNK1bIP3AkTUPX3PCzXjE2PM933DsMIwAIVCiFKhYBhGebl89+7dVCrVaDSKxaJtWu12GwByudzy8nKn09nf26tWq8VicYDq6tWr8/Pzjzce6QAkDv1K9SiXy6VTCUTa63X2duJkMplMJq9cuVSr7GoYXRCFScPs9gfdwaDVG1gOBBEnBuFCxlxIQNDyZlrFTvP8g8JRf5yMBiMoosRhFYA9rzM1jpV+i1eMooAQApSgREUVIZQxhoxKKSSlCrkEImE4fScJAQJSgyU0WQ4xAEAB0+y4AECkolJbPpWAgpgaECIQASjXieuQj0+3yJRUDBG1oAKAzKRzyWQ6CKKe5xumQw0SRLqDioiKIjCXAaKymHIYWKhQxEJEQgkDEQQHJQxUjFDLoJbBGGPESlBEpjBWwIH6YUgUcFCDOKw+qnqhZ1qUMWIZKATvdDoh5XEYCc6RGdrudRnGGOm/0RGOnlKKSmMH9C0XiAgoBedaxckGUK6rgy4hpIwFKkkFt0ytpTEQIjaZyUyGqNKZrFJKUzxlMpmlpaUg8H/yk590Op2LFy+ur615nre19aTZbKYSyWIp7/v+/MxMLpcDgNpJ5eDgoN/v53K5fqNvmMx2LM/3hRDUMAI/Ojw8nJqZKZenTctpNpteGAEq0zIIowyoyYw4jFAKg7JUIpkoJ3K5XBiGWgZIciF5xJCk08nFuflBLGr9gWb+z+VyjFDTNMMwbLfbQmQXF5Yty6OUJhKpmZm5Wq0x6Aau63IRSSkt23AcKwz9TqeTz2flkFQfDcMIg4gxphSm0+np6elkMqkZbNPptGVZvX5f92l1hJJIJM6dP4+IjLHPH9z/8V/+ValUunjxvBDi1uc3oyhaXFy0TSubzZRKJUqI7td3um09AMl5NPA9wzD8KFaUeWFEmOnHsQREMuQyH/sxAc/q9/C8Hqic1KUR+tUAEx35ye9kQpJl8lAyVshGAS4qpSQCQ2SWJWPOla6pSKEAKdUCMYpSqRTX0qiEDQ3SpHLEXCiR0hE5Oh+GyQYiSpAEkDKDUiJijogU1RDshgoJRUoJCKRATcsGyxr0FRKuAAAHXoCIlAAhwKaKGaUERa3MLhQP/SiWUegYDJSkSkokwAgFk2CMwAwnb5qmAtIPwiCKgl4fiKcQeoO+H/mOYxNC+v1uX3LbthzH6QUdOTpASAUElAKldEeIETQMhqhAClRKCFRiSESiS9aIKJWK43gwGCSobqkpIYTkClCCJIxxy7IAjL6SGkVhECoActn0/uGBZeeXFhcPDvaOj4/q9fr+/v7Zs2fjOL5582a326UU8/l8KpHknF+5cqXbaj19+tR1XdsydD+g2+026r1sNmuaJhIihGg2mwohnU43m+2pmelsLmea5u7BYRRFlmU5ju0q0zGtdrs9XC5SJpNJwzCazXa5WBRCdFttKeW1K1eXlpYYxf1K7WBvl4AKgmBxcREAENXR0dHly5drtZrOmqNYPHq8mcsXi6Wpva3ddCZpmowQ1em2dIMxl8sNBv04jhkz4zjW+RZjplJxrdFYjqKjzc3BYNBoNC5evFirnuzv7y8uLvba3TAMbdsOw7BWq8VxnE6nOedLSwvFYpFSOj87e+XSpXa7tb+7l0i4fjA4Pgp1n1aJGKRAZboJJxa8XTmxE26t3kJCewPfspNSoSTUZAYhDKlEBIKUiOcsatKhqdEBz0oDZPI5OGJAkxOq0i8ejDHCKBAiQQkllFCKDxuJMZcR19xiQBANpQhRAFQoopSKJQKAQRARhURm2Aq51AyLGotECAB4XmAySoEioFJAkCAalNHQj4aDxEM/D5QqCsoistvs9gf+3OJCpMTG5uMwjrP5DGMGDikMgQne15+RAJGSICgETgj3A89mVF+mUkwqFfpBL4p61E5nbSDoBYEXBMyxlCADvz8IfEQQMmIeEAIGIyMAK2o1L4bEdMwo4iKWOpWnSAijACCiOAg0wZH+B3DGGIDyfZ8ApQpCHu7v71vz88V0OooiwtA2HUQS8ZiYpNvtSylt2zUNNxZSCMGY2W6352fnfH9wfHxICPZ7nWajdunieTeRiOM44bq+77uuvba21m62tp4+yWaztmHMzMxowLuu3ff7/cXFeanH5qWKAiElSFDtdncQ+IZpnz1/LpFI9bzBo0ePUqnU+sKZ6tbhYNBjmgQIFIA6PNiTCqempnZ399fPnjk+Pl5bXX/48OHCwoKUUshYKh5GPmPG/v5+JpPRsOzj42POuU7VBoOB53m9Xu+f//N//sufvLdkLezubvf7Pce1bJvZjqVAmibzPE9Hhr1ejxDGOU+lMlJGWkjQtO2FhYUoiphpaEbmmbnZdDJ1dHTU6/XKhWKhULBte+n8mVKpdHBwEHjezs5Ov9+bmZpOpRPJlJvPZpRStm2LOAy5KBbzhJBBv6fp83jX84OIS4yFpABomI5BFRkKJAohIhEJKU3TmbQ3HOG2fc8zDMO0rCHIUbudOB4r4E6+amyKMFHXGXoqGUMUSyBAEDSXklScc6mQS8GlUkjBAIJMAAgA27SH56cAABwIJZQyM+ZcEcaFxqYRykwuZRiGhDFgTGgpQUREGkkVDnykLIpjgso0TQTgYaBAUmYEQYiUmo49NT0tQCTTWRfEIBjosTVKiUGRKRlLKWPJoxBMxgxKTEKpRTlSQslI4UVKyaWUkYgCwc0oBIJ+FPhhQCSXSvW9PuccGBhEa8eAjFUUAgXkJJKcy2FSCGPAge7SCCEoDrkJdBkbUQ/djxJxEEoRKdWYN0mNaD8oEEpZMPCEEEoCUN2QUswwGSPBoI+oDMNIJtyB35eSMwaIYBjG5ubm3OzsjRs3Wq1Gr9dDxGKx2Gw2s6lUGIbFYpFRTCQSnU4HEU2Dca61uJg0QICSgvNY5LL5/f39ge+trKwsLi56ntdsNg8P9w0cj9gIBQpRK5+SSqVy5sza1tOnP/rRjx4/fpxMJra2tmq1mmUaBiWuYzuO0+v1gtAjhOQL2UqlQik1LdbpdXd3d3//93//5s2bf/Pe37766suEEMsyFxbPISohYs8f6MjTshylAh6LKI4BYkoNznkUBc1m0/f9ZrNpmmbSdZVSGuVTKBSWl5fn5ub0Teh2u41WKyYKpMym05zzRMKdLpdPahXP86LAV2I+m0unk66UVq1Wq9dr3W53ZnoaEahhIKGU0kgoRRk1TDQsociQmkGjkyklE9DQsQPUKZ+eKZ9M/+AU0P/5Y9IIJz0qIQxGAEl4xmzKQElESoimUBzOQehdQPvlIfGPlFwIHZnrkwz/NPLdzBjOfEqpWw+EAlFIheJKKc15QBCRGQpAIjFMI/a5pro0HduwzFjGFtoAGu0qpFRsjKlVUnHOlQCgjFGUSlGtdqPZhygBBQoJECVBaAAgoNDEmjEPCSEU0WQGMwhFApKLmMeCBxgM+7aoGIHhdMioUUFAciAaDQRSaSIPADKsjqHSA1YAONItkXHEKWNxLMCgiiACoYQCAaUw4jHnQg+CZjKZMAqoYyRsJ4r7yZSTzizZtj0YiK2trSgMv/71rxsG9X3f6w8454Zh9Pt9RIzjOOGm6vV6u90Ow9DKOUJwIUbcwcqIueCc7+zszC3Mu26iVqt5oee6NkC27w1EGFFKkTIFoKRSiiM1CWIQBPVmo1jMNxoNpPTe7dvdbjeZTF44t/Z0+8lg0A9Cv9frcc5jLmzbbrfbqVSKC5VMJl3XtizDde1bd27/j//4v//www8qlcrA6/X7XUIgl88iomHYmsswDKIgCKQEBIMSw06wMI78cCjNGUQRATBNs9VqPXr0qFarpVIpRiilNJ1OT01NPdne0swgX355Swl5/aWr8/PzP/zd3zmpVWzbDj2v3WkIIXgcJpOuaWQ8r0+ZCbpkLZQXRlKBQMIIlRI5KC64lFKNJC75hLo1Uc/MTJNZDJe6HOq9kd9skzqbnTS/oVFRJqXU8w0AgIoQoIQQPxa6+KdGMaN+iRiRf8oJqpvJY7zp40g3e/wqgKHyCKAUXOgWHIwmpzTdkUTlR0G31wvjiDEGFPTcj+bOAK6k5CyMhGEYJjN0uVLDMogCRjAmhChAkCbVVV/KlZIghIwVokIJFAgqQE5QUAKMMtOgtmEySkUUBrGQQgY8EEIqISUCl8NcdNiNFVLvHkqOlcqVkEIqLpDoyS39yQmgazsEWRRFvu8jReYYRCghOAMNmDaVUiYFzbloW9bZc2d2d7f10HQYeqDidCrtum42m3rnnXdMw6hUKo8fbyilVpdXXnvttc3NzXajkUgkKpVKMuHs7e3phTXwepxzHksglKAWBkEhVD5fVAqPj4/jOEplU/l8ljJstOoYEwMMgyAo1Kp0RApCjYWl+cPD4z/8b/7h559/ns8VT05OYsG//vWvnz23fuvLz5v1EyFEIpUqlRYHg0Gz1SEEgygYDLz1M6ulmdLP3//l9PR0q9X4kz/5nyuVyrnzZ1KphO8Pjo6OXNd98uTJ6uoqY4wSRqnmQFSIGEUxs4aex0m4tmkFQeAFgSPEK6+8cnh4WKvVTNNUplk5qZaLpXQ6ffnyZc/zlJCvvfJqIpEAlLdvffHZzY+vXbtWzOUMk+bzWQLo+d1Wu1av1wv5KcO0FbJuv+eHQa8fhLEwhRJccAlCgQaO6dE+JEoOSXcBETWSWFsL0xetFIjhikdEVCAmFDjGdqi+ijNbP6LtV3N3a4dJlAKBMuaSc8G5EAIpIYoQZADDnVI+ryc3aYSjuGZonFJwRERQhCAAUgJIlJKAcsjPj6BluVApikoSShRAEPmRiAyLUUo5j2CksSRkLLlgUUyRGIyaoBSPleCUAiOEcsGlQJAKJMRUCV2lFdgXXc4jRVQQhZxzhcilUErEMQcpBEFJUCmQXIlQhH4kkOs6gZQSUBqUoa53CSm0rLEiI1C3ktobAwhUcjh7hwSBAEkmk4QQ3/cpAjNZcliwIoNWn1JqWJJSQylkyBg1GWMbDx7uH+5ZNi2WcgpixkABD8Lep59+kU6nGaXb29vJZDKbzSql2u22lJIx1u/3bdv2PE8Py+ZyubAfyBEp+LCfKaWUkEtnKyfHCuXMzLRS6uHDh4ByemaqXekhIh/q/gmlRzYJC+KIWWx/fz+ZSu0d7Nqu9fTp05WVlf297W63FQQDzsXUVOnCxTOBH20+3TJMWq2dKCUT6SSltNtruwkbKXEcZ3p6+vr16z/96XutVqPX73734nd93x8MBjpYIIS4rkuJQSkDgEajoSsKtm2PQcOxEIyxqampZDK5srKiyVpiwR9vPtHBOedxPp8HlNXjyrlz54qlfDAYKBCDgWdQYlrMMIxk0nUc6/CgZpi25ST7/X7EhR8GElAoFccCCAUAwhgq1E1UNam1OKxfIpkcMR1GeaAFTwGfCzsnQ9Nx2eaUwWgNU83YggIkAckVEI4KUA19rJIKQCmqzQYB9GzueIBDW5kkCJSgQD1NKEEBKBkGoZZIYhQJISbVjG0xGYrpAUihkV5aaMO0DTdhIyLnUcJJGhaTIAgQAVKBkCiBKMYFgwjjiHPORRQzStLJpJNIRH6AACKKYxEQgVIRooBzCNGTKlaIseAAEhklqAyGQkgCSnIuCRcERcxFHIuQc8Y1q6KGnSmiEIGo4UATQQV0dCul0qTjCoiiw5heKaEm/kmc8yjiccx1OZ4xSixHSskjFcogiiKpeBzHUvHDw4MgHhQKWSlTrmvH3Ds5OfKDQavVtm17EEWI+PLLLxcKhY8//OiTTz5ZXV0tFAp7e3tra2sn1eO5uTltpchBCCm4EgoEf5bPHBwc2K6VL+YYY4eVw4OD/WTKzeQy6XQ6DMPBYBBEoZSSMUNTJtdqNcMy//LH/+v5cxcrtZNOv7d6ZrXZae7t7SDIYiHXbLfa7ValchzHvF4/iaK41+tkMrlOpwVAvv+7379///65c+dq+5V0JqnBPQDged7+/v6DB/ey2TwhRArNo8mIOUyNQh4TLReNmEgk3GTCcRzXdZ/u7MzNzCilarWabdvpdLpUKmlITRiGT548VkrNzk2bJut0W1tPHuXzuUIx51imSgshVLfbFnHUaDQALSGBWk4QRZRSjZhRoAMZQgihBJFQEEQqopTy4yHdPZKRsqAa5nhCKzMoRYeGqTEbz6FGxynlqdrp2FtybYFAQCuHSVRcKSJMw0A9ykS4ZgDSuFXDGColD7dZeKYGqcc8KdUAHjFCTMYGQYKMIBAikRCQCpUyhkBzIblUCITBqOihCEMv9Kq1k6yMhgxGFA2DAiqiCEFgng+MAVEqigSPhcXANNF2DMMyQUglIn2XlGICQCEjZCAlF6CU5Eh1JkgoNVCaSkoKKIUQAiXnPBIy5gKHeFGpZwjp0OUN4+whTzdSQKGUlEPeSDWBYhNKcAljympEDMPQ9307mbBMo5CfGgwG/X4/9KMwDgDAMDjnfGFhznaY6TDLogp4t6d6/U4YBr/zO7/TaDSajcZgMNjY2BBCDHr9tbW1MAz1jFmz2RRC+L6vUeaMMUqVoDLmIAVXEglhpgnEYKZltZqd/qBjOua582fa7davf/3rly69HMVcIWgaC4mgZckyubxpmgpxEPjpdJoQ8s43v9FsNs+eXUdU/X5/Z2+3Vmvs7++GYdxsNcIgSqUSs7PTlUrFNM2FpXnDMFKpVI3v1uvBxsYD0zSz2Wwi6Uop8/m8lGBZFgLxvCAIAh5L23YYG85Yj1vPhmEAVQCQTqeTyWSn0xn4XqfTUUotLi5qnHc2mzVNo9vtHh8eSSmYwRYXF5aWFwiBZqNWq9U0L2CpVMrn81tPD3SoJoSghgkAgJQrSRGlUhIUKgSlKCGUGADgcT6emyNIYFSCGyL4R434ob1JOZJrPd2dHz+iRlNy+lepNO0ZQUQKqAVbpATLoEAkJwACpVIwhqQqAUozbymAZwYvhSDaw1DNNKN5rrlBkVAALWUmhh6ZgKSMooKYx0IKJpGAoUCikn4YCSE63dbu7vYgKHe73TAMqElNO4F6mgEo8wahbaNhGACUi1gKafQjAoNcJgtaf4LaRAEzTAIoiFC8K+UQ8IogKEWlh8gZihilkLGIpRRxGMVBGIahZHJsTqg0EkKpZ6ovEwEAgFKKMqoQpB7i0gLVCnSlWIs6EUDO+WAwcFJJalFmuQAwisSY41j5fDaTz1SrR6m0I6UIoyibTS2vLM7OTaXTyY8+3P6Lv/iLOIoWFxcRlZ4toJQ2Gg2/308mk59//vn1a1f29/dnZ2dBC+5M7AgAQ8lyg2DMuVLKsiypZLvd9n1fQ1IIIaZpJxIJYrAgCJq9VqvTzuTypm1/7fr1drt7/+GDVCbdbrcJIQuLc8eVQ8/v61Q+8LyIc8YY2LC2fiaTL3zyyScJN/Xnf/7njuM8fMi+8/bbX3755e3bt/VgRzqTOjo6Yoxls/lUKgUKa7VGFDU5F9rkZCTHA+ae52ksnoi5ZVm9TieKoitXrvgDb2Njo16vr6+vb25uJhKJdDpVq9WymdTa2srR8QEPw9u3b+cyaSQqk0kxZsRh0Ov1NDxFjlwZ0qGn4rGEEdGRQKREKYrmaDoWxuNIBHXYqUbsvSAlRZ16IygllRqDuV+0Q3iBZkYppWmvh05M5zmgtf+U0hSGUkoQIIfg1UhGL4a1iKgZ93BEezN+L4sZlFClJBccERkhehyeaVuNQQkuKdHESFobiRCIoqjVahmOEfFYaRJ8AogUNT1Tww0MEluGySwaM8GDMFJCgFIxWkgJVWBIVKAgirmI49hESwhBpFQEqaJEUJSoADSZhSIgpezLIJRhSENuSzUs2VAlUAghFScAkkhiAiqCiDFILlAxAwAVZ0P2MaUMBQwJGfI3QsxDI2lxk2ZS6ZNKZffexqtOaqY4MwgqMfcUDEwmHCeRdC2I/Ppep1mrYugDU0HkH1LpR8FJvdoddNdnX3rj6kthGFqWEYahi4QQIv1gZXbG9/1z587UasuWbTgW7XRajDEnXdIZVxAEIY8VoOmYhmlGUdw8qYdBnMlkTMceDAbKdNNJM4o7UspYhhgrEUnfD6mJ03NTnX57Zn768y9vrq6snT27qlR878Htc+fO3bn/pDi1sLVzlEjl+GGVGvbmxj3LMouFstf1ZCSDjmcqtjq7kMlktra29maevvXWa/v7+7nc2ffff991Enu7+7lcDoEc7B9ms1nP6wPw2blpKaVtk8qBX3ByKJWbzkZR1Ot2FhYWhBB60LnRaDze2Dg4OFhcXDxzZv3BgwdvvHI5DEPGWDHr7O3tdVv1TqMxMzNTyOdN09zf3+9141TKHnjQ6/cBIGW7hmmFvkg66d1KM5LScGxghkA1gnAKANDVQAWYNp1hECT0fAGICYV6ZFQhxiBRKUBAg47y8FEtlKBCIqUMeDzkaEXUJU/tXB0eAAAqBDFkk0dESUFJ1N4PJRJFgRJGGaWUD6ujihCkhOKQz1tSZgqphgzoQJQCRGKYTMSEK0REYAoQQ83ARYArSUChRSg1CUWpqzLIlOkKEfcDLE5lGLUdaszM5I6PD00CBBVIJSVnRI3GiHAYZGvahZCGQBlVILlgCoExRACCfFgAVqjhsXq70o4ClJIqHivLgtIDY6fKWZPpNbwAkNdFAiklyOHQPQISRNu2O50ORTI/O2cyFgXhYDDodDqFrG0yI47j6lG11Wim01mTMs/zas2aAhGLuD1ouUnHck0pIQ7iVqsVRZFpMsZYtVq1LGN9fX3g9S3L0FQOepoJAHTXvuedSClBEcMwtHQHYyYzjMHAY4xFJBoMBoPA17z3ngdCeolEwrZtpVQYxFLKRCKRzuYfP358clJ7urU9VZ4ulUr5fPGoUm23u4jqz//8z4Mg+M53vhOG4f7+fiaTTiaTURjt7Ow4jgMAmUxGTw8JIZ4+fbqwsJDP5/WU4BdffJHL5TRoZqzFa5qmZkPsdDr5fF6HCdq0MpmM5tGYn5/d2trKZNJhGL7zzju6ZmOYtNvt6t5MPp+/ePGi4ziJRKLVaiWTyX6/L4QwDOPp06d62LLf79vUVEBj+az3oBSAUoKPWmugBAqFIBEIKGoZ45EaHA3R6wx/vDYmazDP6WHqFHJysP2Zgq86ta5e9Jn6h1Nn+MpXTSafX+V8n6vHjuJ8hkoqMYyNpRAoRRzHxDEJMaTig8EgnXE5571BP45DiRaj1LQppa52/MMLopQCpVIpL/ANQgVlRAEKSQAtUAyJRD3aqIbOXLfxcWiBQiohRDysC3KlFBAE8WwsGl+oL0/eDngB/SClxFH2rpeRrq0VCgXJhWEYjUbDpMyyLIoQR4E3CEzGhGEMvL5jWVEURTwSsTw+rFRODhvtFjNZzQ2+9rU3TdPM5/Nf+9rXKpWjo6Ojp0+fLi7OE0Lq9bplWb3uIJNN6aKo53mGYTCGoBSPoijiQnSFkgSZ49iI2Ov1okDLmqsgiAgF3w/jWDDGTNtKJtJKqVarxZhx69YtZljZbP7mzc/jOHZdd2tru1Y5OHfu3Jkz5/b29m7duqWLlvV6PZvNtnZblNJCIaflPnU7u91ub25uuq57/vz5hYWF//gf/3J1dS6RSEgpXdcFgH6/r0ag0CiKHMNtt9uGYQCA41oas55IOHEcnz9/fmFhXncLhRD9QbdYLEZhjEAs087nCvNzC1EUxRH3Bv7hwREhJJvJFYvFzSdbiUQik85aps3DiAv0hYpCTVmNKJXUdJ2jf7dUoMFDkgCdaK/jaEJicq2f+lnh6U18WMWbYIWZNIZnrx1jw0eHHKNtEEZzR89Fs6fW3lcaIQ6fpvSQudJRMwiltMqT7n4rRYYam0HgOa4lJQz8vmnOJNJJLoJkJikUF0hjUEJJxiiFMRUfpcQweBQHUWQZkdQkcFygAg7KoFQpJRVyQD1nBEgkICjgursghRAi5oJzTZyjG7Ry8iZqKxrDYU8ZIQDoWFxKqYQkCpDptUd7vd5UseS6brfbtQxjdXVVKVWr1aKgL3kgeZxKJW3DNCiTXKCCfC4fC14qlWbmZir16vsf/NKwnRs3blT2W81m03XdXq/3H//jf7As67vf+07MI9/3DYNKCXEcm6aJQDOZDCKGnFuWOaRwRilEHARBGHPHcdxEghDieQMQyjCZkhCGoSYy1/zblBiIpN/vNdqtXLZAiTE1M21Zzu3bd13XLZSKqVTqpWsXFhcX6/X6zZufuq5brZ5wzpeXV3K5XBjE2Ww2nU53u/3BYJDNZovFot9ra97RTqdzfHy8uDilDe/o6Gh6erpQKBiGsbOzU6lUtHShpnzWk5PJlJtMJrWHZ4xNTZU/+uijdrvd6bZ++MMf+r7vuu7du3dc152bmwOA3d1d27ZbrdbMzIxhGLlcjjFWKpX29vbCMKzX6/V6fX5mMYy4F8ZBEPKR96OAxDSlnqlVAqQkIBEVUaMOxATv49gNvmiHv8UjfeWD4/KMmrA/OS60vhCCfeXJxyY9Xpww6T9RV1Oe1YqUUgpUHId6uAIkB0IZAjJGAPt8YLKEYVIE7rhmKu10Or5tm1JFiMAl5xFnupA8pMgkqBhDISTnoeBIiSb5UkLGsaKCAIBBUKtAoxadGkEKtPgv5zyWQg9c6Q80NjwYhR9f6QnH38VI5FEpBYCEENM0HdOKwlD/yfO8MJl0XVdzqDE47rTaMQ/TSVdYdrfTC/2IEmLbtvC9KOS9bn/Q8/xB0O13T45OEM1MJnNycuK69je+8Y16vb63t6er9gBSCNFs1h3H8X2/kC8FQcBVqOnPhDDthEtKRAgRhHG1WiUUDKCWZQgQqDV1UCmJmXQ2mUz6vl9vNDwvYIxZphOG8fe//7u9QX/76W4+V+ScV45P/u7f/bvZrCWEcF338uXLmUzmZz/7hZRydnbWNM1sLm0apmmaUvJEwikWi4h46+Yn2Ww2juNWq/XZZ5+9/vrrt2/fbjabcRzrMFVKWa1WdcGTc86U4bpuvpBljOmAQjN8P3782DDY/fv3F5fmV1dXLctqt9sbGxuri3NBECAlQRTWarWFhYWt7adra2u26+QK+W63G/H44uVL9Xr90aNHzXZrtrzo+0FvEAQR17V9RKTUoIRqIxwiREGhkkCGGrfjiFRXvMlIqfeUHSqlJt3Zi1v2i5HUUAVswgKHdDQTbnDMNqaeT4VOLcWvPLQPRNSjvajGqqAIBNRIcEwAGNqxJSyWSdjZXJrzMGkbQcIZdJQEnk6nhIijKJKSM30j2LCUq6cYKackFpwqxgiVBLiUsYypIkopm1Ht2obTgRp5M2QclAJ0p0oB6h3omd7VJFsMvBCaT+5Do/uCDIfcaq7tpFMpPWmacFxKqe/7QohyudxvVYLA8we9hJ1GRCUFYySdzj68/8B2HS1wmS/m3nrrbSEEEMwVih9//LFhGMlMcmtnmxk0mUzuHx0uLi4iEiF4z/MTSPq+V56ZBkqIZemxa87RMaxEIgEAnh9IyYWScSwy2RTzWRBEQsSJhGMwWwjVbnd93+92+57nWaaTzBiUklqt4QfB9vaOUkAIZcwIw+j48GRpaWV5cbHX6dTr9QvnzqwsrymlqtUTxUWjXY3DkBGyvLhYKBQoqjiO9/f3NfeEBqweHx+7rouIvu+3Wi3DMIrFolJKY9+ogY6bUEpFUSSECENfCNFoNFZWlqenp7/7vW/rucqbN2/Ozs5OT5cvXry4sbFxcnKiuQX0Z9/e3kbEVCplGEaz2eScN5tNx3GWlpY6/X670xv4MScUkVFKpUICugg3ZtYTQxEuAAXPEDB6sZGRAsxXrn5EeM7njEK+cX6oU0o1noGaOIEcmxZqYMyzQaoxKHRc/Jw0wvEqVS+EwRpPrWsiSikgCkEiISajqKQAKcWwrA9EKiESFiYoLk2VOQ/KuUw2YaUdFovY8/pcxpHBAkqY4oJQpusxSkiiaW0IiQVnUhBGBUUpUSklQCpQko/kYxUhisCowzMckFdSIShyOsqf9OnjD3bK4+vjGRGGJj0ekeFls9lapRoEgSYRlFI2m01E7DUroe8pJYWMURLDMKjFXNd97bU3ylNTfW9wcHyQyqSn56b9MKhWq9Vq9cyZMysrK5XqcbfX0fNNb7/9dqVSKRbzQohMJhOGISEsCCLfD/PFnBBCy8ozgDhmnMtur1ssFaonNc4jx7GQ0jBsGAZNpVJhAJqs2rbdZCJNiQapqHK5/OEHH6WzmXq93un0CCGFYvHosPIP/uvvPXr06Cc/+dtWq5NIJBhjCkSpNLW9vZ1IJNrtdrvdTCRSmkV7MBg4jqMT1zAM19bWdnd3c7mclFKPIwVBMDc3l8/nNRY0lUo1TxrJZDIIAsdxEglHr7mTk+r58+e+uPXZ7u6ulPLatWtB4BkGTSRy29u79Xqz1WpxLl966aW9vYPV1fWDg4N8Pm8Y1uzs7ObmZqVysrHxeHp6ut/3+u2o2x/ECu1ExrAMU0rOhZRcCiLgWb1EgKSAEsAwhkp4MAr8yGiQYtIRjfdoTa82GR/pHV9nuXKC83d4ktEz5cgf6tB0bIE6QRq/1+T7jteknNDoPuUeNP+FVEJKhagIaOJ3hQSI0MMHEpFQVBQpEGVEPvp+OZlQyio6iUQpv1DORlGwtb1JDMYYAaRMfzxUoIQcf1SdCxlSMgCFQyQaaDj5KO7XDNNqJIY+Cut1QqrUBK/2ZAj6lY7+xahgeH/lSOxOSkTUG3O/39cI42az+fjxY4c1TWradooAiQOp6Xx83//yyztLy8tA8LByHIvIuu/EIvJ9f2554f6Dewpkr9cbDAaFQmFra+uzzz6bm5srFEqUGrOz80+fPiWENJvNTqdTni5JKTnnYegLpQzDCIKg0+kkEgnPG/R6/UwmAyg1gXQqlapWDoVQmUwul8tFcSyEQsR8qVgslI6PKp1O5/iktjg3ryPGfD7/8ccfr62tcc41ZuXLL+/85V/+5Y0bNxBxaqocBEG73fa8frfbjmNRrVYdx5mfn7dtW88EahP6kz/5E7036fTPNM10Or2wsFAul282f51MJoPAy+ezU1NThJAwDI6PjzYePWi1Wrlcbm5u5uzZ9ZdffunRo0c7OzuXL166cu1as9m0LKs0NfWXP/7xH/zBH+wfHi4sLdWbTTeZ9MPQcpx8sTgzN/dkayvoq74XUMOw3LTFjEjEhEshuVIUFMhngSFq1D+ZoJ2ftMYXw8JxxDR2euMXjgOr8XnGT9NObXyucXlGDhEycGr9jU8++f0rF+TwDSgOC01KKAVIFAClSGTMFQwLGZSCQRmjlFFqBT0a+ElABYQFfirpJm3XR1goFFOpVL5YzBXyzGQGSBXL+NmUECihJGGUSxGLoS6v1MpVBMUQ1QcKlVBC89cZ1NAyQ2PwK4x2qTAMNMxlTETNRjP1pzY/bWymYegXMsYUF7pfIiw7CALGWBiGhVxeKVWpVHTfGVmQKKQSdiL0QwnCMAzBYTAYJJNJy7LmFubPXbzQ6bUr9Vq313YTiUIpf9G40Gw3opCvrq0RQoIwNE0zm8sxw8jmcpzzMIrCiB8dHSmltra2lpaWNDtg7fg4lUppcSjd5u50uoyx7qAvuGKMHR0d5XI5x3Fu3bqlgammab/88st7hwfz8/Obm5ux4EnH3Ts8KGRzuXy+2+1+42tvSCkf3n+Qy+Webm51262pUrl6XHnzza/dvHmz1+3EYTQ3N5fP5hBR8rjf7dRqtWQyWSwWC4WCdo+zs7OtVuvkpAYAtVrt8PDw8uXL+/v7zWZzZWUpnU5vb29r4M7GxkPHcV5//fVur23bdrvdHAwG77333vT0tG2b8/Ozu7u7hUJBA1Dv3Lmzurp6eHjIGPvlL39548aNzc1N7Yf1uKNpmiHjEhQjJJlMGo7VHQyiwEfDAqJ1FIfsLABACCWEcB6ON2K9u+kKzTieHKc2oxXyjKl6aAUjLnN9Er2c1KigYE5KHbKhsLbQmB4lY8GFbvNRggQVQBzHmtVBozJ0GVCfajJMG+8IABq7+Ez0WykhJDimRZXk2olqaCqAAignk8Vc0avWssX0oFrvVY8VUYRCp9v27Vb3pH7i6hbFRGMAEWEiVdP3YrwJAUAshh+eTmwPoAC0HAs+M0KtPCh4NDknppTiL8g+njrGD46dp5Sy3++blElmBEHQ7/cNOmwnmrYDQPwo7HZ6/b5HwKCESQmpdFohHFaOJYhUNrO4NH9cYQ8ebex+tGua5szMjOM4A68XRVEunzl/7uJ7772nlAqCwLKsfD4fxzwMo9nZ2e2dxzs7O77vSwnnzp3jnLtustVpM8a63V61WjUMQ8/Laf/T6wa+P7h27YplOSe1WiqVOjo62NvbY4x1Op3eoI+ocunM9PR0Kp3O5XJPnz7NZDKOa62sLh0eHk5NTW1vb6fT2R//+C8RaTabLZUs3ZozTTOO47Nnz56cnOhKxvz8PCHkV7/6lW3bP/zhD//Df/gPnufdvn377bffbjabFy5caDQat27d+r3f+735+fmDg/16vaYj3jDyCSFRFLRaLULI9HT50qULT548effdd7/zre/HsVCK/+3f/uT69euLi8uffvop5zyKol/96sNr1661Wh0p5RtvvPXBBx8YhpHJJk3LMUwrlU4wK8EVUMsTinb6fVBICRUAOKSSHaoyTq6u8RobpyHjHG/8tMnlNz5OvWq86Ws6DAnPXJkcucSxb4SJ7sUYJ6BPKCfULOD5MHXiykeQN5QwbHaAklyqcayIksRc24vf7ymoxGGjwoSIQh4qEEjBdR2khBiMPVMjmVBoGQ4R6TMKPeSnUD1ru+unCS06P7oR4/muYYgPwyvWbzH+AOMRaT00Of54k592+OCERI4uvJqJJCIGQeB5XiaV1ucMY8GCUErZHXj9vmdS03UThmml02lmmhGPu91+q9+2XZsrmc1np2YLx8fHSolEIqs33Vqt9knrk2vXrsVxPD09PT09s7+/L6XMZrNCiNdff9M0Te0NkBqtVmt3d7fZas/NzcWCx7FApBqaYBjW9HTq+OhOqTRFKTVN1ut3hIwvX768f3TIOWcGDcMgCMJUyoiioNuTVtOYLk7/u3/37374wx8+fvy4VC406q1arUYIyWazr7zyCiHs6dOnOslMJBKBH1YqlV6vt7q6ure3FwTBW2+9VSqVPM978uTJzMzM1tbT1dUVfbWPHj06Pj5++eWX4zjiPC6Xy1EUOY5Tq1cNY35j40GhULh06ZLjWJ1OZ3t7u9lsGobhBf7Tp0/7/f73vve9hYWFf/kv/6WUcmVlJZ/Pc865FLbrlMvljz/+uFgu7e7uEoiEkhxUq9M0LD+MZBgM+kGExCTAFQIhRElUChVIPVMytsBTRjWZhk16ocnvL+7dk1atzW/MKayUUs8HlS+2LtTIbZ4ywnHYDM8fQxFCAKDaLCUgEqWpuBVolI1SHBGFBJC2aVAlY9+LQ+A8kpKjoRhjzW5PgoZGaxaA8dfEgYg6FEClKCLTGk6EEEZ1PKDrJVqM3jAMRqkeb3wmWCO0fvdzNRj8DVpzkzd3sg8LEzID2tQ9zxuSrykVx3HMVawAqWVajuW4yJhQGAtRrdX2Dvbbva6TsIUS2wc7zXatPFu0E66dcPOlYnmmXJouLa+uLiwtWa5zcHwElGzv7X1x+0tmmZWT+mdffBnG4t7dB81Ge3dnP5vJVyrVYrGUyWTffPNrrVYnDONMOpdJ5xgzlVJSQjKZvnjxolLi4sXztVp1MOjlcrm/fe9dpcSjRw/b7SYXUSLhZjJp0zQIwSgKH9y9C0K8/sqrv/e7P3jr9Tc2H2/MzcwsLSysLC3kM1mv1/X7A5Rq0O15vT4BVS6XlVJTU1O5XO6TTz7Z3d1dXFyklH7xxRfnzp2bmZm+fPlyu93OZDL9fr/f76+sLH/55Ze7u7vpdDoIgkazVq1Wq9XjqampXq/XaNQ453qoP4yCZCqRTqevXbu2sLCwvb398ccf37hx45/9s3/26NGjR48eaSkFwzB+9rOfTU1N6XBUobRt23EtxqhhUDdhJl3boIDAAQVBhagQFRAgFBV5NkQ76eLGv45Tu8mN+5TVvWiHX/mgUkqOSjIS1Fda4IsvP+WBX3xQ52uKDLWHQNdTRpaJRKECxQXnXPBIcO6Hnhf4/cGg3+/7vh9FkYwlCBnHMY9iHnEZxWycmD5ngUoZlFIkQ93tkUKbAhUCII4/7bArTYDoiUSlNwPts4etwmcfY3IG7Ld/VP1C/TydOTi2rZSKoogD6gY0pTSKIsclgNSyXWY4zPSCQRBFkR8G7V631+vZrrW4tlyem0qXM7GIFMqjo4NSqXT9+vU4jh8/fsyoeebMmWKxvLe3t7OzUy6X0+l05fik1+tNTU0dHx8nEknGjCAIT05q29vbYRC3Wi2pqFIYBjGjNueSEGbbbuCHnXb3pFZdXll+uPGAMnL9+rVKpcp5FIQegMzlMrlCNplMAwDn3LCMKA5+57vfHAwG/9P/9H/9gz/4g2r1pFwuf+tb39ra2vY878GDBxpAp3Owk5OT2dnZl199rdVqdbtdDVe4devW6upqKpX6+te/ns1mL168WK1WX3311W63++qrr968efPhw4eVSuXq1auIeHi0Pzc3p2n8X3755Vqt2u22wzBMphJKKc3e/2jjSbVa/Z3f+R0p5c2bN4+Pjzvt3o2XXgGA2knDYNY3v/nN7333d/7Fv/gXf/RHf8Q5/9M/+wvbti3LMkzDtE0kVEgZxGbPjxA4IAUAJIgKFCGEYORF8FyKNTx0G3OyoADDwd/nGlovms2p3sZw7WmdUKXUhGOctMCxzx1XMSayUJh0g6feV4JC1GzVz10YSoGEEiSCgE7DJFBUwKlCjexUSohYKo6xIv5wbSMiAmUSQZ2yfl0XIVQTglIClKBWMJVK4uiTK6VQKkEBFQBRRM+FaaiMeBbWa7nsyQ/82+/pZAygE1SlFOdcgzxQAUUCAHEcE0Icx2l26iFXsUTXdCzblQq5kkqK5ZWVmIe9Qa/dbZkJY2Fplhqs3W4tLy8jYqVy1Gi0jo6OTNMcDAZhGO7u7jqOW61WNx4+LpfLu7u7tVpteXn1zNpKv+/Ztru3d1AqToVhnM8XgyBwnWSz3YrjjpTSdt1UMl2v1yuVE6XE0dGB4zjb20ez8zMvzVz/27/92zfeeD2K4lqt1un2pZQHBweEkHOL55XCL7/8Us/pPnjwoFarr66uPn78+MmTrenp6Ww2u5Zd29jY8H3/zJk1zwsMw/jkk0/CMPz88887nc6rr766s7ODqIdg4M6dO7qo67ru/fv3HcfR25brusmUi0TNzc2VSiVK8enTTc0ymkwmDw4O9H04f/58IpH44P2by8vLvu9nMplvf/vbBwcHn3/+udYDTSaTV69e/fzzz3u93j/5J//kww8/DIJAKREEXhj6hBmG71FKvTAOQ58RVIRJkJqJHgklFJEimTC2SV832bQY/wkm5hjG1nLK3U3+ipqzdIRYBt0h1M8ZmZ18Hs42aXjjtx6Xdk6tRqWUBKEpEIdXq7mShk+SOhHTrwSpCKAwKTEoMSmlhEkqJVeSA0AUhwQoAhClmPbaI2zZkDVRCYlIpZIUUQkJSCghUilQQCYUP8c2o/eaYf9FKqUlowARUVI6uaPgC63CF7+f/tgjVSpBqMkMRlkcx/1+nzGWSqWqTT8II2/gu27SNkzJVcwlF4oxli/mcrJwUj/2Q88LvFyykCvm0m7m4cOHW1tbSql0OhuGYeX4SbvdPjw8brc7uVxOcHVwcCClXF8/q6PuZDK5ML9Ur9dNx261WoSwQd+jlBFCbdvu9zwpwbZdQlgcB5ls4sKFCw8ePEink2EY1mqbC4tzrVZramqqVqsxRhgzCYFCoXDjxg3f9xMk+uyzzwghn3/++fz8/DvvvPOf/tN/ImQIZC+Xy/fv3xdCvPTSS/1+PwzjT359M5PJcM47nc7s7KxSam5u7vHjx7rDWSgU3n777SdPnly6dMl13StXrlQPDxNJ5+DgwDCM5eXlWq3mOFY2m7VtEwBs287mMtlchnNuWZbneUEULi4v7ezs6Lgxl8tlclnGmCYpD6Lw4OhwfX395uefuclE5aRaLBUGfW/gexAJLiUhRAJathHFQqDej4lmTyAEKGPMNMcWOF5y6oXyzOQaGNvk5AuHFjWRWOrXak4mNS40nPZjz2/6XxWUjs37RSMHAAXjEsazqgwAcM4ZstGAMpdINOlTs983DGpajDFKUAFIPbyOgERJUASVGDYJxDODenbgyKehVCA1Rb3USSCllGl6rSEGcOS+pQINHRzJzY1F5ya3txfd4Fc6xsm/djqdfr8fhmEURb1er9FoaAEJN5VExN7Aq9fr1WrtpF47qdfqzcYHH334+Ze3Or32zPzcysqSbdtRFCAqKWUqlZqdnV1aWtLdtoODg2QyKaXM5/MahmLbNgDxfZ8xdv/ewzCIEbFUKnU6HUSqaQVbrZZpmrMz8/l8XodSjJnJZLpSqVQqFe1ker2Onk6o108AwLLM+YW5a9eura6uFot5w6CVylGn0/n617++sbHxwx/+EBGbzWYymVxdXb148eLu7m6tXi0Wi6lUamdn51e/+tXu7vb58+ebzebU1NTa2prulywuLq6srGxsbBwcHPi+3+v1Dg8PLcuKoqjZbO7t7ywuLtq2/fTpUyllr9dJp9NvvfWWlFKL/i4uLmoqxGazGUXRtWvXHj16hIjb29ubm5s//OEPdXr59OnTSqXy/vvva2EcXRZ+4403MpkMM5kQPAxDPxj4vs85Z0z/xxWgUjDKl7T+5BjeGMdRFIVhGARBEATy+UM935Z48RhXccZeC34zGelXL6pRxngq/nrxh8lfXzRLbdrDduXzua5SAkymGOGAoRBBzP0w6Id+3/O4FLEUkeCxkMymBudccK6egV+RmEaslGb4QBnHSGzLkBLCMFaxP747hFJCEVDEikuUwEBRNXkHAcAQz+BpMOEPx7cbJrAIiCgkR4YoFZexjuwpoFRRIulQmwZy4McqwVxUYMcsibbXEUEgTNOMlWh7bde1E4lEPOhGKjYs48mTJ5mT1PnzZw3DCCM/DMOZ2ZVmo7u+vl6pVMIgRqBnzpy5devW1NSURqjk8knAeGY2XywWc7lc9fiwPJ358MMP9/cOX3rlZUpZo94ybbNcLinEhw8fUmok0qlOe+A6yZOTWtBH1yycnFQ6DS9fyLz26svbO1t37ny5Z9FsNjtTSGdT5pnF6ffee69VOajVaiDS5XIxm17kkflf/70/+vjjD4rFYr1e29i4T4h68OAeALEt9969hzPTc/VaY25u4dqVyx988MH8N75RP6lm06mdp1tn19cSjl0oFOon1YO93Rs3brRarU6r2Ww233n769lsNplMzs/OJBNuu9n68IP333777f29vVKpaDCadBN/9Vf/ayaTefTwQaFQuHDx2o2Xrjx48ODv/uiHtVrt//F//79ZltXvtdbX1w8Odvr9fiaT+fLW/ptvvnn50rlf/OIX8aA/l0/P5jI3f30LgU3PLpbKM082d6amptt9r+cHiGgaVHAZiyAOQwPpcysflCKodIRFkIMiqCdkiZJSSGmBBCWEiJWUusmhzdh1Xc0nQ4Y+UIESinNAXajQ4ocjrkAc6q2hUkNaaQCt/kkQlZRIiA7pYcipARp6CQDyuYKlIhyJ5iZVFCUSJTVxBDVYpFSoFJiOZFY0MoG5uK2UORQ+UlRKoutFQUyHjlQR9uIeA8/3EqSUOgETQoRhyMgQdjR2gJNbxW9ycS9+nzTC8QamtPa30uwWMMpch7NWpmnapouIFInv+/v7/Xa7baOpQZK+P8jlcoi00WgwgxSLRcZYOp1OJNxeb2CarN/v375z6wc/nL5z545pmk+fPg2C4M0339RYMMMwFhcX7969q0d46/U6Iu7s7IT+IAiiQqEwP7dYrdfq9cbc7EKxXP71p5+5yWQcx5zLM7PnKDEGg8HOzm4ul6vVahpLoMmplpaWSqVCPp+7ffv2wcHBzZs3y+WpK1eubW5uIuL6mTOVylEun3306FGxlB8MButnlj/44INer5tMJnO5/Orq+p3b9wBgd2+bUfPu3buGYVy9enV7e/vs2bMbGxv9fj+dTj969CibzepPsby8XKlUzp8/3+l0CoXCxsaGUuqb3/zmn/3Z/1IulxljzWbz+vXr1Wrl7t27lNJMJnflyqVCoRDH8dbWlg527t27Z1nWt771rVqt1u/3Hcf53d/93VarpVPoSqXy13/915zz6WLh448/Nk17dXXVYPaDjU0u4Oq1yzv7x6DLLZQKRKEkgCTEGOsNvuh/vvIYgiKV0msDR7yDp+Bm4zPIySI8PkOujQc1EBEmIjIppSbBUBNwGTlRySHquVKqhptP4nX0NZjGs/M/l7gqPUfCQLNFSQVKKoWUapsngMAmDeZUsD52WZMoWzGUCnzWToHny1zwvENXLzyifzjVkx1f9yj6/4pggDGmiw2SiyAIBr1Ou90uZEoLCwsS214Y5Sjxo7DTH8zPz9Zq1bnFhWvXrlqW8Ytf/mxjY2N+fr5QmvrTP/3TTqejad5v377d6/WOj48Hg0G3202lUmEYFgoFvQqnpqZardbc3EKlUuGcX7l8bWFhAQDjOK5Wq91ud2pmxrbcdrsbBAGPvTiOGaNra2vVanVubu74+HB1dfXOnTvFUl6IeGdnWzcAq9WTpaVl23ajKBr0/bt3bwdBUCjmX331VR1nci6FEOfPn9/f32+1WicnJ2EYplLJdruDiIuLi0dHR1NTU7OzszpPS6fTH3zwgeu6UkqNQygUCr1eL5vNRlF0++6dUqn0+PHjge8hZe1uR0r55ptvbm9vOY6TzmYQ0XVdpKxyUj04OLhw/qpuAuVyOb271et1TUizv79///5913VfffVVPZUPABcuXEilUo1G68njbduKzp5bz+aKBwcHQgBoqRWKCMi5Ekqh4AoovGA8z+zheYtSE90LeH4A6tSSm1hvEysKQBdG1W/OgHR/W02IW0yWiPRBX3ihmoiTx4985fUMWd50tUgB6tEjbZCjy2OT55WjyS4cVWxhDB4f9QYHcXzKnE6Z/viejj3eixc3ebyQLo7pZ8a3FWDEpazTibECrmEYvX4/jKJkKhMLPvCDMPQJpaZt+WF0+869k1rt+kvXCsVp09rlQp05e6ZR76VSmZ/85GdKqXw+L4S6ceOVTqfz/vvvV6s13w8tK6CU9vve1tb2yUn96uXLiUTi1he3Dw8P3/7GNwihW5vbJqHlcrlQKBweHBsGPTk5AQDfC7UQfCqVIgQWFxd3d3c3Nzc3t2SxmM/n89///vefPNlUCsIw3tzcLORLCTcVeCSby8zOTi8uLr7/q59HUSBk/Morr6yvr//4xz8OguDBgwe5bEEIUSwWNAHy7OzsnTt3vvvd7yYSienpaULIX/3VX/X7ng7q6vX65uamnsbqdrsEmWMnms3mzZs3v/3tb29sbHQ6rSiKpqZmFhfnnz59Wq/Xa7WaZVmpVObq1WK3MwCAZrN58eJFpZTOD7WAx+HhYT6fTyaTzWaz0WjMzMwUi8VardZotKSAbDarUbupNC8W8/VmN5ZCciEAFKEAmmRTmuw5rOKpZTPOdMZP0FTOkwtJP2EsAwzPV93hq4zhK+1k0gGMf3j2oEabvGDpGu817pCPsQdj2t9RZ214fjb6IoAKlVA4HPDi2o6Ifs5wExpbnd4ShBBDwPgEu5GcoCHAiSLyqf3gOU/4G27KZA49aYSaoQilAgCCY8eI+gPjCNNgmqZB0bKs0JPNZtt1XdOwm80moZBKJaqV2ptvfC2bS1cqR416k3NeqzWq1dr8/GIqlXrppZeEEAcHB+vr6x9//LHmz/3BD35Qq9VgNFWsFSBefvnlzz777OqV6+vr6/1+/6OPPmo22pTSkpsoFoue5+3sPj2zfk4Auq7baGwLrvr9/vz8/MbGg5dffun/9T//29dee6Xba7/zzts///nPDcOcm5uLY+G6rmMnXDcZBEFD9DmPZmZm7t2/c/Xq1Tt3vkylUoVC7uDgYGpqilJ6eHgMANVKLZ8vFosFXSy5d+/erVu3zp07Rynd29u7cuWKHmLK5/PdbvfBgwfLy8vVarVSqWTTmVar9d3vfL9YLO7vHbbb7W9969ubm48//fRTjYY/f/68RsN1u93Dw0PTcPSM0t7enpaIOTk5oZRWq9U4jsvlsmVZ6XRaA3empqbu37svpVxdWc/ni/Vao/pkM5VKlaZmB37ox5HkoQCiKANAJTiXihFj0ga+cp1M/jze/cdQfr0etBGOPRKMYyiYXJajX/Grl+I4xD3lCSbOdvqQExeMiFrQdtJXP+9RlEkoI9QgI/MhIKUCpCPnotf5RAg6WVNxHEerz47xrPrQd2TS6sbH5B2c9IST8IgXb+5kADzeDsY3YgzNIXq613G0gK5pmvofk85m+t6g1qh3ev0gCgk1FJBqrSaUsp3E9s7ehx/9mhrmN7/13StXX1KAX//612u12q1bt/7sz/7M9/1Lly4RQjKZzPHx8e3bt3d3d3V0qgd55+bmFheWEXF9ff3ixUue55mmqQX9giDo9XqpVGpubs4w6GAwUEoBylar1e22S6XSyclJKpWyLOvq1av5fLHf77/77rudTi8MQ9dJvv32O9lsFgA5jwBgd3f7s88+syzrtddec103CAK9RyDi/Py8aZqFYo4QyOUyL7/8cqfTeeWVV05OTnZ3dzOZjBaEymaza2trZ86c0ZIy2k6azeba2roQsl5vcM63t7fzuWKz2fzlL3+1tnbGMIxGo7W3d3B0dNTtdmemZ+dm5xcWFhBxdnZ2b2/v+Pi4VCodHw+zOwB49OiRhgecO3euVqvt7u7Ozs6+8fpbKysrJycnnj+4fv3q6ury1uYTQiUjoNkBFQiQHAAYQfVVx3jRv7hOxj5AE3xMgkX/i44X7fxFy3nRGvGFr7FmBk7AueI4PmXh44/Dhl/IFBpAGICBaCA4Bht/Md2PmrQrbRLjeeexkxy+8cTN0vHhqTsIL2xyk1c2dqGT/nPcGH1WLAVERErJsBdCIIoiOaLGQAKEEFRCKRVxbrtur9fzPM+yDWoayOjUzNztu3cLpVJ5ZvbguPL+Bx8xxpLJJDWsmXLfdd1yuZzNZm/evGnbtu/7nudls1kNwjRNM5PJCCFM02y326Zp3b17T0q5sLBAkAkVN5vt3d3dleU1LsXq6ioXUa/Xq9Vqi4uLcRzXq82Dg4OZmanNrce6BWKY9MmTJ9/73u98+OGH9Xq9WqlFobx69drq6hqP1Te/8e2NjQ0/GDx+/PiXv/zlxYvnw8jP5Qqu687Pz3/00UeImMsVNIupkPHZMxfff/993Vd4+PDh3/k7f8dxnI8++ujoqFoqlXRLXZdegiBwXffWl3fOnF1rNBrbu7vNdjvi/O79e7NzCzdeflkIsbTsD7xeIplutNqLyytCwe72juM4rVarUCgAQLPZTCQS+Xw+iiLDMObm5nZ2drRKlGEYlmWls/njkyoI6fuDXK6QTCVs28zlU34QIBGubVEpg1hyyRllhmEE8VdLfL74oH5EN4S0M8DRJBSMREVfLOw9t/YAlJI4JM6dOO0Ly1WdjkVPH2R8VvLc/PEp//niIyPrHY24j/i/2bAYiwAw7OZNOmV9oiiKoijSRF3jCtWkacnRyMmk7zp1nDLLU99PeT/9LuPLOAVP1VA1fUlKKS25nkwm2+22TlSklJpVxTTt69evFwqlarVaO6kzaliWk83mE4lUpXLyk5/8NIpiRPKDH/xwdnYum81dvnzl+LiSSCTn5ubjmHue3+32Go0mpaxeb3S7XcZYv+8BkLNnzyYSCcMwfvSjH+Xzec/zhBCPHj3y/QGAvHz5crlcnpoqLS0t1ev1lZWVZrN59uzZTrs3GAz29/c9z9vbPbAsp9vtPXm8NT+3KCUsLs2Hkf/aa6/atlUqlW7fvp3LFtbX1zmXlA7p1bLZdLNZZ4w0m/VHjx5p73ThwgXHccrlsl6pc3PTFy5cuHLlyhtvvPH666+fnJzoIZ1Go1GvNU+q9adbO/Nzi8VikVJ27tz5RqO1tbVtGIZtudev3Ui4qXQqe3h4eHJy8s4775imefbsWT1xn0ql9Kz95uZmuVxeXl7Wqvdnz54lhOzs7Ekp55cW19fX0+lkHIQxj2zb9LwuKuXYLGFbjCBIwRBcyz71r//KZTO5oPUY93j/1UBlc3SM/dL4tF/Zb/zKWEwfQ8D3ZIo4+pmoZ184+hpbhLbb8fqc9OfPfQSJ4y+QGsk++SVRSWaapu/7ajTnPx6cxVH2NeYg1NOAMLIQGHk2bTnPgDzPQ++EeDa4NLYuHE1djKec4NmMswAYQsBjKaSUQBkwMjMz47puIuHGcez1B0EQMAKU0kwms7+/77jWxUvn9e44GPT+5m/+5uWXX3769KlSSvfNs7lMEASlUulw58nW1lapVELEw8ND/T8GgA8++KBYLJbL5b29PQ0Z09JL8zPz3W5fi6483txcXz9bKpW2trZSyUy5XE6nU7du3fpv/tv/9vbt241GLY5DnSvq0cdz587NzMycO3fu019/XDk+mZmesyynWj15tLFhGu7Pf/7LVCrzs5/99Fvf+ub9+/f10Mo//sf/eGNj4xc/f//MmTONRmN9fX15eTmKwlQqyRgtFPL9nqeFEwuFwvT09L/+1/+6VCqFYfjtb3/76tWrpmnu7OxwzqenpweDAec8m82bpu374blz5xBVoVBAoIVCARFN06pWK2trZ/7Nv/njH/3o93d3d/O54ltvvKlXtsb3HR0d2bady+WazWY+n1dK5XK5Xq8npczn87pO+9lnn+3s7OXz2Watvrq6+uTJo0KhBEpkc8lYkUazA0oggSgMCepxgGfH2CrIV024A4DeWOM4HnfFJt3AZGFmHEaN0xwBI5AnwTgWw/MrpZ5TCFZKKQGSjOqd+i10FqfhKEopzrmIYyEEY0yHh8P0bCRPgqPGnhztA8MPJUH7OTkxi6yxCmN7GcJfTkENcKI6qibKxFJK9lUjHpPbzH/mPqeeP8aP654kQ/0uEoa1Ncjn845lE/Ksk6GUjON44IUntcra2oplGZVKZWpqampqtdVqbWw80DrP8/PzlUrl448+sSzr1VdfzWbylNJGvTWw/bnZBV3Hf3B/QyNUHMdZXzu7uLh47949wzDeevPtJ48egSJrq2ds237waKNcLqdSqV/+6v1ioXzn3t2rV6/atrWzu/3Sjevdbvfk5CRUMaX4ta99Lebh3/zNu3fv3p2ZmWk22oyxIIja7a5SYNvu/Pxit9u1TDtbSObzWc/r/+Ef/uHNmze1AMbly5c554lE4o033vj0009937vx8vXt7e3dve1SYVGb0MnJybVr13K53K1bt2CUY+uuhud5W1tbOsw+c/bq5tOdfLEc8nh9fZ0QEu7u2G7y3XffNQx648YNapjf+u53ugNPSvnw8ZP1tZUHDx7k8/lsNqttT0fpxWKx3W7rjSmVSsVxPDMzg4g7Wwf5fP7b3/52pXKUz6YZY3MzU/VmM5dPu7Y5CCPDoGnimpbwgphSRuG5mGu8GH7LOhkvPzI6xvHaGPY5PmccBuPtHhFBzwSLUywyvy2rVEppV0MAKRK94GE4y6DEb5hAOGU+zz4doUgZUkaI0iT/qKQatk+GA5Zs8l5IKfWIkM70JvFockSTTC3rK2/ZOFA+ZY2TofbIeJ79dbKyNLxmQhhjhpYnlYIx5tqOY5saKsBRaX4RwzB4FARBADJKJSx/0NvfHXDOk+7S/Ow0gZf29/efPn363t/8TalUKpVKxXy+VCqKOOr3+6urq+12W5dVNA5zfn7+5s2b58+fz+Vyh4eHug0gpXz48GEwCHQvpFQqzfa6iUSi3++7rru4uLiytlqr1XK53Pz8fL1en5qaqp58rGKyuDjf7/ePjg/OnDkDAA8fPkwmk41Go9/3qtWTTDoHALOzs4LLc+cu9P2D48rht7/zzQ8++GBra2t6ejqfz6+urk5PT3t+/8MPfzU9PRVG/k9/+tMw9PU0vRBicXFRx8nz8/OffvppoVDIZDKWZWkumTiOe70eYyyKoq2tp3qJbj/dXVs9I6Sam1vY3Hw6MzMTxzEi1fLAukV54fzFo6Oj/f39tbW1hw8fdjodwzAuXbokhDg6OiqXy8lkcmlpSatWcc6DIFg/e6byQfWLL744ONjLpBILC3OWZXARmaYlZRz6nuAhY44BjEDEowCo+1sM4DdZxbgwcSp70vHXZPc8iEJ8VnQApUBKKZQEeAYsUc/X9odvgUBG2kPjtUq1nUulkAgcmc5EW04+G8Ianhafb9dJQKlQAVGgEToEQJCh5x9eBhundkOfO3K4zyEMJmb8XizIjt8PXnCD6oUcVz0fb0we41CWDJG4ilKqyaRTSdfzPIpEh6AmM6SUmn/FMoyz6+uVSqXTap07d67X6fz8pz/N5/MEYGVpKZVImKYphOh3u65t8yiuVCpLS0tBEBweHvZ6vXa7nUwmE4mEZsUFgLm5Oa2sVK/Xu91u0knrUqdp2jMzc73eYGtr69rVl1rtxrVz1wmB3d3dpaWFv/7rv06lEufOnSHSiONYVw7X19dM06zX60EQJBIpzwsdOxEEkWFYJ9Xa3t7BwsJSp3/cbLa++c1vXrx4MZPOPX78+Oiocu/eg1/84hf1ev2b33pHiOjjTz58++23Egnn888/55x/8cUXCwsLpVLp4cOHUkqtRm7bdrfbdV1XZ0qFQqHZbBJC9vb2Ll26tLu7pwmCK5XKpUuXfn3zk9XVVcdxoigol6ebzSYAefDgASHk0427c3NzQRAopXZ3dy9fvryxsaGVNmq12oMHD2ZnZx8+fPj9738/juPl5WXHzhYKDzudzo0bN0BGs7Mz9+/f9/v9QtkRUiqQBCSAVFzwKJZSCniW9r8Yjr544ETlHF844Pn4EwA0nGO8BE/5i7EbHL+vtr1T61MpUEJqL6qE1COykxY4sTuM3+u0Yesj4AqFJuAf5pmAyBUwJMPWIoFnQbZ+5TDQff5Ek8Y2tsYXTevU41/5tFPH+MHxD5pURtdgdBZqmqZt247jaCIQz/P6/b4u0Pm+X57KWzaN4oAyTKZcypAZZHZuOoz8bq+dL2QvXb6wtr6iGV++9rWv5fNFIVSj0QrD2LKcIIhqtUa/77322hvpdDaORbFYTqeznMswjKenZ5PJZCqV0ngUSmkYhkqpZDJpGMbNmzeXl5ejKKpWq/Pz8w8ePBhDxtyEvbKy8vjx4yiKLl26JIRqNBq+75fLZdM0C/ni4eFxFPFWq+N53ve//733338/l8sVi8WpqSlE/Pf//t8rhf/oH/2jdrudy+VWV1d/8YufdXvtVrvh+z4hRLfjpZSa996yLMbYzs4OY0ynT4VCodVqcc6npmc9P2y0mmfOnbUcJ4iiIAovXb5anppZWV3N5Yul8jRSEvH40ZPNh48eLyws5HK5vb29jY0NAIii6NGjR9vb267rnjt37urVq6+++uq5c+cIIb/+9a9/8pOfvPvuu4OBXyqV5ufn0+l0r9fjIpqaLuTz2WIxXy7k8/lswnUoAYpK9zn+i45TIejE6n+OPWycECYSCcuyJlf1ZEB3alkO/Qo++1VNNOqEECLmIuZxHCsu9JA6PB+OTlZ9XjQWpVSsVCRlJGWsMAaIAWIFHNCPIz/mARd+HA8TQv1dC2jhaD5t8spwVFA59fnHzzm1sb14/KY/jT/P6ANoVCtoPiXHcXR5Wn/aMAzDMGSEOo6TSqUcxzEYebr1hKBaXJiJAt80zXNn1l3b+vt/77+6efPm4eHxg3v3TdPMpJMn1eOPPvwV57zf7wNAuVyenp7W1q5r+rlc7vj4+NNPPzVNc2VlJZVKdbtd27Bbrdbq6qqUst3rJpPJGzduVE9OVldX792/PxgMrl+//vDhw0KhQCn95JNPrl58SYMwdaw7NTUlpcxkMnt7e3HMwzDsdvsGs5XCs2fPz88tommvri1fODz/2We/tm334sWLnEs9S6Vjvw8/fH/gddbW1iqVytRUOQzDa9eu3b17lzF25syZ27dvdzqdpaWlsevzfV9XTXSTybCsZrM5VZ7xvXBzc3NhYaFeb6ytrepNwfcHu7u7nudVqkda62J/f7/b7RaLRdd1L1y4sLOzQyl1XVeTcGvU6NzcnBBienradV1GUlPTpUTCeXD/Do+8J5sbQsSU0pn5OUBDIUFq9vyo2/MAQE/eTPqx3742xstj8vt4Bb7YV6OUGsYzODQhiECklEoNSRO1J1QTWdJz16AAAKieldeoaRkLQDJUiFXk+e6alBJHKDmNByCEjONSbTgcUCAROOTdp1rpGRA1EgglIn22M403DP0e+qInyQj0E+REr+KU/ZyyzNGHe26E99RrJ1+uf9VhMENiWVYi4TqOg4hRFPX7fQIoRzVVy7JSCYcxdrC9oThfWFiYmZkdDAaDQa/fbfd6vaODg3q9XigU2s16IpO6du3Kndv32o1md9DX6RMhpN/v60omIaTZbPZ6vWazmUqlCCG9Xk/nfsCRxyKdylSqx0opz/NKpdJxpXJ8fJxKpZ48eTI1NdVut4WIL148/2d/9mfn1i5+7Wtfu/Xl5wCwtLSUyWQ++OCDMAxnZ+fiOD6pNqWUBwcH+Xz5ymWn3+9fubH8F3/xFz/43d978mQznc5KAfv7+8vLy4lE4l/9q3917vyZP/zDP3zvJ3/9y1/+fHllYXd3N2HzYrE4Oztbq9XOnDkTBEEikXBdV0fUUkrHcXQSqzPVdqejlEqn03t7e6Zprq+vSykbjeadO3cMw8hkMvfu3UPEoWnxMJVK5XI5SulgMLh//363233jjTeklBqFI6Ws1+uZTGZ7e1srGTbr/rXrVwyDVo4Pzp9dTafTntdNJpODbk8h9cNYSDAINShhBAlD+I3m9tWHnJizgVEFcrxyyAjUNV70WqJchy3EYKCQc87HpdEJOxxb7+kFqxQqoBS1aJLGUOKzOtBzNR6p5IuLf/KcQgGXigxh61Iq1KB0ZtDhmD6oZznh+NBp4fgqJ9uAOCJO/crj1O7yWxzjZCBx6i4MadIp0/5ZKRUEgS+57/uu7WiSC4pkvE3oFdnpdJ4+3dIlmHQ6c/bs2XfffXdz87FlXZmZmbly5cqFCxe2t7cNk85a8zMzM61WS8d1k9ZYKBSy2ezc3JxSynXdw8NDSqnNHC2yeXh4OLsw/+DBg8FgkEqn/WCQTCYXFhY++eSTXLGQyaRN0zxz5sze3p6ueUxPTx8c7CPi9PR0s9nc3d1LJtKtVqtQKJ5UG8lk0jTNRqMRx5lcLieEWF1dff/9Dy5euCyl3Nvb63a78/OzhJA//dM/PXtu9dvf+ea/+3f//vz55UEX9/b2Xn/99YODg263a5rmG2+8kU6nB4MBpbTX62mfHARBPp8/PDx0nIwQsW4wZLPZyvHJzOyUUuratWtCCN/3AeD4+FgjgZLJZKO6c/HixV6vl0gkbt26NT8/H8fxu+++WygUVlZWjo+Pdb6wurraarVeffXVW59vrK+vP3nyaHV11XWdTCbTatVeffXV+/fvS2BRLNFwKXUsy7JtGykTg/+ynPDUHg0TIFIcUdpOeDmIo1Abp27fgcLxEMZvWrdf8Y4KCCFKyJEVAn3GUPwcEdszp/obCq4xl5RLJFLz0BAplFJCxGGsADRtKZA46pqGUDII/R4jYJsmSpQxyBhRsnymOF2aTdgJRggBCDxvMpaQE5AimLDYF2ue8LzrG0cU8ELB1zRchqYShAi0wHTRMAQQXywWpmgYBe1O2rbnp8vFbFozRKLJ0GSl2ekLVy+jyRLZtBcHH938JAbxO7//g53Dnb3jvYPqwa8++dXm7maz1/zy9t16o7W5tX3/wUat3tx49EQBqVRr2Vzhzt37SNj9BxupdDbmkjKzUCwvr68EPPzi7peLqytuIlkslW/duUOZ1en4Cu39w3o2PwNg+z5dXr7suuVCPt/tdF575VUd7208eSxAOalkfmYqUEKY9KTbVo4ZMezwAJP28WHv0cO9Rxs7J9Xm4uKi7bBLl9cTSWY7mM44c3MzjFkEEj977/Nz6y/Vq6o4taSII9HeP6pVa+3Np7tcqtnZWd8fGCZaJpyc7B0ebnW7FX9QTyVBmBgz9Hg0CHyCiinJ+x4LedzpfvnRhwlGTg73Svn07Gzx7LmVRufkf/jf/I9WIjkIg67fNR1ycPyUWfKP/ru//0f/3d/vdRsH+7ulQjlhpB7d3dq8f9A4GlRre//n/8v/6enWxtWrl3/x/ocDjwtpv/ezjxsd7seGmSpZifxAQDsITwa9p0dHJkhDCUMJJjmTnCrBQDKQqLj+UsClivV3qeIYZAwyUkJ/xSAFAUEADMpR6QcFAUmRowol1/QWAlQkeBiGcRwjom1aJjMMpFQhSoWxIFwSLqlQTFGmdMZKlEKtdxQjDoTwQAWMhAYNLDYwSI9ClyiqJJGCKmkgmAQZpQSRIEqpxxuBS+QShSISqEI2sJNNNE4E1pXRRKuh7CY4HZquS7cu3RpP1HiCTSa1zw9bgW3b2WzWsiylRH8AnHPdwDiV48oJYYBTlga/oSOjXmhdwAuec2zP4z9ZlpVOF+YXF6QU+/v7YRzl89nLly/rJDYMw62tLc0qrcsht27d+qf/9J/W6/Uf//jH09PTL7/8MiKeWSNHR0e1Wm1qaspxnGKxWK1Ws9msUurVV1/d29t75ZVXfvKTn5w7d+7hw4cvv/zyL37+829+85uXstl33303lUpduHDx0qVLd+/dnp6a7XRae3t7qVSmenKSyWS+/PKLbDbtMJrL5bRchC5dLi8vd3s9ANREt1IBoEJE0zSTyaROIOM43tzcPHtuHQDOnDmzu7t7//79a9eucc7PnDnz8MFGoVCoVCrZbE5H5lLK69ev1+v1RqPR6bRmpsprayumxUAKyzYBIIqCQa8vZNw6juM4FlLpd9cgeCFEFITvvPNOPp///ne/92/+5H/5P/7v//nV61cR8Ve/+nBvb+fSpQuZbGp5YZEy3N3dvn/33vT0bCGff+utt9KpfLPWXFlZ2ds7uHXr1oXLF37v937v6GD/6OjozTffBICbN28uLC0KIYIgEApiSToDPwzDdDKVzxU73fA/0yP99mPSHY0fmdzNdXN/zP8yLmoMR1Wfr+hMvvwrUqqJdXsqdpsM6L7yhZHgiEjU6drSJLkhG+PRxkaoa6kanqNpiXU55CsvaNzTP5Vwj211nAScMjn5VUi3UzasxvP7hnF0dDQzXTZNc29vjzE6MzNju04chz9576fr6+ual/7C+YtBENy9cy+fz6dTmVKx/HRre3t7+7vf+d7Ozs4Hv/pwfX393t3Hq6ur3/jGN3Qb8OLFi6lUan19/csvv+z1eouLi9Vq9R/8g3/w2WefXb9+/fDwMJtNP368kc5mbMfMZrP37t31wyCdzuwf7Nqum8lkGGPZXNr3gi+//NK27aRl7u3tZbPpo6OjarWyuLK8u7u7tLzc6/UopQQUNUwA1E1ZAOh2u5ZlWZbl+/7+/r6UPAwDRNTRMiJms9l2uz09Pb2/v6+USiYTe3u7u7u7b775+v1790ql0t7eTqPRuHjxvB9wPwhMyygWi4wRg7I4jveaNSEECmnbpk4XpZRRGN145eU//uN/owd//w//u//tF5991m639/Z3pVALi/MLC0uua5+oI0Q4f/5iPltAxHarG4YRo3atVgsDXiqVlpeXj+p7m5ubUkrGOvqC8/l8s9k+f+ESECoBu4Mwijrtdjvi0rJdJM5vWuL/XxwvLv1xjDa0llEbf4yChOez0skh4MkFPF6HL3qXU+/1m4xWn4oaxvgCYMLTTK7/8TTU0AjHl6jrbHpSrtNpScUdx5m05smkEV6gMxz/iZLTO9aLHwZeMD8yum5KqUEpYUYy6Rby2TgOB4NBIuG6rouUVKvNV199tVwub25uptPplZUV7asXFhZ+/vOff+Mb31BKxXF8+/btTCbzox/96P3333/ttde63e6jR4+63W4ul7ty5Uqj0fjTP/3TpaWldrstpZyfn6eU6uwuDMN8Put5XrNZDwKv0235weCkVqvX6zMzM2EYLiwsvPfee4VSsVqtaVrOs8ur77zzTqfX/s53vnPnzu04js+cWTNMO4oiEcUAYJkMkIFS/sDrW53ATS0sLCQSiStXrrTajWw2fXh4UC6XhRC7u7v5fKFSqayvr6dSGQDodLqDwQARG41at9u1LCOfz29vbwFAs9m0HRMAeCw6nQ4iUkqTyWQqFTAkgkd67oRLLuI49AfvvvvuK6+8tre384tfvD87O/vWG28KJQkAEDY3N/fe3/5seWXJYjTm/jvvvNPr9A3DMJldOT558uTJ/s7++XMXUqnM/v5+rX0Sx4elciGRSMwOG6omYca9e/csx83k8nYiU8jmJKAfxoyZvQGH/38cL+aK8HytYZgrqmfSDCNjeL4iODH6BJMR3MjAXrTDF4+vtMahLSADRBjqdg85tAFAPA9QIafeXh+6DaU7clJKnVjrf+SYvuk3vXbypkzGrpM/fGXqOP60Y1zsuEmYz+c1IGt9fV1XBfb29gAgjuNPPvlEKZVOp//6r//6888/L5VK/X7/H/7Df/jrX//6z//8z8+ePcsY63a7U1NT3/rWtxYXF3UhsVwuSyn/+I//+MmTJ7lcDhFzuZzWCfvFL37R7/cfPXo0Pz9PKJSnismkm8vlwtBfXV2eX5jN5TKFQi6Xz5anStMzU9lsNpNJeV6/1+vU6/VPPvkklczoYk8unfnoo48OD/YAgDFmGQYhBJRUggsehZFfrVaPjo7u3r27v79v23a5XC4Wi4ZhrK+v625kv99PJBKaTK3dbm9tbeXz2Ww2u729rZTq97u1Wq3X61UqFcMwEomUlHJ7e/vevXudTs80zWI+m0g6OGIuD8MQCBq2pQemHj58+Ps/+OHS0ko2nalVT1658XIymb5w4dI3v/ntt978Wqk01Wr2OJcXL1xZWV7d3zu8f/++ZVnLy8uGYSgl5udnL125XK2dzM0urK6uPnz4qN3uXrx4eW9vz7UdKaU3CHQ7d9DrN2v14+PD32pZ/wXHi0vu1AJTEx2/UwUIMnFMwjbVuD34nEjmc0CU33Qxp2LAsc1zgTGH8VcUK/1dSDL+YmM2NCE0Y4VSapgWju3Btm3TYoSQMAzHVaxJKC28MA/2lRsVTDhAOTG5rx8Z/zBErhnGeOaQAnY6nSiKbNuM45jz2PM8REylUp1OB0aVaz1wqAljPvrooxs3bnQ6nTt37gwGg7W1tV//+teXL1++/fhhtVrVyz2VSt29e1drSDiO0+/3X3nllY2Njampqd3dXT1SqJTsdNrMNC5cOLe/v28YtFgsahZ6J5FCVH/v7/3Bk82n6+trX375peu6x3tVQumnn3565eqlmZmZIAiuXLnS6/XSqYSfz/V6vTCMfS8kjmOajBFaLucZo8VisVarLizO1Wq1TCaTSqXS6XQmk6lWq4Swg/0jRKpRAaZp53I5ANjb33ntlVc1E/b9+/eXlxejKGo3WwDSdRKO42i5mGLRDf2g2+4IyQnBZDKZTqYQZNtoV4+OX331dWaxZDL55MkT23b/7b/9f77y+tc+/eTm1HQpCKLFxSXbtr+8dUdJaZpseXnZ98NarZZyU6++9koYhre/vNMJBm+99bUgin76k59PT0/nsumnT5+eO3uh3W4PPK/T9RJBSA3bNM1SqeQ4Tq0d/Bcb3Fcd4/UzubTURINuHJeOVuZYHez5EVZAnFBHnDzGS3rSE04iV+EFz3nqQESlkWfaGU5c53PhqG7pwnAbEFKCUkgIjoY1dDNAjmnnTdMcv8Gkyx6DEiaNcLjrP98knPw+PtX4B0IoHYnj6LvDOVcKUqlUsZATIu71eoRgKpVCSnzfO7N+NpfLPX78eHt71zRtzuX+/uGNGzfm5xd3d/eiKJqfn5+bm0PEzc3NL7+883RrL5FIZLPZIAgIIcvLyycnJ4i4tLRECHn48KHWkCoUCoPBQJMapdPpYrnUbreZQROJhOXYvV4nm806iUSn0zIM4/GTjQsXLpw9t57P57kvd3d3FxYWHj16lMtl5+dnj6r+0tJSrVabKpfDIGg2WjyWtmEalEgedTodPZWjuyD1+onj2I8fP37y5Ekcx61We3FxOQxix0msrKycnNSIaUVx0GjWhBC2bV6/fv3zLz472j+IosgbBK1WK5VKZTJpPWVfrZ4QACUFKOladrFYzBeLFLHXaZuWNbMwbxhGMpn84tZtyvDi6ophGEopy7JmpucMg0khHMc1TWduZqZer3f8ZiKRWFlZQakePXr45MmWEKI4vxALhSCpYbTb7cXFxYXFZakwlUp3et2IAxqmH4Rerx/EkWe7QBP/v9je+JAThNnjR04trbEdSikBhgRlOKEdNo7G4HlfMml+MGGHAEDoc4IZpyz2xXdHYpw6J2gsJ04Y4Rj8qi9VSkCgAKB9kZ7eD4KACeI4zrh/OPl+k1c/6fr1g5pd68Xj1LWOf9Dx8ThCkFJy3eNErFarhkFX1lbn5+eUUt1+L47DDz/8UHMNEUJu3boVx7Ee+avVaqZp6r7Z+fPnf/rTn66srHiex2PQ0XW32w2C4NKlS/v7+ysrK7pDvbOzk8/n9cWsrq7atr22dqNSqUzPzrRanTNnztTrdT8IPM8HgJmZmUQioUkxwjDMZrP7+/vlcjmTyZgmk4rHcby3t5fNZm2ToVSJhFMoFASXYRhbJlOSD3qhbchisaBRL+lMslDIPX261e12k8mk53m1Wo0xc3FheTDwi8WilFJy3uv1PK+fy+UajcaZM2fW19cNQvf39+fm5rSsfL3e9DxP19l6rXav3eFhZNtmNp9xE3a9Xt8+2Hv77bf7ne7jx49TmfRrb7yue8Wm5czOL5oWe7q9mclkirlsFPJCodBstsIwHAz84+Nqt9vVknUA8pVXbqCbabdavV7HsRNB6B0dHW9tbbZarU6nC0gz+UIxn7YdHoQx+tS2nX70n2Vj/x+P8dp7MQQ75ZrGbmfkCUcrcyieeRp8MunT1Aup4FfWRH5TmIqIoVQwhJU+F9NOIvgYIcTzvCiKCKGccymBUSaEFiIdyneYpknokHVGf8fnDzVBkXjKSjXGZfyS8XNM09R4cUTUBq+U0nNWzLKEEP1+nwI6lsGF8Lo9QqBYLAoRHxwchGGwsLCg0cmpVEoLOGslCaXU7OysnrRYX19/8ODBgwcPdK1F45tff/31vb29vb29dDqt1Y7+36z9V7AkWZoeiB3l2sM9tLj65k2dpUVXy6kW0z0zmCUGoC3Apa0ZZ7kGGtb4vEvjE1/JB7yAfADNCBJY0NpADBc7GEwPMHpaVFd1VXdVV6WoyryZV8vQruURfDj3Rkbem1XTDdCtLCtuhIeHe8T5/Vff/32O42iatre3J0fRMcYSdVmr1Vqt1kcffYQxxgpRFK1SqaRp2mg2CVEODg4kVf5kMpHU19J1F0WhqiohqNlqr6wse97k6OioP0Ddbrcsy+WFXhrFEwQELaej4fXrN7M8khUUXVcRQpqmrq6uTqfTer2+v79fq9W++93vnp4Mut2FSqVy69ath08e27bZaNTW19ffeeed23dura+vO5b92WcPRqPRjevX4zgsy3IymdCSFzl99PBTXdebjdrXvvpVx3WTNIUIbVy9WnB2PB5OkygTLMjT3d39ZrNZ73bzIh6OglqtBqFgjHmet7K6dOfW7X6/f+ocS9gahFAOGWq68slnjzzP46wMw7BiGghAhLDrVhcWFoMwPu4PipKVjE+nnqabeZ4CaM0WKIQQn9fz+PlgHhVndT6ZKF0uy8ttFr7Nm9AssATnGYosziOE5BQFnxMXku/FWLlgvfK9EigyM7AZfrUontHhnGE5ZR8BPOtUZse5bNJ8bhLyGUCtmMOYz+jEhQSji6d3lHnv/J+/yY+Y2XBZUgwhApBAwDknhBiaZqqa49i+76+vr46nE8kjdnRy3Gq1To5Lw7AqFbfXW9Q0LY7jPC9939/c3BwOx3JOvNtd+NM//XPP8958800ggjzPpceo1WrtdlvKm5ZlGQSBEAIhJNlydV1njF25chUhVHEdKbV9fHSyvLqyuLjY7S6wkxNKabPZrrhOq9XinOd5fnrobW0/BoDrhvoXf/EXGxvr7U5THlzBxDRN17HdSoUxTss8Cn2AhK7rnHPf9xvN2sHBwZMnjyuVShiGb7zxxo9+9OOiKK5evQoA+qu/+qsoivM8v3nzhm3brVbrXVb+8R//seu6tmFKzhvp/23buX7dWV+/4jhOf+AnSRInicwSBYITb5pl2dif3nn11Ve+9CUhxOPHj70kspibsnKwt7m1tZWmebPZvLK6Vq3WPvroow/ZLzY2NnwvCMMwCAICgbwvk1M0nnpRFGEIsrRAAnh+mMShEGI4HBJFkwBDzIWu6wjjLMugYc8WK4RQXB4VeFYS74uRNJe3iyUGMYvRnoad8w/mSxtiDh8Gzxtvs1M9C80uAGXmPuuC2zz7FAjALBA9w9ZAeRx564EAPDNPeOEi5/2seFbI+8L1XDih+S/oaSg898YL39QMdsQ5N00TQ0iLsijLJEniOMYQYgCyLJNTFI7jMEYnk0kURYyVt27d2t/ff/DggZxk73Q6vu8Ph8NGo7G0tCQJiP7pP/2ntm2//fbb9+/fZ1TIafetra2dnZ2Dg4Pl5eWrV69+8MEHvV5PUjPFcSxTNdu2t7YeyHYIoxxBWRDGEOLBYBBFiarqiqIM+qO93QOpEb+xseG4dpZlN25cG42GnPPj4+MiyxcXexhDXdcd26q6lSwrhBBlnt24c8uyrFqtJqnya7WaBFL3+/1vfvObf//v//3RaOJUqh999DGEcGNj4+6n9z3POzg4SNN0ZWVFOoqTk5OlpaWFhYWjo6O1tbW7d+/euHHjvffeE0IstDocAsrZldUV1TIKynXbNCp2EEeKqh8eHnqBr1n6a1/5MoRwa2vrcPshgEA3UJxM//wvPr2yfvXo6KTTahdFkaeZnGnSFVKW5WQ62t/fh5Ue5wBiWDLqhVlZlkkcYozr9bpl2zhKiiyPkiRLUt1ArKTEfGqB8OmA3lOMKEZ4Zg8zM7i8PbfPfHm7sLw552cZ2fMqmTOvw8+nZ+ed3gXruPzGC0sdPC+rvOAJ5UZm38j80Wfl3HPEC4Twojk99/MuHOfyns+1z/naFEIInPvGLMviOFYw1jCxLGNpceXJk83l1RVVNQ4ODhYWFh4+/HRxYcEwjFqtFgSB5GKwbfvmzZuDwWAwGKytrb311lue50nZsKIoIMBJkty/f1+mi2tra1mW3b179+rVq+PxWCIwW62WEKIoiv39/V5vgXOua4bS0LtdRVV1RPBoOKnYrlWpSfmKvf2Dk5MTQghCOAiChd7SyemRhI8rilKU2UsvvUAIYZRqKtE0rV51OAOU8qpTURRla2vrtddeK8vytH/81ltvZln6l3/5l9evX8cY37hxI0k+efz4MSHktddeOzo6RhhoulJvLIeRT2lxdHSAEMAAGoZTFFTXzNFwoqlGmuSKommaBqnQNK0UXFGUJEnCNIGahi3DS6KNhYXbneYnd+/9+Mc/Pjw8tCzLsqyuCXu9HsbKZBx88N57mqbs7x/bpjWdTvI0H4/H4/HY1HRCUBQHeZ6TRCnL0jL1sqQInBGOSAxDrVZDiIwmXp7njDEExXysePbg0vqR5Amz4sLnGeEXOA/wjCeYfcLMxp6prEomh1kVcMYqKF3ObGh4Zp/oeRYl0zTwrNWdfeoZH6l8aW79CwbOWaDIvOVcvs4zjbdzpOnsOufd8bzhzYfFZ7udv2vey4NnXeK8Hfq+TxBCACoYywl6TdNs3VhbW1MVLKPWPM89z2t3O91u9+S4jxCCALtOLYqiMIgDP1JVtdFo6Vr85PF2p937ype/9uDBg3/5L/6Vruv379//2te+Jif3fv7zn3uel+e5pJp2XVf26Hzfz/O82WwuLS0F/jjwgzQryrJ0nGoQRIqqcg5s2y5ZGQZRpVLpdDq1WmM0Gu3v7xcpsyxreXkZQlGvVuI4rNdcSUiRxBFC2K1YhqZpmlakpWmaaZpKNc80TSV+TRaiNU2TCsHdbvff/k9/+Lu/+7+Qx3/ppZem06kUeFnsLchwenByypjY2dl57ZVX33333TfffPPg4OC1V1/VNO360npW5MeDfqtZLwColPkoDIfeJCny/8f/+C8++uTj0XgCCe52u9evbrz55pu3LF9VdM8LojBVCVpeXt16stuotW3LGY9GxyeIUsoIU1XiVKqoCrjRyLJEVRSaF3mWJElGCHIBCMOwYruSpFRAmGclVpU0zeeNRC4jcY4gma/+P9dj/OpGOGchswDtqRz3fC4373bAnIu+YBczI7wccF5Y/5f8zVNqtmdf4s8Y4YVrkKf0bMQIwbNgnwuObv7GcNlhXth/fof5T5eXTQWFEKqKqitEahLKud6yLHe2nywtLem6PpmMAQD3799/443XmvXWZDIZjUb1er3dbkMIT09PkyT58MMPV1dXGWPvv//+4eGhZIw3DEOGrJ7nLS4uvvjii91u9+TkZDqdGoaRZdnp6Wmj0eh0OlJGoixLjBXHqVbrNc65ZVZURbcqtqKoSZLkZcmY8LxA1bVuZ2FpcWVxYfn48IQQ8vrrr1cq1mcP7g4GQNeUzz777M7tWwghS9fVBlGJYpp2HMcK0WLGGo267FIsLS1tb29//PEvFxYWqtWqYRij0bgsmZQBjeN4dXU1pzljbHFx8eTkBCFkWVaj0ag57vb2ruu6YRjWarXT01PTNPf3969cuTIdDpGq5ll2cnTsZXGOoJ/GkyT59NHmz+9+nJel3aojhLws/WznSXtlcblTtFqt3kJzPPJqdfvRo89sq1qvVzudnm1ZEMKVlRVT04sik1FDobdCP+CcJklURCUNSwyFrmrNVl2CrvI0lWJehmkWRRFfWNyXVshs4aA57unL2+flihcynVlO+FxnMx9tzm8SYQuetUl5evjpeV5c4fNWMNtHiAu293TlP/24p+f3bKJ7vuvFZy4ccXau8833+X3QXKg9u/LLXnT2vGmaCsYqUVSMIIRSQKtUy93d3Sj0Fxa60+k0DMNWqzX1PYTQhx9+KE2UMSYVMw3D2NjYkL3+9fV1Xdffe++90WhUrVYxxrVabTAYfPbZZ/1+X9M0ORk8nU7zPJdxlIRxyuJNo9GIIwIAqNcaEpJ/dHQcp0me565Tk0xbnhdEcdzpdBYXFxFCuq7HcfzZZ581m/XDw8NGo1arOoqCMcaGqlmWxSjFiJimiQE0DIMnmaap/X4/z3NVI8PhsCiKer3+xhtvSNF5RdH+0T/6R3lenpycMMaBwj1vsr+/a1lWp92R+hz3Dg51XW80GlLBdzKZvPHGm48fbXba3fh02F7oOZYtiWdU16k4DtNUP4kAQablCATTggIAwjQZTidDHDSa7sLiGkKo223/hz/5s29/+3v1etVxHMAhhFBRFBUTqbmNMd6bZEmWyuEyTdMwhAqGtm23W92iKDzPi6KEcoEVYnJTURQsnh9eAjZTin+6QAEA5bnswoXtgg3Mthl88uxfMbNJNHfUi8eZDSGAZ7PQy0t3/tYw840zw7vsJwV/DvgGnLFuyx4+eD7dwOyDz1NBIQSfN5sLbvC5j794E8/mkPIxQmiuKPuU2hQAkGWZ1Ljc3t1RFPLSSy+9+vprURRI/XrGmOM4Evgix8CHw6HjOMfHx5PJRAKgZWlnf3/fsqw4jnd2diQc/MqVK/J2GEVRURSS4VvTtDAMi6JYX1sLgkDq/pUlHY1G/eFgNJxomlYwLnF8aZYdHBy9++7PptPp22+/vbK0vLW1hTHsdrsbG+vHRwerq6tJHDFS6rqexHFRFApK5aoFSTadTqXWYlEUq6urGxtXDg4O3n333c8++8wwzN/6rd+ZTqf7+4ebm5tLS8ubOw8BAJ1OZ2lpZTqeHB4e/vZv/3an2XrnnXfDMOy02kdHR6+99ppUtknTtNPpWIZ5kB2VBZR0VVGZH03Gx6enOS0rFTtJUwGh67pMiKP+qbFcOz4+KgvGGLx567pbrVSr1dPT09XVjdCPJENkgXAURUKISqXS00zdUzllRZ4mYZSkcZnnUIBarSbp3hBCpq5xIMqyTNMUW/aFdTC/HjjnApx5p3lzeu76ee7z8/BmaYTnK/ai/cHzouDMhcBnyzBCUhg+K5OIVMznZoh/lXU+/+CpEc5hrQlEDEIBoayccggBwgAhPLsDibNRYikezqFA880TCKDgZ3VUxsCMqRsAAIXAUPINX2AiPbs3yCuE51g+eRBTVRSsAEaDuMAYI9wouRLlotlZHftR4MWdzqpC8KA/zZJiMhm9euuaFEK6d+/e2mL7ypWrg9HwF+//1LAsADkmsN1t6abmxf76+kYURZXmchRFBTTDqV/SwND0NNuqWHYYeLwsrqytMz0DhglNff94U1XV7bt7jHPN1qGOBpOjadCvtbQXX9ugNOp224phT2N25BUP90eHB+PQ0D/2Dh6l48Od/esnfVDQ5fanL968rhtOo95u1Jw/+P/+fzauXL/9wmu+nx8eDUZhXnG1ovAcxwVAJGnAGKO0LGm++fihaentdvODD96L42R1ddXzR0QBb73y5Xffffev/+PffO1rX1tcXGSOePDxZ4ZhfPMbv/mzn/0sjuhCb/3e3c0XXnjh5q1X9vb2kKs9PHo8CYZra2uL9SZSTNNs3v20/8f//hc/+vmH/6f/y/9ZrSuNpv3w4ce9Tv10MhmewKsbNxE3LMPByNhYf2l/d7S8tE6palh1t8a4KBSFVEGe8mgcjcbTMwknQytME6iqNp0mUEWD6TCOU4EgxkgIXhZFliQYoSbKfd/XDUvR1IkXKIqqmZafxDrGDMJSACY4g4goaiF4URRVeO6XhFx/QIqoMM6FlLEFApwPCkEIgaCcPxNznZcYz9pgMoxlrGQMYIwxIjOPBM+A1tJYJLSFIygABAid2WdSzPUtz5NBcN7qkO+VL8mTgORchercp8r/YYzA2ZS+OCP2nXOvQPqgz7PsGSgWzqXU/NKE7vnRBL6kIzf/6lzofPYgTdOyYAAAwABCiFIqmSa2trbWVpYBAK1Wa2lxYWvrcVEUb7311ns/+qs0zSGEDMDxeKqbJ7Va7c23vvKnf/qndhAWZalpmh/FeZ5LtqWFpRsSylwUBYCiLMvxeDwZjS1Tp3k2GAwEK23TarVaAgJN07Ks+OUnHxFdfeVLLwdB8JWvfOXll68ppGA8UzUSpQWaJiEItGFsOhXFUaN4gLFi2laz3bJV3VKU3YP9z+5/dPvGxtpq7+2338ZIPT09dd1O1aktLa+leZ9SurK6dHx8VJwmhmHs7e1CCI+Pj2/fvv3yyy+//7OfD4fD733ve3fuvPjRRx8dHBx885vfLIoCIbS3t5ckme/7Jycn/+v/6r+u1+vD4QgAsLKyAiE8ONh3HCcryvF0ktMSYmBVTKxaJ9OIw/Lf/MH3d0cTiBjCOEziJEujKFpe6RGixnEchsnGFUfGF+NRIPmRdV1XFGU4GuV5lqSBEMAwDDWmqqpKnvwojGXPWla2JWk6hFBisBRFkQUw3/ctRm3oQiQEYAIwVVXiPOcICybkxCoXgnNelJTjc7qjp0b4nO1CKDh7EpznRzPm7NlME4QQYyxX+uX1OctF5x3j7NVfK/q7fHA4ByoAM8pDxhiEZ10Uzrn4vGu9ZDxgzoTmP/LCPs89lQvPz79F5h4SQi0Jj998801WFluPH2EMFxd6N27cODza/+u//uuX7tzgnGdpQZk4PjjojyfXrl3r9LpxmmQlZYwRTT0rPFI6HA7D+GxUEgqmEEVVVU0hBGHL1IGh27ZtaMrCwsLaympRFJSV/iS1bWvojUxTv3Pnzne+851Wx84yryhDiFG4fzTyp35SKJYFtcwbBy7mKcu5ggtIia7Vm4121TYJfPcnf/Vk67MvfelLluUcH41cN4vC0nXq2/ubvV5vd3fXMHRGxUcffdTptA8ODr797W/X63WZsrbbbUqpZVWuXbv20c/vjkajOI6vXLmysrJyejqQrI2MMamvWK83X331VUldRQj+bPdxWZRX1tfcei0p07JIh9NJtWH+wR/9a8WpqSZOioQIUK1WqWCMsSTOYiPpdpYQQoEf7e7ucobRKqKU2rad5+5gcOr7fpZHElAYx7FlWfM2INHF4/GYMQEhlBQ+CCFZfDKwIgTXDMO2TaSQOC8YKwnGqkIEQogAwAXCClYVDoRSMp7O4dwE5BBIsXg4K6UCB215mAABAABJREFUAKAA4pmexOXU6UJ2d2EFznsO+YDP6688b60+jfieV4mcP7pM5y5YFD+PB4GcJzxvTYLZSV6GqM82eGm2av5cL1z2F5zchXfNDkgwQRBDCME5dhSpqorU4+PjimV+9RtfR4L/0R/9Eef0lVdfwgqp1lqnp6cZY4ZdqVRrRFHCKBl/9khR9ThLi7xEWV7QkhDCudA0fTQamaZpmjqGkHNOCK5WqzXXMQ1DUKYouOq4Gxsb6+vraZwMh0PE0At3bvz0/en+/t5v/Rffwxg/ePiZYytE4UEa+2mqmFYyip/s7wYFvnL7BoiOB6dDVtICCrddc6uuW3O7Nfs7xnf/3f/0B59++qnr1peXNpr19uPNXyz21izLWlxcFIBVqy5jpedPFheXiqKwbEPX9U8+vgchfOWVV/K8/OCDD770pS9Vq26WpRjjoijSNL179+Nms/29731P1cje7gnnvNVqyN5XtVrd29s7PBkuLnRXr15hZer7U4FRkI4Vw4iSISKMKHo0ndqO1e22R8PTLMsYU5Mka7U6gkMIcOBHjUZbUkhJqFq9Xs+LhE6KsT9N09SwaoqipGkqqfoAAIqiyJ1loxIhlGdlHMfT6VQIsdBqckGLIs+LTGoWUM6IbmiWLRAWlJWM0TJlvGRCUEqJmCtbiFn1UK5pLteOEAKcq2DPFub8DR2eswbPIr5ZWw8h8txVOmuZyG1mwxdM47I5/K3bZaN9qk94Phr//MrvbJvhqmdZ7HlR6xnDm8Wo8FKzdXbGzzVCyhhCEAFYlqxIs0KnhJCKa7uu++DBvTgJf/Nb3/zG279xcnRYr9c1TTkdjTJK7YrjVmumZZeMjkajew8e2Lbt+wHEZ9T/mmkAWmJVWVnpyrYH4JwxppwT5CyvrGCEaJ5pisoEjOPYtu0OwYjziW9UHPP45NAwDD8KNdXQbaOk6XB6RAFeXr/yeBQ/3N5url3/vf/qH6w2lH/5//of7//iEy8Lsa1RAsbBBIN84+qV/8P/8X/4b/43v//b3/svNjY23n3no7/7u7/34MHDRs+5d+/etesbn376YDIZdbvdd955p9Np05K3l7t5VvZ6C4ZhaJqRJMnhwTE6J6e6d++ubVcIIdevX5XC4IZh9Pv9yWSiaZptO47jCCGKkmu6yTiY+FMBcrda1QIeJpOr1xe2T0ZJmgKeIa7GkZ+GQXNjw7Z1VdF9P6jX2lE0vX37NqWiUqnoui5XW6VSSbPqdDr2/XA6HX/rO6/leT6dTqXu1Tl5NC+KgpCz6VNFAXIinFJ6OjihJSeEACSIqiMEEACaplQdmyOcFwVI0rQoGC/OCFuwMrd0ZjO4s6xPyBUr7ZALgcHTkgnnF0mZnpsWzfYRz7KufF78eWG3L7bDC5Hg7LPmWyxPBd/OjQpjjOd95XO3C37sQjg6yzOFOE+EL4XOs1sLvFRoEkJwCDgXZVkKAUu7hBD6YXjz5s0sjbd2d9rNhmlbnud1Oq3JNDQtlxDk+/5w6mVZNplMsqzgII7TrNlsAoSyLEOQFDmVWk5lWQrGTNOoVqu2aRGEGCt93+92Oo7jVCs2QiiM02q92ak1/MGJPx22m/UCMkXFiqKsrVwL4lEeZrphjcfTx4NHx4NBc6HbaLf60/GdazeBSqChelE4CTyaJW3bchoLJaBxFPzDf/gP/+gPfyCY+spLX7l7916vu2w5jBCCIDEMs9PpKCrudruu69Tr9SiK2u12peJ8+OEvIYSNeivPc4nelLTi9XqtVqvdunXr4ODA87yl5YUgCCCEq6urEq1aluVgMLl9g5RlycuCgyyLPdOATU2/cXXxdDIIJoGtq4iV/aO+AtHtjQ2FTxYXV8qCIUR++tP3bKs6HEx0XbcsK8sy3/cluD8M4zTJTdOWA9O+75um2W4bsr6f56V0MpLQrSzOiCoRQpZONE0TAGiaYVZsh9WKsjRte+wHACPGBOQFFoxghIhGFY6edihmqxYCAATkQMilyyF8imgBc9DTeTuRswGSThqdb/J+8dxgbWYtF1zi5f7kvHU9d7t8/AtRLpmdEDzTSJKn+LlN0vnYev6eMSNcE+c6TfCckQ48z2vPF4TmD6UrhHMAITxjcRUwL4soicu8uPrWm6enx5988kmjVpUaRv3hKReqbdtpmg5GwyxLAAAY47X1jdFopJug1ekIITzPAwCkaWoYxmQ6NjW9Wq02m816vW4ZJoRQsHJw2lcUxTSMarWqa3qWJQUtSVlAxE1Ly/txiVAcx05ePT0ZJHlAQa4ZlbwcPHryeOSlrYU2V+CPfvLDn/3ojz755ce9WttwLAp4lIQaoF44BUxrVCpf/upbP/jBn967d29p4ZpttTgH0+l0fX398eNHlUpFVev7B9s3btwYjUae5z169Gh9/YplBZTSx5tbb7zxRqVSQRioGmk1W2tra48ePYqi6ODggHO+tbXV6/VUjTSbTV3XKaVpGt+9+7E/DlnBYi80VK0s0lH/ULXNTr0WZu4LGyuc7k29WEfqSq3RqNc7lqukiW1VVVXf2d7/7NNH6+sbhmHpuo4QCENfckxlWTYejyGEVzeu9fv98Xg8nU7b7bZTceWvXJas2WwmSTYcDqMoCsNY/sqUUsizaq2R53lWFpVKBUCc5BnR9HqrhTCCUBAEORYQAogFAZCx+UX/1A6Z4BCdiS6dCQc9uzIvL9HnesLnGszMU8086sxG5LjPcw3h+Rt/jtohAM8QoJFnEANn7Zm/3QjnW/PS0mQ6PjPCp/oWZQmeV7yakZ3K52fXTAVnXBCIsKIQQCjlSZJAATRFPTw5btVrnXZzOp1wWtTr1TDy949GWVH4vh9FIcY4iKMsy6pVx3YdkqnVajXPc7l0ZLw0HfmSQtNxbFqWcRLKNvrrb74RBeHp6WmSpq7rIoXoli0gXF1brLj2J589OJ1OOAOKouzvH968c3U0HaRlVKs2bt++UzzZe+/+I6aGt15uZ97IMAzHcQqac0HtimmamhCMENRo1LzheG1tzR9njx9v/db37uzvHXVWdM/zTNOMk5BzpqnGaf9YCKaq6pUrG6urq7u7+1/60pcG/VGa5kFw1G63R6PR1BvfNK5Pp9NutxsE/t7e/srKymAwcF13cak3Gg9URZcyiXWzWoTZwdbexnqz5bpZPIK0YHFY15U379yOvPgXB6eVhvPCyy+0ay0eRLV2y/fiTqfy859/WK3WVVW7c+eOpmkyBWWMqarCqIiimBBlZWVtc+tJURQQwmq1WqvWx+OxEAJCvLu7WxQ0DMM8z+UUtWmaQoiNK4vdbjdNcj8K3VpV07Q4zYmqYKJyCJIkHU8mXhjkeU6ZYIydnkbPLkB0vn7kWubnz3AAuLgk/ALOfRQhTznNZhV+earzOz81krkYdb6nhxCet4JfxbAvH/bCxxGpHyKtglEOISYEUPqUgu38bOTbhGTBgOcwvNkdQhI8AgAopUVRSA5fCKFyLm8ozjm85RtnsofgvMEqznuPEjQkmBAQqKqqqxpRFUzI6elpMJ1YtlGxLKKpJycnU2+cUZzmOUJIM8yyLDudjqZpZZlLrlvD0FhZtho1AECe50mSdFvdXq8n2RxHw2EQBAsLC2mafvjhh9VqtdlulWV5dHxsmrZuBJK2rNqovvTSS8d//cPxeHxTu7OyfOXRp4+RwoECNVVturWVRdYP0n6YRv6kaTu9ZjsK/bXOEuAMAaASJABzHPvDj37RqjZef/317/+//y1CzuMn206lUa06hmHJryUvEhky5Xkqv42yLI+Pj994/Uvr6+sLC0vvvvvu4kJ3aWmJcz4YDG7dvoEgQQg1m804ToSIkyTyfR8hJKsgo/HArOij40HrxmLqxXmYmETRFB2rRrVi+Rn71htfXm2shF7UMCvXF5fWlpZZcHLr1p1/+S/+1Q/++D/8t//tP3r/Z7/43/2jf7yzs3d6etputz3P++lPf7q5+bBarVYqlYcPHym6urKy8vjx448++qhRb8p46ujopFKpEAJee+01QggtOedcesvj45293YOJ743H45JRy7J008YYE1VhHCRJsriyXBSFomqHuztxHGtW+3zCG3HOqaAAACAQwgicEWmflWfmSy+zuHFmJDOhMXGu6TBbhxdMbj6aA+dB3Aw0oigYPGve8BzZMxtBlGtYsiRr+tM5+Hm3KQ3kGSNECHF+ZmZfbNyXM0B4PtQ478HRpSneCzWl2V1E7iZzCTFHbM6ogEIICFIAGGN6tVaWZY6hUuBRniVxSGmhKJKNDY/Hw6IoGo0GpXQymRiqstDpagq2TR1Cg1KKIJBAtsFwQhTkuLamaRDCPM8fP37s+76u64ZViePU8wLXdevNFlbIcDx68+byLz+5ZzmV2y/c8f3w3t0HVzZW6tVWyZM4DSM/CLMUFpkOBcjjcHiKPMLzrGraTcdxdN1QiKVqFdNgtKzXq4KJ034/itMkzSeer6qO5OGXGUueF3lecgYgxJqmHR4eUsoVRRmPx5ubT8qSdbu9Ws2RpKkff3x3fX1d1+FPf/reCy+8ILWZLMtyHFv+lJJh6fT4sa3h9W5NdetIEF6UhGITmWFcsIx13PrG166CEsReoCJsUNpcvf6z937x/e//6//+v/8ffvjDH373u9/9/vf/9T/4B/9gc3NTCLG1tbW9va2qapqk3jS4fv26bmt7e3vT6fTVV191ner7778fx3G1Wh8MBmEY+75fliWCRELVfd+nLJtf4rTkvDxjTqlWqxXLrujmydRPo1jDuNbtTtMZ0b2AEMwGnQAAQvDzsI4DIOaC1V97uxypfcGTF8xBPpCsKNLZzkA2GGNB2cU3CgEAwHMBKZGKKzPACgDPcGNf+NTZ85ftUHZp5+808t+ZdsWFs39aPj3Pd88OiyCngjN2JuWNiDymqmu0yKUvjaJ4MplgDFuthmCo3z9ZWFhYXV3lgrl2pVZzw8ifjieclnmWWpalKDhJkmA6Kcuy2V2SFfw8SygtVFWdTqcyoLIsy3acwWBw0u8TVau41bUrG15wZNiV6zduPtk7+uu/+tHXvsFevHMn8Ee9Vmc0hb7vGxC27cpyozGd+mNvnEDV0PSeW207bkVRFSBUCDSMCBA119nbPtjc3AyT2I/inYMDy2kR7GiapusqQkDPVdma4YKORqPV1XVK6d7uwd27dw3D0DTd9/04jnVdf+edd7rd7ssvv/yjH/3oysbawsLC9vZ2GIaGYQAAKC3KsvQ8bzQaxaF6uHe003KrNm7UdShIFpUCpu2FFRgkcUphznRFdet1BSFdUf7kB3/67/7dv/vOt793/96nN67farU6v/M7v/uTn/zEMAxJJhDHsaZpACBVVTnnH330Ua/Xe+WVV3q9XppkUq1xZWXt/v37um7K2xxEghAiKSFP+4Ner3fz5m27UmGMSf2PMAw9LxhmQw4BLUuMkKkZkEvWWUEpo+WZaDTA4NwBivm1PcsMf93tuUv68/aEc3rA84VJMIcJmwHCznBg50rVF6xgHqFKJAwCISS58sEZYOZp1+EMjH5++3nu6V5w+uBZ/w4+56Zy+bsQQsivWABAMFZUVVd0NIfxgxBSzhVFsSsVBGUNIL9x40ZZlnEYrK6upmk8GQ9ffvnlvb2dyWiYRqFOcKXqqArGiKcpiMPg+Pg49L0sK4Ig4Jz3+31CyPr6+mA0PDw+ct3q1atX6/W6LAiJMGh1e3a9bdm/2Nt7b2nlIPRix3QQBRZW67pBMGwYJgAgDkOFlYaw3EqlZ1VdRTchMiDUEVIAUAnOkuSzzz579OSxgCBMk+3d/cWVa+MxBgAoKqEyaOM8y7IsTwzDkFXHjY0NTTPSNOec37hxI038/f39lZWVOI6fPNms1+v379+vVCqyPWDblkQ4AAA8D1uWwbh2Mjz5+BNqqOLO7Y2KpVHBC5+O/J1mZ7lRreVpAblAEB7v7zx++Nnf/OyT27df8Lzg5ZdfXltbq1Qqm5ubpmlOJpNf/vKXMmQYDoftdrvTWTo8PIqiSMo2QQgXFhZ0Xf/ggw/+/M//8stf/nK1WpU/epFTibiIoqjV7uYFvXvvQRAERVHIQbBud2Fxcbnb7kgl8Pv37+9v7cjBTlmeKGkua6EIQelpEIZn6R886xqeL7BfI0Obt4rPC/Fmz8AzSbIzK0BzIsGzHWQ8jM5lVL44opw9JjIWvbDHPKj84pmBZ56fWSa6JKN99ufchP6FS5pPUp/WgdiZ8CKfUXcDAITwPM/UNQEYpUXFMhcXF4syCwLvxrWbjx8/rlRsRXHu3f3YsoyNjY2tx49u3bp5Yqj9/gktU29aAiA0jJ1Go0RGGIamaUrpIkppGCUQwp+9//6NG7ekWj3n/OjoyDRs16l13KoAEGLy1a//xnAUPnm08x9/8B//m//6f5V4I0MnDcvBCaAEL7huvNB1NVVjtq7ojm03NLNqW25FrzuGo6tlmjzZfPThhx8OBr5VqZWUnQ4HI8+3rEXLsjRdBUBGXDAIzgg48jxP4qxer+d5SYhyfHwchmEcjQEArVYny7LNzc1vfOMbUjxYVRUt04QQnjfVNB0AIHniJnHqTb008xUVlKzodFpmxdYs5+B0kFCj1uBJFKeRlwaT3ScPHz246zauJ3H+0ouvttvtdrs7Ho9dt7K9vb23tyeJXgEAiqK4bg1jZTr1v/vd7wIApFc8PDzc29vjnP/O7/zOkydP6vXmeccil3NPZVmGcVypVBqNZqfTjeM4CIKTo5OToxMAwEeMVSyz2Wwu9HrLi4uMsTzPx4OpDNdnEdYswQGAnYWgUAAA4Fk4+p9ohM99ad5U5J8zZwDOvZ9czFJXFyEkXRpCSApFKucTueCc2+IsX5sPR2fWMosM590X+JU9IUKIzammgfOAEz1vygM82/SfN3g+tydjLAMFgQgCgMQ5SoJzRdeazSZlBQA8TZONjStREKoK/s53vsWKcjA8NW17f28HQ9BuNfIk8X2/yFNs27pmUwZs25aaoWVZTjw/L4swiJeWVlzXHQyGp4PhK6+8IrOav/rh33z7a7c13QJQuXLl6mtvvLGzs/s3f/k3q63WG6/dabWbnZp7PD6ZponQxJVuu1N1Se5CCFWEK5bdrruNaoXgktHk0aOHH3zw/vbWYyGAomgCgKyko/H45KTPGLNsEyEgBBuNRoPBwPMmkqzRsqzJxEuSxDAM3/cffrb5W7/1DSmTeuPGje3t7R/+8IdXr17t9/sLCwtydv7w8LDb7cnKDcY44rlQcQrY48ODsTd13Uqr0220F3THff/De0kWZ2kYTAaJPzQUXqtZnIPl5dVvfes7JydHw8HolVdf/tGPfvRnf/Zntm0vLi5KxNyLL77suu7x8fHS0tLW1tbp6anrujdv3lxcWMqybGdnZzLxbNuWNE2yNysl3IqimEyDOErLgkmbLPICQqyqhJe0Ua0tLi4iCIus8DyvUqmYpiFbAnL1I4TgWaGSF6WUsj2rjkIIf13zmy3d2eN5nzaPjp49ObMUmb7NFvYsHZtvQs6CVThnAjNDmD8ymbUKwFMn+0WcGQB+ruMW562LeRc3M/3nxqWz/qTE6wghOORIIQRhCDGGCCGiIEww1oiiaYpCEIZAfgWWZa2srAhKgiDoLXQajUYSRrqhtpvN05Oj8Xi40G03mw2nYhm6qhHsuq5t23/yo/d93x8MBlLp8nQwTNN0MvEsq1Kr1Uy74nneZOKNx9M0TRcWFj57+OjK1Wu64RIOGvXWV9/66kc/e+8P/+0f0sRTv/b62nrP1UzGSoghxrioMK1oAQAwQpaqVyuWjsBoND493nvn3Z/cu38/yzLbUZjgEEOiqSPfT9NUCKGqKsaQsVLX9Xq9btvm3t6e7/tJkty4ccuyrOPjk1q1ceP6rTxPfvCDH3z729++f/+u69YopQ8fPnzrra+UZVmrKUmcCSFc18nzIsuySqWiWInlWCrkoMjHUeIn+TjKjX6o206YZhwwVQGs9AXNalWnt7xYc178O3/n7wjBoii5c+fWD3/4w3/2z/7Z1atX5OylJOGv1+tCCCDg4sLS3vHj27dvb2xsJEmyvb3d7/cVRel0ekIIxrgkFqlWqzJq9X1fMy1KKaUcAESIqiqKZCSheTGdTvf39sqybDdbk8mkf3I6nU4bt+9I2S94Rj9RzhdgZgtyzop+bU/4zHL9nJee+cRz5yHmhErBeUAnzguTMiiVStlfvJFZTw88Hbf9Ih89s+bLkeeFWYp5v/rc65n5dEl6L30jA0xRNE1RESIIQIwVXVE1VRVCYCAIhgTBsiynga/pzXa7vfV417T0KIqGpyeaprVbjcXFhS9/+cu7208whnmWjkIfA1Gr1bI8yYv0zp07w+Hw8PAQYyxDi4WFBdtyHj/e2tnfM3Sz2+1eu3bj8PBQMilyDpI4U1THsuyFhQXXcrIg+MW7P/wX//z/mYWjb//m13Rbg1AYWBEIlYIbik0QxggpCCoQRb6392T7088+uffxJ/vHUaWmVlTbC0pVgbqqx3Hc7/cdxzZNU1ERpQXn3DRN1634vn/jxo0kSYIg8jwPY2yZZhzHUTR4+eWXdV3f2tr61re+s7Oz8+Uvf3mWmAGQSVJjSqksOFlV21AVHSHMmSqQgnHJYJKxNEwUXSNEFaKoONWVhdWbG8uLvWav+Zbk1b969er3v//9e/fv/t7v/V6axr7vHx4elmVpmvbR0ZFcf1mWSR1lSRgnhEjTNE1TySk8nfplWZqm2Wp2FEUJw9D3fctpIYQMTbcsC0KYxkkSZ3mSFkVR5oXquqxM5SCibdudTiejdLbSGGOUczkQe7kW+sVFh8/b5nPCv3XPy4t/vtEtzkkDAQDSJRJCePH8wsz8RggHsGSgoJADLgADgmBVanHOducASKlSABCY4z6chZSzNuCs5jOrfFqKJq0RzY1Lzk56hpvheSmTwjTPVLWgigIA4JRBCDVNM1QtjmNVVTVVPZObREoYUQATQwNxNEYItdp1AMBgNDwd9B9tOqZpW1ZF0+pYMz0vmCZFl9V7vd6Cgxa7PVPTf/GLDxlj167e0HW9iPP15ZWDJ9sKUYswnp6OKpVKw3Z2Hm7S5UVdy7JkdHxwWq/X7Yp27YXrQe7/8pe//ItPHh/k6ObNm6urq7WaW9E0AIBmar7vjz0vjuPJZLK7u/vo0aOjo5PxCHBhT8YEIUyAAuNMFaGmCH9QUzYUR7dVlTBI8yROBbd0c2mxE0UTzrlhwDyH/cGx1MfOkrRZ16sV9bd+81tlQZvV2ulBX1W05eVlb+ClWdLptGge58nUsbBgQaPQVFY6FRsiHAc+wqBXdXAFj8d9XOLXXnuz013gCDY6XdO2sEKqLTVJaCHYJDhZudLLWRIm/vb27rWrN/KS5SVvd3sYK5ubm5RSRVFuvHRlaWnp8fZ2HEUY44k3zcvC96cPHtzTVdU2rSSYHoY+hFAn5LUXbwqkaJo2Go08b6CqKqMlRqDb7Z6ennIhNBMvrV4Nw1DL1FIUmqbdqLpFUYymXpDEUIA8yxhE1UYjyQoAEBAIMAYFRAArBGOMS5Ryzjk762ALhBjjjDHBhQIBRpBxSUgtIIQIQoFKAIC4hGyBT4WMoPzvrOCPz94Lz0QEzwyFi7OJLbm7AFQAIQAFc33FWU4HzqkTz4xQ1icUReEcQC4YE5TSgrIZ3f3l+8FzrfnCHWVmb7KJP/tzZoQz1MIF2IGiqUKIPM+FEIALIURRFAmIpKbXzP7l3de2bauuHh0dlSVzXdcwTMdxbctZXl5+8OAzSdq/uLh49epV3w/39vY+/fTTrJj87u/+7te+9jVFUX7845882vzs6sb1Gzdu+L4fhuHh4SFCqFKpWJaVpiml9PT0FGN89erV5ZWlVqtFCGKsFIKtrq7+7P13/+f/+Q+rVffmzZvr66u9Xq/ZbCZ5Eoah53lh5Pu+PxqNhoNxGMZu1SFYEwIWOeMMqaqpEFKW+f7+/sbGhlutQCgAEWmaJkkWx+HW9uNrN64RQo6OjtI0dR3H87yf/OSd3/nOV4+Ojr3p9PHmzvLyqoLJytIyxsSbThVF0VUtDqNHnz0cj4fedOrYlb3jsYqJ6zjLS4sV87ptGY5tQChM0+QQLK+v6aYhMLFchwlAVIXmdGdn5+4n9waDwXg81jSj1WoRQp48eSKJsGzbFgJ2Oh0IoRwKk97A87woisIwpJQeHx93u908TeXcY9VxJddzkiRPdva73a6maZJtZDweSzwqhHA6ncqCsLxHt1qtRqNx75f3HccxbKvWakKVHA+GByen/f5ptd6QSwYJDASEAHBOOecQw/l7/SwQO/sTPNW75vCMYuK56/zX3Walo/mkUVZrwFwl5XKKByTvKDwXIcMAUsplQfnzjHB2bZcrNBf+lEfmJbvwpHww60xe+JpkxfJsCBqcpbyUcZmgz2fGckg0DoMoSgEAQoAsy05PBhAO87y8c+fOw4cPP/30U1VV19fXF3qLL730wq1bN4Lo9Ec/+pu/+Zu/efXVV7/73d/85S8/3j/YlfGbaeqqSqI4HI2HktuGMZZlYnt7GwAgAJ9Op7Wa67ru7du3d3d3S/q667rT6eT09PTo6EAS3XJRyAhfCIEQxBhbhmabZlkwTTM4gyFLqWAYMlZmWVb0anVCiKropcgF55pmaJrmeWGr1UIASZ5Vx3GSOM2zouaKvb296dRbW7tSr1fX1tbiKEuSaDSaXLt2TbLIcA50Q1VUwjmXqteQi2q1euvmnZWlrmloGiGMl7ZrCwg0w0jyQiCYJNnx6YlC1CeffRaFsVTCkqoHp6enjIk4StfW1mq1epIku7u7nhc0Gg0AAPKZN51maer7/ng8lgO+0/Ek9H1d1y3HgRBKy5TJv2maiqJIpnMhhGEYEMJKpbK2tra/vz+dTiULhvzdB4OB41Qghr7vT0Kf6EZeUsexDctOs6LkDDAmECFIgQIwzigtVXyGgBEQ8fPqBhMAzFkghwiALzK+52ZPX7zN1vB8asY5n+8HXqjQzJ4neZ7Lm5mqqgQTQgAEZcn+9r6nmKvciGdHM+bDX3xpygk8heGhWUNydk5Zll3IIQkhCJ89D88rsVEU+b6PMRZ5Zhp2pVJp1FsQQsExIaTT6QwGA0VRNjbWTdM2DMMPvKIoOOclDVZXV03TVBQ8mXgQAl3XsjzBGHc6raLIfD/M83Q8KQBAjUaDYMUPvP7gNC+ysixXV5dv374NIej1ei+9/ILv+++99+4nn3wSx3FRFOPx2DAkaz5RVdW2TckmDACaTjwhIC2BpqpZWpQlZ1SYBg6C4OTkpNVqKLpCWU5ZVhSFELDV7ERxIDFoqqrmeW6a5uLiYh4PbfuMpuX09DSKAjkemaZxWZZ5nqoagVDIFfzw4fbqxutFkUnlqVqtoavKGeaEM83QAUB5XgKMfD843D/UDYuWLMuyRqNh23a93oQQJknW6XRUVV1dWY+iSPorhEClUoEQcp4NBgPf9yEXGEDXdbvtDispFKLVarUaTc6553lREMrf2jSFJICSN744jgEARVHI+4WmaVmWycQ4SZK9vT1qFKZlIYwBgkIwVuR5nlJKEVFYyShlGBKocYIUDIWAnAMoIALwbF1SfiaGIgCSNRsOEbhU87ywdP8Ttpl6CpgLAy87KvC85JPMqjKzPWSZZC7oFGdXcXaSYmYk86Yyw6xdCC+fjnvN1XNlJHPZDQIA8iyVyoRCCEoZAEBVVYUoUpWNYIwQkhM9UIAwDF1DM00TAHJ8PJCGKimG6vW6hFD2+ydhGBJCVldXr11bj5Ix57wsS8Hh8vJip9N6/Hjr008/vXbtmqoRw9TyPFdVQikNQy/PY8epCyEURSnLMgi8MKwGQUBpIesKlUrl6tVrUm17OBw+efKk0bAoLeSQq6oSRcGc0zzPq7VKFCWMlYoK8pzFiQ8EsqzKxPM2n2xbTmVpqct4UZSZXN9pmkoM7XQ6VVVV0zTLsDEkSwsLYRD5fqgrqj8Z16uugqBVqRzu766trfU6a0WRTSaTg9290JuaGgrDsMyLwI88z/erDrPNim1qmtofDvSSCoymU09AGCUpLQGxlIWFhdFodHx8jBDpdrsY435/6LruwsJCFCabm5ue50uV76WlxbIsp0F/MhxJo1JVlVOW53mZ50tLS5qmyV/Ndd2KZQdB4Hme7H9KPlLGmCTRYoxtb283Gg3LsjjnhmGsra3FcRyGoe9NDNNUFcwgABghDIBgrMyFEKygJS05JBgCRQMAAYwhnaO3ZbMSI8RACA4RhwDIkQuAZnWOeV/yn2OH89tz1/bnbWS2CSHKomBMcPYrnYeYi4Bn+8/Z3nmvjz4jFAPnkLVg7spnm6xHS3i3YE8LvrZtE0JkYUbi7CSFScVYtJ2a67ppGpdMlnjK0cSLszQIgiiKyjKngqqKatiaU3OSbCphPRCJJI2iMFFVcv361a2t7V6vt7a2CtdhmmYnJyejcToaD6bT0LIMVSUY4zRNZemSECK5qJut+sbGxu3bt2XxsNFoKLiQM41B6CkKllzURVEYhhUGcRBE06kX+gFnBcGKpgJccZIkGY/H1aoNIGe80HUCOdQUJUkSAlGv3YEQjsfTlKWGphUFK4oiDP12uyvN+Ojo5OjogDGqKNgwtLLMkzTOsowQ4rpuzgVCiFMWRZE3DRACmqYACDVNS/OCcjYeTcMkLilPo5QQdZpGCBEIseu6tVotCCKJAjk5OeFnrIQwTZN+n8pSwng4Ojk5iaKo1WppiprneRyGMpLnlOU8l+m9/NVqtZofpaPRSN7UpCcsyzLLsuXlZdu2IYRSyVSqJiOEut0uRCgMgzDNDNsyTNtxKpppHh6dAAIQwBACDDnnJQIYAsEYRAgJiAQAZxSBECP4FMsFziwQzC/U/3zD488O5cFz2Pe8LXxeiEtkNogQopRleUEpx+isJ/MFnnD2eU+NbQ4jOl8LlTetmfOcvQTmzHj+G4ECzSZH5IyixLmbpgnO+xkSdM5KWhTFg/ufTcbe8vKyW62Ypk1pIb8RBAkhxDR1IbQkiZIk2tvbo5TG0WRpaUkIsbm5CSG8du1avVHd3toVggnBGo2abdu+76uqYlnm6enpdBJRSkejEUKoKIokibIsWVxcrFaraZoGfgTPZ8yWlpZu3LiRBKcAgDRNoyhQFNxoNCzb4JxHYQIhPDnpf/LxgzLLLUPTNZMQcnRYTCaTvb09zgvLNqq1CoF85E+XV3pxHIdhUHOrCCF/6uV57tiVTqPmjb0sSQenfdu2gRAYAoxEwWj/9GTQP5WORcGIFuWwP20tLAjCMMZZlkVZZGZqGJKcTmzbnvoeAEi2SYFAURzHccJoKvWqgiAaDEZlWdZqNcdxNjc3u52FdrutaVq/3/d9jxDMOR+PjvI8B5wrGOuqmkKIEDIMA4ozYighRJ7niqJUq1VN06I4l2mIrusyQpHez7IsmRkJIbIsk0KurutOvSGCpGDUtoyFxZ5pO1ESx0lmaSrXVcHPOPMpZSUrGGMM2QAADKGY08FG5zT1f+v2n+wM4RyaR65zcd5RBM/OWzwnHOVzrBtlWTImsKrIm/cXn+uFY81GM+D5SKK0bZGXYq6JL/vy0o9d8KLwfBJKsi0ihBQ8i0vPtllOyDmnRZnned1t5Hk5Gk2EEISQvEgxhpZl7e3tWbbR63Wq1epoNNjc3Nzd3T05Pb62sXp4eGjb9q1btzjncojh+o2rQeidnJwM3ju1LadSqfR6PcexAeCGbkMIJf44SZLBYLC9va0oiiT2RAjFcSwBkLpmpEm+0FsyDEMAFoY+Y8yyDEXFlFLLspxKlRBy9+7dLI8VoikqTJI4SQrJ+KbrRFE77caqZRlxEhBCVpYWj4/h0dGRRpRuqwkhFpTlacYZbdTrURRhBMbDwVlHWJRFnsqalq7rFdvWNU1wkMYhQijN4igKk6SSOTaL/CAIHj3ePD09tStuWbKJFxBCwjDyfR9AtrKysrd3kGVZq9WSSdrDhw/7/X5RFEmSMMZ0Q7NtuyiKzc1N28ASHGMZJiEEAYgQ0hSVc+77viw3OI5TrVYRQp7nmZar6zpCSGZ9cviGMWbb9mg0AgDIzv5kMpmpNRe0zPNcFnVqbqUsy7AMTF1HCGFMKGdZliUsKYq8LEuhm9IYEARM+gN4tlbROYkgBHzmDP/W+uKvuM0a3TPsqFztn2f88+ZzNkSDMcZYUEoJUXVdT7IcnutjQCQAQjPwmjSh+dbCzCCl7YE5zdT5aFs8C02QG3+Wq4afz3fJ90IAZ/cV2eqY/TAzp1pSzgT3Ap9y5rqVSqVimjpR0GA08E8CAKFbq66uX6k16v1+fzgcAoH6p8OD4qjT6aysrPR6PTkAKTFfjuPohnpyekQUJAXrb926dXp6Oh6P5WC+FPTNskwKJzmOs7S0BACQyvL1ej3L1DwvEEIQKrquQYhoyRln7Xb78eaW3G0ymTiVaq/X8zyPAwggz7IkTsIktU5PTxEW/cEJEGWWZVHgs7IQBGOECMFFUQxP+0Wec86btVql4jSqtTAMoWCAKxgyhFHBKCtyQ9V67U4axaMwKcsSIVGxdFVDELKClsfHx2EcRVFk6BNV1bOsiMIYEgwAeu3VV588eUKwevXq0vLyMmPs6OhoNBrJezQhxPO88XgsDa/eqAKaDwaD80DdPT09VVV1eXlZiuEcHBzI8uDR0ZGu68fHx9VaCwAgoaTSknVdl89IdeQ8zw3DSJLE9/12u61oGhNn925W0iSKaV5oRDk8PLQct1qtGqrKypJTBgHXVFIAoSkEIZSXFHCGMRac5UUBIeQCQM4xAOIcniV7ZE/X2yUfNV9unN/zwlvk+pwfWZQB+YVwdOYbpR3N6iNEQpllrUIatLxgeo5UEOdsbdJsZjRVs6PLE50fZbpQpLlwYV+crcI5mJuYXfmMGh0AuRRmB8mLlDGmaZpt25VKxXVdxso4jr/yla8GgV+WpTcNBIeuWysKenR0kiRZp9Nrt9vVqsMYm078LMsAAN/59ncRQoeHh7JMV6vVIISUUlUjr7z6kjRgwzAqtithmZKLOoqi0WgimyULCwtXroAiDg3DsCzDtHTZDZb3rqPDkzRNCVGbzebKykoUJsfHxwcH+7X6jeEwjCKa53Ych4PBKVFgniZFmdMipbSAgmMIIGCcgjLPu0sNqR4DABCCY0xM04AQZFm/LHPOOYCIEFUWjdrtNkU0z1PD1BACRZF5gZem6XA8BABM/Cn0/ZXFFSGYW3WajdbS0sruwQ7G+MqVK+12+/j4+Be/+AXn/MbNa81mUyqEjsfjsiw5p6ZpIgSiKP7aV74qyTgIIcvLy7quf3r/wer6Wr1ed113dXXVsqzj4+ODg4ODgwO7UpNz+icnJ7I4J8OKsizjOK7ValEUXblyRQixsLCgadrj3SdYIVLIFQCQZQUvSgLglfV13/dZVpiuDoSYjEd5UXQ6HVYWZQExxrKJDwUEQkDBMcICinOoNwMACyC44AI9Y3i/lhucT/8u1ClnBsbn5vUuLP7Z24mkfpBtN9M0MVYkH4S8Pz3d+6lhPGNCsw+bJYEzP3m5DSg+X9/juZfHnyaN8Mz45+ZEZBdRwqZMU7csAwAQx6Gsqu3u7jqO0+12KpVKFCWKolRs98r6VdfWJ5PJ4eHRZDJ1nAqldDyejsfjPM+bjXbVre/t7zx48GB7e3ttba1ardq2Va/XHz58uL29LauCnXZPktg2Go0kyYIg6HQ6d+7cUVV1PJ5mumaaZV7ygnFdV1WVqBpWFIVyBLEWRX6SFbppJ1lRUFZtNARnWR5xoRAFaZoCIAMQGKZepElRZIJTjABGQAgGOAW8APKeKITglNGCYwEBUwhQCCqKglIOASq44BwALtyKAxXshyFRkBAsTiKAOWUMEkgp7Xa7/dPB/tHhZDi5c+fF3Z2dD95/f3XjCmNMlqaKopCS4PKLGgwGx8fHQrBer9NsNsuynE6nNcc9OTyK43h1aVnqcOi6/vrrr0+nU3mfkjFnlmXVeu1r3/g6EMRxHIyxZIWr1WqLi4tS9dU0TZkHyX7PZDLJ8xxrqqYaQhEEY1YwKigUwNQNxgSkIC9TjDHNCwIRNgzLNGMvp0VGZxUHrnDOaVkqpk0A54ALiBCCAEh2Xc7RU3qLy0WKX3GtzrvEC9Dt+ZdmJjMfGAohiJR31nWdsZQQASHiAsjZ0LP88pLdg7kbxnxGd+E2cNawvuT3fkVTFEJIiByEEIjzsREhZPSIEIIII4QYYJZlVNwKA+zo9EiWJSCEvV7PsoxOb6Hdbo/H4939Iwnnj8KYlpyWfDL2wiC2bKPb7V25cuXk5KRWqyMEEEK6Zk6mI0n+vbu7K4TgnN26dbNeb0wmkzDygyBkjCFEhBB5nodhvLOzJyv7v/3dv8NYnhfM80OiIFXFbrXiODbnAkASp1leMLda4wJRSru9xU/v72GMDEPTdaIbimnpCPI8jynLIKCqAgEACFBOM4IU09AiPyjLEnJGsIIAF6zklNIit009x7DIKaUijZM0zTkHhqZpFQdjUDDKRZkkAVGh6Vo9sxVGSa+3WKlUsjSP/KBimVEQJlF0fHzc6XQIIYPBYDqdWpbJGEvSSJZSrl+/Kr+Wk5MjCVrquXVJexNFERDCNE0kwMnJiWwRQQ3KPoQfBmEc2bbdbHRn8Zj8V5bfdnd3FUU5OTnp9Xr7+/u9Xs/3fUVRGMJZSWleIAAVpKhEwZAQpAz7J1gqiARByVi73UYIcSGqtikjZ0oZFwiCEglBECCQUQjgGff8LLbjCCnz6/PXssML3ujC47NQc07gafaWCyZARqNRURSSOjIrSowVTTV005IdHiEEhGimpSHE0wlifmlK8IKrlbGrnJuaDzJnl/p5FzbvOWeecHZkfk4ZTpAs8EQIcUoLIUSWZYwJ2VV7+PBhURRbWzvLy6uGYViW1W63a7UaKJKtra3BYIQQWllZqdfqURTt7u5yziVRN6XUdV3G2OPHj8fjcRz/Ynl5Ncuyer0ehmEQhEVReNPgjTfeaDQapmlqmkEpvX//Psb4zu0XppNYCAGRIAQAyAmBbtWq1ap5kVqWoSrGwsKS6zYODw+Pjo4o5apK6g3HMLSSFmHoGxpSCEjj0LZNTJCqEMA456wsctUilmUiATDGlCKE5YCKAEAgBBUFYayrKk+TUpZPVFV3HCcTimmpOlQBEmmRh7FPDKKbxpUrawih7kJnZWmVIJQXKQT8zTffnESxqurdbvuVV16Jomh7e2s6ncqcByGEMSSEuG6lUrFkP7lbb/e6vTSKj46OFEVpNBoAwslw5LpuEAREVer1+urqasnowcHBaDSyraoQIkkSecx+vy/rApJPMY5j2bhWVbXRaGCM9076nPMiy0zdUBXN0o08SYs01xWNEMKhUJjCgeAA5GVRpGmz3cqyTPZpKaUcCACBimQGxgUQACEAuBCCAQYAK4UKnvUlX2yH8wne/KpGl4Zyn/uuC4ZwZoQnJyeuU6vX65pmlIynaZ5n5Rn49Tz+PJ/jfWY4anYq8/Yji11wrn8oKdzmne8XnCu4dHeBF9zx+T4y3IUQVhw7z/PJNEYQ67puWTrGihDCNM1Go2UYxunJoCiKdrudZ+WTx9sapKZprq5cGY2HDx9uQiiazWa9Xi3LstGsqaoqi2xSoVT2x2TN5ujo+PT09Nat2zdu3ECQfPrpp7VagzE2HA51Xa9V6zIBGI0DxpiiQsPQiQIAoEwAxkGSRu12yzAMwyQAKpbtANifTEe6qakaUlWFiSLNYsZtSzMMQ0OQIwAR5ggBWjAhABJcxcjUrTzP00yUZV4wijBECBAMozAlhECBBKdQAE0lmqphiCjNLFO3KpbA6Lh/HEQ+A8yq2GbFzrJMlAAAvrKynETZaDDcfPjpKEpd15XoIk3TbLsShqHv+RAJVSWKolQqlm3bMlYqaWEycu/evUePHtXr9VqtNhqPDcO4fft2VhZFUWRFHgRBkqXLy8srKysrKyu7O4ey6CD7THKSCwBw9epVKRoph/cfPnyo63qSJABhDLGuIdetNmpNTVF9BrIkduyK53kCgmqjjgieTKcZE5VKBbAciVJBnGNIS0aLAiKCVZUAxhEUAkAoOEDiDLT9nKDsV1yolxf/hUU+81XzQSJ41lfJjZRlaVmWadqcA6Jqvh/2T4ccQErpzAgFhACetTtmLY15OwFzFIaSX/XCqxdO7le8tlmSiWYSxxCCc2dIIQUAEILKEkAoMEGarpqGiRDiHAwGIwk8yLIsCKIwjHd29j3Pe/XONTmyXZa5aZqtVrNarRKC87w8Oe4jDGq1Wq/XlSp/JycnlUolDMMrV67s7R1sbW1JQnjD1OQmFcIajYahmwcHB48ePer17uR5hnLAAdKBgjHiAHJANMNiAiVZLpHiaV6ougkQ0Q1EC2DZhuOYCsGMlZRi3dBYmVHGAAeIEEXFgAHKiiSNFKzO94U5ZxBiCKEMpEtKpcvSdYUzMZlMiG2bltnttgVBfuIFSSAgxyoejQYQYg2rg8Gp67orSysKJh9/+NEgiLvdbrvdPjnpHx8fCiHq9drKyoofTG/dutHr9Uajwe7uLmOs3Wk1Go3+kwNNUd2KI9M/WdLb3d2VIHin6h4dHR0cHGRZVpalpmlhGBuGIWv3sj1o27asjXme12q1ZK7heV6v11MUhXKiqSoSQFMNxkTJcyiAqZlJkhQFpbRQDV0zdFnUIESZjscQQkSIShDDsOBUAAABQVAAOREBBEKAc8EBR4DPE7LMp3C/7jYzLVlhmX/mQh532YGRIAgGgwGlp1lW2I5LKQ+CiKja3Amdy0ZJI2T8gpHMMsCzlsa55czO7/JVfYEdPnPw809AEM76kLNAlyPMGJOeStM0QpBMBizLMgyr11t89PDxcDjsdHoLC0uci+l0Gsex53mSji5N8yiKxuOxALwoCggFALzdbiuKwhhXVVUyt3e73SAIrl+/3ustxnHsedOjo6PVlfWyLH/+859HUdLr9WzbfnD/0/F4/PWvfz2KSFGwsswYYzbVDVMVAmKMEcZCiKJgYRhPJp5Ee2OMNUVJBbdts9PplEUaBZMk9lUCHdsqy5wJQDBWFEUAVpa5YBwDVQ6qqSoBkMnRASGYFIgPgwjjXNMQxkrgJ5PJZLFWM03dcRyOhUSZVutup9erVCpBEGlYBwIMh/2DvYN6tfG9733vL995//DwcGdnx3EcJKV287zb7d64eS2Oww8++ODo6MA0zSsb66Zpjsfjg4ODRqOh6zpWiGmaIEuzLJNCWpZT0QxdCCGJWCWjsVOpdzqdNE3H47FsD0odGyllMa80LBuMqZdhjHlJoyg6KakKMcHYta08zy3diFIxPO0TQ7MrFYTQZDIp81zWOFRVxec+w9BVJgCCgp8NIYkZK9Q8XcWva36Xl/G8M5TbbIrighHO+OYAACQryP7RSMafYZKfHbpIIYScMdnePFPb5RxwUSIhzoVv0HnnAAHIOccIyT2xAIBxAiHEhM1dsAACwLN/ARACyBInOPv3bJ74/HoA4EAgIRjjJQAEYwEABQJDKKeAOQScM6RZjPE057SkJQRCgyY2dJ0cb23DPNVAKQofUogx0vWs0cCawSEsq5ZzeppNJn5ZijwrHceROXCtSqbj5KPp/XrdabbqhmF+9NEn+/v7EKg3btxY6C1vbm6GQbq/f3h0dLS6uq4oyqNHj5I43djYaDabH330EcAoCILJxPvN3/zNW7evTqdTRsVw4C0sLIRBGIaeputRFNQbNUUFtboNssn6UldV9DQNypIxirOyFLpWlgaCmooxAggzhIGQrIiNXgVCGMVBFkQYobIsiixXVZUDoaqaQqGOlZSVWZZM/dHEG5cH6k3rJkIEQtZt1C0VY4zUIjt9coKIIjTr8OBUN8wvf/nrllnxPB+ohVklUZSlxdQ0zXbPtW2L8vjkdNfSjV6n7joqhsjSlTwOIt/Xm2aGi0Kl0+mQRKTd7tZrDbNmjYaT1fXV3d1dwzCvX79+fHz86suvbG5unvaHURRwzuXwsRCsLHMAQJqWrlspy7LfPxGC27ZZFFkQeJwpAAFdVXNGwziQBpbHNKQZAKBEgmFM87KkPuecl8yoVDRNo4xVHQcQUm02JQheEkBSSuU8qkKwqkAhMBUlZ4xzjgEmSMEQcQ44EwAgAZAAiAMoAOLnYDcCmezeYYxl0V5WX2bhiXybOP9PkTpLsx7jrGCDZ15KnL2ZzzFqzxyRDC/BeTZ59id6jtzx3+rZLgSis6T2Cyo0zz3UhZ3F+bXOTl46SUrpN77xjTAM/WAaJVFZ5hCAbrfrOM64fzKdenmeV6uOU3GLgnpeYJkVRVEgEq1WgxAyngym0+HJ6VFR5L1er9vtLvSWfN/f399P01Q25b/yla/Eceo4jqqqhwdHw+FQuk2A0dtvv40QopTLSkO73WaMRXFACHr55Ze3d54AAN5///3FxR7G2K3Xq9UqZ2DiTYfDcRJnlmU5lQrGGApMsBRUIRgJBRNVJWmeSU8ufzchBCKYqEpRFCWlaVGmaZrmlHIBISSaeu/evZWVFcuykjyhJVcUDRFYlvTVV1+beEEYxpVKxa5Ui6Ioco8Q8vbbbwdBcHh4ODztCyEsy9I1jVLKWNms1SGEN25eOzo41DTt/v37k8lkRjwhh5KCIJDa4zdv3D4+PvY87/S0X6lUVFV95513GGMvvPhylmUzWiQJqUEItdttGbJyzqMoGo1GUuVXjozI0Qpd103TlPjBGaO2XBWyCIkxJqpaFIXMaTnnUiBRKp9HUZQkiThn6JSVRV1RAFE455wKTnkpGIFEwbhkshUEEAAcQQgBBE9LE//JznO2PVMdnRnDfAZ5OX6d3+Bljhlw5uTPXuUXnfJTm3k2RAbPGvCvEqZ+3jMQQuk1iyJLkmhzc5OygjGWl3lRZAJBDhjGuFqtWpbVbLbKQhwfnw76Q01TllcWkyRRVdJqNSqOXW+4aRZmWVoUuRDiyZMnO9t7vV7v6tWrnHNNM770pS89fPjw5z//EADQ6/WOj06Kouh2uwght171vKmqapTSbnfh4HBimJqu641G5969Tz797F4Y+s1m/aWXXlhYWCiKLJ2eSGLcOI4Hg0ESZ61WS7Q6RVFAATiGgmPEmYIRhgBCHKWJoWolpSVniPGyLBmniGAuAGM0y/O0KPOy5AICTFRNs22Hcx6nGRdCIuMNS1cUxfdC3w/TJLcrzvLyqqroYRjV681rL93Z3d0Npt5xeQghlDYWhuGrr748GY6EED//4Geu647H44WFhW63K3XRIISWZTHGJhMvjmNJS2WaZqfTURTVcRzJf/HSSy9t7+xNJhNJ9atpmqxIG4ZRFIX0jRIkHEURhNC2bYxMCU7AGDuOY9v2DOE0W7cSRyXNksKnY98AAIlcUVVVgpyeqjOcz+Bahi2n7WhOsyxjJccIE6JwVvKzXAxAwJFAHHIAgRDsfCxYLmBZImHgIreNOGfmR/PLdWYX83A2Mn8xYC7NE3NjR/PmLj6fnnH2AZdfuuwJL9ve31qwAXNWLea8Hzh31BDAsiyldkUY+QAAXdecqttuN5FCIAaEEFamw+FwMpnqmtlstq5cueJ7AcZKvV6ltBCAp2nMWFmW5WQyGY0Gn3762dWrVyHAf/VXf8UYW1lZsazK4eHhm2+++eqrr8ZxfHR0tLu722w2r127trGxsXu4U6vVOOfv/vRnnU7n1q0buq7//Oc/z/MUAAEhvHPnVhgFlmUkSeS6bhkixliSJEmSSD9QFEWcJgghBARnQJSAl1hBUEEYIoAARwhRAbgQTIC0yPM8LyhTFEUAxIAs0UBKOeOcQ3Ttxq2spP3+cGlpYWFhZepPIBS27TzZ3tV1vVarIKw4lWoUxScnfUXRFteXLd2QtyrP8+Ioarfba2trWZwkSeK6rqSikgNWQghN06T8E8ZYdtiLopBI0S996UsPHjzQdePo6OjJkycSKGOYdlmWcnJC/qaappmm+eDBA8aYoiiWZQEApKsUQlQqFUqphIAbhiG1biTDvPRm0nmcAZWFKMtMGjw6n1YFAMi5sBkj68wIMcYZ55ADIQCCGCkaA5xzwCkjQDAgmEyJABdQcC4EBBg+053/VdbtZT90wX+Seej3BSOZf/N8mDp/eHDJE86OAP+2pudlT/sF0enls589I2bdTwGYYJAzCHi16srbSlnmeY5VBA3NNEwtDfNbt264bm17e2d3d9uyKoSojuPIoDGOQ9+fFmVWFLmmKaurq9VqYzQa5VnZ7XZl7V7y7Z+cnAwGg3v37g0Gg+WllRdffFFRlOFwqCjwwYNP6vXm8spiveH+8qNPqtXqq6++cnBwYFdM01Idx4mTIMsSiESzVQfZZBa4YowNgwAAgyBwKw4+v60xJiAAEHIkEKWlwlQIASQKBBwRzHNRMlqUDGAkIMKKAijPaBqlWRTHULHzzGCMaZqhaCTL8yiKJl7EmHDdumlVgiDK8wJCbFdct1r/+OOP5QxUq9UaD0fHx8eWZa0uLW9uPmw0GnEUvf322w8fPnzxxRefbD6WnL9SGVsIked5EAQAAEppv9/f2dk5OjqqVBxCyO3bt4UQuq5L6if52+V5Lu0kiqJGoyGBaZRSQogMK4qiGI+mYRjKvh8AQNK9yUbibEHOLLAoipIXjluhlMZJlKRxq9XSdJUL1h+MZrEepVSCOhBCll2RfGiEEJOoHIE4TYu8UFQNQ1QKToFAApSccSAEFwAjCAAEQMKpZ4Y4W5fw/En54LnmJy7oE15AeV+Am80A1uB8oWPl+TnhBTf4jJE8j0T1/3+eEHMhtcgBhJKKWQjBh8O+aZq2bcuVQQV3q9VGo+HaqsRblWWxura8ceWazEBOTw/b7XazWXerlhCcUqppSqfTOTg4Ho/He7sHe3t77Xb7pZde2tnZ+4u/+AtFUer1Zq1WW15eXl+7IiVsr1y58md//cfNVmOht7Czs5Pn6Qsv3nZd1zRNLuhw2E+S6LR/dPv2TcbKZqvuOI7a6Y69qYxPCCEQYCGEnKkjEBEsR+MEAAAhAAUUEDIBABcAQVXRhRCUA4xxGIYIKgACJkTBWZKlQRxFScpAsLy8WrHd8cQTgOm6XnHraZq2Wz3TcgWH00kQhnmr1VlYWFhcXD69eyIR6rqqLSwsUFYSiA4PDxcWFhCEw8Hg008/Pdjbl7P2lmVJi9V1Xf4cknXbMIzr127KqvLu7h6E8MaNG+dM3mMZOkp7m9UdsixzHAcAIDlmarWaoihxHEOgKgq2bVMIQWkRRaWmaYZhlGXOWAkAVxQs5VSKosjzFCBBIKKMp1nMitI2zLN2SEnP2xhkZoSEkCiImBAAAA0T2zAhhARhBWYCIy5kKCoAEwwIweWk7Rdh0L5g0V4wivn9ybyZgfNolT/LOjH/GZdjTgBm+qnPGQyZBbfzz1z2tLOjffHFXC7MyB+Rcy4gQhgRhDSVaKqiaqhSser1mqKqaZqHSSx/aUMV7XZ7cXFRJjbDUT/LMqKgvOAlzcsoB0A4joMQSNN0OBzKW9Irr7zy6quv9vt9KQb45S9/eTwej8dTCGG1Wm2325L4aHt7+/r1K9vbu8cnB0TBYehPp36WZbdu3RoO+/V6fWNjPQj9drt5cnLkeV6e523TcCmtVqv1ai1K0iKnaVHAgo6nnkoUQ1V0RREEQagQAQHEmqFDDLM854JhjCEmRNUQQrplcw5KxgtO85JmeVkwziEwbUvVtYKyaOIRQjTDMnSdUnDt+vXJZOp5Xkl5lie6HmZZsbOz0213hBBlXjDG2u12r9PO83x/f79im3GWLSws7Gw9uXLlShAE3W7X930OZrQDQZqmEuTAGNvf39/f33/llVfiOJbd1CAIMMZS/LQsSzltOJtR6vf7EuokRx/lsqxWq5p6xuUhg/aiKOS4xs7OjhyskZhnVVUl2kkQKKsyssMk1VSlk5SDCzLgl64bAKAizASDACsAISAIViqGrhGlYLQUgHAGGYWQCnYOGQP8jH7wrEzDn00Rn67NL84J53c9Y8WZDRPNnB6ao/ieRdK/lnmAOQv8Yk/4q2yX95/dIM4BdBwhrKhY0xRdVw1DK8tyPB5iogIA8pKGUTSZeFndOKOrgVASfuV5xjlrNOqe521tbUEIX3zxTr1el1xPk8nENM1r165JXJVlWZrGZWNa6goeHh6envRfe+2169evR1FkuGxpaQFjfHh47PnTWq2W56ZhGEKIWq2WZUm1Wk2SZHFxceqN6/V65g0IIfV6vdUKseePpz7PspyXcRxzTScQKYQIiBFRsKIqqqoZhqTDK/NMwhgoY0gIx6mmRc7STGSQUUEFwIiomm6apu/7qqq2Wq1qo06wOp0E4+lkbf3qyUk/zTJdN1VVrbi1oiiCKMon4cLCAiHk5OQkjWOVYNd1DU1/+PBhrVZjJW02m4qiLPZ6QRDEYVhrNQkhsgGbZVm1Wpe54mAwiOP45OSk1WrVarXpdHr9+nUJe5B4QFnykaNMskVUqVQqlUq1Wh2Px1EUAQAcxxn0p3J9SsxqkiSmacqcULYHZJf4XE9FMEbzPCWESBQUQiAIQkppterIZVMURZYlYRhwzhVF0YEMa1nBhWBcVUuMFVXBEEIEOGACACIEZ5IsigPIxVnvQgAAzqA3kAt0Po1xoUXxq6xnIhG00mXLe4n02hjj2YgUAEByHIhnqzpP3SMAiqKIWUsDPHVx7JIW79m5ztHgg2cd9DMe8rwVOfvcWbQsj1CWTCEEQoyBwBhrmoIJzPNkMhkyxoqiZIJrquHWG/VGy7btyB94XvDZZ48cxzFNHZxjfZKEm6b+0ksv5HkeBFEUJVJlZWNjAwBwenqaZZlhGJzzIIiiKGo2mxDilZWVv/t3/65pWE+ePEmSRFEUVcXj8bjIabVahRBnaa7ruu/7zWYziiKEgKZpcRyapl6vNSGEEAFaMLkQJ55/Nl3OmRdEpX42jcY5l+SZRUGJpaVpDhAyLJszltMCAUSIGmdpxXbygiFSBlEc+GGz3ZlMJr7vX716tdPp5CXNs2IcTQ6OjoMg6LQXS8qTJPM8j6i6W2s8ePAgz/MrG0tCCMuyNtbXhRCtVrPMC9kq4JTFcaxpim2alUql2WxeuXIFEJymabPZrFQqnueVJZOFTV3Xm82mLJnqun7r1q0wDBuNxvb2dp7ncq5XrhbP86bT6euvv46QbLFO4viMnkPTtIpj27ataVocx5ggRSVCiDiJIAK6qkkPgQkyTJ1xO8vTeqseBIFt20WeK4qSpWkUhoqihEHQ7/dd122326WmlbpOKQ2CIGNnxNMqJipxTV3nAGRZtrS8Mva94Xjke76fRJQxxdQty6IUnS+8clbdkTWk5xobf1boYba8nwlHJRLlPK7js5fn6Srms8R5LPVTuxKAUipnmTDGEIpZLRigzw0vn7td8Kiz5ubnhakYY4wwIUBXSM216zXXMlQI+GQy2d/fp7RstttOpZqVNAgChBAAMI6TarW2sbFRluWDB/fCMJQYjnq93mw2G41WWZZlSQkhUuekLMuyYAiher1er9fjOI2i6MGDB7/xG9+8fv16nud7u/uU0kajwRir180oilRFr9ebSZKlSSa/BzmPl6ZpWVCpXZckWZ6nNUVACHWFGIYhWQYZFXme2bYDCaZMZAXFGKsqAAgjTLJM1gWZQAxwUZZUSqJEScIZTOKMM6CqqqIoRVEUOW133c5Cp6Ss3WkdHhxhRX28uZVlmR/86euvv9nrLa5dubqzuzWeDFfXlgEAjq0Dzv3ptN/vU0rTOC7L0vf90bBvmqbjOIamFEUxHA7jOE6SRDUN2fdLkoRzLlHjhBCMlNFoVJalpukYYzlfJpm5JaeRZMeSTg9jfHJyIsejJLuMbdtCiN3d3W53SXLwJEkiR5zkjx4EgbRSybiFEHIcxzCMaTANgkDGHZL2u9/vM8YWFhYcx+l0OpJpSlZoIYQcCICgPI7jOoamZVlRluXHH3+k6ppZsddWl5M8mwZ+nCZ5mii6fZZOAQYghxBAJCASYmY7MzgK5AByhM6CzflA9EIk+NQIZ8YG5/oTGM9rMn5RAMkYA+eUFrKJfFY+fl5z/1cxxfkYevZgdjGzHQhSAOCUUoakNgBASHpm7LoV0zQNQy+KIi9Kt2q1Wm1As6tXr4Zh+Gd/9udFkb/88stS1aTdbhuGITlXwjBK4gxCqKpqliemaRKsRlF0xu0HsWEYvV5PFiHkMMFgMAAANBqNw8Ptfr9fqzYopScnJ6cn/Xa722i0IMQIEc5BmuZ5VmCkViqaadowPhGcFUVBWSG7ZIyCgjImOMsKmtMyLwC3CcIK4QhxGiaUFZALBgUQjJcUQM45BwykaZ7npYDY0E1VTfKkoJRubKxXKtZwOAYAuLWqlNzwAv/FF19eXFwsKHVcuyzL49OjSqUyGAy+9Nqrsj5p23YUhHEcc841VV1fXwcAEInkFQIKYBmmoel2/YyNQs7ixHEsw6V6rWlZFsZYtl6kqUyn0zjJKKW6rsvSyGQyybJMdiYIIbJvYVmW67ryOJVKRaoOW5ZlWZbs5TDG1tfXj46OKKW1Wg1jvLW1BQBYWlpy3cpkMpL65K5b0XU1DH1K6cnJkeM4kv9O1nIUBVuW4XsJJgRgBBBkgheMFqxgvGy26hBCQDBnOS8zAoRjGFhVgpQBAYAAGCKIAAAAI4whOpMREwIAIdm75QixuDTld9mUyOUpeHFOWjo/DzE7BJ8rtCB0bq7iqVcUQsBz+5GjGL/WdsHm5zEK4HMeCy4Y4wUoSppTVjIOAWcQilrNDYJoOp1ipLZ7C72FJdu2G+7iX/7lD9M0/upXv+xWK3fvfhJFwbVr16RYqjykqujIViGUNKy8Wq1WbFdO2QghdN1sNBqyLHF6enrz5s2VlZUoiiR1hWEYMnxijAEB6/Vmp9MzDXs4GFmWZRq2aellyQzD0jSFc753+CnnXK7Ler0uIFaVMMnyMIgppQXnQghVpQZlCmUAUEOFBBKAORRAcAEQQhBjiLGuFAUtioJyIBgQVKRprqtGo1kjCuoudMqyWFlZznP69d/4BmPs8PD43/7hHx4dHd154dZoMtQ0rSiyLEvefffdpaUlQ9OhAIwxzwuAEJVKpdHopFGcJElepARhwzBkuJ7xM55oyeobxyk8n0va2NhQVXVrazvLsoWFhUqlsru7W6vVdF2XTUXOeb1en/3usnHvuq6iKFEUKYrS7XYHg1NZ15G8dVmWpGmcJImEZCiKEgSexMRK0CTgAnIRB+FxXgjKXNd1LJtS2m40K5WKlLJREEYCpGnGOYeKQoGgZZ56uR8EukwviYxRIVEViDEhmLIyCMMyKqlSkUY1I3MBc3wuF5bxM4nVpZeeGqH8BueD1HlzuuA9GWNYUWc7Sy1VIQSUOd7Ml3JpogghVPJfieXqbzVCedu5HJcKIRCEiqLoOpaCZzXXIhh4njfoD6MoAkD0FjqLyyuUga2trYdJkcSpAODJk+1er7O0tMw5E4JtbW0ZhuW61apbd90aISpnglKu6VC2NDDGrVbLNM0sKySxRaPRStP08PBQIWq32x2NRtvb23deWRccFgUNgkhV1VqtBQTa2to2TTPLckoppazIqRAwirLpdBqGvqJomm7olm3ZVUS0JC38MDJNsywpKynEmANRMiHJIEzVkLBDwUoKEEGKvFfmeR6Eke8FaZZTAUI/KEq2sr6uaIpTdTRVj6KEUhrGgaZp3W6XqPq7P/tZXqTD8eD4+FDTtKOj/eWVxSJlg9O+PCBjjJ6XCSbDkSRAURUi6wVRFDHGpnHIGJtOpxLpImfEy7K0TF227E3TlNQn0mijKKpUKhhjSdsjYeKe51mWpaqqLOokSdLv9/GZTBCZFWaks5UeQg4fS1mLarW6uroqhDg+PlbVMwJBic6XY2hhGEoZ49kksTiXs/YlZoCLssh5STVFbdRqNbcqBM9zWhQFURUAoUawoRAgWJxn6FzCCM7J2c/Swrl6jADgc0eZnskJ5ZvlPX5WzJTOcNarkJmrRCqgeUDMJccq33hWOPrCMccv2J4pqIqngSi4lDECeV8giBBMiCzhMggFIQohCGHYbNZr9bbj1HzfP+2PwjDcfby3sNC9fv3q6tqKpilJGoRhUJal41QBAEmS5BnD2IMAcQ44B71Fh3OO4NmXwDlXFE3yjrZanWq1Gsfx5uam/BqDIPjpT3+6uLiMMR70x1lW6HoCBE6SrN3unp6e3rt3rygKyzIMwzRNI/CjhYWFsmQlZcX5byn7YEAgCFEuBKMsS3MEIGNMV1QdCkVRIOdZljFaYHjG8+X7fhgnWZJFScIAKQqqavqt6zdWVxerVWdra6fd6o1GE4zVD97/QDXMRr1FWbG4tPTyyy+/+urLtVrVC70XXry9fX8rSZKjo6MkSeSZZFnGSypXv0oUOQifpmmZ5WWWc3JWPJf6BVmWSROVReCiKGSrVmoYKooCQCYh1JIqSgJiOOej0Wh5eVnTNM/z0jSV2fhwOHzllddkLTqO4zRNEULNZpMQ0mw2kyQBAMg8YjgcysZGmsaUUtM0Za1LDoVmWSbFTPv9fr/fl7UuCfSJiwxCiACUfCGc8yTLTLOoVh2JZQWcQQRVRanYtsXNeJpKA5F3Fmk7su9/wRDOndjzPeH8M0TMlWTAnKuZTXzNyqfwfF5pdixwHoXC87nfs8IM/1Wb75e3uTuKAADg88H8eea1eVOklBEEAECU0iSJPA9jyHSNFEVRr9fVjoawNpn4h0f9OMlVRX/hzktc0DCMt7d2KMuLImu26isry61WqyxpFCbTqR/4cVkyjBWFaHfv3lUUxXVqruvKz63VGp1OZ0ZfqyjKZDyNosh1XQDAwcGR69YgxHEcm6ajKjqEqNdbpCWPwuTw4HgwGFi2cePGjbW1NVXVqlUSRUkZRvI+KLteecYmnlfQUgqG5SgXQqiqCg2c5zlgnHMeR1GZZ4qi6KqEPUFVVXXDVvWUCUCI6rjVt978Ur1VyfN8Oh3bVgVhXJbl7u7uxA++9a1v3bp1i6j4+vWrjlNRDXU0HigKls06aRhFludFnuc5BvBsqQHIGGGMZXEi+bW4RiRRmBywlHpmcvWnaTqZTHTdgBDK6ovUDJVJnWziSbI/WUlijIVhmGVnViqEIITIuFQattxTjixgjKXSweLiommae3t7CKFr165t72xOpqO8SG3b5oIGoRfFgaLi3b1t27Y9fxKEnm3buq7XiKsoSjQc5Xle5gUQnArOGIuSWMGQFrnjOK1Wq1qtljQfDIeDwSBJknZrMc/zGdYcAIDPWWXOPIecKDhHzMxXMcDndMLJbA7wQiFEfimze7Ps6lBK6fxQ75wRCiHweWEGwqeaZ7+u6M2FgUg0m+rnz+/jl2WpEYLQDFwgpCWbpmkYRpoUB4dHnheqqq7pVp6VrGTj8QQT2Ou1iIKTJJlMgOM4YRiZpmnoVrVaV4heFJxgVdf1Vlf3fZ9RzjmvVCqyspdl2cbGRhynsgAohGi1WpZlDYfDtbU1SunW1mPOxFe/eqNWbUwmvmnYh4eHmqatra2ZppnlCaVnbBqD07GASFVVy3EFIHg0HU+8GdpYToRICjzTtKrVKkpiRcGspBhjhhCBiGAVIWTbGsaKXXGCJC0oM7ygVm/cunXr0cldINDS0lJRFKpqPHnyUNf1OiaO47z8yitTb9zr9aIoPD4+pqzw/enJycm1a9dkc+9gb78/OCWESNyJpqi1Wq1WdymlIzIIwxAAIPGcMjaTLmuG6pReKE1TKTAqwaira1fkejMMQ1q7LALZtn18fDwYDOQkZ5ZlEMJarTYcDmUWAACQuG3Jzy37DTIclUVX+a7XXnsty7LpdCozbUkz1Ww2Hzx4IBmlZPUVnAt7tbud8Xg8ybIiz3heAi6Q4BBwWV5OkoQLmud5EsemadZqNVZpSx2UOI5loC5tUMbql23sgtuYZXbP5IRsjixx/mU54Cw51yThPITQMAyp4HF23BlOTkKQhBDn8SoXXEAg0Fw/Y+5fcA68lrgDcS7XxoXAkuJQmrYQQsyIwM+usAAAMCTQ2fSKiQudkIquWaapaZpODAJNgswoDOOpBwFvOU5d1+I4LmmCLDiMioojABBFEeW5KPI8oGKH7ne7XV2pVBpVmbyNx2PP84bDUW9hQSE08MeKUnS7blEUp8d9XVeXl5fjOBa0jPxJFEW0MEJ/VObR9kH21ltvLTL48/c/+L/93/+vv/3d7xmafnBw0Gv3KpZz5/oV19QsrdJzOya36q36ONVomWuEaIoCeF5zyMaKZQD19HgQ+lFRZEbdbrh1t1LXNBWXhauHSADFQpqjYahhca5koGhBkkW5h0Ta9/2GbrzywhorxrfX1v743/8AEWV1/VrBkEqUarW6vLauEG2hu9Rudp9sPkrjyLR0moCdzZ1J4D3eemKa5iuvvPx3/5e/ByH8xS8++NnPfjYZeLW6u967igkpswRVtCSaXL16VQ7j6qamGep4OomS0LQNoigIIcrLrMgVRUEMUc5sWLEqdp6k0+l0Y2Pj4OBgOBzKEd6z9l2WVyvOYDBQMeGct1qtMssnw4GgZRCFL770ku3aDzcf1Wo1igUjQABm6kTRFUGZppJOs2FZ1mDY1zQVABGGAWMMEVJxXYiVVm+BAVJrdjvLa2EY9/v9PM/1Sfj6izcCQg4oHzGQYS1J85xjU3O8HAhViCQ3SmaqpF6t8SJP0ySYnnjTKeCiZmiUIsYYLbMoiiqmJUHnBtEBYPI2SghJsSwmnxc+uQAAIclgJgAACApAZuv7spOZ7xzKZFRuT73cHNoGzJnKcxzcnO198Q7PVGWevWfIz5JLbnbXqJhmlmVRFBm62mq1HMtUdU3T9DiK0jQtyszQdALP4XgQIoRqtRo6E2aDtm0jhDgD+/v7w+FwOp0uLi7KwEnX9UajAdCZ4M50Or179y5jTFe1drsp7zuS29P3p1Iiqtls5go+Ojj8xS8+ePDg0xfv3HRdlzN6enq69fhxu9l5/bW3bt++XeRUJjBurSoEVBStyPMsDU0dq6parzUNRQWcaopXmnnFchrVlmU6mmKqqmqUSCVIU1RDIRhBwCkUAEDMIYxLmgRRnCTNduv2K69du3lHcPjhhx/WarV2Z/Hje/eJZn/1q191q48mfrC4uPjjH//44ODgK299aTrOTk6P4jC6fv36p5sPp9PpdDp95513/vAP/1AI8fWvf/Uf/+N/LIT47OGDP/mTPymKotVqfPOb38yybG9v7/bt23Ieem9vjxC10+kkSbK4sCznlSSoRQjBGJdTFEEZLC4uyo4oIUTX9cXFxf39fUmuJRnWkiSp1Woy+NzY2BiNRmmaPnz40HIqGGPP87I8F0IUZRlFUZkXrCiLPGclJYQAIqrVqmEYYRiGYZSXpRACQry+vr6zvXd4eGi7jhCQc+44zsrKypMnTzAmEMJqtUo59KM4z0skQJZlUHBGC2YaqmPrmgLkkuMcnNcdFUXRNQ1CqGmagrAkH5NjSQAALpl4FSixbkJgzrnEugkhIH8qz0aeeqpLmM+ZHQIA8Dlh8PlE6Zl9yqR8hn64bHKzg8O51t8X2OH8cS7vNisNn2OUmEwzbNt2HLvZaFQqFUltYBhGMJ1QSpGBFE1BWVYWVABOiFGtugghz/OLolAULJnmZBcrTVPGSk0z5FCcZZmDwZBS6jgO53w6nSqY9Hq9paWFo6MjuYZ83//kk3s7O1umaTabzQyw69ev/29///c3Nx9alkXLoigKKDgh5PDoqN3ai+N489FW6Ed37ry4tnGFx9ypOoFfhmGsKRYxdKTrTsVs1Nzd7Z2j/aMio9NgGoWxZVWqTq1qaQrGEIg0L2iRQyB0RcUqTNK8P54UDCyurK5fv/Xi629g3fj00aaqaMNk2hKiWmuYtiuEiKJoeXn53/ybf7O2tn779u00Tbd3dwAXZZH9+Mfv/L3/8u/pur6zs5OmqcSRHR4e/5N/8k9ardZv/873/vk//+cHBwf9fv8//IcfSJu5f//+0tJSr9djTMgxwkePHslIPssKmdepqiab7Lqu66pRa9SDIIjTJC8LytnC0mKcJlmRU86iJK7VakVRmLZVFIWAQFpys9ksKRVCuK47GAyEEIZhFBBqmlZ1XNeutFutquNSSg9PD1RVlTqtGGPbcWRG+uDBA4VonU5H0bXxeCqLOnEcB0FgmhbGCiYqhyjNizhOoyio1WpAgDzPC4I4N4UQlLGyLKGqAsGEELTMAQCQEBUTFSOCkUIQxlhXVCEEQWfCEBixubDuHFjDZ7UZhMScEV7e0Dml/rxNypwNnStjz3vCC43IeWObFTwvZJ6Xt8vlVvlAFmzm01d5nNF00m21a7WaYRh5ng/GIyEEbiBd13XLVBSl0ahhDNMolikWMJjEMcjsVQhOaVEUmeM4EMKizAbDUwiwnLjhnFoVxzCMVqslGC+yPE1Tz5skSSTLgEdHRycnJ0mSvP76m6+99lq9Xj8cHDPG+ifH4+Go025dv/5K6E0nw9G9e/dMS3/hpTtra1fCKN7aeXf67k8m3viNt16p1+uEKEAgoupYIYATw9DddgcAoCBlOp6Ox9PTUR8NBrbt0JpVr1erFQdAQBnkjArAVMwnQeiFaXtp8dU33uwsrxLdiLKcCq5pxkJvCSmqbdscgP39/TAM8Wj01a9+9f333ycEJ1G0sbEBBXj5lReTKH7v5+85jvPWW289fvz4Zz97bzgcrqysmKbZbrf/+N//yR/8wR90Op3f//3f/+3f/ju9XmdnZ+cv//IvDw8Pj49P4zi+cePG+vr6yvJavV6XJOWyhCidw9nMrm5J3icZC2RZJgnwp9OpTPlUVZUSogCAPM/zNJPLwDCMWq1mOxWE0OlggDEusiwMQ8E4BtDQ9TiMRqNRvV2ToO1+vx+GUavTOVOeUZWlxRVd15/sbO/vH0rBxiAIFhcXsaIWBWUcQKIAiCBCcmSFiVIwAIAhVzvSNGZbumlyWpZ5DuS8Py04wmVZClVVEEYIQsAxRkhTGEFCCJ2Vz67nC9VRDsDzjFBayEzmeobenrUxwLMDUfKlC23GCx2FmQX+iuHo5SNIGi95d5lx9SOEVlfW5FSb7/tFntq2XXNcVVWDwFdVXbfsar3GSko0VQbSCEPOKda0Wt0FACCEyoJVq24QBJZlCSEk/EpRlLxI+4MTJ00BAGWeMcZGo0FZllmWJEly584dz/M8z6OUdjqdWr2+f3Dw45/8pCizmzdvLvZ6t2/eSNMUcsY5f/PN169fv+p5gVt3j06P9g73GWCdTqPeaUiaas4BxAQhAgQCiGi6GUVRrdZo1ls0L0+OTjc3nxwfnnjR9P3Hn16/fv3G9WuWbnBAMlrkrIA59+K81Vu8fvvFdm+pACCeekhRm91eMBkWZfT48Van2xtPw/d+9kGl1rDK8v6Du/3h4Lvf/S7GOEuid378k62tLcPUsiLd2dmRTK0vvPDiZDKWTmwwGFy9enVzc3N1dfVf/at/ZVmWaZpXr1797/67/32e5z/5yU92dna2t7e3nuy8/fbbQohKxZ1OfcuydN2wLEvXdYnJPj09DYJgZWVF/qaGYezu7uJzjSBJZiGpKOTYhFtxCCGPHm+61apZsVGCu92uFwSu62IIoygydUPCvmlRyuUqZTB2dnYOD08o5wsLC9Vq1Q8DCfWsVquKogEARqPR/fuffvs33srLJIjikgnLqgAAMAJlWY68CeDM0DRD0yhnEELF0FVVVWsuhjBL0rIsEYCUUgwgBxAJgCBEEMqYU8rbc84NmEMIIXhqLHwWlZ5v5MKin23SCM9haGCWB+I5OkMxt80m9Oet7kII+kwD8HO2+VcvPBYznhsA8LmS6cLCQlEUEgOlaoYso3mBHwUhwQgphDN5LZhoKoRQswyJZZPHJIRwDsxcl7FlURSTyTTLMoQAY2UUBSfHfXCOX4cQNptNia15/PhxURTNZvPWrTuO41BKR6NRGMYbV1am48ng9OT2C7fzPC/yNM2Tbq+LMdZMw7SMJEuXV5c6vW6z2VpZWbE1Q1E0WuaaahRFKRjHiBcFFUJUHcexbU65pmkAQTnZEE5DAYgfxJNpIOVvVV3DhBwORm+/+MrGzduckDjNKUKM0ZKyshSj0WTryXa7szD1vcdbW29/a63Zai0t+cvLy0mWxmGUxuF/+Q//wc7W9tbWVr1ZV4h2fHy8s7MjhLBtu1KxJaZMwmgP9o9UVV1aWjo6Ovrxj3/8/e9//+tf//rGxsbf+3t/b3tr9969e6urq7u7+5LZkVIWBAFjTNfMIj/D1mCMbdv+/5H232GWp1d5KPp9v5x3jpVT5+7pMDM9WWKUJSSEQBIS0TZwbLiC4+NrAT72se8FTLA54CNfc4UIAgQCC0kocqWRJkdNT3dP566uXDvn/cvx+84fq2qrZkbisZ+7n3762V1dtfeuvX/rW2u977ve1Wq1QM7W7XZhbWgYhrquwyHreR4hxHEcFjMLCwvValXVNNd1G63mkSNH4P23xmNCSKRqmqxQSlVVzefzURR2u93xeEwISaV0SVHgyD569Kjr+LVarTvoW5aDEFJV9fjxo8PxCCFk2264P9s4Ho9c12MxE8UhIonnO67rcizmGIwQyhsqi5EgcDyLSSyGIWYQZhnEcRzPcgghEkeI7plrkDgWaDwBSClGFFECxSiDEUIUH8iE3y886Gu3mgFeOuHxD7KLB3/kjSGN3lCR/uO3g2nwdaEIumrYE0oI2dzegm6YFziO48IoanV7/X4/n8sgjKMoGQwGYeQTQgRBCigmhIgiz7IYhkfT6SxC1HXtbC4NL09VFYbZs8oTBIHFnCzLsCZaFGXAxyH+fT9sNFqm7UA3mMvlTmRPDloNSmmSRK9evmzbNiewpVKB5/mhOWQYzg8ChmEWlhd4XhRFuVyp8DETxyHLCalUmpIgSgKWZcOYpI20JCsxoZ7rIo4tV6teEFieXTx8VNf1kOLRyHQcj+M4HsUEx0auVKjOYlF2w1BSNTPwu91uQtGw1jAyueMnTzle0Gp2Tpw4cfbs2d167Z577tnbKkPi1Tu3EEJrq3emp6dfeumlpaUlSmkYxNMz1Wq1OhwOgZELw/D06dNXrlxJp9MvvfQS7HuhlK6vr1+8ePHa1RvHjh1bWVnJZHLpdPapp55SFJUQ0ul0RkNT0zTQx2iaFifJcDSCQ9S0LFGSEMYJIRzPR3FsGIbreQhjPwjwvkva1NSUrCj90dC0rVqt5vo+kBwMwyBCe72ewPNpI+V5XkTDzc3NWq1m27YoSkBL2rZru46q6LquswJPSKvb7QKuWyqkZFnlRRkWuULDFRlR6Pm2bSOaoIQkSQTEYBT4jMBalgUAIYuxyPMMw7AMTpKEIoIJjcMQISRQnmEYShIGXHs5WA2ECKYIU0IJphjhPej/u+jo68IGhBEHWzLo/SZOO/A70AME+vesbP8Ho+514Tf5WXSgHUX7e3yB24WXNx5ZmUxG0zSe5+MkimMiCoyqKqIscQwbR6HjWEkYMeweA9kf9NKZFKV0OBzCYATLCkDyAMcF4Q14j6Iord1WNpUGWpnneUri4dCFwXDTtG3XkWX59F1nzpw+q+janTt36ruNM2fuqkxVv/3tx+rthqYpR48eVlX1W48/rihacThADMdxPPVc1226vjOdnXIcS1HlbCYTx0GSuIrESxIXRJFCEcYcwwsZ3WARtl1vc3tnZ72WcnxBEMIwSjAbJtjpjnqj4S987Jdy5anOyI5IUqhmEs+3/aBQKIhVrtVqFYvV6blZ03K26/UwDgqFQq1ZazRaqiTXajWO43q9Qa3RtF3v7rvvNgwDnM6iMGk0GqIoPvTQw/V6rd1u7+zUoLYEtbc2ZbAMXylPzc9xYRhub2+3Wp1ut3vmzJl3v/s9sNHl0sVXd3d3YaQ9k8mA+ygMWCVJUq/XGYaBDhzML1RVdRxHFEXLsiRJymeyjUZDlCXdMDL5nGboN27caLRaGGNFkniedz1nPBiSJImLEcZ4dnEGvBQ4jqMUQe2WyaRN2xJFET5ZQRCq1arjOK1Wi+NQmk6EL5EoivlMmuf5Wq3mOjROYoypKIqiJBFCfIpgdlkURZ7d64k4lmUwHo1GwKwEQcBiBnY0YIwJZTDCDGL298bHkKMoQyfD8Difz74xANCB+hMhdFDJDa6sE+nMJEOC8TPGeDKXOAmkSSwdLF+Z/eVN+IAWllLKfZ9VMRPtjiRJYKYAT5TRjX3EKWEYJpMy5udnZ6anFVHoddvD4YASgkkSBEFCIo7jRE20LCuOklQqlc1mOY7z/TAIgjCMdV23bbvT7hmGMTc3Z9v2zZs3jx86miRJs9m0LIeXRNM02+0uRYhSmspkDcPI5wqHjh4xTdNxnB/+4R9ev3r9nnvO9YeDxx77xiuXX/nRD/5IdbqyvrFx/dbNr33tsamZ4g++7/2pVPrxJ566du3G/ffff8/xu3u9XqtdP3LkULmUW1qed50xpjHL4nwuo8lSGAUkThBC1tjsdrvPf/PF9fX1VCqVxKTdbntesLSy/JM//VOCosqqmmAaIyqocpwktucSRKOBOxgOr1+/furM6SCJrly7tluvlavT6+vr1WpVkpRRfzDoDSmlHMM4jjMY9AzD+OAHP0hp8sUvfjGdTi8sLFy7fuXRRx8lJAGraMsaZ7NZXdcHg8H8/DwhZDwe9/v9u+++98knn+R5PpvNtVotRdYOHTr0Az/wAxsbG48//uTFixez2ayuq7dv356dnZ2ZmUmn0zs7O7DLiVIKFSlUKLA623EcXVFTqZSRTm1tbyOWOX7yhOd5t1ZXB4MBgxDLstPVqXwm26jXPcddXl4WFB7ETN1ud3t7p9luI4Q0zdBTBkZsEARBHIVhDAvestksw9FMJjM/OwdHT+B5Yeibo3Gv1+v3+2k9nc/np6enYbUGy7KuZ8VxXCgUDE2HopfnOMuyhsMhhxlw9Tc0HY5pSZIkp78XXAyFC53lGMrgMNzzuaKYcP9IRjoYP5P4BLXOwViaZFEIS8Bv9g6JA8sGJg/L7E/xf/90+D1u3P4NcDa8zxkORyOO42RJ4nmexShM4vHYEoVOEgZh5OuqVq2UGYbpNBuDwQAhVMgXU0YaftxxnPHY4nk+m83OzhYdxynkS4dWjty6detrX/uaqmrnzp3b2dwqFIpT1erO7m6z2TQy2XPnznh+yDDcxtbm9tYOywvVapVh+eMnTjVbneWVQ41mZ319bafeePDBhxFmeE48cuTI5s728vJsJpsnURIGcSaVPnr4yOnTp48cOtZttUWRV1UdM1wYJJbp+b67tDBHqWC7set6DEWKomhGjhPlueXBwHK63f5wPNK11NnThxdXltVsMaEoxnyUhAmDSRDbrjsyRxhjqzEybWtoWnfWN+5/4IEwjvvDwclTxwuFQrPZZFnsRyHLc/lsrtVqkYS++c1v3tzc/M3f/M33vve9/+pf/etvfeubly9fXlxcvHXr1mAweNObHoEfzOVyzzzzVDqdXl9f932/WCzzvJjJZHzfVxT19u3bSUxLRyuNRuMP//CTnufdf//9jz76KMuyq+urnCg4jtPqdgbjEcaYEwWBJI7jjPs9hFA2nwOSsNls9kfDSrHUarWiJM7lcpbrXLlyxbZtUZbDMGQxTpI9WlwURd/1hsNhVa9A5QyAKmRUx3HS2YwkKoIgcGHAsiHYTCZJwgi84zjtbsfzvCD0aJxgRBGmi/NzAsfGcTIaD8BTw/E9RdFWlmbBQzEmCULI87xREPieV6lUwjAkPeJ5XkwSnGBKaRRFHCvuBRFDMcYE05ggRAjFAqyWQIhyb2znXheErwubiR09eq0GZ3IHQnTCqgNgc7B7nOTVg4/zPe8fvIGAkGGYiV4W/EgiipOEhlFECCEkdl03CSPbNleWl0G4ZFoWRiiIo1QqlU6n+2ZvMBghhMrl8tTUTLmcuK6fJMnq7bV8Pt/v1TY3twRBePvb3wGLZpeWlhiGeeWVVxzHfcc73lEolF58+TvNZrPRaOkp474HH3j00Uc/81efnZqampqa2draGUn9ZrNer9dnZuf/l3/+C1EcXLn2aqvRSsLk1KnTlXK1XK0oiha4AYe3IjeM41jRldnZ2Uw2bRhapVLSNM1xbMzKFPMMpixHoygw7SAIAtd1lUxuanEl4kUfc6WpmSOnzxqpzHaji1iGYZiYJJhFLIsH45HjWKIszU/NkVrtIz/2kwmNB6P+cGyWqhWGYbL5zK3Vm2kjA0pIjuNYljMy6WeffT6Tybz3ve995ZVXNjc3//k//+fz8/N/+7d/G8XBT/7kT966dYtSks1mNzY2yuXq6dOnwY+wVqstLCy88MILhIBDD0MJzufzrus7jjcYDC5dukQpjuO4VC08/PDDCwsLURQ9/vjjGxsb/X4fzlaO41RV9X3/xRdfNE3z5MmTH/3oR9u7ddM0eZ43DMP23GazOR6Py9WqZVlzMzMgolAleWZmppDLg85J07TRaDTp3IIgpBSbpomNPRMtmINxXc/3/enUdBAElmXxHEPjhCQxhxkW0ZnpKk2I7TqmaUVx4Eccz4vpdDpKCKVoZFocw8iynJVlkRfS6XQYhrZpea7vhxFGDKwjJRQFCDP4u3JOEHRSiliWxRO7mkIh9/2CcNLyHQRIDzaBB4OQvGb6/rtl6mQs+OD3oAPl7sFylBDC0+8dhIIg4AMS2ImsnEXsHneCCEJI5Ll0JpVLZ3LZtCzwsiIyCPue67ouQkQURT/0WJZVFA0caX3fb7e6wIal0xmWZU3TDoIAWpQkSdauvVoqlR568JFStXL58qtXr11r9/rNVufo0eNvffvbLl5+9fNf+PtHH330Iz/xkz//8z9/9913tzZrS0tLXuBOTVVKpYKR0m/dusGy+Oy504IgDMeW67qFQonBnGma6XTa9wMw+ZR4IU4ihmEcy3Rdt1It8SyHMd2bIbBM0zRt2w4pTpLEspxut2/oqZWVw4IgWZZVKBRYFlyCUETiwbAXx2E+n0/x6eu3rp88eZITuM6w2+o0wyS88MorlUplc3OzUCh5jt9utA0jpSlqLlcY9NpXrlxBCK2sLD3//POZTObjH//4cNj/0pe+1O403/ve97Isu729BZEGkxZBELTb7YcffvjatevAvK3d2WBZFobKIXN6XhBFUaVSabRrcKTed9991Wp1YWFhAmZeunRpY2MDFlcEQQAr71fmFizL0lOG5/uO70mKHARBlCS2bWNKBUFIGylD1TBCEAwDs5/JZMbj8Wg04ji+Pxx2Oh1BkBRNzeeKSZL0hoMgiOBEI4TouZTvuqmUXikWOIzjKGARJkmk63qvN6CU2o4XRTEnSAzLl8tlP3Cz2SzPciC1H4/HvuPm83nDMGzL2t3dHY/HkiACmCwIAo08juNEnuc4joVxe0IJgakDghFiKMGgwJrE0uRv5oDR0yRCDk48TWpU+gZmDyIN+kZQeE5IxYOw6vfsCb9fELL7G7xhGwGIg+M4FgSJxgnGWBRFTZUNQyvmC/lsptNu5vNZiRf6vY5pjnluT+yfyhgzMzOIMlevXl1bW1NVdWlpec84jKBUKqVpWrfT39rakiRpeXn5Zz7yI6ZpfvWrX33p5VckSbJshxXEBx96ZGFh6a8/+zeXr1750Q9+eGlp5T/8+v/7oQcf2djYmCnPTE1NCSIvivxubTOXyziW6ThWsVhkWdxqdRBChw8fLRQK1tjqdDpjx15eXj58+LAoikkYweyPLMssw4T7t8DzgPhGCK23ahhjx3bDMFZVvVSsFAolhmEYiiRZQCSJ4iD03NF4yLK4XC6uX98pT1V7g24Q+YVK/tbqzSAOnnjyyepURZFVjhPSRmZ3tz7o9o8dPaHIsjnqx3G8vb09Go3e/e531mq1xx9/4uMf/1fvfOc7//N//s+ixN+4cQOExPPzcxzHnT9//tq1a9lsThTF6enpp5566sTxU1/72tcEQTCMNKBZzWZbFEVAXyzfBF1EoVBotVqg7b7rrrviOIYJCQg/y7JAHu30R1EURUl8Z20tpuTw0SOe521sbaVSqcMrK5lMptfpupZdLBQMTbcsa2D2U6kU2HAlCbEcxzRNhuHS2Uw2k4+iqDccxDEY9oQMwwQ0iaIgl0mViwWRZVkGqaKAMbUty7E9RdPDKHH9IIhiy3T0dIbjkK7rNEng4zBNk2e5paUlQgiNk36/7zrOZEZXkiQaeSzLChzPC6zAciyDGIQYRBBNGEowQpgS7nXMwcHy8nWQ6SSbHWwUJ/ch2PCexwwGTgnodfzaMWS8v1L7dT3nPw6iwkPRfS93juNA0JQkFGEk8byqqpqmyLKKMQ7DMCGEEBSGoe06rutpmgqLtbudPiUYYxxF8czM7NTUVC5XQAjNzs4nMe12u83GerFY/tCHfqxYLAZB8Bef/rOdndpgNGQo1jTtzT/w6NTsXK8//OQnP4kZ9rd+63deuXjxDz7xf8Vx3Gg1wzjq9kZ31jeWlxcLhfyx43dREpJ8/tqVV9/5znffvn1ze7Pmum672a4UyudO321ZVmfcLRQKMzMztm333EG91kqlUplMRpG1IIgpQQwjMCyNk9APCMMwA8cOw9Aa25lMjhGVkesxlslQxrWddErnMI5CF9OEJoQiHLiBG4Qsy9u2m9Dw5e+8srmzdvTEUVmRXN/LF4qdVhtm5Fv1Zr/fr3vee971drTvQv/MM8+Nx8OHHnrwxo1bN27ceOihhy5fvux53iOPPLK+vm4YqUKhoOvG7Owcx3G1Wq3T6dR2G7qWWlhYePXVq4QgnucnRQd0VvlKDo7gUqk0MzNTr9dv3ry5vr4OLwMhZFkWxrhcLlcqFUmSzt91tt1ujy2T43k38MEvqxIEMDxRKBRInNA4SafTuqqBjRCMSgFcFEQRIWQ0GsQkiSNCKXU9N0koaCR4nmcUUZIksPAKkyRjKLlcRuD5HssKgpBKZwllXT8YmZbrh1EUxQQlIxMgD0opJ0j5bK5YqqyvrcmyrKczPC8CRkopYTmBZWOEEMUkSUiMEow4jsEsgzCBaKQYUVwqFQ5G3cF6clKLQvzsDfUemLfAB5ScE7AUggS+AplzMpUzybEY42ivi/sfRUcnywPANR0mreI4FgSFEMJxjK6okiTwHCdwLC9wgeuUisVqqcgLrGPZo9Ew8H2EEC+IkiSl0+lsNmsYac/zNjY2Nta3dF2fn58/c+bcysoKpXRjY3N1dbXb7W7fujg9PZ1QZBjGmXP3MCx35fqNXn947733yYp6c/XOyxcuXLt56/Cho2sb65Ik+S6ulIsrK0t+4BZzOcwkR48cKhbzKV3b2drudrvFfBFjrEpqtVrd3anbxGEYZmpqGiEGIbS9tZtL5zKZnGmavh9A6+v7Xrvdtk0LY9xMhpRSy3IqlSlV0k3TlnjJtR2JF3LZNEYk9l1VFjmW+q4bJ2Fte9juNBut+vKhpd3GjpKS773vnmvXr25sbS4sLLQbbVXVDS1T294NgzhJaOTZv/prv6Kq6qc+9SlYQL+zs3Xs2DHfdwuFwtve9rannn7ixRdfBGMewzByudzCwoKqqoZhbGxsiILcbrfPnj178eJl0zRd1y+XyyBhwxgvLS5nSilo2NLp9JkzZ4bD4auvvrq9vY0Qgmxp2zbM7ILXUzVbaDQaDMdatm17riCJcRwzHMey7M7W1uzs7Pl77rVG4zurq6qsLCws+LFn2za45Xe7PYbjCCFraxssz6WMjCRJmGMJQb1eD1pNIrDZbLpaLCCS+I6ZSxmlQp5jGd/3h8ORKMgRoUFELMfdrTcxZgmlgiDMzc1NT0+7rttutWAVR6VYyuVyHMOapjkej4fDIaY0nU6nNRrHcRyFJIoRSTgWiywjcIzIcSwhDCLMZIrijbfX5aVJfEIPPWkO9z0IGMuyJs0eQghKC6DaoKuBxVTotZK3/6kbxCrGGAJyIm0lhIRh7DMsQoTwPCIcwnR2dp4kked5DCtHUWSaFsvgQqHw0MOPAA6+sbH1jW88Ztv2XafO/NiP/djRo0dN0+52u88//3yv13ddVxCEYrHIx8uyLBtGmuHYa9eumZZdLlff8fa3y5r+xFNP37x5O53OLi4u1hp1gHBu3dg+cfIuyx7Xa80rV16tlAq3b946ddeJuempfq9naPr09HSr3qrVaojgeq2GVMY0zSAIZVlNGZnhcGzbfqfTL5VKju0BXxeGoWWPMca6phUVQZKU8cgyjDSJCWYZWVN5XrTNcRjGgW97luXLPIdJo1HvtBqmSfOFLHRusBdla2vrAx/4wH/87d8CYbrnBaIoFsvl+k69UilvrN783H//ux//iY9+/OMf/6u/+qs7d+6cO3eu2+1WKiXLsj772c8+/MiDnU6n3+8rinLs2DFIgIANnj59GiMWdPCNRgNcemF5PSHEc30E/suynCTJxsYGxng4HIJJ1OnTp03T7Ha7IL6HCUOMMcyyKJo6HI382q5t22EYSooCMxC6rt++fduznWw2K4vSzZs3C5V8p9MBLTHGGHRwlFJIITzPaykjSSg0nBhjPw4KhZwsy4Hn+r4/ogSRJI7CE0ePeZ4fBpHj+X5EXM/3fZ9hOF4S683GxAAuiiJd0yY6FhhlhqOEUsqL4nQ5FwaBS+MgTJIkRAnlODZKGIVjWUwZShlM8VS1iA5oryc3yHjkwBz9BFYBDgT6PfhVwzCE2daDuRQeVlRkTdMy6TQQjKZp9rs9y7I0TSNRvGfhelCVxpLXPQg8jizLE+4x2V/ymk6no8Egk8lJomJZznhk86yQzRYNPcUz/NLSEk3I2tqapitvectbThw7FsfxhevPbG1tWZazsnz47NlzqVSmXmtubW01Gg3XdcfmMEmiTCady2cwpq7rpnghn88/+9xzU1NTKysrzWbzgYcf0nX9rz771xzH3bhxI18sgLVeOp2+cOHCqZN3J0mCWfbSpcuEEITZ8lR1MBhRhHQ9dequM0mSlKemPM9Tdc0wjMR0KaWQ3uM4BoGloijD4RCUg6qqAvEFZYiSkZl9O3cGYT9wbdNyXddQNVEQwtDvd7qj0SiOIvCZdoMRz/NJTAVJEwRxMBjpWur8+fONVv3bjz/25je/6Zlnnzhz5tSPfvhH//d/+2uiyM/MnPvGN/7hRz7w/n/2z/7J9evXVu/c8l1na3tjenq6vrOrKEoYxrdv3vrAB350d3eXJEhNG/V6PZvNJkmyvn7n2OEjd911125t++knn1peXmRZtr67myRxNpvFGNu2HYTm7OzseDxeWFgwTWtra2dpaanV7Ph+yHG8KIqIMq7rg7V+GIYwrrm0sowY3B8ObdfZ3NwMk7hQKIgcLwjC4ZVDjmVhihzLzmazgsiura1FSTw1NSUK8ng8Hlum63qdTiedzWqaIcsyw7Htdrtea4ZhyIrorhMnBUHI53KB622sreeyWU3TdnZ2dF3P5nKaoXth0Ov3x+NxEEeuJ/i+H0UBy7I8z/IcIwi8KPGYklK5wPOcY4193w9DH2wdccQzDJPEcRT4CFNFFCSRZxFFScKzmOcYhhI8N1s9mPoOXvoHg2FyH0hziASWZcGZJwgCdKBpPBg8XhhgjAWeh6I/lUqldEOW5Xa77dnOeDy2bTuOIvCQ5Hne8pyDMTy5ua7reSEhSFUF2He3xxuGgeM4SZgoim7oaVGUWSxgxBTz+dHQXFhYeNMjDwmCcPXqFdd15xdmlTSTz+cVRWvUW5cuXa7Xm8VCeXl5+ebNm3EcJyQSRT6Xy+qG6vvucDi0O12GZU+ePMlx3MbGxgc/+MFOv/eZz3wmV8j3+33DMMaWOTc3NzMz8/nPf/7UqVOt5uDOnTvpbLbX6/d6vff84Ptqtdqdjc3ZubkgiDDL2Jb7zve8e2dnN5fL5fN5bzCGIw88pKHelmW53+8DvGYYBijUoe4gfAI1vCAIIi/EcWyZI9eyHcfJZlKiKA57/e2drWF/AP0zYiOGYQI/4nlREtX+aEwSNDc39453vfOLX/z8tetXfuFj/+Lf/O8fX1lZ/q3f+s1f+uX/x9raiNLkB978iKrK/9v/9i8vXX4lpWuf+uNPBkFweHnliSeeMIx0p9V+85sf/fCHP/yLv/Cxs+fvmZ6e3tzcvHHjhijyLMK6rp89d3ppYfHatSvdblfkeVmWqtUqyESNlFir1TBmjh8/Xi6X//zP/xIj1nVdjFnAioeDcRzHKysri4vLo9Go06zVarVMLivKEsNxDMdub2+bjp1KpXiGVRRlujrFYkziJPQDWZZTaW17e9vxXFmW44iMx+OROR6NxtVq1XKcJKG5XE5PGcPhsNPuEUIYgR5aWu71er1u9/iRoxzD9nu9Q4cOgRQEYSwpMuZYx3XH47Hje0Eoj0aD0WgUhiFCRBJ5VVVUTY4CP5fPFLLZdMbAGLdajeFwKAmioeQwxkkc+r6fxBEH2y3iSJElgWdlgedYFq8sz9MDwmi037Ml+47Xb+wVXyd2AaBpIuCmrwVLWYEnhJB9WJXneYHjWZadmZlJ60YmkxEEwbasWq22tbXV7XYVQ6P7yOpBSgO2hUD1CxQtRH7ZSFFKdT2la4Zleb12T5G16emZo4ePPProo/ls9ubNGyzLnj17OgiCF55/tjOqdTqdXm+AKKPrhqrqAi/tvTBB4HjYNUwSEoG13nypeuTIkcuXL2ua9sEPfvC/f/7vkiRRFGVrZ9vzPEmSKELHjx8H+2qe5zOZQhAE37lwgeO4fn9w733nL1+6stuo//N/8YtbWzuvXr3iOv5HP/rRJ55+KpPJaJo+V6yAwhjKCvDGpZQ2m00ISAjCSe/tJh6005LAi6JIkmQ47FujcX/QTaI4jqMkilzXdix7b7sYChHCvh8ymFM03XPDwcjEGH/kxz9ar+/+5V//5Y995MO3Vm+urt744Q+8//Tp07/x658gJE7icDweHj9+7Md/4iO+62Rz6du3bz/z5FMcx43HViGXt203CIL/49/9h1euXH722Wd7vV4+n7es8c7mFsMwhWKOY9iHH34QY7x+5w7DYLDcTqfTa+s3MpmMbbuDweDcuXOapm2sb7344ou6ntI0bTQaR1HEcVw2mz118vRgMOh32tvb27woyKrC8jzDscPh0AsDjuMkXiiXy2kjlTYMx7JJnMRx7AeOJEkE0eFwyGBuZmYmJsmtW7cxxs12O45JtVpVdW04HI5HFsMwksaDG2KjXl+YnSsXSzdv3ICLLY5jihDDsWESm5ZlmqYfhVEsQ8GJMeVYFiHCcazAsyyLWQ7rilKpljRNa7eb4+FIVVVDybEMQ2kShiElCYcZeHuTMBAFXhYlQeQ4dt/fHgrCyYc9SUSvowehoJ94gKP9EWNIiZO0OUE+Hd8DyTU8EXiPQ2G5E+/R7rqmlcvlRx99NJfL9UZD2DFo2zZoHQCdh/KM7lshpdMZWAPiDUZLS0sMwzYarerU1I99+KO5XKG+Uzt58mS73b5y9dV+v9vptP775/9G4JhMJlOayk5PT8/NLVim0+l0TdPMpPlCoaDruqIoHM+Y5qjTaZvWCESDLMtevnz53LlzU1NTf/hHn5ydnd3d3d2t11KplCzLlm2fPXt2c3Nzc3Pz0be99dlnn52amitVKtu7u/1+/95774UFQB/84AdHw3632+62W0srhwbDnm2OZEH0PI9NKMzOKhzLiYIgS0mSmKZVKJfgjYXXAJ9CQhKRl1jMERIzDBOFoeM45nA0Gg8cx+l1OmNzKLCcJAksx6Y0VVGU2u4Og1lEKMEEEaqqchjG/eHwS1/60i/+0seu3rj56T//zO/8zm/98Z/+8X/87U98+s/+8H3ve9/f/M1fZzMpyxr/8A//8Gc+85mP/eIvPPX0E+9617u21jcuXrzIMJxlWaIoq6p64cKFwyeOWZb12GOP9fv9xcX5yA/6/f7Kykqv0/3Sl7508uTJ+++/v9Gob2xsIIR6vd799z9s2/bFixcr5SnbcquV6a2trdnZ2X5/CFbZi4uLmUym2WzazjgI3SRJDMOQFJnluf5waDk2QoiXRMdxBJ0zDAMYY1BswlxLr9dDDC6VSiRB4/E4Jkkmk9nc3CSEpFIpsCGGN9bzPNsOrNH4wQcfPHbs2NrtVdu0oMuANVv5QoEXhf5oOByNoGuwbD9JEoZBgiByLCYkASIglysKHMMwOEkSRGhKN0ReUFU18SnGOEkwy7IMx4LmmUShGccRxUwcJzBF8T3LzklKxAcocoZhNE2DccyDIphJBL6ukkQITTgT6CElSeKMFMuyw+EQnNTiOO52u61W69KlSwghLZ3h92vXbDY/N7cAjpRgMs+yLABxDMPADgMJ4+FgPDU1dfbs3ebI+upXv37lyuckQfy7L34+m82mdBUhwrK4UM6qsswwDMxlY8zKkloqlXQ9xTI8dJiUUkCPYOkXQoRl2Tt37tx1110zMzN/98UvaJq2vr4+Go38MDAMY2yas7OzHMddfPXyAw8+sL29vby8HCXJhQsXwPR+bm725u3bhWLu/vvv//KXv9zrthmGOXHs6NVXL/f7fXCh7jXa6XQ6l8tls1myvwzLdd1cLgcZEqqSZH8tpqzKDMIMw4ehb9njYa/f63UsexyHIWZoSlMJjYPQYxiczRgzM9PDbpdlWYyYiFCBFzTDUFWdE4R6s/Unf/xnP/+//IuLr17+4z/58/f/yAcb7fZv/c7/+Tu/+XsXLnxnY/0Ox3G/+Zu/eeLksU996lMPPHjfr//6r//e7/6nz372s2trG71Od3NzO5VKlUvVzdqOJEmnTp2CYQswOwQTpPPnz9u2/corr9x//33Hjx+/ceNGv9+/fPmKpmlxTLrdvucFhw4d4XlREESMaRB4mqbJslguFwmJDUMjJLZHlq7rqq7JqhIlieXYgiBwPA+BxPM8IhTW0RiaLstyFEXVahUxuN/v+16YzWa9wN/a2gZuE8oogecqlYqmOqurq7OzU77jWpZVLBSCIGg5rTOnTzMM02q1wCRGUZQwiW1wFkc0l1Mty3Idx3UshBDGSBQ4XuQty5qdrhqGznOMLMuSJJimGQSByMrgBAn4sCiKHMdSQlhRoZQ6cURCn0MJwZTCchmoPjGilCSIUlCYUYoIoZQQhmUhbCa1KNqHOrl9A/M3tnOpVArKUagwwzCMKAK8IQ5C2H+CDgwfdpoduA8JGQgZjPHEHzaKInhDYZbih3/ofcVSeWw7f/CJ/7p6YxUmXADv8UNPQ5Kuq7zAcBjFNMAEgwubIEgYsRzH8zyvqYYgCDCjBJvlJUnkBdayxpZlPfjgg6VS6dN/+ReLi4tbW1uj0ahYLsG4TTabnZ2dffHl75w6dQowzIWFheHAAjXGu9/9bsdx2u3WRz/60Vs3rokir6rqzHR1qlr+2te/QimNwyAOA0Q4sC1jGEbXdRj2KRaLQA9AkQ9vHQyvJEmCMaKU+q49HPZ7vd5o3As8T9VkkeUZkSUk4XmGEOKFdqO9yzEsSQjDMighTmRxgsDxIsdxo9Fo/Ykn3v+BH6lWZm/dWTt85MT584+sr6//+Z//+fvf//7P/OWfy7L4wgvP12q1JApFif/xH//xX/7lX3744Yc//vGP/+kf/8mb3vQDt27devzxx7VMyjTNH/iBH3j44YdfeOG5fqfLsuzNmzfPnTl78eLFqampM3fdBVble8RgrXX16lWW5cIwlCT5c5/7u52d7cXFRWDwS+VCp9tSVEmUeMwkQegghMIwjEYjWVUMwzBti2EYN/BFUeQ4Lo5jgeNN03RdVxalQqHQatdFUWR5DiZjVFVleY7juFQqNRiNYLgxXyyUy+WUEYDAJZtK37hxo5FK8Rw3tuxerwe7ljHGvV7PcuwgjhzH6ff7Y9tSlJzveXEcYowYhmHw/ph7EnEcx7FsEPiQe6CeBN9Kx3MBnZY5RZAkjDEOAoAzwzDmDraC9ADHAKfypBwl+0uJgWyYzEzAxYFfy/XjAxT/eDxmGJj73ytuKdkDWqMgCMMQYywKAsMwYEEpicqkR40TEoV7rSmDuSSmXhwghHLZwtGjR0+dOjUzM/P0M0/evL26s10bDgY8J0J9EsfxwtK8IokUJZZjKkQoFvO8wDqO47t+kiQsG3CsYBipyVQUzDFNynIARXie5yX5xZcvzM7O3759R5blhcVlhmF2d2pLK8szMzNbuzuEoFOnTn/pS186d/fdnW5/Z3ubYdF4bB89evSv//qvFxYWTp488fTTTzWbzdFgqChKo1FLorBSqRi6bOjyaOjHcWRZJvgXh2GQzWY1TbMsC+ptoEMppbCqTeR4iEbPd3zXcT07Dvck9WDihRDhBRYh1vOcwaCnY81yHZbl4oTanh8nVJIVyx4fO3bsiaee+sQnPvELv/iL/6/f+I1/+S//n5/+9Kd/6X/9X8fj8Z07d970pjdtbKw9+uijmWzqwndeunPnDs/z73nPe55++ul2u/vjH/mo43jPP//83NycF4elUunFF19cW1t785sfETn+lVdeOXTo0Pb2drlctm37pZdeOnPmdCaTqdfrg8FAVdLT07OZTGptba1cqubz+Xvvvafb7bquTSldWlq6dOkV2zZFUWy3Q9d1EEJRFLmWl8qk4QqBqQCWZcFhqFQoxmEI7AvDMCsrK88//3xCyalTp6IweemllxzPLZcrcRybtg3uEp7n+b4vSXI+nw+Jl81mDcNAlCqiVC6WOJaVJKlcLnueF4RhEAR+FELAxHE8Gg2hIpMkWRRg2QvlGGZ2dlbXVLpPInCYSetpscDXdtuQpQD01lMZ8BYbDocEB2yCMWG4g2GDD0wbQRhMiIrJfcdxgiCA+vhgxzhh21+HahqGEUVRGASwe4DjOIHjwV8ZnojZ13MDnagoGXTAffQgVBtFka7rhw8fPnnypCRJly5d+qM/+iNVk0mCWJbN5nIs5jDGrMCn02nbtnu9Ds+hcrlUqhRVRaKUcJxhJZYoihwnUIKBEgj8vU3xoigaKQ0h4jg2pbRSqVSr1a3VjeXl5W984xv5QkE3DNd1y+UyxcgwDNXQv/Od7/zoj/7o7dVVTdehQvY8z3Xdu++5u97Y/dKXv/iff+/3/uqv/mowGABhpWmaNR5VSsViPgf9sKYpjuNQmoShPxz2HceCYSKAf6DbAUd6MBCYKlU9z3EcJwx9zFCMKcaIZ/Go3ytXirquWPZ4OBwmSSQIvKoqfMQTQhCKEcIIkSSJCE0IIZub6/fdd98rly4/88yzH/rQh37n9/7zE48/9Za3vO1T/5//srOz9bP/7J8cO3as3W59/R++urK0uLm1vr6+HvnBD/3QD62urv3sz/7shz70Y7/xG7/xXz/x39a2N2dmZrrd7u3bt5eXFx944AFVVb/z8ovHjhxtt5uwIA38s0HPOT21cPPmzSiK3vGOdwWB98yzT9XrdVVVVFX2fKdQKMzOziqKPB6Px+OxYRg0jGRZhpEFEKOBsMZxHEWUYKoo9H3Hss3R2PM8z7fL5XJMkl6vhxF76NChIAp7vT402IqiybJsu06j0SiXqtlsdmB2R6PRvffe2+t2V2/eKuYLlFJw6WYYhuU4gihEoCRJoiIjLNi27bte4LtJzAiCoMqyLIs0IaCAFTkeCxQYfJY10ul0EASYZcMwFESZ53nMMoRgQZRjxMYUEYbFhxfnXsdAkH0btdcBLVAfJvh7L7uYNIEHQxohxIlCFEVRGAKmAkU8wBUswlBtIkrhCvN9X9dyk4edlKMMw9x3330LCwuSJL366qvPPffcYDSYrk7Pzc21Os04jpMY9tLsQfyqLDIMo6qiIouKImeyeiZtUEps21QFfXNzM0noVHVG1w2OExRZE0WRUrq1tSWIXCaTkmVpfmHW85xvf/vbR5YOE0J836/Vau94xzu2drYtyzIM4z3ve++HPvShf/fv/p3tOE8//fReafrii77vX79+/bOf/ey//tf/+p577rnr1Knf+I3fOHLkSBAE+XyeZdlms3nPPfcMBoPNzc1isbiz04KtfWfOnGk0GmD4lyQJEGVQ5U5EDnEcK4IMjoCua0uSUCoXLWssSUKv38nnc5lMqtNt+b7L87znuYZhqIwex7Fp2cPh0PEDzHAMxyaUMS2nPxqLkuK4/n/9wz/8iz//zNrG+r/7d//+r/7iU//wD187d/b0f/gP/8edO6t/8ZefHg36iipls1l7bJ4/f/6uu858+7Fvra1t3H333W9+06N9c/SFL3wBYmM47BeyuRMnTmi6cvXVK0kSJUnSajQOHz6UzWbjOL506dIjD79lfX0dVr4QEucL2TAMJUk0zZHnO9PT06Y5EkVhNBqB5aFvEVEURVmyHLvb7/OioKqqH4Xdbndhdg4htLK0bGjarRs3VVmZmZkZjfvNZtMPA0EQwiAOwxAxOI6TMAyDKCIEsSyLYJERYhFCfuyU8oVcLuc6Tq/dETheVRTf94vF4mAwsB1H0dRCuSSI4mAwaHU7ju0HQQC+OwzDsHg/h1GSSqWqpXK1Ws1m0xzHhUHg+/7q+pbrumEY6ykDlLS5bKHRbi0uLouSZNv2cDjiDiac18UVs6/bpvu21hzHIfpd7fVBIHTys/i1KlAoEuBigt4GUwTVbBLFe054oqgoSjqd5jhuNHRBtcyyrGEYU1NT09PT2Wx2e3v761//+tbWFsMws7Ozx48f7/f7r776arlawJgyLGIxphRBWmBZNpXSSRJBDaApKqUUEjjP89PT0wzD8ZyI9u1MwB5b13XXsxFCMzMztVrt1q0b2WwWIWZ7e3txcfHUqdOb2zuO47qu/673vPf//L0/+PCHPjI9M/dHf/RH7Xb3nvvu/8Y3vjEcDlVJ/Lf/9t9+/etfb7fbR48e/YM/+IO5uZlMJjUajdbWVt/97ncHrsMxxDYHDz94vt1uF87e5fv+eNS3rVEYuDynciySJSkKvSj0JsUCu7/FlbAMolES+yQJkxj7rhP4LqGh6zpxnKKUAl6VK+bCKOptb88XFsIwtFzHDfwoiRhKMGUShGdmq6VqSZL1wWD0hc997r57726321/4u8+99a1vXV29tbm5+cQTT9x//32/+qu/+pm/+PNsLp3JZJ558qkXX3yx3x/ec889mmZsbW397ku/e/8jD/3SL/3Sl7/85a9+9atvetPDKU1fXV29+56zHMeZ5iiXy/3UT/0Uz3M3btyo1+vz8/Pf/OY3K5XKysoSITEsh0mS2DTNH/mRH7lz587YHG5tbYA6n+NYjDHIvnVdF2XJC4Juv9dqtSzXgTbStm3XdXVVBdEIIaTdbluWJSmypmk+G7Isq2iqqmobGxuNVqtSmUqn01s726IoplOpzc3NVE6DSXGWYeIgJPGe0NI0zZWVlWwuNxyPGu2WZdtw5Vy5chVhJCsiz7D7sAhhMM5kMjzDRlHUarWGgx7w2AzD8KLExYnrB1EUJRS7lsNwAsMwtuNQaHejkJsEzyTYDgYhfq21hCiKNI4m3/m6cnFy52BPSBFVFIVlGMuyAFBhGQZwJ1YQ9zjJOAavAT+IU3qqWCzOz8+Xy2WO4waDwZ07t/v9fqfTkWU5m00jhNrtZqNRS6VShw4tW/aQwZRhEceyHOYYhpFlRZWlbDozGPYCz4/CMI4JTRISJwIrXL58eXZ2dm5uAVHGNC3f913Hbzab4J1z/r57VlaWbt++tb2zqapqHMe3bt0C/cri8hL2mFdfffVXfu3X/uzP/mw0Gv3qv/m1X/7lX75x48aP/9RPfvWrX93Y2Hj3u999+8b1+fn5T3/60+fOndvZ3ZIkYXd3d3FxUeTZfDbNs3hufrpRq0mCkESRyPMch9zYn5kqMQyjyhVFUQaDAZA6cOKCjGEShBIvRZEsy6IkMizLihJLkcwwmGPY0WggSQKl1LRtThCSJBkMRmzSgMehlCIGY2BsKTUtvpAvmbZZyKdfufDi/ffd+5Y3P/zE409yj9z3Uz/1U7/9W7+5trb2pjc98uWvfP1nfuZn/uIvP33z5s2zZ8/WajWE0Obm5unTp6Mo4ljhzp07Tz/99L//9//+7rvvfvLJx+M4Ho/Hnud9+MMffv75Z2/dutXr9aIoBG4T7GHD0B+NBwiTOAnr9TrPc5Ikffvb36aUzsxOHTlyTFVV0zTr9d1Go5lPVaCLI4iCOkMQhIwoxHEMHxmlFLr3+m5tNBpVqkUoBXVdVxVUr9fX1tZGozF4Z8D5DlZdkx8EmYQgCKlUisQJRgg++vF43B8MLMfmREHTtG63u7q+pigKz+xJNaHZA5eLlKbDXVjcAA8oimLfDlU9ZTleGBOGYcBDQJZlczzEDI2TmKIEH56boa+9TUpKGNgDGADwTEVRzMADqAD2DaADdOIba1GEEMGoUCgIPD8YDGzbFgSBY1hIdJEfwK4fSRThwUVRXFlY8TwP+gfTNGFumlJaKBQ6nY7neZlMJp/PY4xt2/Z9X1Y5SimDOUEQeVbgOE6RNVVRcrlcp9MyhyNB5PL5TDpjyKLEsnhnp4YQ0jRDFGSEsCTtLQB1HOfYsWOYoY5jqarS63eGw/709HQcEFhCMB6PL1++/KEf+7Enn3zy6tWrf/B//ZdPfvKTq6urWsqYnZ29ceNGqVTK5XKYJOfOnfu1X/u1T/zX//L5z39+2B8oiuS7bi6XKxYLKd1wXMs0TUWSTdPM5XKDwci27XK5PFmE0u12wQ0FdJLAwQLAjBDKptLwGQVBQDCCxSkI47E53K3VpqamOI7b3NmGHp4Q4nuJ67pxHEqSJMkilPc8z6czmWPHjj/99LOHDx1vNluIMj/4g+/7yle+5pP4Pe951xOPfyudNiglb3nrD+xub1Wqpa9//esz1SmGYZ544qlTJ05SihVFabe6V2/dqFQq7Xb7rW996wc+8P5mrX7hwoWLly6wmHnzmx9RFGVzfb3RqB87diyO452dnVwu12g0ANuAYSW4oKvVsu/7kiTdvHlT07R8Pp/P5yVJGvdNx3HGlhmTxA/DVqc9GAwIRhzH3X3mbBRFiND52dlOq7166zbP8w8+dN+VK1fGllkoFDhWGA6Hpm2FYTQej6dmZhzHi+N4enYG5khTqRQrIvAQSuKYxglNCM9xMHefTqdZjrNdx49CzDBhGI4sM/RdIAYButMUVZZlDnOu6xqGAQaNsJI5jiJKacjJuVyu2aq7rsuy7Hg8xhgLHK8oUqFQ4DguDvcz4UFtCn7DlODBgvOgkAVuB7vBN96AUaCwdDqKKKUJwwLMBa4NsiynDMMwDGAgXr1yCUAIKF/321Hc7bUZFucLWU3TgtCDYfB8IRv4Y0IIxglGCaUxiWkcBXHAJWGkyVroeqY5YjHWVYMV2cAN5ubmbty4YZr2kcPHisVSFCW25WKMz58/Twi5/OrFdru5tLQoiBxCaDwee1YoSQ5J6PXr16dn52RZsSz7t3/3d1944cVr166fvOsuSZJqtdrszJykyN/69uO/9R9//RN/8F9+/Cc+sr6+HgSBLIuzs7O9TjuTSUdBmJrRa7vbKytL/X6/UimFvq8pAiICixNz1APPvyiKUrpMKeUYQlkqcIgmge95hBBJkhx7xLKsF/jwZjoOJ4pyQgnDMJSQOCYMg3hehN4hjmOCY8vzvShGfMQTXpSFbDadz+fN0bhYyBq6oqn8+XNn/u7vvtDc3Xrg3jN/+5Wvdzqd97///V/60hcJSY4fP/7tx7551+mTsizX6/XhcHj+/PmLF14RBCmfz7/7XT+YMKjdbouiOB6P/+Iv/kKTlVOnTrEcfuqJJ69cuXLq1Kn5+fn5+bnt7W3LsjzP293dVlUVLgxV1avVahzHV69ezWQyQRAyDJckNIqSfn/Y6w08zyvnS77v266jGfp0scjynGmaY9vK5/OgyR72BzzLMggDvf70008zDLO8sry4uDjoj2D/WRzHMzMzpUql2+0Ph0O4/PaG41i0Z5kZxzzDBp5PkgQW0QyHQ0mWwzjqDQeO6yqKksnnNE2D6zNJElmUYNdiEkb5fNbQdU01CCFhEDi2DU8tFqtVWdL0lB9ECGPYHmvbJqVJJqULrMRg8l2y/nWaT7o/OjgpLyd88Rtjj752MOJgMmRZ1vO8MAgADCSEJBRhjBVFUUQJBlgxQqZpbm5uWpbFYTwB5aEABrrMtu0oihzHRoiCcWgcx5ZlKhJKEKaUkDhKaIwSFAVB6PmqrCiKkktnaJywiMEEJQmNgpjhQ0mSVFUHSTGA3aVSybKsmzdvcjyzuLjYarUYFuVyGdM056eXUqnU+vr6/Pz8oaNHfv3Xf/13f/d3X3jhhb/5m785f//9u7u7Kysr6XS61WmPt8bvete7ut3uxVcv/9q/+ZVPfvKTqqpm06lOp3PmrrvM0bjZqnuOXSoVWq1WLpPieX61vptPp9VcBiGU1pROxyahzzMMSxNJkmKf5TFVVTlJEhL6BGNV5IfmUFG0MHAxxoTQJIh4no/CMIyTdDotCEK73bEtZ2ZmhlLcbrdZSSEMSiiJkyQiSUxjWRYLhdytm9dr9a181qBJyLH0xPHDG2u33/Oe95w9e3Y4HPI8XyqVHMf+7d/+7Y98+EN/+7d/+7GPfexLX/hivV5vt9vnz5+/fftOFEWf+cxn3vne93z729+em5u7fPny0tJCtVT+1Kc+9c53vf1jH/vY5csXm80mxzBzc7PwmQZBEIQWwxKeF1VNYRjGsseypKZSKc/zer2eKEqu66VSaZblzLHd6w1iPxIEwQt8lueKgpDNZovFopYyQNcO00PQ20OFefLkyeFw6DjOtWvXet2B4zjpdFrXDUVReoOBruuyLDfbLU3TFhYWarUamyBNVnieN3Q9Y6RGg6E5HiuKcvTo0WazORqNFE2dnp4OwtDzvDAMMylVFEUWM1DNCoIUer4VRpBCEN4rj7PZbDqdZll2c2DanhfEcYJoEsBeACZJkvGwn9JkFIdRHOwBM5PbRLAG7enBoILAIOg1JmsHRW0Hv3lyR1GUIAjCPWpFiuOYQZjjOMMwQs9vtVq+79P9spbjOA5j0LjhvRmlEE4UVVWhSCOEuK4L5F6SJAgjhChDMaIxJSiOEE0og9g4DKkoCoJkaDqoBVjMptMZwsZzc3NJQre2tizLzueLiwvLhmE89thji4uLCYksa5zL5Xr9DgRYv9+/fft2qVS66+yZb3/72x/96Ee7/d7LL7/8jne84+Lly3DSf/mrX0mn06IofuADH/hPv/tbP/MzP/XNb36zUCiUy8VbN26uHFq2bdu0RqlUajQaGYaxvblezGe3t7eDwDdHw5mZGdd1F+ZmMSUwOhCHQYioY5lxHIs8Bx5WSRKTOCJRKHKYSJwsq5RSzw8NTRlbNIwTVdHjOB4MBlFCoyhxHM+2XZqQmCBVN3K5lCiwCYm8MPB8t1wuDHs9TZF9z9naXOM53Gk0n3/m6XJl/oknvl0s5E6ePPn5z/9dLp8hhExPT//xH//x3WfOMgxz69bqxYsXK5WpbrdLCP3GN77x8MMP7+zswOAS7Db71re+tbm+cf/95zHGTz7+eKvVvOeeeziOe+6550rlNFjf67oahkGn08lm8gzDuK5LKc1ms5pqVCqVKIpUxTKMNCZROp3u9nsjc7y5uel4rmmaoiLD0NDU1BTPctZ4TBMCXYxmyePxmGKQauF0Os0JPDisjsdjUZShtmdZFuQ1Ed3zf0ilUuVSGVMUBgFsHzMMI5VOIwZ7YUAI4Xkec2wQBJqiQtOYJEkc78GN9VoNNr8KnAj5Ay5jQYyarc54PGQw9mw7Cv1sOqVIgu/FPIMFnkV0n6Cf1KKvQ0oPBhiEBN1XtLH7twl+M8mNB3lCGIEHQ2Vuf5sSwzAHdaF0X3bD8zzHM5ihCYnCyI/iAGEiiJymK1EcEBpHceAHbkIiXmB5gaUoQUmMSExRAgoGntszmCKEeJ4XBSHsWw6DWOSkarkCm150XRdFcWpqCsiDL3/5y/fccw+suXRd99q1a7qunzp1Coqora2te+65Z21tDWP89re//fd///fT6TQh5O6775Ykqd/vq6pqWdbZs2e/8pWv6Lr+4IMPHvTtS+vGcDis1+uyLKqqev3G1ZWVlcFgMBz006kURYkkC45rCSKHGcpymOOZKA54gUWYUJQIIqcbaiqta7oiK2IQehQlPMPK8t5wM5BJoDhxXY9SynHccDhutVoJIbbjMgxTKJUWV5bnFuZTqVScRKY5qlQq/UF3NB56ru3aZqfZmJufvfLqpWw2Wy6Xoyja3t7+4Ac/OD09ffXq1be//e2wY1gURYi34XDY7/fhE/z617/O8/xb3/rWTqfz0ksvaZp28uRJSulzzz337LPPHj9+fHl52TTNjY2NQ4cOKYoyMzOTy2WjKCSE5HI5SRY2NjbS6XShUCwUCjzPdzq9jY2t8Xis63q73Y7jGEDsbrfb7XZd1wX1WafTGY1GruuOx3uyb5CbEUJ0Xed5HuRslmXB+B8YpQP9SAjZ3t6GGQBCCIicbNseDAatVqvdbo9GI8hpEAggXRqPx6PBEC7d0WjUarXazeZw1Pd9H65hSZIgW4Dn4tbWlqjI3W630+mEYeg4DhTDiqLADE2xmK+Uy0zCMQnHUIGjAkd4lvAsfIXwbMziENMAkQCRENOIQTGLGUJxQmgUkzCiUYwTwiEsMCxLEUMo/MEJwQlBcYLixB6bPMPqqsZiJoliliCGosD1Yj+ghHAsK7IchxkWYZ5hBZYLYHKO7qnm9oKcIpEXcEIZgmRelDBH/Yj6kYhYL0AxYQnlowSFcUxQQnESEz/BfqGcNXLa2O6b9lhSRDd0X7rw8kyphKMoCdyZal4R0TNP/sPVq8/ff/+JwWCbF8PhqLGxeXN+aaoyVVzbuJPNZ9Y2t+4+f9/G9k6/P/yJn/ipr33tH04eP9VqtDfWNhmCZ6szu5s7b37gkdPHTp07ecaQtIfuObl69RXP7OUMqdfcVUTG9yxKwkIh1+12O71uOpPT0pkgIVomRzmhPzY3dnaxKL568+bAcvumY0XJyI8GXuhSxkP8eqt3u9bijIJamL6921EzRSdC6fKUG9EAseu7tZgTepbjJKQ1Ho5832dYKZ3q2BZWFYckVR1hpy0EY2yNarduFVU9r2TmKwuqoB9aOqFrWdsJsoUKLyt3NjcKU2UcD+49fajfqrmjcVrO3Lh4Z+Nm+7GvvPiLP/sr//UPPk0i7ud/9ucwE4+tBmYtxfADx4x9p9usqSL3yAP3v+mRh7Y213e3d2q1miBIa2sbrU6PE2TL8adnF+46c7cgqbyoveVt7z589PSNm5uN5jCdmVpaOdHpjR0varQ6mMNOYPqxlWCfEaITp08FSXRnfW08HuuqqooSTggTJdOFkshyta1tczQaDAYjc5wvFwuVUqZcwZLcGQwJxUZalxVeVQSS+IHviDyTzuilSllPZQaW2xmNsaKM2v3GzrY9GsaB7zgWy1FJ4cfWgONJEFq+MwzsAfKtoi4tlLIlRcgZeUNOCVg05NRUcTqlZWnMYMpUK9OnTp5OGZl6o2HZtqbrumGomhYMzJlC+dDsAvUjTGgxk2MxRgidO3cuncs6Qdgd9PfGQ6GPh7MEvdbX8P//2wTOId/Lbe1gXp2Yi05y7Pd8wNclXrTPi8CAhWEY6XQ6n8/D9+RyOdgRa5qmruvXrl2jlML/ApZt2/aNGzeuXLny0ksv1Wq1hx9+OJPJpFKpwWCwvr5+7ty5U6dOXbp06ed+7ueeeeaZIAhyudzZs2cXFhaAec/n81/4whcIIU888cSFCxeKxWKtVoPqALbt7cFompZOp+M4XlhYgMkA3/dhMHI4HhFCLNPRdR3QM4ZhoijSNAPG0scjazAYgG9CGMWCKHGcEMaJaZrd/rDV6kRR1O9044j0+31D07vdriRw1mjMIux5Hix5NwwDHt913evXrzcaje3tbVD8QqewuLi4uLi4trZWKpVYFkuS9MrFlz/+8Y97vguOLOfOnXv++ecLhdLU1MzKygrYb0IVmiTJ9evX19fXVVU9e/ZsEATHjh3L5/MnT54UBOH27dsbGxvPPPPMP/zDP5w/f75QKKytrYVhCPuAV1dXt7a2AKLjOA4akP7+LYqilZWVu+++O5PJOI4Dq4fG43GtVnMcR1XVXC535MgRSZJGo1Gz2YS+APKSpmkgSQNDCowxTPGDHykoLnO5TLFY5Hke3H4lSSkUCrBPW1VV6CEppa7rWrYNfVCSJJ7nWZZlWVYYhuCW0u/3x+PxYDCo1Wr1eh0KYIB/YEINdokXi8VMJoMx3tzcHA6HoihWq9MMv7+PHto8dn8D2T8SUf9TtzfGIcTG6/45ibrJoTCZ4Tj4UG8U5UyK4ck/IaRBcgEVJgjiZFleWlp6+OE3ZTK59fXNK1eu+X64tLSysnzY0NPn771f11NzcwvZbP7y5SutVuftb3/nO9/5blmWO53Oxz/+8T/90z89duwYOCAxDAMi1VarxTBMKpV605veND8//xM/8RP9fp9SOjc35zhOs9mE7fY3b96cyBXgjuM4AG/KqhLH8aA/goY5TGLLsjmO8/0QJnolUQnDEFjpKIoYVnTccHNrZ2e75gUJQszOzs729k69Xu/3+57jJlFI4oQmRBS4lKEpijI9PQ2zLDzP+74vCAKcDuC8RAgZDoe9Xi9Jkm63m8vlLly4cPTo0a2treeff351dTWdTj/+xLefe+65H/zBdxNCPvaxj/3Tf/pP6/V6uVxlGEaW5enp6Vwu1+/3t7e3G43GysrK3NzctWvXnn32Wdd1fd/vdDqqqsL+90984hO7u7vZbPb8+fOnT5/GGK+vryuKAhcxqIXgUAB2lFIqSRLoaSeXShiG/X4fLnRoWTHGsO8efKJgLBOMsTHG+Xx+amoKIQTDuJqmQWTCgyPEQGxjsAhLEp7n44gQQjDHyrICw+i5XK5YKkGkOY4DGQV0eaVSiVJ68+bNzc1NyAEgYJyenl5aWur3u2traxQRTdNM0xwOh4qiQPMFnRE3wWDI/u5B9AbpzGuD8PsG5/f8OsMyEzT1IH4z6Tkxwgef96BMZxK6iPxj6CtCCNE9rWkcxz71wzAkSZIyQkLIYDBIkqRYLILPuWMNWJYtFouVSgW4OJ7ni8Xi9evXwSTmySefzOVyOzs7cRzPzs6+8sorP/3TP/2Vr3xlOBx+5StfyWaz0JacOnUKHDc2NjbOnz///PPPv/jii8eOHauW5ampKUEQBoPB5JeNogjEk+CGlMlk2u02kFEpI8MyfLPZvOuuu9Y2N7PZXLfbLRTLAPExLA9eYFEURYQyvGA5PqU0CILhcCwpOqK42xsGQaDrumvbmUzGGo+zht7v97P5oizJU4WsYRgsMx4MBkDEgSAOXEwn0FqhUFhcXNzd3YWxqdXVVYZhjhw58hu/8Rv/7b/9f+v1+tbWRq934tixY3ES/P7v//5P/9Q/ee75pxuNmihrYBEfx7Gqqjs7O4VCYWpqKp1Ow+RELpe7ffv2aDQChvDo0aO2bX/2s581jPTJE6fuvffeVCoDLDbGmOdZjmM4nmVZ7Hlev993TB/YAlC0wwaEidceqJ2q1Sos/cxms+B1FIUBJtSyxiSJDMOA758Q2gzDwEPZnhsmcTAeZVPpubkFTVO2traGg0EulylXK1Hox3EchEESx0EQ2LbdarUIEsHwG7AZgB5gqVu/3xcEoVwuC4LQ7XZhsMF1bdhrAsvhGrs7iiqlUnqpVIrjsNcbRIHHTAgTuCX7K+y/d6ghRL/PH0Lp9/yDD7R28DfcJmzkwQIVfqvveXtjBMJt4roPNi1wfCKEUqmU4zjdbpdSCjO7nU7n0qVL169f7/V6oihCoQJzIYPBQJKkRqNh2zaUsul0OpVKXb9+/ad/+qfX1tba7bau667rttttcOOzbfvOnTv1en08Hs/MzKyvry8sLFQqFUopOJRIkgQguO/7d999N/TuSZL0er12u82ybDqdhuzEcRzPC6KiqKoqy7IsqXCJ9AejbreLMcvwQm84ajabjuPU6m3Xi2TF4AWl2xv1+mPbcnPZQiaVVmVF4HgWMzQhSRSjOGIpAc8ymI0ihMCGKVmWJ9lGVVVVVZeXl2dmZgByJITUajUwtD169PCf/ukfv+1tb+n1ek8//fTi4uLKygpJ6JNPPv2Od7wzjgkgFjs7O71er1KpaJoGwjE4cLe3twEygfWPCKGXX35Z1/W3ve1toij6vr+6ugrAI/ArcAUqilIoFFKp1B4Jwe0tWoGFH2CYnclk4L+8fVNWjLGqqtlsdk87NhzCp8yy7GAwaDQaLMtC5QxpKtm30Od5XtE1SVVYlqeUIox5nk+lUpRgkHlxomgYKUBcyuUyDGHDUCtAsvV6HRLy7OwsXCQgW7t27dorr7xSKpXuvuecJEm9XgdgWHCXRoiBF8+BehMsFSZw6D+S2f5nMyE94OGNEMTa3tTFngnifkkJqXIiVqb7S03f+JiTCEQIkYRAeMMlJXAcSRAhxDRNSnAmkykVCoEfbm5uBkFgGEa5XISTu9PpQJsUJ8lwOCyXyzdXb9dqtVKptLq6WiwWFxcXl5aWMKYvvPCcJEkTXfVTTz1x//33y7J44cIqpfTYsWMvvfTC5ub6Aw88sLu73etsnjhxwvMCUZQNI2VZDiGIUkwpVhRtPLYEQarVGocOHWIYjmHQcDgaDod6OlWv1w09Xa/XFU11HI/lBNd1CUGypnueV6814IrRUsWg00+Hie0GQehTiglBDMYIMYIgDLq9YrE4Hg3K+RwlSUpVhsOhIAi52TxMuIVhOBh04K2gFMOCIZBGbGxsrK+vT83PXrt2LZPJVavV1dU7b3rzw7/9n353cXHxyJFDtmPevn17YWFhNOrduXNne+v4I4+86cJ3rgNHBzRgsVicNEL5fB5yIEIICkXLsmB4GhZrt/iW43hTUzNhGAKVHwQewyBFlQWBkySJ45hqtQq29teuXYPyARbI8DwP+6HgzFUUxbbtOI71lOE4jm1psixLkiSKPMyXDofDbC4ny7JpuVa7PbYd6OiSmDMKBsuyOzs7PM+zLJ9KpQjFzWaz3W67tiPJAsdxDM/pvJHL5TlRdl03CAIgKrPZrCzLEIqwvNnzPMdxQBMWx3F1qkJo0mw2u902x3HpbLpYLCqKVK/XQbyZz1QZcCKBKhlk+5CRvm8m/Ed7vzfeXmc3ivadvA+GEwip4Jbsj/9OforZX839PeMQhjsnTSbk8yiKTNPEGKdSKUVRLMtqt9twVjEMp2lGqVjJZvKU4OFg7Puhquq+Hz766FsXF5cNI/2D73nfwvyS74Xnzt7z2GOPLSwsAEp+7Ngxy7IWFxfDMNzc3BwMBtPT0/fee28QBIcOHWIYBnYh2FAWWlaj0dB1nRCytbWVSqXgHQD6aHLk9YfjMCaypFqmE8exF4SEoMFgYNsuQgwvShgxruPZth3GEcPxHC+32v16s2Watut4hpFKp9OSqIgcjwmlhPAcoymqoWuKLPEc2+l04jieXLXj8RievdfrNZtNGBQE6nVra4sQsr21kyTJ5uampiuLi4tXrlz5uX/yc6+88vJHPvKR7e3tqakpURTb7e6jj7716tXrU9W5SqUSBIGmaalUqlarwR1BEFqtFs/zsLnJMIwTJ07kcrnl5eVqtdput5vNJrARSZLYtg1XsCAIuq5jjLvd7vb2Nqyd2N3drdfrvu8DTGIYRqVSWVxcjPcduyH8IJ9D6wtoja7rULczDKPrOrAacNzYtg2QgaZpURLHCXEdDySvlMEIMa7rmqYdhiHFyPdCeKPWtzZb3Q7LstBZQKSJopjL5cD2oVar9ft9WHkCg3v5fH5+fr7f7167dmU8HqdSKeDGUqnU2tpaq9UKgoBlWQ7oO9gSDqAcTK8BG/7GG/k+AjXy/TJhQiaZEGNM99drcyxLD1COE76LvMGZG2OM9nVy+A36OEVRkiRJYhoEQRRFiJA4InEcrywvi4IcBMHu7q5pmlB8+r7/4P33bmxs3F5d1XW9VCoRQgLL5Hl+Zmbmm9/85rlz5yRJarValNK3vOUtn/rUpxBhbty4cfbsWUmS7ty5Ax//9PR0v9+fmZnRNO3VV1+9cePG0tLSaDSqVqsnj8+3Wi1A+brd7vT0NELIMIxSqbS2tpbL5VzXnZmZcRwnlUpBEZXPF0VR5gTRcV3DMKIwSSjxPIfheMMwOE6IEsLygq4osiw7XgijzxiRbtc2NK1YLC0tLASe06gH5XI59IOpSrnVasmybA4HwKr1ej04bR3HWV4+BGjecDj2fT+fz1um4/s+vGxZ17e3t48dO/H8888vLa4MBj2IgW8//tjHPvax//7f/yafz73jHe9YX78zHJjf/Oa33vLo27/61a+2222e503TBPeT8Xi8vb2tqirLsuvr6yzLZjKZWq2WSqXOnTt3/frN0Wi0srJiW47r+uAr5zjO1NRUsZiXZTFOIteNZVlOpw0aU9gZjDF2HKder8MehGKxCP1YEATgVgqOWITBgIRTRZVlURQ4QgjYMsRx7PoBZIUoiliWzeVy3rAD3WahUMimM45rm6bJUCQIRNdTsiwihDzPkwQRbNQ0LdtsNuEFm6ZpWRYcIkmSpNNpaHwIIaIoIoQ6nU775nVRFGdnZ23bxBiJosjzrOu6umaompJKZSRJYqANAGAKMilwnRASE5UdSPhBb4335TITRAcdMKR5HfQCPwVSXShB9+3rE2iH4CvwzaAURwfQ1NdlxYPPC18EWhxsmuCVZLPZlZUVhBDP8/BSM5kMaDJt2waaAboX27b7/T5YfZqmOTc3B93a5ubmO97xjj/6oz/ieb5YLNx333lCkkIhPz8/5/ve2bNnwjB4/PFvLyzMLy0tvvTSi7qu3XXXqfvvv6/b7Vy7eiPwo0p56tjRE/lcMfAjluE5VnAdX5ZURdYowQzmlhZXzLHNMvyRY8eH45HtOppqYMQ2W51mu53N5hiOkyTFcbz+cIARSwm2LRdRBsqWsTl0XVdRJI7j0oaBSAzvjyLteQtompbSNZEXqtUqy7KmaVJKOY47c+YMCHemp6ePHj1arVZBLQgrZY4dO5bL5ebn51VVDYJA05VUykiSaGFh7uLFC3NzMwAtqqq+traxtLTCMsLnPve5H/3RH22324VC4cEHH7x48eLq6qogCDBr1mg0isViNpvt9/vHjh2bm5uDdjEIgq2trd3d3Wazee3atbW1NcuyRFGs1+vgr/O2t72N47jDhw9nMhlonIrFIqQv+E0Hg0Gv17t16xYE4UsvvQTX1UsvvTQYDBYWFhBCOzs7/X7fsqyFhQXYMgJ8A7AUuq4HQRDHxLZdyI2dXtf3AkVRZE1FCGVzuZu3VkHeOL+4sLxyuNcfbm1tlUql+fn5Uql06NAhOMcVRaGUguJ8MBjU63XgXTDGsHc9CDx45RPkH8qTRqNx5846BwnE8zyw44dCEYKBHoBVDpIKZH+SEL4f79u0TWLvdZkKIQQLrhFCKNkzp0n2K15CCED2EKLfO89CVkTfYwcGtLmdTgdjLMtyOpUy9LQoiqPh0PfCJElUWdZUHTp427anysuO7S2tTAFq6vvhoUNHKKW2bbdb3fvuu880zZ/4iZ966aWXgyDa2tpJovj+++8Pw3B3dxfeh5s3b169enV5eRkwgGKxePTo0Xa7PRwOWZYle+u4Atg3Au8tnItgz16pVKDesyzrkUce2djYkCVVFOThcNgbDAihURRt79YhScYJBfyAMizGOEwIolTgMc/LkiAQksiiGCfhaDQKfJfnWVmWTWscx2I2m5ZleWdr++jRI+l0uqf3wzCczO9kMhlVVWGX+Pb2tq6lYAvA3NzcPzz2mG3b5XK1Wi0DrzUej13XEQTh61//+vz8nCiKzUb77rvvvXjxIkDzn/vc537lV37lC1/4wtbWlizLKysrjUajVCqxLBtFEYjUQNwzHo+NlCgIQr/fZxjO9wLLsmR5T4O2u7t7+PBKGPpRHNbrdVmWV1dXdSUNvRbGuFKptFqtdDoNkqNOp8MwzPz8/OHDhweDwe3bt69fvz47Owu7soHYWJifNQxjPB47jieIIsMwYFOmaZrvB61Wa2VuGt7qJKF7xENCMINAqK0oSq8/PHbsRK/X6Xa7mqbdvHEHFshSSkulEsdxpVLp1KlTL7zwQq1Wu3PnDsdxkBLgF49YDiG0F4E8g/GeFzjHcZQS3/d9x+VA1g3pCMpCiDEYxp2E3yQYGI7FGCOMKaEUI8wyCMFWi/36cxKKeC94IHHB64Awg8echBP09HuN3/cpdyEI33iDQwSOeSgCoQsKgwCuLU3TGMwAGpZKpXzfr9frLM+xLNtutyGBW5YVRdHS0pJlWXNzc5TSy5cvA845NzMLGCxUQZDSp6enC4XCqVOnYMhYFEXQl/d6vbnpAqV0Y2MDnhGKGZjDkiQJIdRoNFRVBfB2Z2fHcVxZVjiOb7bb7XZbllSCmGazSQgSJYnjBM/zhuY4iiKe58MgEnlGV0SYiEMIqZLIUDI2+yIPSkVeiiRF12SBZ1mWIApshOu6lUpFVVXgkeEKg8sUpAUwfdNutw8fPnzlypWtrS3f93d2dgwjvXJoKZ3K3rlz5/r1a2fPns3n841GY2Fh4cknntZUI47jx5549vz580eOHPnSl770lre85fnnn19cXLx9+/aJEyfu3LnTarXuuuuufr9/8eJFwzAOH7lHFOU4jiVJsS1nOBxLkmLb9uLiYq/XKxQKQeCNzdHm5mYul2s2G+qCkclkEELD4XDCT0y2VhiGsb6+XqvVXNc1TbNSqWCeW1xcbDbq5nB06tQJw9BAE+d5nqKqhUKBUGZ7tz4ajRiGTafT9MCuFJ5lESIMw/AsF4ZhfzSemZnp9/ulUmlra4fn+VRKh1UZkwJwZ2fHNE1JkuBVQUqEOQe4VFzPhlIOKGvAxnzfL5VKe85dUciA9T+cRhPyAHKgJO1NOaD9Bb2Ag4GwBgQuEwXpweyEXkshQEUKrm8QM8neZOl3d4lC3QgZ4/sG4Rt6RYwxFJPwOgEEGwwG4/EYXhuoGQBtB3hwa2trPB5fuHABpqenpqZgqQDAbouLi9ls9ktf+lIulxuNRqdPn+4Pus89/0ychKLEExpLslCr7zSatfvuv7fX71y6/MrxE0c5nrl2/cqdtdv5QpZluSQhvV7fdb1isZTL5QmhSUJs2wnDqN3u3L69Cv8VhtEzzzzrBX4Qhbbj9XvD8cgK4ihJqO+H3V7Pdf0gCMa2ZZl2EhOeEziOUyVOUwRdFQ1NSulyylBVReRZNp/PMQxmWLZUKRaLeYJREEeSqoCJo2VZzWYTAAxIRLBPAiGk6zp4TzSbzdu3b0N97vsuTCclSYQx7vU7mq7ANvlisaQo6vr6xqFDh0ejMULonjMn/v7v/17TtPe///2bm5uQKMbjMQiaofVKpVIAijz77LNra2vdbleW5XK5XCgUyuUyyNa3tnZeffXVXq9HKZ0Qj5ZlVSqVYrHYarVg5IVS2ul0SqUS2IXA3L0sy/AsIASH2srzvNFoBA0LlLWe50FdCtbvuVwujvfHnCklCGHMchzHi4LAi6PRaGp2jlJ64cIFx3V1wxgMx9DJTyQKiqJEUbS+vg7g6vT0dCaTiePYdV0QRUOkQZcI7wYMlEwwPN8POJAdQBRNRnUppaD/gnUue+N/DCNJUpTEE2QPHXBkmoQHPbBoDU3I9EkiZRj4ItnXKDAsi/aL2+8Xgf/IDU5HIFrgn7AVWVNVx/ZGo1E+m52ZnpVl+fbt291u1zGtYrFouw7PC1NT07pujOv1dDodBtHi8oogiHGcJAm5c2ftLW95y/Xr16empkqlUjabvXr16szMzF133eX7/pEjRyBv9Pv9s2fPXrp0CQ6U0WgUOAz4iGqatri4OB6PG40GnFxra2sAaguCcPPmzZs3byqK4nshALmj0WhsOwQzcRxHMfG8gFLKcDxGDKWUZVlZ1QzDSPFBkiSyxEkSzzCMqkosL7AcVjU5IVEqrXMcJylyGEcJQaJiI4SWl5dZhltdXfU8b3FxUZaF3d3dMAxd14dPCs7BJElkWd7c3HRdFxa1m6YtSVKSRLXaTiaTYznc6XQAg7l9+87Zs2c7nV4YWblczvf9mzdvVqvVBx98sNFoDIdDhNBjjz0Gk6xPPfVUuVyen5/f3d3t9oaqqg+HQ9u2VUWDaQOO4zRNO3r08Hg8FgQum8sAsRkEnsDKuq6DjAbkDdBTAXjW6XSKxSKldDgcFgoF8Mt48cUXc9nM1NTUnTt3Dh9aPnv2bLfbLRbzt27fbm9uKapBCIVytN/vl3IZlucwhQsVrDlpHMdCSkSONR6PeUn2fR/qI4TQ8ePH4akNw4CZr0KhMBgMQAsOXY+iKNls1nXdbrfLgq2TwKuqIssSJhQhBEQAFJ4izzOAhU5oBvgPSE2QqSck/uQ+2AQAYDPpEics/Ot4eXxAU3bw/p4V8X7gJUkC2fL7BdtBLuQgNcLzPFSzGGPDMDKZDMdx4F8IuRoYKqCMwXEA9GudTofjuMuXL49GI9/3M5mMKIq7u7urq6tQm4HV74ULF+C0k2UZIvDVV1+1LOuZZ54B57Vut3vr1q25ublqtbqxsQHKDwAAAO4CxgwUKrlcrlqt9vv9GzdumKYJJ3er1Wq1Wp4XhGE4Go2GgzF8ojGhECGCIPGiJEmSoij5jGaokq5KmZSWNlRZ5FVFyOdScRzm89lsLheEoeO6upHOFvKIZcBPVZKkkydPTk9Pdzqder0+kYkNh0NVVaGNgZN7cXERWp3xeOx5jm2blUrl2LFjSRKNRiOWZa9evYoQk81mLdOZnZmP43htbQ00Ky+//DL0QhjjZrO5ubkJEjPf92GmwTCMI0eO5PN52HELmC20XpIkPfDAA8ViEa6chYUFQRCAlAemvlKppFIpjLHv+2AGeezYsePHj+dyuV6vB7MdCwsLmUwG9AkQJ0BOCIIAfCnP8zDTwHGcqqqQMH3fD6JwcmH7YQg6R5bhrl+/Tgg5duxEqVTZ2dkRRTGTyST7znfdbhe6tn6/r+s6MHxQf004TCgHYMkUqA5YlgHgc1JCctCoQCKC4hCCagJpwlNCYnUchxV4iEiIWLpffzL7lANEzOTvSfV88G+gKKDEBfH2JJjx99HqQPzRN9zCIICjBarNIAhsy4UlM+VSFTJ5rVaD6jqVSvXaHdM0OYFvNBrgiXT4yBGEUKVSeeGFF5aWlp599tluv3fixIk/+ZM/KZfLLEMxxnfu3AGt2bVr16ampkCjdOPGDYBkFxYWer0ex3FLS0uBY8JusDiOYQ0YQmg4HEIPBt5EzWZT1/VKpTIcDgllbdsVRZHhOUVRMGIp8jmOS6XScBQqisZwPFw0iqKk9YTDSNOUbCEbRYnrewylvCQRSiVJ4gU2SRIvDBhekHmO5bgkSdbX11mGu+uuu1iWrdVqluXk83lBEPL5oud55XLZ90JVVXu9HkJoJpsHqQqlFCaJYB3AhJTb2Ng8dOjI4UNHn3322ePHT8IvNRqNADP81Kc+9cu//Mvf/OY3BUEoFAoLCwsAJ05NTa2vr58/f363tgb1fxi2NFUH1jSXy21ubnIc1+/3NU3hLJZlMdTJQH9blgWBBO8q7KPfc0vhuHK5HMcxsIsLK8uHDh2q7e4Mh8MjRw5RSu/cuVOtVuv1piTLxWIxTlCz3Y0pMoyUoamjfhf6TEEQWMxQmvA8jzELrAb8V5jEw+GQEEQI2djYgKMf5EeapvE8n8lkQFQMM+hhGAKRKIoilsUkSTRNg3YRkoFhGJqi7knE4piDoVtAESBLEkIAj4JClNnfiABYKIcFfEA8DaFMD3iuTcJm75/7ghioSzGh8M1kz5U7EURuwrazLBuR8B8Jwu+ZEnme51gBTjUWY1lSK5VK4PsATrpBQFUE4nqe50kUNxqN/nAA2eDo0aNnz57FGN+4cSObzb744ouu6545c+YLX/hCtVrtdDqiwELbOTs7e/PmzVar9ZGPfORrX/savKpUKrW1taVpGihFs9nsnU4Teg/QoKVSqUajsba2Njc312g0ms0mIGPT09OtVuv69euipAFiKbCMIAgCLzmeixBSFBWzrCBIHMdRjFiWVVVV11KKbEYxr2pyNm04XuB5XpyELOIzmYzr+mEYCrKEMe72esAL67pMCGk129evXwdameOETqdTKBSq1elGowFw/9TUFHzKgADBwKSiKNvb24DrgMIWChaGYQzdWF/fnJqaATQBdF4XL148c+bMl7/85dnZ2TiOa7UabC5ZXV0FTAuwrvHYymazSULBANY0bYTQ0tISGOqk02nPd69du1YoFFiWrdfrPM+PRiOGYer1uqqqhJDhcAg6xFQqBfjq7OxsvV5HCI1GI9BwYk3r9/tR6IP+RBAE0zSDOJFlOZPJ9IajIAhUQ5+MXOwDkHvXJ8gzlheP+4F38+ZNXVEXFhYGg4E56ubzeaDmARyBTYnXrl2D6hQkPkAhIoSMtErI3sYxSGxg7gZyc8dxPNvjACjhedEwZHjLRqOR74eqqrIszzCwihC6VsQwXOhHlFIGs4gimiBCKUzThn4E6RXtV9aQ9CRRRAjQ14RhGMSwhJAoTliWZQQREeJTiqIImsMEY4bhv4u+7IcZhC6hFFOGQSxlecqyCUUkIUmEKEGUp5jhOZ5HDI4Z1o1oJl++ub5RLlZK+UKhVB4NBoVctZDLf2f88uKpo47jPPfcc8tLS+ff/FCn03nh2efy+fz2lUvj4SiXyzXr9ely2bXt6elpJ2ZGdiRpua/8w+NhEOi6vrZZ833iuNb09LTvRa7jj4bmsN9fWVm5cukKIwiBFxSL1WazoTZ73SvXS6VSwrKXrl8PEVnb2WQYhuXw4NowSRIlo8KHbdumruuGJkVRpIiI4xhNx5IkmOYgpaY4jsNMXCgYtdotU65WlpZrtVqW5Ub+iFFEXdd3dnY4jsvlclHs6hrX7/cX52cty+p6fXXq5Hg8jmLkuGF/sK2qaq8/DgKSL+jTM/NBQFOp1KVL1xHm2p3hPfcsl/LGhjloN3YRZRYWFk6dOJ5Op/P54vbuTqfb96M4ZujXv/UP73vfD9398Lnd3k5WVAlJWMRjgo+sHFlbW2U5rKtCNiMHvsJgt1SSZTlIkj4hvdXVF0msRn4UB3EYhnEQMgzIU8xuuwYkBE3iwPV0RRc5kec1XpQ4jvP39nCozXZ7bm5OlOWYkFQmE4ZhbzBA+3i7IIoiSiRMsMT3HTOJWdcLlg4d5nl+p9n1YzQc2wRhQRJTuuF4rj0eqxzruq4XBFA6chyH4sSx7Ewmk0llMWYZzJeK1TAMR6bDi0q1OkUpDYJAVlWEUKfXa66vb+7uMhybsAxFtNbvIoTCOOR0FSGUuFE6nVYEmYmIyolUVuM4tkdjYG4xQpqu7OGckBkB6wcABh1wfDoIS9LXTkJAEZvsOzLhA7Js+B4oMidfwfujSUDO4H0xDd2XrfLMa55iklTRa1mKyddlWZ6IeBiGoRjFcRwGcafTyWWzhmHU6/UkileWlqanpzutdhTFCKF2u/PAAw/OTE/duHEDlteCsAsg3NFoNDMz0263ESIcx+3u7pZKpWarHgVhKnX4O995MQoClmU7nfbM1HQURQgTVZUbjdpw2I8o1jTN9z0g/W3bjOJAURTTHCX7y8M933ddV5KkQiFHwphlsSAKsiIKgsCyDMOIkiSJEo8QyeVy6XQKqkGGRaIoRmGCKGNbriQqnhsUCoU4jkVBxhgH/t5ugl53IIkKy7ICL0H5BH0XxwkwkwFLOJrN5nA4hLmHQqGgaUYURZ1OR1LkdDaTxHRjazMMo51afX5+EZACmWOXl88Konjx4sV77r77U3/yx49+8MOdTocQkkqlbt26kUrrQeA///zz5+4+I8ui67rlUlVVVde1l5ZWEEKrt+qg8gWwHthUkFLMzMyAswHGOIoiMIPVjD2eSdf1dDqtKAqoT/HeykoPIQRN5h6CGLqpTJphmGazGUZJThCCIGi1Wmh/QgAAkCgOQMsFgQcVH3RuwP3AvNJEVUcphSlE2+ahHICUA1VkGIaO58IgIsGIZVkY2en3+yk9tbCwkMvlYCEHIKIw5k/2bSu+q9sGIBXvr1Iib3CO2Ss4Dyi86WuB0El4TNLg5D488oTln/wsVLMHgxBsoPABpdtr4vAAjQgPwnFcRBJCCIMxyzIUoyiKoigRRRE0fnCgdLtdSZBJnICkUJKklaXlmdnpb33rW51OZ2lpaTDoqYpUKhc4jknpGV2Xo8iI4xijxPO8x594NW2kaMIcPXboiSeeYBHO57P1Wl0UuG63LbCcKIqtVst1XVlP+763vb0JA3u8wI5GI9s2NU2jlMRxjBmaJCLLYlEU8/n8sDPSdBlaeYxpkrCSJBkprVKpdDqtyRuoaVoURcVCmVIKkkXbtqF6BHAYYABA7W/evLmzswNTGlCb7fXP+709GGfBABFM/c7MzDAMF8ex6w42NjZs266Up6ampk6dumt9c1NVdVlV0qnst598Yjy2Tp46dfny5aNHjz765h8YDvscxzSbbccZO45z/MRRSollVUvFShB6rmsLAg+UPUJ7py2QCjAMAQWqqqqrq6sYY4C1QBzDcdzCwsKVa9dBKANsIUJoz7MvSUCmDzpnVVVBN8LQvYEEhBjgMwBukSSJECS6bhQlDKCGLIJtaoDQwk9BEwHCVLCTDIIAEBfY1SNwDEheoygCHkIUxXQ6zZljWMFdKJcopbVaLQzDdDrdaDTS6TQEpO/74Get63q/3wfIKooiDmJvgpccTEEH895Bag4dWOv7xqw1yX7wU/H+GsOD7eLr8uTrfnbyLAx+fcjBC9kT1hAKAFKCKORIAgMFGMOg0PbWFou5w8srkiA2m03X9iqlciqd2dnZev/7fiiKoueeey6TyRSyGd/3eQFHQXj06Eq/3z+0vPTKK69wPBPFYRSxsiy5flCtiMdPHBkO++Vitlar6caMZfLjcT+K/SRCmCGKKooSJ6qqZVkJiUgYa5rGcVBoJAyDOZ6P7CCOIsMwJEnwPG88HqqaBAwSLJyK41jVRMPQUil9OOxLkjQej8MwIoSCMaksa47jVKszzWZnenp6a2urUqlYlitJqmk6sqyVSqVMJh9FkesGk7FXGFcfDsdwYQEWWigUkphOT0/funWrWCyGYQyCzJSRKeRLXuC32+2NjQ3X88rl6k5tt15rwkVpjseGob/88nd+8Affe/XlF+fn5zudVqvdmJqumKZpmuMTJ05Y1ng0Hhw5cmRra8tx3HJpCiaGAckAJg3ADCBICoWCoihwbiKEANj0fb9arcLFDSPae6OVUZROpwHlt20b1JSyLKfT6SR0tre3GYYTRNEwjChKgiDQVJ1ihDGOknSSJAzD7YnpRc5znAl9B04zEBuCIMCAL8wlA8zOcZwVeCBPg/9yXVdV1ampqVQm3Wg09mYgHafRaMmyCB65vu8DNi7LMsxeAJ4EhLnv+1xyYICQ7pN19A1q7O8ZhAcjCh/w6p5Up5Oget134v11vwelp1AYJ2EM/wsxt/dEdE9qAzKcPYkcgdM9Yfg9/yiQvQPLAnyApuhhGEZBmMvleFZot9t2y3vzm99crU5/7WtfiaNk4dBcp9MKI19TZKqI6YweeDbLUZZLWBbxCRYQxjh55KFzzWZzZrby9a989dixo9VqKZdNGbrU6/Wyad20RoQks3NVz/P8gJJYmqqUu91uIZ/p9/vzc7OKIq2treVLZUUSx+OxoasIoQFNKCGKIsqyrKqwi5fyPKMosixLu7u7lmXBpQZiJkIoy2KW4eOIiCl5FJs8Jwq8xHOiJCo8JzbqrWajvbKywrGCqujj8dixPVg6e+AUQ5IkZTKZq1evchzHsQIA+js7O7Kslstlc2wXSsVjx45du3ZtY2PzuRdeyGazsqz2er2nnnrq6NGjkqr0+31FlvO5nCxLS8sLhJBcPkNotLCwcOnSJZCh1uu7iqKwDE8JZhkxmykSgmDPJEQyNMOQzWCeExhtz/OGw+FDDz2UzWaffPLJytQ0HKmGYciybJrm9vb2aDQ66GEBVQBsNfYdFCc08t29/MYSjDFJiB8GlFJJEBiOgyGjJIp5nhdUFUga4NtgYA0OL7gBfIgxDoLAcRxEYphLBE1yQikMyHd63dnZWcdxVtfXhsNhNptOpVK2bS9OzeD9AUUgfvr9Pmg5AZjxfZ+baD4nMQOB8T1T4sHK8/uFKFSVk4RG91esvS5c0f44xYTtwG/Ie9+dzKB7gc3iPRRr0hdO2MiEkCQhFKMJY5lOpWRRkQXRGps8zzOIQQgVCiVFUR577DHbtqcq5bW1VUkWpirldqdVrU4FnqPpUrvdOHpkpd/v+76YYtVut5vP5wSB73Wap8+cdBzn1Mmj4/G4WMrSJNB1PYrdMAhkiQ98p1wpt9tt3VD8QJmdnRZFvlIpMQwSRb5SqcRJ2G4LkiTB+E82m0UkkCReEBmEEGYEhmEUVeJ53vP2WiNJkhiGY1m+WCwLgtBsdCnFo5GZTmeHw3GpVEkSks8X4zhOp7OQ7rrdPlzf5XIVPl4Yb6V0z7oSnDI2NjYWF5Z7vR7IhqrV6TiOR+ZYVtXx2NQ0/dTp09vb24IgbW1tzc7PHTl6iGGYbqtdKJeazfr995+nhJw5c/prX/saw+BcLscwjKIohw8fZhjGcbzp6dlOpyeKsqrqpmmXS9O1Wg3IGzhWgIGwLGs8HmuaVqvVZmdnT506BaPYkLFBZAs6FZiEgDxJCIFhImDhEELAuCaRDxew5wWZPEmn05lMptftA6tBKRWQhDFGiDAM4hisp9PAHwIpALOCwBTE+z6A7D79RgjJpFKUUhiCyefziGHa7fba2lpv0IcNNrBSBQTiPM+7vg98NScIcRyPTNPzPEGSEEIJpQQhijEHsMpE0gk9A7yIN8bM98VLDiRJdKDr20to+3cmPwhp8I2Pf7A0nXw/hONeikbfPQWYPbUHm1AaRRFFCGNGEAVBEHhOgEYIIZTJZGzTGo1GumrMzc1Vlme/9a3HZVm87577nn7mSde13v9D7+1229lsWpVFa9xPZ/TQ5yqVih/YLEdZOR1G7mjcO3H02Pb29r333n3l1Uuarlj2iGeYSqWMGSqJfCGf5TjOMLRyOR+GLsa0VCoUi3lVlSVJ9H0/k02xHHa9EM5soD0qlYrrdDmOYzBLKWVZDGq7hETFYjGKYkIIy/IIJXFEctkCxth1dwDq4nnBth2O4z3PSxLS7w8qlYphpDDGpmmBlnVqatrzbADPgMICkg0hDFMOMEoCLwk6GdcPJEVpdTqAkfh+yLJ8r9crlktHjhwBsqGYyzeN1Kg/uHLp4nz1raoqE0Isy+p223Dx9fv9fD7veb7juCzLBkEYBCEh1Bw77U57coGBJAtS1uLiIkKoWCwuLCyIothut0EDDM2FIAhQ+4FZ4OzsLOQrqF0RQmEYAlgyHI1Gls3zPGIZvG+8AMkKwsz3XSZkQt/HlAocN1kxAHwDNJzgAQNT/JNrG65hoAEhg8my3On1oP3TDH1nZ0fTtHQuKwgCkIRHjhyxBiPQS8DqX3hMMDqZpBAO7atV6D5bckDl/QbS77Xhhw6AMcxrNwpOMuf3S55wpOF9/hDt94fca2vXvRimrzkC9jId2ntJe6o6llUUFSAsBnOtVqtYKCiSCv4uiiRlMpmpqalv/P++8cADDxSL+e985zuzs7OVcmFjY2Pl0KI6Erq9JkJUlZWlhflWuxEEXhgGkhBpspLPZAWBO3r08LWrry4uLtTr9WIur6hSJpMZD0eGoR09egQUxmPTK+QyGONUKkWTiGdxEgUpXc0cO04pHfb6uqJOV6c4zLiuOzc9U28G8BHEEdk/nmLfT/K5IsaM4zj5fD4M4uFwGIbhcDhmMOe4Vqqc6XUHqqrWa02e54fDYafT1VQjiqJ8Pn/k8DHbtm/evNnvDcPIgcwAOw/hpIvjGGY7RqORoiitVmtxcVHTDN/3VUPN54sXLlyYm5sDQzRZlkF5t7a2Zprm/Pz8hVe+c/jwYUWVdnd3vvjFLwJvBnlDUZSdnZ0oinTduHLlSqVScWyPYZhSqXRndT2XKxQKBbiIofWChlDX9atXry4tLaXT6dXVVcuyNE0D2QogosVicWK+JkkSKJ+AexwOh81m0/M8QRCKxaIsy8Bk5rJ5gRfbrU4cJbIsFwoFsCYALCQMfUIIwyAoNwghE5UYnA6T9ZtJksDlBiprkFhDyMApwHFcOp12fU9RFIwxWGal0+lqtVoqlUgQEUJAb2AYRqFQGA6HMMQMyY9Syk2OyQlGAo3j5KKn+1bce4Xfa123mQMzfnsXUxwn390hwUymePEBZzS6b6t6MG73ytc4+u4/J3KcPWkfwhShfQAJMyzDMK7rKLqm63oUx1EU27ADIEyAWZZleWNjo9FovOdd7zq8cuTXfuVXT993zrIsjGmlUomjwLZtTdM827Ftu1QoDoZ9MP81x8NqpUII2W1bMJ7PMAxGKJVKpVIpEsWGYaiKxPN83bY1TYvCUJYkeKNqtVqlUikUChMVMshlgiDIZrOAts3MzDSbTdd1YcXsVHXGdd0giDiO6/UGmXQuiiKGYaMoMU2TJKjX67VanSRJ7r/vIXCaAhwCBuRv3759+vRpkDgPBoNcLifL8tTUFAwKViqVTCYzGo1qtcbhw4cFQbBtR5blbDbbbLRXV1dlWR4MBmEYp1IpNa15gZ8vFlieI2EwHo/7w0Gz3QIkc3F+IZPJnDpxkud5nuUW5uZfefn5EydOFAqFo0eP3rhxa2Fh4cUXX+Q4vt/vq4rOsQLLhjzPO443Gpm6nuJ4zvM8sCmo1Wqj0ejkyZOrq6scx2WzWfDC6Ha7hw4dAsxjt94AaX4URZZlgTdxv98HZrzf7zMMA6PS4M3F8lwqnQmCIEpi1/cUTQ2ikCAqy7JtWqqs8Cw3GAyWFheKxeJwONxY34aJwU6nw/N8pVIZDAYXL16emqpM+jIIEKgNAUEFNQtCSPI8mF3mRQFaTdt0QUQOczPrd+40m81isZjJZUHR1h8OEkpEWZqoPrmD2QwfgENfUw0eiJZJz8rsD0BABTXZWU8P+HbT15KKBzPkpFJ9Y2pFb7jBccWyLMdyUDZMXmShUPCj0HGcOEkkSVY0led5kiCMsSLLt27dknjh53/+523T/PVf//UHHnjAJYHneRy7t2zD9VyUxEkU6JpsWU6r2S7kM7MzM3R62rLGV69e5dVCFEW+68VhlMlkioXy3PRsxkj3e90wjEVRLuTyoW6AvVoURYIgwTaLCxculMtlx3EGg8GhQ4d0PdXpdFRVJwSZpk0ptW3XspyxOc5kcq7rc5yAMR+GYcrIQMWYzeYHg4FtueVymWX5MPSKxeLNmzeTJIFuCopA0IuEYWgYBvhwN5tNjuPAz+Lc4jmQXGezWUlSJjsFwPEJlD2gaEWIGY1GyRjZtr2wsLC5uanreqvVAvkYgzBYg3qeQ2mSzRZUVW42m7lcYXt7d3n5UBTFjuPs7tbS6Qyg9o7t9ft9WK6CELPfX6GJ0hI6uunp6UajEYbh1tYWyI9g4pFSCm9ppVLpdDrdbrdarYK4FOb34VxDCGUymYkquN3tybKKMTscDsHzBiHU6/V6ne7c/OxgMJBluVDMAz+0ubm5vLwMCieQlUHufeSRhzY3N+Eih1IWFqIwDAM6GFCTMwxjuy6UpnrKmHAhuVwO7PwAHYUxRXD6msxIgGo3n8/ncjluEn7Qp33PGDh4m5BOUEBCKsf7HOAkSU6+n933U5mgNXRftobeANVM7h98BLTvZcjsewTvFbqIYow9z4spYRiGZxiouVVV1VSD5/lbN28KnPhPf/pn4jD6+7//+2w6JwiCbbvmeBiFviQJlJLxYOj7rudr1ljgBZZnBUJw4Cc8z8YBiQOipSWRlyml3e4wDGOGop3txmDYy6YztmVxDKdpKUqJ53kkIiInekHY7/Y0RU2lUlEQirwgavLudm1+nuu2e7OzsyzmXNdlGCbyiTVyZSUl8MpwMK5WqzyHBv1xJpOJIrK9vc1xQqPewhjD0btnGToaRFEEck2QhmUyGZZldV1nGGaCrQPiBxUUDK1PGGRZlg0jxTBMr9cDYTpwGHFMNjc3WUXp9TqZfK7RbmU8z3MDeDrXdiilYehHURQFwXg4TKKIxFGpVL506VK/PyiVSo7jYsTxPI8RyzJ8KiVsbGyUSiWMcRQFkiRgTEejMcMwpmnW63Xo2ZrNJhR4pVJpdnaW4zjwdMrn88vLy5/7/Bfife8vKJ7B9wmsQ8DWudvtQo3tOI5p26qqIoZBDMdxgqqqQPQnUTwxblMlmedZz7ZK+Vyr0wfBAAwWg0g1k8koigLPC9tjwZ8OITTodSbkPqgOgNXA+6N/iGVABrSxsbG5uZlNpYH/oPuegFCntFotCCWWZV8ThAfz1esgTbRvYMFz3CTXTUpQdMAl8WAjCzf6P2DidjDe0GvzMEII0dc4ke6/Howxdl2XMhhOQUKoF/imafpeSAi59957Txw7+cILLziWffTo0bXV9U6no2ZVOCw4jsOEgg+ayEuj0bBULGSz+SiK+r2RpkgYceXSlEcYSVQ5jrFMW+JlhmGiKMGE4znZc/oMclVVRYgEbsiyQqlU3tzdIoTAmFy73YVf//btO/l8sVFv5bKFOI6HAzOTyWQy2Xy+kMqqg8HA9yPPCxmGCQMShbRYKK/d2RgNzSiKGIbrdHqwGqHRaFiWPR6Pg9Cr1Wrz8/OixJcrRYRQGIZh5AehJysiZqhuqJIkwUQvAOJhGEbRuFqtEkIYhh0Oh51ORxIVnucBAlFV3XVdFmNF0ba2dliW7w0HYG3GZtjl5eUkiSRJEnneskzf9xkGHzl0uFbvZDPFSxev/vAPL4uCyvPieDwEWoXn+f6gm82lM9lUq9USJd6yx45jr6ys3L59u9FonDx5stPpbGxsgGfk1tYWz/OKorTbbZD4yLJcKpVgTl9V1e985zuqqq6srHQ6HYQQqDTBkBKQSVEUp2bkKAhBM81xnOcFgFjKorRb20mn083arq7rqqpeuHDhrW95S73ZS6VSYDkHF61lWRsbG6COoJTClFyyv1MMIgda0Em5QSkdDofgUMzwHBAwMPWyubVlmibQZjzi/SicjHqGSdxot/qjITeJwEmCoq81R5uUlBBRYRRNIu3gOG+0//XJ33vTum+wEj4Ipb4xJv/vwt6zSY4syw5877lWoUVGSiQSKABV1VVd09NLLm2bazPkrPiD/BHD5bddcse4NNoOh5zp7mlZ1SUgMpEytHTt/sR+OBGOAFDDDSuDZWVGRkRG+H333nPPOXc/CN/9qwheIeabeGqqbemzSZ6hF7VtRzcNzrng6uDg4J//83/+y7//1fDhoVlvoM6ZzWbd4y5jxDZ02zAJVZZl6Yw26i3LsnRNJ0pKwT0/6HX7URTFUS5yahquZWiyJLwkjJE4TAnRNM1k1JCCFgUXRZkkBaNmLWgdH/NHjx5PJpOrq+t6vZ5lxXg8bjbaumbmeTGbzYVQk8lM181Wq31wcDhfzuKocJ36/d1E07RGo8W5VIoeHh4LIY6OTgzDAIXK85yrqzf1egtHMmMM6lIooeGf32w2MXCL4xiiHsNgtVpts9lYlvX27Q0E7JRS1Mm1QAZBkKYpAI96vb7Oi5OTY2wLFsJYhZtWvWEYhu/7eZ5mSbIpcimlKPl6uaJErpbp4/Onv/nNb8bj6fHx6dXVFdzzNE1LsxhOH4PBwdu3V4PBACYmEKGT3QJ2qIGOj4+vr6+TJAEzBjSM+XzeaLUfHh4wi68k7eAw4I8qyxLsE6T0lO/Sg2CEcGVZwDjCMJRSMrUt35I4ztLYMLQnT57AcBH5EP0nak62076ynQKBMea7NgJV7pw7gafEaYJfVIwiVtG04wiQUlbkUFgfDQYD8GTey4T7RebHubEKwn0Xtv3f+qCrpDsmhNpx1vaxzSrsP45D+v5MsvoXVx6yrr5zy0FYQnISxXEYRo7n9vv9Rr31+PHjf/Nv/s2nzz87PDz8w+9+H3ie67rddieNQkJIQmlZlrqhpXFSFMVisXIdC6cmEYQojSgtS0sldVNjZVbkiRyPZkRpusYUViApZtu+bdtE8LwsiNKklFlaHh+fYjEQISwMY8t0BgdHZcmjKLYsRwhVCxpxlMZR6tjlaDj7/uUPrVaj2+2+/OFS07TTk0ebzebm+r7d7sAKDRNexojjOPP5XAje63WbzaYQvCwLTWNhuJFS9vu9+Xzuug5jrNfr2rbtOPZ6vXry5DF2DD158kQIAYn98fGJ7/sAEjAGwKW5O+Otbrf33Xffdtrty8vX56dnjNFer2dZxu319fX12267c3xyuF6vb25u5jPe7w9OTx/94fff/OxnX/23//bfnj9/7jgWYyRO4DIqazXfsoz+QVupFqPmarWCdeJwOCyKAk0jIFPLsvI8H4/HWZY9e/as1Wq9vbl1HOfNmzeEkE6nszWeCYJmswl8FXJN1NW6rk+u7nr9jlJqOp1qlLXbbctyVqtVFG5++tOfLmfT09NTQ9cXi9mLFy/u7+/9em8+ny8WC0AvWP8MXQj6QDSEpmnuUhFFAoR0Tu2szDzPw7Fo2BbCcjQa3d7eVgbTkASCZ88YgzjT87xGo6Hvx8+7Cfs/XTSCZYfeD8cS6l1AumqP5oZgo7scu59OqzS7X9Zug20X+WrP/JeSvXHFDqqiCudlZjo23g4otaAF+du//dtPPvlkuVzOihJ/6tOnTzerdVFm5e7mB15ZltiedXgwKIqiWasnSZqlPE3KKIosU5eShWGqlCKEtdtd27TSLO73+2UhNWZoSouStOTCMKyiKDarMGjaV2/emqbZ7/am07ltWZ129/r6erMKPcdVgrSbHSXI7e1tHMab1WYynjYbrShMlWSlFLPZSimBq9DzHSF2K98MA7vchsNhq9XIssT3XSnlyckJpRQ80vv7W0qbZZl3u+2yLB3Hur6+evz4ERwA+v0+pm1gY+FqgAsBxuWaZkgpTdvCQR5F0dHh4cXFhW3blKnr6+ter7MlfNgO53yz2QzvH5LEXi7Ck5Ozv/u7v/v888891+ech2FRlnkYrYXgjJEwDJlGlFKHhwe2VXv9+jVUrW/evDFNs9PpYKgwn89RtmFXdq/XS9O01+udnJxcXl5Op9NWq4W1MHme53kOOAevBNt+arXa+cXjdrOVZnEcx1mSxnEsynKzWTm2VZblYrHgnJcajaKo2+1OxxNBLEoppgtkJ2rHEBLmVJjjQxcuhHDtLdcCjHZML9M0pRqD3jJo1G3bBpYLD3iYdHDOwVwDGIudGWDzvpcJ9/NVFRsffIFNRtVN29ud9EHNSd6HW/eDs+oVq8Rb/W7FTkD/VwUhbnSnvzQMQ5QcHMI0y8IwtB3ns88+O310dnd39+tf/eYv/uIvfv+7362Xm4tH55+9+DRcr7///vuzk1PHqYEuZJi67/tUESysB3WD5wUWGGqaFkcR8f3u4HATroqicBzP8wJR8ulkXhbi5PhQSpXnBerAVssjkuZ5OZnMfN+fTqd5noPIcnV1ZVmOZakgqK9WK6yCieOk2+3puu55dcb08XhaqzWKInvz5k2v15OSfPPNNxdPzsuylPKdA93x8bHrugcHB5hJ6rr+/PlzrEa5vr5eLBZPnz4FYDOdTrEUAcWP4zhJkgCTbDQaSIme5/V7A0rpdDrFBKLX6zlCWI4dh5HjOIZp/tVf/a9v37zWdDqdTk1TZ4w26w0gq2Ve9Hq9V6+Ws9ms1WoJIWaz2ePHj2/vbsJwbRiakKUQXNO08WQYx/H9/W273by/exNFEezlcRwsl0s4AhNC0MgBb4uiaLlcekGNMYadSrPZrN1uHx8fv3r1qpJrSilvbm5Wq5Vpmp7nbfL8YTSUXKAlBnmlXg/yLP3222+zOCry3HEs0zSzJD05OXl9PcRKXXhbVTJiLC+B3wKYt3BIytMY7DmkH3CPpJS+7zmOQymFpBs1s+d5wJ/Vzj20Cpkvv/wS7HnMxt91aHJPOaHtrF/IXn9ICNGZodSWUbYtO3dw5XbSKGHGxpQgQknNMqiUGmF0B5wKriSVmkbVbljJyFY6KKUUrrZT0BNFKN3iMqRRb6Bf94M604wwSiiljWYrz/M0589ffP7ll18+PDz8H3/971zb+d/+6n/57W9/65l297zlOPZ8MSWEtPpNachws4zjWCmla2o2Lcqy9AO71a7VW36UrheLhWmazKbrdGV7tuEbV5d/fPr0aRzzZt3UWS5IuViMGOOue7FaLe/v723bef78+cPDg1JqvV63eMM0vSgaep53eXl3dHS0WsetljUaTwGpfffDt57nFTwTqrRt+/io57mWFM7V1RUMLxazOS9Eu9m7fTvq9/tJnNu27Tudg+6jxWLRbvcuL6+fPn2KtHZ3N2SM9fuH63V0dmbf3Nw/fvx4s9nUavy3v/3D8fHxbLmazBdCiG+++/6TTz755rtvYZAzW85N01yGC8/zjh8dwVC03W7HcUgpdxpmr+WEy4fbt1YUhY7jXDx5dHV1tV5tbNt+9OSJ67qvLq/DMHZ9pWjy8vUfWm3vT9/+/he/+MXvfv+Pz58/H4/HR72Tu7u70XD5ySeftJvp/e3o4pyEm2Q6XazXa9j1Usvkko4m86wUpaLdwVFeluXdwzrNv7982263R6MJEO/1OrQsqyzFmzdXpmmHYbjZRJSOKaVK0SCoC6Fub+91x9hsNv1+P/Dt0WgUOL0g8K6vr03dKMuy1WrpjOGSzrIsSkulFMbu6OKAZAJHLcsSNpYIIaQBzlSeRkqn6yyOeU4ZpaZm6vYmDpM85ZwfHR2dnp6A+zadTmku83XChOJJbhOjzMrrHy4PDg5kUtrE0HSS57t12eTH5gQfNH67MpXu32H/nj/6nYqlUZEBKNm616gde1vt5UmAUVUyZFujDHX59u2Tx48ty7q5eksphb0vFOtffvmlZZr/9b/+1zAMP/nkE8eyv//++6IovJqDAaa7VwP4jgllDQabOC8x5WOMtVotVCB43xeLheOam3C5XC6Xy3m9Hriu22rVgsBbrqYlzylTSRqORsP7+zuUhZqp+v2+41pZniyWM9PSh6P7+WIahiFEVZZlFWXmerYiIssTSrTZbBLHcRiuKSW2bZZlrpRcLueu6/q+m+epEKVp6kkSTSajg6PPANyNx+N6vf6P//iPYRjCNRTGnoyxy8vLp0+f5nn+u9/97nmRYRlLvV5XShmGgcMb5RDZ8Y3QH87n8263PRqNgKm22+16vZmm+Xg8tiyr2+32ewfoIWezGWrgNMpRa8VxfH5+/vLly1qthkwSBEG1GhEjk8Vi4bouMg+KQMO2CCFQ/SA9sp2zPdwrw+Wq2+2apnl4eLhcLi8vL4+PjweDwc3NzXq9Bq4LA2w8Jnow6BVN00zTdDabZVlmBSZEEtbOVRATiG63ixeAtIlhia7rMDKEZAmFKH693qmz3VZpuqN5Sil/9rOfgVyOp4OXXKPRuPruDcIHbBsYGsHTiDEGexS9ipaqINwvSvf7wyqE9uOtusn3FUnVYwrYtwn5LpIJoZAj7TCb6oGUUlyW+79eVbjnZ2dXV1dCqfPjk1qtBu/08/PzTqcjpZxMJnB5qSp4WAzAuZkxhrUQx8fHpsYty1osFmBvwrgFvwVcDsYzSqlms9lqtYKamSQJ1STR+GI94arBVbEKZ6XIKaVZEa1W64KnsI0pimI0Kut13zBYUaRFkWZZnOdJHG9M06zVvDRNXdcSgjcagRAFjIxm8yEhxLRovV4/GLSn0ylT6vCou1qtuEgfX5wQQkajUZaHp2eD+/v7zWZDCAHxGrpYuBhiYZBt27e3t48ePcLU+9WrV5RSz/Ngb6OUSpKkXq8/e/YMC70wtQPgvlqt0jQGCbPRaOm6Ph6PHceBveejs3MU7cPhcD6fT6fTfr9fFJnnOUKo8/OzJ08e397etloNzstGo0cIcV3bsixCpGUZvu8mSeR5XczWtr5eGiOE5Hnu+h5MFl3fxyGoadpyubR1A+NQWGmt1+ujoyNYVKxWK1zxIOUBGsGkAaS8drvt2U69Xn/8+PF0PCl2y94RdSgvqWYAnYI/Nb7Wdf3s7Ay8cKxwYrBKSZKUp+CyAqGsxtevX7+GpxMGQnhti8XipHuEPITjHtQfpdRsNsMzGoah70fgx3G4D5kgzBhl++H3cQB/8MU2j4t3kw9V9Yq7e7L9lS/vr0yjuy0U0+m0Xq/DNwkd/OHh4cnJyWq1evPmTZ5lEKRtNpssSWG0yvMCHycMPxqNxsnJyXxyC9QLD4tGAkqTOI6hcENx32w2B4MB0xOp8v5BS5EiDOMoXpY8SRJeFInruiVP02xj2azSE+Z5GidrqQrT0jrdRrMVcNHGWOX45GCxWHielyRJr9fNsiyMlorkRRnV63XXa3a73VarESdLxhg4K5QK22GMMdthQqaO6y1WOWbBwN/wlq5Wq3q9jvXu0L+u12sp5enpKZeyLEvTsGzLWS5WvhfMpvPNOvz000/hHJMkSZpkRFGN6VEYF4WBOQekepPJ5Pnz50KI1XJNKR2Px+PxGKo/4HuiFEDCBoNBkiT9fh9eZsA20AXN53POOcAYvMIkScD1ofo2a5Vl2e/3cTICe4N70tnpWRRFeDQImpCmcGrgsgSVbLvvqeaCUApaQhJGSZKcnZ2B2qbrumUYSikEYZIkab71pEYFhLGBrus4sJCN8VCoqjRbp5QJIZUq1dagUIciCnvXwGjVdQPzWCiw8RTYeAFhJPI20ux7mbCqS6sESN8PCSkl0Bu2J3v/OIA/eMx9wKa6A2OM7IwS93kwTN8ybLZSQ7VNsMBgNE1Lk7Tdbl9cXFBKsfAM8HRF5VFKodw6PDhgjI3HY16Wp6eng8FgOp2uFgs8IBjAsFfFuGk0GsVxjIXGOLHSNFVsxUXWbDal7JRlzhhtNuuAxfzA0w1NN2i71XEcq1YL4jjudpumqXNeeJ5Tq/m2bSslkGSCwCuKzHFsTaOdTivLsjgOLVtvtmr1es0wjFrNMy2t3WlYluX59ld/9sVoNBqN7gkhjWZjuVz+/g+/se02qizTNL/++mus4xwOh0+fPp3NZm/fvr25ucHsC06HXhBg1TYGVo8ePfrDH/6wWCwAmuOUxMS52WzCQlfTtMVisVwu2+22bTmtZns8mjx79mwwGIBvBT2ulHKz2Xi2h9HC69ev8zw/Pz9XSnmeB94sGipgUfgaK4crv6Mt0E1pWZYgi8+XSxyOIMegigNfdDgcttvtbfRSCu5Yde1xzouiCFo1dMsYr2dpgimIbVqoM/chD1CoMUureJTQJSIU8VPbtuFsr5RahEs8EXKgZVnYWzidTkFbw0GD+lYpxbnwfb9izMPzW0p5fHxciX3fy4T7QfJB5FR9HaGyiqL9+3xwf/JBq/kRGQ5VKHl/aKGUEuTHVcWHh4fYtvX5i0+Pj483mw1WDiA9WqYJel4URUQqePvDgpYQ0mq1sCBtOp06pgKUXNHhweXnnNdqNSyvAniIQBVSosdwXAvFm67rQVCHSQznknNeMTmVUhcXF2AJ4ngmhOCJMMXCB2NZFhoSIQSeER85qql+v49xWb1ex3Cl+kRN0xRCgKHW6XTm8/njx4+BEyLMTNMcjUZYKA9e1WQyAf3ScRwEAwbfOPtxt16vB27XYDBAZwHk/dNPP6WUWpaFMx5ygVartVwukW9RvIVh+NOf/vS7777D4pdarYZJAKxx8GfiAgUiCoC92WzWajWqa8gGs9EckzcEuRACDhebzQbTlNlsBufSJEnw+VJKTdPMdzfM/W9ubur1OiEE4KSp6ajgHh4eoGNQQqCeRPAouiUMgDNAKcXRgLJos9lAYITGD68QoGhlgYEmCFQHQggeDW1qGIY1J5CUlFJkZZHzUhClG7pGaVrkcZqsozAMw3fAzH4m/CAg94NQEbE/fvg4YD6I4S13lLxTD6IF3C90q6dWO8UGqSaNO73SYrGAL60oym+++Qaek7AAQtmDiiWKIkPTYcuNhanHx8eB78NUE4d91TpiC5W2cx8PgoAQAhCGUroFaXSfc6mIrul2s91RSsVRKiRpd/pKqTRNNd0yLVdRRjVmux5jpmVpppmnaWrb1LIs163lec6YyTmxbR/igCQpGGN5LvK87Pf7i8XCNLdiv9PTWpJky+VS102laL8/WC6XWVYwpn/55VfTafTkyZPZbGYYxvn5Od5e7AbGZjz0eGhxDw8PuZTNZvPZs2fwAr++vp7P5+jKHMdZrVaoJEGb7Pf7cN02TVsIZRjWZrOZTGau67969QOuKgx4TNPs9jrT6TQJ46Dm+YH76WfPm83mN998o+n05vbedV2mkZLnuHaxRACnTFmWkC/5vh8mMfyUCCFXV1e+7wul0By+ffu21Wppiui6PhwOUbngOMOYju3tBcMoQtf1QpXgciRJslwuRVFCxGybFmaA6I/AKbVtO0qWbLeTE2HJGIPSBd0mwkzX9SrxIH/iCEBhqZQCI3S1WgF6QXWGF4w3DY8AHm+73R4Oh6iHi6L4kUz4cQTuJzelZJW4Pw6/DxAdpRRjKC/fEXEQhFJuTdLUBzf6jiVHCNG07QP+7Ks/Wy6Xb9++zeIExxgaCciu8yy7vb3dYdDaeDxGjY7VnCBegoaLfLXVzgqBK0DTNDjSwlqr1Wo9fvxY07TxeNzsOESVZSE1Znfa9bIsw/V9EpetphvHaRQWnHON2UlcBkHQbPjz2cZ1XaKMJN74HqnXAkN31+v1bLq2rcxxHKIM32smSeI6ruc2RsNpq9lN4tzQ7Tzj08miXmuNx+PZbHZ0eOrY2mw2u357V6vVNGZqzIThClySWq3WYrHA5Yj1w0VRnJ2dIc5x1pSlmM3GpmnHcex5wcXFU8fxxHYdHU2SjDHmOF6aput1WK83oyiJ49T3/STJlKKTyeyHH77DzobJZAJZY1mWrXbz4OBgs9lQSUDv/OKLL/Da8PIAuSGn9ft9IMO2bTOqAzuxLAv1Ht7/zz///PLy0nEc1/dxuk0mS8/zbN2AAzpQqLu7u0ajUZYlRAwY1kFXAeMMpW9Nn5BRDcdFCapRBpp7tNkgaHEZAHRBAsSyKogqdF3HX4qhPN+thxBcSUEo0XRdd2zPtlyNGVKoq5trrAPYNorMYFSnRENKL4oC+g+U8Shizd2KoXdBWMGbdNf1iT0DGBTulFKNvaOtbaNmj3Czn8QwlEzLnBBC1XtJdRvAO6iG7bFkdEOXUqJvVkrV/aDf77fbbRALkyTB0ILthFRSyvl8zssSLMqyLCUVkIRW6mTPdcFXZIyVZY6AhLESYO5vv/325OQEJGBs+cCFIoQQXCtynme5bdt5JotCdNoDwzDms8i27U570G53b29va0FnuVw2Gg3XcjRmM1qulvHhwImjQggRbjJe0rdX9+122zCMX//q951O5+TE63YOpzM5n22WiyiOijRNn1y8GA1Ho9GMEPLyh6sgCGazZZGrOCps2758c3t4eIhF8P1+v9FoYCvQxcXFs2fPvv/++1//+tfPnz/HdoSiKLAQt9/vYy53fX2NYtgwDCz6Mgzj5z//uRDij3/8IwyzdW24XC4p0TrtXriJ4zg+P7+Yz6etVgs7QDVNcz1HKQU8NlxtcBS+fv0aBx/nHP/2er3b21u0bZizTyaTPCsNw3j69Cnn/Pz8fPTLCVI3fDHKsuRSQkF/eNidTqd1z4/jmBDiOM5kMmm1Wu12+/7+/urqCppDuMWhiXh4eKi168irqGtEUTYajTiO18sVrnv0csvlEvW8ZVmz2ezRo0eYsoBJc3d3B12Y4zhIjABslFLAC+hOdYTeEjLI2Wzm+z5aCcTeer0mUjqOYzl2vdkwbYtqrNVux2mim0YYRxBkvQNm6Pvyv/3MRveat/3v/P/efjTB4sYYo3tVKNlR4fI4xRHVbrdrtZptmEIIbGvYWg9YdqVjlDunRrXTEFNKGWWU0na77ZgW6Py8LDGcNQwjixdgFcODAH0a8LrFYhEEASp7bE3Tdd20g+U64ZwbZlByuVqlrusdHB6G8ds0l0kSpbmcTNeEMM9vtdr9IkmTuIijnChdcKqkzLI8CjPBqeB0uQgPDw+LXNqWz0timZ6u2etVrDFrPlt1Oh3TcDvtAyW1JEke7ieWtXZd17EDJZXgVNdsYBJRFAFuAevi9evXp6eneZ5jbzF26y6Xy3q9LpTGGOt2u8PhsNFojUaTbrcbx/Mvv/zKtu3lcvny5et2u/3s2YskSf7tv/13n336xWq11jSdMe3+/l7XzE6nXREdTVOXUgrJlVKo08x2K4oiz3OlFPf3d6j0iiKXUobh5uCgH8dxrRas16s4juI4yjPOOUfSE0JUBvtAvwkh+KNAswQU1+l00GHGcRyGoRDC87zxeHx3dwfTGtiWojFDV4IMWRSFLDkOdMwe1+v14eGhUmoymaCLZroJ5o1t29fX1wBIa7Xa27dv8fJwVFW+UpbjGYal66XjuLVaw7bdOI5Xq81u/sEcZzvZLwqe52XgOWznsIoFYd1uF4U9qOdKKX2/qvwgwOieFp7u1vcQ9d6yiA+KUrWnCZb/XXUi0FHyUTmKjwRWJaZpllmOJVKmbpimqWmaZ291A8BFP0jgVbmLzAxqvLbL5HDIwk/BVkP3WA1kUaUAQEfyTC5vUNAzuuGch2FclnIymZWF4rwMN6kULE1z27Y5F0lSEM7RNYFyAXMNFCGcczwUcuxkMjFNM8/L9Xr9+eef397eP3nyyXodNptNIdTRkXN5eSmEME1b100URZgQ4l3FNhLMpkejUavVsm370aNHzWYTmm7UbOPpEo0QCj+M2nVdn81mlmV99tlnl5eX8/n89va21WodHx9DamxZDiFsPJ4MBgPX8RljlGq1Ws333SzLsjxVSrmuyxhxTAfjnzRNHx4eut2u3FlCZFmGRYX4mKAh7rT7Ukq0r1j/hFeFfSFFUYxGI4QN2vJ6vQ5vC4gScFifnJzc3d3NZrPNJnJd29/dgiC4G99jsy/+ZCK2rtbMMOH1cnhwgF1RWIxr2w6AFjDgwOFWSoFWRXb7nrePRohm2bVaDa8EPtG2bR8eHoL1ho9YKVXJaNnOJ0pKCZ+OWq0GgTI460mS6B8P2fcrzP0acssRVXsE638i46k90OWfCkIpJdkT4LMdmch2HcBTIA3FmxCzJkyBGGOWYeJNwbB1axb+EYaU57lkHNcuNYyqfA03S0xE8GahQcewFbDNdDqFcQiQxkLQZrPJmHZ9fS2EAFY5mUwM3cLHgJmkaZoYnTUDF0CRrmtRtAEYqJRwXUeI0rIMSlWjUQvDNT7mxXI6GAzCMHzx4sWf/vSnn/zkJ69evTo6OrJtu9fr4XLMsqzRaKDvD8MQi9rhHo8KEAEA6g98hNBH0d3Wx+FwqGkalntiDkEp/earxkMAADSASURBVPbbb3u9nq7r2P4nhDBN03ebgquyEIwxw7DKsry7u8uz0vMUUk1lDgRLVUIIIBagWbiIISMACo2cjKGfYRhxHPu+f3p6aprmw8MD/Lan06lQcr1e76jkGorGWq2GdYVYxgpd8u3tbZ7n/X5/OBw2GjUEKmBYlAl5nmPeyxjT6VaOFIahpmmY3MxmM6DNRVH4tQasa6o+DdaGMIACUKx2mnK8EsMw2u32zggrA0gDO2PEv9qtBDVNk0vpmqZNiGYYhDEu5WK1enN1BdnkaDJJ01QXe4a/+4BnlVcQb/LHVLn731E78LOKq+33K/bnu197LwgZY4puxU34Oa48/D1ESDi3F1m+fVW7lTI40fdfzPZJ5a64JXR7xey0+ZqmlVmEAMNprZRC3QJvvCAIqnkx7lOkWV5meZ5PZ1NKqW7qpSjjOGZMI4QkcUY1ommaYTUKnq43YZ7MMQSr1WrrzYwQQhn1fLPdbkiVe54XRotG07u9vcXBX5TJJly0O/XFcuG4xmw+0nSVpJtNuNhqi7I4juPFkjVUw3GNakaHTw2RVqvVxuNxJagROxfJPM8p0aQgUhDLNAO/XgsalmkrSV3PO+gf+l5tPJr6vn90eJKm6Tdff/uXf/G/C6Fub681TcPIYTi8DwK/KNws02zbrDyEgIjEcdjptHSdFUXW73dd1767u8MvYl5qmvr19egnP/lsNBo5jvVwPwU8OJ/PdV1fzFcnJyfdbrcUHFc/AIhqhIOFVpAgdzoddFzAxgkhGN8DyNlsNug+it3ePsaYVFtHIvwvIhkcNOhuAZkCjwGpqHLmrdAKXGk4u4VQaRqZpt1stlutDhakPTw81Ot1xnRCSkqZELwsha4rQhikmBiTwoe7LMuHhwesfMOcZhuEVbxV4cT2RL2oZHYX+4/vpt/vJ98LTvYu8PaDUO2USlW4boEfTQMAAxDJdk2EJQZxZLdRAHMFBKHaU/orpQACJUliMG07jTAMvttz2mvXtZ1BCIp+PHu73ea7ZWb4SPAeFWRSlLFlm59+9pQxbbPZxPFSERrUPF7KzWY5md4LIYQ8KnnGNJFkK8aYwRzN4KvN3Pd923FsWzNtZVhSM/h4PD48PCxF1Gg0TG74tQPGWJpt8iI6GPTRzcZxLJUgvBSCO65OqDmZ3sfJ6uTkZBMmhmFgLAZeVXVq4DQpiqLT6eBszrKMUl0IcX5+nqYpBsSGYQyHQ9u2sTsByvrZbOa6brPZvLx8m2XJ3d2DrrN6/VNoZ7vdTp7nnBeUKlyLZZmj1OecY/EtmjdcbUiMKOxRJ8NkLc9zKTSMT0H7xDr7R48eXV2/xbJUdGVoDWAJ43nexcUFsMRqhn57ezuZTNBcoDaGFBD7xfTdSm2MAaB+BrCEq//u7g7jB+zcBtiTpulkMoFfKJrSqnfA0YNhMnx6wG7FcwGIIoSgGMHpCdS60ajXajUQlbG1Al3Sy5cvqwnHNggRcvsZbD8mxc7QjlJKiUb+u64w1f9uUyi+uReE+Bq9m/roBmSJEILhtZQSosl2s7UFYNS7cWL1CuW+kzdlIHYJul23yPb+wHLnbwcYwNyt3QOYAbwL7y/OV2ZI29Db7eZgMBBCkHtRcsziqes6RennebFabUoe2w7z/EBTOt5PzzNn88KymWlRRYo4WVk2o4xruiS0bLZ837fxgp8/f/6f//N//pf/8l/+8pe//PLLL1erlWVrhDLPD8A7qTfcOFlF8ZKyAV75er0uyxKzKYyq0QOj5Ts+Pp7P5xhgWLYLClhV5KMoaLfbUsp//Md/rK57gGGj4Qy1XByn6OK63a7neUkaxXGm63qr1fA8LwwxjOAw8Ib+ixCS5zmWH4LFBgVdp9NBLcoYs+1cKfXdd9+9efOGc76Jt4UJnAKbzWaSZdh81mw25/O5pkiSJOijgAVEUfT27dvpdLrZhK1WixACpeVisRiNRjiesCt3vV6DtsY5bzWalNIKjcNvlWW5DuNarYYzGgw1jDH1nXB8H/ZTSimxbXaw4RSHHQgMEP6CIAr0wfM8QjggVvw6WpVyZ0OBzKxXhc3+nKBq0sjOkxsVCKbn5CPg9Edve0nuRzLhvlRK7Q1I5tMF6ml8ohkXwGmASaDKxxtEd/IrKWWl36/4N47jUKn274M/AQvu8OtorNEb1Ov1wWAALwkMADRNm0wmrmcFQWDb1mw+zrOCMdXtNoVQNzc3R0cn9UaglGKaajYb0MK4ZolBGQa8lqUxJrMsk9IAaH542HNdp1ZzkZkJYZwXf/EX//Pd3d2f/dlP/+Zv/uYv//IvX79+zTk/PT0VopxOJ91ut9GobTabNI2VssE+JYQ0m816vQ6EBmoA27b7/f7FxcUPP/zguu7bt29X6ywIgtVq5fs+xt/oyhBa8G5DtKB/xu7hZrN+d3cHpOH4+BDvHg5y0zQJ9bIswTdrtRr6Xsdx0EFgoHd2dvbNN9+AV+R53v39PYg+yJ/T6bRKC69evbJtu+Blu90+ODiYzudSykajAQu5Ikkx4J5Opxi1qZ2boOPYqGgw+gcFDNyAer0OQs9iOkN1KoTANP/t27fw9oVZVl5wQgj0n+v1utForFYrSikip6q6wcw2DCMtJQ7xwWDg+37VgsIAju/cij3POzg46PV6b9++3jKuHAf0LFBE0I1rMOemTN8mJUIxusN/QpIkTZBVFWFCEqUIZQwe2Eh2iKh35evu+5RSyii6JiJKQray3G1kEkIIKbiglFKNUko5kVwWSihCCAAYHNhVrGLMQLeuNu97CmNvzM4IhFIqiVJKKkqURhljSmNSZ8TUNSwJlaZhGGmqhMg0jRal0jTt+OTM8xw/cBljtmtacyPLEqF4t9+kVLeo7ZuB23Axk8RGsW6jX3frGByfDk5hoPTmzRu33YuiqEjWqzBpt09gsdVstDRNc23nzZs3FxcXUkqDabyM7h8eXvz0RZbzMkqTlM/mm6dPf6LrtfPzz/7jf/x/wg0pCm6a9eWCHB6e5+nEd08vr/84HN3863/9r//2//27NI0ajWCzWf7iF//Tcrn84x+/Nk0TC7fvbh9OT0//2f/wP/6f/+H/3oQrz/Nsx2x3mo5jnz8+e3h4MC19Mh09f/HJr3/9a8Mw6o3g/uHW853lctpoNOJ0ZNp5p+fEqa1I1u32nFj7Z//sZ5eXl9gV4Thev98PguCHb38Yj8effvrp7e3tfD4/PDy8vRk9efIkz6Tr1A/6x8PhkChD1xzfa87nc8t1er1eccUn81m73QZi2Wq1yrIM15vlfNFvd7KT0ySKV7P5oNu7ebh3a8EqCm3fe/32CkdzmqYHwcHJ+SPLcx3X1W3r8uZ6PB4vNuvBwdHt7W1ZiE6753u1rOCbKHn69GmSps1WW1BW7/byPJe6YXj+aac7HA7vRsOiKBaLxTrcSKJQPaJu1zSNKaJTZmq6bZimac7ml5Zech4nIfXsrpCyzLK6b1MpeMCKgjiObts+ISQJRw/5QuVespK6ro2miyjeaBrlqSBcdZsN13MYI0WRvdcTvldGSokyd39KQfesCj+oQquEtn9n+j49bf8L9f7aw+pHbO9lVJFeJerqR9XL0N7fpbGfVPefC4+gaRr2EKLWNQwLXU0cxwcHvSxPtN2ChIeHO6VUu90WYrso7u7ubj6fQ/NWlmWn0wEHEt2CZVmDweCzzz67vrtJkkxK4vs1TdOIkEopypjreLZtHwyODMNar9fKskzHffLs+Xq9Pjw8pjRnjCVxBl+J29vbp0+f+l4tiqKi4CjI0yxJ0hin+Hw+Pzo6Wq1Wd3d3jx8/uby8fPLkyaNHjzqdznfffXd/N9R1/c2bN2i3lssl1Iz4q0Evhgk0pq/41IBG9Pt9xhg8MxeLBdjtjx49+v3vf39/f48ar3o3Hj9+3Gg0xuPxaDQCmFmVu0hNQDLhVaHrOp4OboKMsYeHh88//xyvStd1rOZ9eHgYj8foxDabjb9bz0gprdoEYNHghQkh4FUD8ucf//jHR48eWZb1y1/+siiKVqslpfz6668/+eQTYDlgaGRZNhgMgA/h+9AlwmijXq+LkmNVliy3lA+UVM+ffwrIlBCi63oYhvi4PS+YTGY3N/dKkV6vDfg0TXNGnCjagO/JebFahWVZ1uv14XAY1Hxd17NsN6L4IAj3o6jCIZGIYLf6QfjtR291xbM9Pf5+MOzfuQqe/X/fv9uHs0dF2f5zsY/CuGoOqzgXuxvnnCtdiIKQjBBiGBqjmiACnI8sywxTw5wRHGsppRRMSbpZR1eX15PJxHXdTqfjed6/+Bf/YjgcrtdroqTGWBylD/ejPM9zWaRZTil1Pd82rSzLiqIwNM2wLKFUvd5I0nQ8mTqWbVnW+fl5PIlXyw1wAkpps9mO4zjLcl3XhVO6rmvbUtd1368pJQ4OequX96enp9PJHIzhly9fnp2dL5fLP/zhD0dHxy9fvuy0e+v1er1eP3nyZDwed/oH6NmwTQXgId5hXddh0wIUfgcJepvNptc7sCzn6uracZwwjO/uHrrd/tu3b4+OjuI4ffTocbO5hi0NlZxS+vDwMBgMANIWRfHw8IA+EMxJQJf9fr9er89Xy++++84wjPF4fHZ2tl6vj4+PUaweHR1JKV+/fg34VCkVRdFqter1erAtw+tHfdTr9Q4ODprNJoYEUkoQ2SmlQGtA3MMEC1u1q/oZzGFMRDabDcgxsLqRnKNotM2tHxyVquLicc4nb4bYn4GDAycyCs56vX50JBhj/X4fMtfNZjMdrymlYRhiS2xR5IoIz3POz88bjZrtWGka/4ioFze526akdtgjmlT2vgXbxwHw0Y8+TIDk/Zt6fyz5rmrdaymVUrB1opRKKqsuWe75cXxwHLCdpxuyN/AeTdOoCeZkXpalaeoVD/jhYRQEHqFyNBoLwXXdlFKORqNwU2Bv3nK5Ngzr5OTs/Pzcdd2XL19vNhuxE3THcRqG8WKx6J0c5aVSShoZL/LtkVkL7LuHyXqxDIIA1vS+KylNO92s0+nd3Nx8/fWfTk5OTNM0jEgIifYDJ3FRFIZhFEUShuvZbEIUOzk+u7y8nM1mL168mE7nd3d3R0dHWZaDqXz+6KLb7f72t79N0/T+/j5KM9u2W60WRqCAfB3HQT5J0xRYa61Wa7VarVaLUrVcLh3HGY/Hy+Xy8ePHaZoOh8Pj42NCCFw8oC2CfIxoOhzHB4MB8Eyc1MvlUt85iwF1NAyj2+3qun59fY3dwEdHR9DBgGwNdBpMSwCBuq4zZmiaBlkZ0hSG8tXyerClwQ2IoqjX6wFah8B/Pp8LIcDGVruJOXjVUBULIaDPIITYtq1RyjmPokhYHF9QqTRNA6mDEOK6frPZhABFCJEkWZrmUZTM50vf9weDI9xZKVoUvCi4rrNer6frdDIZapp2eHRAKc2yBFemrhmu6+v7AAluCDMUDFUaqbLKvmyPvF927sdnFRhVEqPve+xXd64eYYsg7U3w9x9N7qAXqlPMdvCeit0S0g8OBcQGngVBiKkDNzLUP4QQXTctyxSSa5pByNZHvCiKsixw6SyXy+kkAiul0Wg1m83T0zPLcjab6OXL14PB4PBwEMfxZDLhXPq+X683ozgNoyRN09lsgbQTBIEQ6ocfXt7d3MKpodtum7ZTFMU6jOxSZ9So1xu+HyRJslqtCSFgSyF5Z1mqaSxNk5JnRZli8fJgMBiPx0Ko4+Pjm5ubo6Njzvnf//3ff/GTn37//feDweD4+Pj+fnh0dPL9q5cHB/jgs/l8jkEiIWQ43J7osPfu9XrdbrfRaDw83KVpqpRChQnC12azubq6ajQat7e39XodyYoxNp/PfcdttVpwFgMh3nEcVBa46bp+cnICet319bXv+ycnJ9Pp9Oc///nbt2+/+uqrLMsODg4WiwVKfaVUURTT6RSzkEfnj2BAOhwOkyRtNhvNZrPb7YLqgLEH4BDkydlsBgtM1JbQvy8Wi263C6kkCn78yVBOr1YrcN+FEI1aDWgnL8qiKKIo0inDUhqcXJodBLWaECKPIiEkZRrT9KLkQeCbli0ViTbhYrkC9SrLMqr09WaRZrFuMMex2+22bZs4elB6UEp1bc/djOyVhXBWpJSCvVrFD/Lh/hVP90xE2UfOa1s54ftuolXcVk/HdoRvuWd8Wv06Ie/hsZj+KchYsIN893RVGYwivsrkqCiEELnU4NeANQCGoeV5zjSq6yzPM0II3prJZILTsdnuQPOCqaui7Pb+4f7+/umz52DrJlluWLbjeYZlx2k2WazBUUziGEms3SwFJ3kp80JEcdZ1g3qzk+e5UGw6X91ev/7iiy++/OJnQpR5NiSEuJ6NIieOI8uykiRqt5uaVnMcCz4rX3/99RdffGFZzuXlZa1Wq9Xqs9ns9vaWl9KyrCzL8rwkhJydndVqtQspCCGgYqH9gw/feDxGQQtaAiGk8sYsigLJQSn1m9/8BoM+KSWiC1wT4Jyz2Sx0Xdtz11F4dXONqToMrW3bprqW87Ioinqr6fieJR0cbZiqwTYC6Q5L2tBjdzod5BmkO13X4YVBCHFdB+a8GNxjBcV6vQagHQRBEATD4ZDueELAq1Eu4joHQwOnDxIsShV8Z71eY1hvGIbpGtvnpQRTRxBuJsNxHKdJkqxWK7iSel7gecHx8TGkLZpWSkmKgqdpmiSJZZh3dzemafZ6HU3ThCgtK8DT4TopiuKdoHj/WkfGQwrGT6uwqQZ0VetVpcHqd8lusKGUAk/74zjcj1X6/o3tbcWgO1gVH4xS22ExBoDV3aqCGeHH9gh71Y/gXxA49VazU6v7ux0MinNuMD3LCiEEY0RImec559uRzOnpaTWuhJsgLtYwDK+vr4uiqLT5YRje3t4mQuV5HkUJVvMFvgtvON+v9QaHtm03m23dsC6vrh3HSdJ8NlvEcRoE9eUyMQyrLPPAr0u5bDYbm81G11mWMdPSizIryiyMllGUcC6//fZ7vJM3N7dHR0cvX74scn5+fj6bzZ49e6Hr+tdff/306dOrq6s/+/mfAx2BIhmwBJrA2WzW7XZXqxVAdlTX9Xq93x8sl2vX9U3TnkwmcZzatlur1ZbLtW27nEuMCjabTavV0XWGQR/Ga47jAFdUSsF/5P7+Ho1co9EYDAb/8A//ALwEHSbAHlhoA2vB+I4xBifbyiQSgQ1b/jiOoYTG1QvcH4c4YwysQ9ScOHcajQZqXcijq9k69ltRStEWWpbFiwKUzlajicISlA8ACmma2pbrezVG9fUqLHJu6BI4X5HzshCM6rblCh4nWcZLaZnOQb/NeXF8fHx2djadTgkhvV5PKQU+g6YZWZa96wk/uGHgCDCtovBUmGTV++3DoWxvTZrYmawR8iNL7ck/AdLsp+L9tIkEhTtru2lEdf+9BnI7VK2IDghCsnNcZ4wFfuC6LlFss9kkSaKUYIz4vp9mSbfbNgxtPp/ned5sNoWorVZL23azLNtsInhDYQh7eHg8nc7AgW63u6ZpL5frh4eH+/uh3WpKJYnGYOkHVDCJYi4EnJdMx4aVxtHRkWmaF4+fZGk+HA7DMHRdO0kylCio5TzPUUToul6W+WazEqKMo/Lp06f/6T/9p6Ojo08+efbLX/5yMBgsF+snT55Mp1Pbds/OzoQQnU7v/n6ICQqiCxplAIkAaWDVA55KRUi6ubmBI3273ca2pru7O0JIlmWwloIrJFLrkydP1utlURR4h5GmhsMhSsTz83NMvdFcgVuLqMiy7MWLF8jb8N3BpY8PFIxcYCeW51awGWaA+m6zJ5b+Yh0qIgRJDyU3RrWYeEGSbxgGki02i0HQAONguLA6jsMcB50LWFmu61q6gak1MOosy+qtpuU6buCXZWk6NgwN0iKnlJqOLSnJ16tC8Have3p6moSLI3L4/MWzWq0Gyr5tm8PhOIqiNM23xAD2Tyh0q7lq9Zcg8KrSFLfq4q5iUu7WZf9oq7Zf+lYISjV8V7t9GBVBZ7++rQbxamc3UCVkRF1VGFdKQmNHWEPZY5omY3rFzzYMo1Zr+r5r26ams7u7u9lsYpo63EQsy3r06HyxXEKryjRNRzI1zZLz6WxWlmXJ+ZvLS1wBmqa1O52UKGAMvXYHezl1XYcIUEnZ7XYtyxoNR0dHRzjLeZE+PDycnB6jPoEUkGnEcWy4d2n6tlPFNFzXbaXUJ598outGkiTPnj0zdGswGPR6vWZzuw5+NJr4vo+MPR6PNU07Pj7mnB8eHqLRHY/HFxcXEA21Wq0gCO7u7hhjZ2dnYRh+++23X3311a9+9Stwu9FxgVr5i1/84r/8l/+CGq/dbj99+vQffvX3P7x6KYQ4OBzc3t4u16v+4GA4HD5+/Hi+XHz7/XcwtJdE3dzccCkqwAxLCBuNxtdffw1COUrlKIrg6YLrYbVaYWZQXWOO4ziO0+12R6MR0jigF+BMkF/gbrDrLori4OAABHfDMDDJABseFF/M8S3LWi6XkvPqaux0OrZtx5sQ8NXBwYHneS+vr4HxgIQAnhC2hYL2be6WTzw8PHiel4QL7DyE3x+OOayHkZLgk9Wrsm0fayG7RZxqbz1TFXXVpU/3tE4fcFCrUlDX31lC7d8qygvbGT0h0ioLtu2P6DYIcfjt/6LaOW1VQV7FIdmtcEPxjKkreuU0Te3dqleiWJIknBeGoUFU1u93fd9XSiyXy9l8slgsmOGnWbwFZhWXigpZckGYRhzDUkqlaQJALwgCz3eSTagRZdmWptGiyJSUghdECccGPT8URV4UmWVZjmMVRZGXSiohhLAsQ9cZITLPc6m4aRqEENe1a0EDiqeiKG3btq26aVr1egMDZSWpruvn5+dxnMKJdDKZIUv4vg/8EHDFeDx2XReNVr/fv7+/f/LkCdIFpdR1Xfg49XuDNMk77V6R80a9dXf7UBYCUn3H9r75+ttGvRVFUeDXKdHubh8ANn7//feAW3BSwEgbgQSSDQR7nHOdaVtfiSjK8/zy8tLzvDzPgyDYLzvhkorsJ3d0PHNnKC6lHA6HcGEDlQekeSklDPhQtgghcH/gLmzPnQxtBQorPEKe55vNRgkBRBqyj8ViUfeDFy9edDqd6XT6/fffp1JCuQJADj4jjuM8f/4cPCdMPtCkbDYbKqRju2EYhpvIHwRxHE8ms1arZZqmECUyjY46hO7tY8INLWyVFfczpNqDOvfjs4rJ/TaSvg+T7j8O28mXqiJWSkn2EiBjTGdbK0Q0gWoPVsXHY+jvrdPY/wJ1PNktkMHfqBlarVYLgiDP09lsRpk6PDzsdvtRtGk2m7quj8fD29trSPXare5wugR+QCkFXoLXg4sGY1/MiMCnSXgOL1NdI4LnmqbZlm5bupIlkazI4jTeaJT5Xt2xDcHzdrtZlnkUbRgjnHPTNKXihmE4jpskcRjGyOqr5cZxnIP+sWW5o9EI3iT1WhOoBucSAzHOpWVZ4/H45PjUMu0oipIixmu+uLiADljTtE8//fSHH36QUqLAhr5OKfXzn//8P/z7vwmC4Pr6+uTkZDabHRwcYFMSfnE4HGLzKex97+/vj04PwjBEhun3+5jZ4AM9ODjAe4L3DR+H57iAQFEn393dffHFF1jHjSUTuHzb7Taq0DLcgBzLGAPfAL9br9fR5UZRBLkTKjKgKZD8wamNcw6QtkIT0MVBSwEjRry8oigYIYwxz/NmsxmMZ0VRXl9fj0YjUAKn0ZrLMk4jRZVuaqUo1uGKUvry9Q+LxeLg4IBznpf505MnMKFsB+2rq2vP8zwvuLt7aLVaoAdjc6iUklLyDkKsqj7y0U3uqW/3I6qKq/1Wbf/R9mLwwyCs/pftyTXkznimyrraLgirmlnuiarU+zPG/adA74H7VHItTdMcxyWE5Hme5yWl1DQsnIWmaUqhcpEzxgaDI0pVHMej0aikWiFKYAA5L1Spcl6UZen7fpqlWZlLqryaDxXMcrM6OOxiKJfGCXzB6nXfcxwlcs/yAPB4ttPtNDRN40XCGNE0WpSJpmlZlqVZjEQNvB4qONM04zilVMuygjEjiiLH9uq1JhSx0+ncNNNut0uplqbpxcXFv/+//sPp6enDwwNyICEExAAU+dPpFPzm2WzW6XRev36dpulXX3318uXLn/3sZ9hDdnt7OxgMLi8vUaRh9LxYLD7//HNM4WazGei1myhah2G7210ul5999tnvfve7TqeTJEkpxJNaTQjBpVyu10VRCKUYY8vlErAKOrfDw0O4GIK9SfaafDB70Inx3RK4CqTAnQG9oItG1gUj6uTkBCxZpRREjNVcChAdzvTqmK44DLphQFjo2g6K4SLNMJxEVeV4VpIkcRzqus55IUSZprEQYrXa9Pvds7OT9XpNCGm3m2VZClESwqIo0TTDcTzDsGzb9TwvjmNwoTWN6jgP9svRqiLVdosH99OO2iN279/UbnXZfqzuEto7ff0HcYi7VUNIPAXbC6r9+Kx4HkwjVeZUO6Lcx48ssfltZyXEOQdIIIRYrdaaxjzPA2oSx/E334xqtdrZ2Umr3aSULhaz7Uei63GWCQGwDuXi9lxaLGa+73e77aIopORZlpimfnDQcx3DdUzDYJEs8jQqsrjI4jmlWZa16g1d1xkRTFN5FqORKHgphPB9H0pzsEySJLm9vYVSybIspYjjuFlaXl3eCCHKsgRhoCiKxWIVx3G3203THOif4BISHpRYEEys1+vf//73Uspnz55Np9M//elP8GI7PT29urqSUvZ6vT/84Q9//dd/Xa83wjAMgtpyuQqC2suXr0zTyrK80+k+PDw0Gs3FYkEpOzgYcC7a7c5o/sAYg6k7shN8lnADalJuV6b4EONiv5Jt22/evHn69Onr16/xV4MPTSlF8GCwCcEbgAnozvjOhx+5AT0hCmBkNnz6i8VivV6juEWWQ39eYaoIPLzCrRs3pYhMPN3Wlp9S2J/iTHS1gItSCJEX2Wq1WiwXSik/8A1T/+LLnxiGMRoPXdedziaz2SwIgvls9eyTT29urocP45/97GeT6ehv/uY/npwcpWlKqaKU6oam890u4v3g2W8FP7iy99PX/lhivw/c7y1xl4/jtoq6Kly3ma36YrcTuzoXEW8aofs6pqps3u8VKaUVQRQHKt2hpgCpHcfRNLbZRFJyTaemaaNdnE75cDicTEaaprVajU6ns7m/1TRq21ajUUdvqfa8DxDe1ahNCFHka1GmjHDLoLXAwTdFWWpUGCY1NGqZZuDbVAleprqmJGGe55imaVkGIbamab7vr9cb7EKIo1RoSgrSaraB7DHGCSFY/cMYm80WSZJMp/Plcn1/f1+vNW5v7uI4/u1vf1u5qkkpMXBP0xTerZBKdLvdy8tLmI7e3t4eHBy8evXq6PC0Xq/f3d1Bjlh13d1uN4qi4XAI1OHTTz/F2gnginCnHw6Hg8FAKeV5HkibWFmFwg+DuLrnNxqNXq8npYQrB8brKEaAl8IgGPQxw7ExpvM8D9TfsixBJ8IRg5as3W4TQvCeYFwppbQsCzxVDAP3QT6xkwgi9tATmaZJdzjCcrmEV28SRuCyttvto6OjSMZZlsAhBqsXESxxXGRZsl7nWZa0280sS1arxdHRIDC6lLI4zjgvZrM5ti9j3CpEKZXgvNCrUrOKjf2Scr/LYjsK2C66WDUZp7uWshoP0t0KKOxV2o9Dtev65O5W9Zn7ob6NaiU/yNJq74by9YOKVO3kF/tfsz32D3qDLEvX6zUhstvttlotXWdYcmJaxtOnTz3PWywW9/cPzVbdtHQwv+D9WhQFZQqe0GEY2natVqtRSqF/PRz0MBqp1zyn33EtG8+ra5rneWVZapShjISt7XSxwI4h09Q1TfM8Wq830zQ9OztjVC/ykWnaUsKOhwVBrdWqgRht23at1iCE4MxOkuTu7i5uJLBCiqKk0+mAbw0/MiBbr1696vf7x8fH4BALIU5PTzVN+/bbbx8/fnx6evr61dXJycloNOp2u5PJBLp1nDIQKDYajXq9Dihhs9ngcgfoP51Oge4sFgsArQB1N5sN+iVCyOvXr1ut1mq1siyr1Woxxo6Pjw3DuLq6QrQj9hD8mqYhSeD4wyxRSokDAkGodoRKCDJ0XUdaVkrBOA85E3g4alqMMQnZgpOgByL7Sc4p7BiZttls1ut1meUYGBZFMZ/P7Y5LGFWUaIberNWwgCQMwxeffSqEIIxePH3S6/UmkwnTtYKX3UH/V7/6VRyl9UZwfX3t+95PPv+y5HmjQUteFEUhRKmrvSZwP7lVViIfRKDck+3t94E4gcT71r2apknJP+4G9zPtByXlfqRJKSnZPqn2fhn8QVjul69qx06uCqFqyJnnucHMyWSilHJdeGyZnPO3b99KyZvNervdlkpMJhOUT48fPxaGBPcCcjgkZCjoGGNYU053JCld1y3LmM3WWKnVbjZZs2nbtqGzfr/HCF0ul5pGHceiSmlUBUEgKfU8z3EsZGNeSinJbDYbDA6VpIZhtVqtNM2jKJpN52XBV6sZDAsty1ou16vVqtlsUqoVRQEkxvO8yWTiuj6qvk6ng9IUVOl6vV6r1ZbL5U9+8pObm5uzs7PVaoXtOghXaO1fvHjBGPvzP//zt2/fws+bEALwSQhxcnIyHo/hcQZ+DMpLLFGB5ngymUDdZ9v2er0ej8fYREC46Ha7eK9OTk4g2AUbDgwkDKWra6bYbelCqKOVgk9hdZ6iV6w+esQ29O+YBoOOj6MEJylGi+ghAfYg0kRZSikNw1gvV2C9ebaDswAfcU3W6vX68fEh4C7Hsdrt5uHhwcXFxT/8wz/EcdhsNpUShEjXtYUor69vs6woioJR/fDwiBCZpqlusG63W5R5nqecFzoj78AVolS1kpIxRhQhUqndltzthIARxoihM01jlJKdA7LEGSaFYJQwDcinFFwwppSs0uzWk5BSvchzzrkopVKEMqoIQfwKUe7uLCmlbHcQGKYuJaRVOqWalIpSzbIMy9yipsjA1UvFdVNhsFXQztVCM5iu66lJJRdaqlFFiFKddotoWpJnBtN8r24aBo7M2+HIcWxDmb4VNBq1m5u3i8nE85xwOTdNkxIpi8IwDCaUo9u1Vi1M1GK5iSKv223rVmuxTDebKef8aCEoVWkad9ttL+Cz2SwMw4uL8/WsEJlp2y0iCSFEqnK1Xraa/X7v6OWb10meTRdzTdc55ylPf/PH3xz3HjebTV4aSZxYVhmGcRynus4opVmeUkrjWClF1+sw8Os31w/CzAkhPI5qtdomDLE4xTTN2XjyyflFFEUqLx3NiONsdvuA6mtYlFLKFy9eOKbVbjTLLBdCUKk0QmXJN8vV6P7h7Oxstlq/ffv24snjMisF5SUvDMls3aKCdJutgpc13725ueFKJkl8MOj+8PLber3++PTpfLECNfzw9Ozq6mqxCYlp6q4rDH2RxIs44qbBTDNK00WWbjZZu9VKBVmPp4zIZrfNebFYz4LA8wOPMb0sSyXKOAw5557j8FKu16HneZLQyWxONaZpmm7bm8WCJKlhWAYzsjxdTGdpnDBKbc2hnOlUI4LkccYY0zQ9S1JCiG2bcRzXW/X1ZmV5ZpSFTmAfN05N0zRNXZmiLAuRlkUeG6Y2vrqxOLOFxtf5fJktZ0uSsEW2SuwsTRbPPnlEKT0adF5fXWJJa5rxu7vher02bOs9G/wPUg39CDVVSmm69kHKIu/jqx/c5HvqPlkVjR/fk+4ATLKfgfe+L/eIcrhpO2M5+b4/AH1/Vdv+ayt4YRoGChK05kQqqsibN28Gg8HR4NC1bJD62Y45lWVZksRhtGaMRNFGKkEIwQ7XsizzvIiiaLMOy1JQSq/v5rA5gTGmKHPPc9vt9uvXr13X1nVGlZJSYCqdJFEey16v5/n+ZrOZzWZ5nhNGCSGz2YxSCvQlC0Nt56mz2Wy4KBaLhRClplEuSs9zOJeO43ieo+umFCrLCkIk0wjnfJ2sALe6rqvT7e4NSilXPNqEYRhmaco51y1TpyxNU6y2XywWIGSjkNN1/U9/+hMMPuDF5HkeiuowDFer1fPnz9EXzefz0WhUFNlP/+yrzWal6zolClgoHg0FC+d8Op0KotbrNYhEURTphYGGsBRC7LyYXMdJkiSM1qZptJt113XDsExTURQbnWmdTs+2bZ1qQqg4jrMsq9eabGe2r5Siu4uz1WrVg1qz3hBFORmP5/O5qRuNej3KC0IIVsphz4phGAAlgcNVHanc0ULyPF+vl3mRCsF1ndm2bVr67e1tkXPGGCGsLDlKMCHE/f09hkCe511eXo7Go/V6XavVNF0H0qPiSK8u2Y+jaL9Pq26apn8cXeTHVLy7R94vGj8Ecj74dUJI9fjboPqon9wHfiilSr3jzexHb1UY/+gNVSVThDFmaLppGKfHx41Gox7UiNgqXNHWu0wry5xSWpZFlke9Xq9/0PN9dz6fw1xos4miMM6yjHMZx3Gz2Tw9PY3j+Pr6Stf1i/OzIPDjOA58F4WoZRiEkHa73el0kiRq1dxGo0EYXa7KNIuLogBFcj6faqbhunaWJZtwBT69ptMo3mR5VJa5rjMuSl1nvu+GYShE6bq279eEQENueJ6nFA2oAyJ+u9EsyxJiBc/zDEODwQTwCeibZuMJDAthG0F3usosy+r1Okhb0CL88MMP6/X69PQ0Lyz4poEvJimZz+eWZaBN5ZxrpkHIdgkPBjloW2azWc7LrQu9xsqy5HIbeJQxfbevU0k9iqKSSwAn6HEcRzMMjRACyC0XinOJk/FwcFwIjh5V13XdNHCsO45T5sVisSBCSik9z9Moy7KMUJZlWavV8jwP86Fms1mWOfg0OGcJISAhQv+dJEkYrtMsJkRBDacbrCxLx/YsyypLAfQI12ezWU/TdDR66Pf76FaiaJNlie1aeZEWZSYJeaeikHvaPPpjZOvqKlfVhqb3udcfRCP5qHOrMuGPRu8O5Hx/pr/7AueKlFLf80fEUfcBZrOfqPcjEzfH8ZSQZSEIEZZu2Jbte55j2Y16Swo5ny+JkIwxx/ZM06REwwXturYQXEtou93EygF4eOMlwZU4z0shRK1Zm8/n0KQ/efLk5GiQpsnDw8PpydFms2o2641aLU0TsPLv729FTlar1SYKF4uFEMLzPKZpgM5BQFU7U0ac7ppmMEZqdQ8+vJZldLqtkueGodfrQbvV4Vyapm6atu8FRVFIs+k4DlOk3+8XRcHLMssypogeMEkUdPpJHNdqNThDj6fbZbpKKczicNmBsYCxPiZ7Sqmjo6PLqzcwaA2CoN1u2p776aefFkX26tUry9oaLidJjGbBdd3VbOuqzDnXiUKGzNJE13WhJCZD2m6Up2nachHZtu35jmkaZVnmeapptN/v27Ypyh28KYlSFNm+3W7PV0tQW7XdjVLqed48SZWQpqa7rlv3A0boerVq1htYCgScSQgB1CeOY6UEZBaKSJgjcs43m1jtRiZSCil5nudpxo+Ojjw3UErN50sEuev6pmkahgZyDzgPGlGw1apU/PhL33k9qT2zQ7mnJ9q/oNXeHELtaWc/ntTv56UPHqHSN+GLHccCheg/qf2tIrZ60h8Nsx99AdW/uq6XosAnVLXpQojb21vLsmpBUPcDx3E0uh0KB41GnuemaQjBhLQJYbPp/P7hDodFnpe6rteCOiFsPp9HUTKbzf70pz+ZpvlXf/WvPv/882izyrL0k08+MQ1ts1mBdbFabVf8LZfL5XRTlmWUxOBV27bNNA0Tv8V6ZVlWrVbbLj/NMkJIo1lHv+04FtOk73uNRhBFHmh3zVatKErOC00zdEPjgkZh6Fq2tUN316sVCDegpzRqddM0geADAe72DyGJwOZnOBTZtv369WssbPj+++8B8FiWNZ/PgyD46quv7u7uiqJYrVbpZMwYg3oY9bzjeXmelWUZx/FyudTY1qW3LEuLUThE5GWBTAjyJytLzJMQA4ZhGAw7DAspuWW5QRAQInWmGYbluq7BdIxJYQ8HcaOiW6ojHgrvj2PZiotRkuimddDrd9ptzXaw/oBSCm6NpmmrFbSgGSFktVoZpg7YJs/zohCQL2k6LcsiyxLOuSJC7NkimqZZrzVbrY5lWff3t6Zpnp+fg/h+d/0Af7rZbIZ0bQIWr/LeB6lp/ztV37VfuO5f3PvX/X4mrNo2pKvqV7YVKduOK8AoIoRIyX80m+E+lFJEDtkxCgR/xwncZ7Ttv7zq9VBK8zynioCJjbMt5lwJWfeDIAi63a5nO2VZlnmBzifw67oeoyINgsC2zSSJyrJUSt+y4DXD87yyFErRsiyHk9nTp08vLi5OTk5gvwXyVFnmURTd3ZVpHE8m4zzPYeFcUSsxSatgOtu26YZSSn3fN20Lf5RhGI3ANwxjE65KnmkaMQzNMLVa3WeMUaaUErpOLVsnhOg6sW29VA74N3e3t0EQVPF2dXWFckujjDEWJ8l8sciLoh7UFSHM0AVROS8Vo4Iow7YGx0eW60hKxEyto3ATR77vx1l6fDg4Pz9Hoo6izc3dLfDYVqcNnga86vI8x96IVs1HUk3TlGisMghkjIlSglEkd7Y3ZVlSaggh8lwqJT3Hct26bZu6ruV5qtFttlBU4SQFkTCKIs6547mWZUmiIO/gnM9mM40ynbIsy3rtTrvdXq9WScnBnmWMBUGA2nu3zaLcSrQMDZm/KAoiNSkl51wqUu78RXXD2mw2vJR0T91eOa+BhwBHvNF0QghZLBb4TMEl+JFNvep9+UKVx9ie8pB+dCM/dkPe2itK6Xs/2aKldL9sALxRvQz6PjIEEgww5eqVkL1JoNwZNLI9tu7+Lc9zyzAxscBRZJmmqRtYHhSGYbhaZ1lGFcGmWOygTrPYMLRefytTwN4VpEqN6egGcW1JKQ8PD1ut1u3tbRRFrm0qJVerlefaq9VyvSZ3NzeTydhxnMPDQzQJtm0pSgjBDpOMy7Isy3ojIBqBkGIdbhzHwqTLNs1ms0moKIrMD1yAPY5jWpYlpSKU27bjc1dKAtM+O/dbrdZ1koxGI2h2EBjQlUopDcuoNepCiIeHB875YhMppeB1DbMWMJIZY3d3d47jgJSH+rPZbN7d3ZmmuVgsIOFrNpuu615cXMyXC6UEpdSyLNu2MF7HS8LUIcuynJdZloVh6AU+AgaHI9/NnKWUGqOmaeIiIoRYlmUYuuCllNKyTEJIFEVMUV03cW0gfmzbBrM0ThMM2+bz+fX1tUZZs1a3TBO7D5bLZcpFxSVA9wiZhVKqLEs0GsCNQBAnAh6+GWVKKckYMQwDLw8jE8ZYWRZhGCpFXdetNwKpOBeFH7iWbfT73eVyKURpWb6UEhY2Otnr3HD8VHXmfrrbuyn60W0/sD6oJBnb92WqQrBafL/NYFVB+67Zw6Gye0AB26UdAKMq55vdC2B73HH5Y7todq+NUaoRwrIsL9JM13XP8VutNiEsjtMkSm3TdF3XsdADyDhPUBcZhmZbrlRcCGVbTpLGnHPBpdBFlmVRlOR5rjHj/Pz85ubm+vq63W7quj56uIvjSNd12zK63Xa3286SRNc1ZF2lxFTMCCEFL/Xd4vWCl2EYmqbZ7XZt28bmUwzWpJSM0UajLmUpZNnptCglJc8NUzs6HsRxbJmOZdlKqTTJGaO6rsWLzcHBQb1eL9LMcZwsTjCIOzo6wjkCSU4YhqPRKMuyVkeDDblSCqRwpRS0WoZhtFotCGHBWU/T9OjoCL5SUsp6PSiK4rvvvqvVfMKo41igXEdRiHIagcEYg+kLdPd5ntca9eoz0jQNkyV87kmcOo5j2UaSxGEYahr1PEdJURR5zQ9c18+yTJZC103GWDW+r1RskEowxsAp7Xd7zVo93GygQSnLkmk6ukHOueNaUso4jqXcajLozsZB7HbpaNVSRJ0ahg4DTugYqxCQUirJKaWe52V5hM2thBCsKpBSDgYD04YHQr5er/8/mJNilez1C2gAAAAASUVORK5CYII=\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "execution_count": 15
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "1bEUwwzcVG8o"
+ },
+ "source": [
+ "You can also use the visualization API provided by MMClassification to show the inference result."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "id": "BcSNyvAWRx20",
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 304
+ },
+ "outputId": "0d68077f-2ec8-4f3d-8aaa-18d4021ca77b"
+ },
+ "source": [
+ "from mmcls.core.visualization import imshow_infos\n",
+ "\n",
+ "filepath = 'data/cats_dogs_dataset/training_set/training_set/cats/cat.1.jpg'\n",
+ "\n",
+ "result = {\n",
+ " 'pred_class': results['pred_class'][0],\n",
+ " 'pred_label': results['pred_label'][0],\n",
+ " 'pred_score': results['pred_score'][0],\n",
+ "}\n",
+ "\n",
+ "img = imshow_infos(filepath, result)"
+ ],
+ "execution_count": null,
+ "outputs": [
+ {
+ "output_type": "display_data",
+ "data": {
+ "image/png": "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\n",
+ "text/plain": [
+ "
"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/config.md b/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/config.md
new file mode 100644
index 0000000000000000000000000000000000000000..16e43ac27efc20aa8bb3e1fb3e858873b980d8f8
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/config.md
@@ -0,0 +1,417 @@
+# Tutorial 1: Learn about Configs
+
+MMClassification mainly uses python files as configs. The design of our configuration file system integrates modularity and inheritance, facilitating users to conduct various experiments. All configuration files are placed in the `configs` folder, which mainly contains the primitive configuration folder of `_base_` and many algorithm folders such as `resnet`, `swin_transformer`, `vision_transformer`, etc.
+
+If you wish to inspect the config file, you may run `python tools/misc/print_config.py /PATH/TO/CONFIG` to see the complete config.
+
+
+
+- [Config File and Checkpoint Naming Convention](#config-file-and-checkpoint-naming-convention)
+- [Config File Structure](#config-file-structure)
+- [Inherit and Modify Config File](#inherit-and-modify-config-file)
+ - [Use intermediate variables in configs](#use-intermediate-variables-in-configs)
+ - [Ignore some fields in the base configs](#ignore-some-fields-in-the-base-configs)
+ - [Use some fields in the base configs](#use-some-fields-in-the-base-configs)
+- [Modify config through script arguments](#modify-config-through-script-arguments)
+- [Import user-defined modules](#import-user-defined-modules)
+- [FAQ](#faq)
+
+
+
+## Config File and Checkpoint Naming Convention
+
+We follow the below convention to name config files. Contributors are advised to follow the same style. The config file names are divided into four parts: algorithm info, module information, training information and data information. Logically, different parts are concatenated by underscores `'_'`, and words in the same part are concatenated by dashes `'-'`.
+
+```
+{algorithm info}_{module info}_{training info}_{data info}.py
+```
+
+- `algorithm info`:algorithm information, model name and neural network architecture, such as resnet, etc.;
+- `module info`: module information is used to represent some special neck, head and pretrain information;
+- `training info`:Training information, some training schedule, including batch size, lr schedule, data augment and the like;
+- `data info`:Data information, dataset name, input size and so on, such as imagenet, cifar, etc.;
+
+### Algorithm information
+
+The main algorithm name and the corresponding branch architecture information. E.g:
+
+- `resnet50`
+- `mobilenet-v3-large`
+- `vit-small-patch32` : `patch32` represents the size of the partition in `ViT` algorithm;
+- `seresnext101-32x4d` : `SeResNet101` network structure, `32x4d` means that `groups` and `width_per_group` are 32 and 4 respectively in `Bottleneck`;
+
+### Module information
+
+Some special `neck`, `head` and `pretrain` information. In classification tasks, `pretrain` information is the most commonly used:
+
+- `in21k-pre` : pre-trained on ImageNet21k;
+- `in21k-pre-3rd-party` : pre-trained on ImageNet21k and the checkpoint is converted from a third-party repository;
+
+### Training information
+
+Training schedule, including training type, `batch size`, `lr schedule`, data augment, special loss functions and so on:
+
+- format `{gpu x batch_per_gpu}`, such as `8xb32`
+
+Training type (mainly seen in the transformer network, such as the `ViT` algorithm, which is usually divided into two training type: pre-training and fine-tuning):
+
+- `ft` : configuration file for fine-tuning
+- `pt` : configuration file for pretraining
+
+Training recipe. Usually, only the part that is different from the original paper will be marked. These methods will be arranged in the order `{pipeline aug}-{train aug}-{loss trick}-{scheduler}-{epochs}`.
+
+- `coslr-200e` : use cosine scheduler to train 200 epochs
+- `autoaug-mixup-lbs-coslr-50e` : use `autoaug`, `mixup`, `label smooth`, `cosine scheduler` to train 50 epochs
+
+### Data information
+
+- `in1k` : `ImageNet1k` dataset, default to use the input image size of 224x224;
+- `in21k` : `ImageNet21k` dataset, also called `ImageNet22k` dataset, default to use the input image size of 224x224;
+- `in1k-384px` : Indicates that the input image size is 384x384;
+- `cifar100`
+
+### Config File Name Example
+
+```
+repvgg-D2se_deploy_4xb64-autoaug-lbs-mixup-coslr-200e_in1k.py
+```
+
+- `repvgg-D2se`: Algorithm information
+ - `repvgg`: The main algorithm.
+ - `D2se`: The architecture.
+- `deploy`: Module information, means the backbone is in the deploy state.
+- `4xb64-autoaug-lbs-mixup-coslr-200e`: Training information.
+ - `4xb64`: Use 4 GPUs and the size of batches per GPU is 64.
+ - `autoaug`: Use `AutoAugment` in training pipeline.
+ - `lbs`: Use label smoothing loss.
+ - `mixup`: Use `mixup` training augment method.
+ - `coslr`: Use cosine learning rate scheduler.
+ - `200e`: Train the model for 200 epochs.
+- `in1k`: Dataset information. The config is for `ImageNet1k` dataset and the input size is `224x224`.
+
+```{note}
+Some configuration files currently do not follow this naming convention, and related files will be updated in the near future.
+```
+
+### Checkpoint Naming Convention
+
+The naming of the weight mainly includes the configuration file name, date and hash value.
+
+```
+{config_name}_{date}-{hash}.pth
+```
+
+## Config File Structure
+
+There are four kinds of basic component file in the `configs/_base_` folders, namely:
+
+- [models](https://github.com/open-mmlab/mmclassification/tree/master/configs/_base_/models)
+- [datasets](https://github.com/open-mmlab/mmclassification/tree/master/configs/_base_/datasets)
+- [schedules](https://github.com/open-mmlab/mmclassification/tree/master/configs/_base_/schedules)
+- [runtime](https://github.com/open-mmlab/mmclassification/blob/master/configs/_base_/default_runtime.py)
+
+You can easily build your own training config file by inherit some base config files. And the configs that are composed by components from `_base_` are called _primitive_.
+
+For easy understanding, we use [ResNet50 primitive config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnet/resnet50_8xb32_in1k.py) as a example and comment the meaning of each line. For more detaile, please refer to the API documentation.
+
+```python
+_base_ = [
+ '../_base_/models/resnet50.py', # model
+ '../_base_/datasets/imagenet_bs32.py', # data
+ '../_base_/schedules/imagenet_bs256.py', # training schedule
+ '../_base_/default_runtime.py' # runtime setting
+]
+```
+
+The four parts are explained separately below, and the above-mentioned ResNet50 primitive config are also used as an example.
+
+### model
+
+The parameter `"model"` is a python dictionary in the configuration file, which mainly includes information such as network structure and loss function:
+
+- `type` : Classifier name, MMCls supports `ImageClassifier`, refer to [API documentation](https://mmclassification.readthedocs.io/en/latest/api/models.html#classifier).
+- `backbone` : Backbone configs, refer to [API documentation](https://mmclassification.readthedocs.io/en/latest/api/models.html#backbones) for available options.
+- `neck` :Neck network name, MMCls supports `GlobalAveragePooling`, please refer to [API documentation](https://mmclassification.readthedocs.io/en/latest/api/models.html#necks).
+- `head`: Head network name, MMCls supports single-label and multi-label classification head networks, available options refer to [API documentation](https://mmclassification.readthedocs.io/en/latest/api/models.html#heads).
+ - `loss`: Loss function type, supports `CrossEntropyLoss`, [`LabelSmoothLoss`](https://github.com/open-mmlab/mmclassification/blob/master/configs/_base_/models/resnet50_label_smooth.py) etc., For available options, refer to [API documentation](https://mmclassification.readthedocs.io/en/latest/api/models.html#losses).
+- `train_cfg` :Training augment config, MMCls supports [`mixup`](https://github.com/open-mmlab/mmclassification/blob/master/configs/_base_/models/resnet50_mixup.py), [`cutmix`](https://github.com/open-mmlab/mmclassification/blob/master/configs/_base_/models/resnet50_cutmix.py) and other augments.
+
+```{note}
+The 'type' in the configuration file is not a constructed parameter, but a class name.
+```
+
+```python
+model = dict(
+ type='ImageClassifier', # Classifier name
+ backbone=dict(
+ type='ResNet', # Backbones name
+ depth=50, # depth of backbone, ResNet has options of 18, 34, 50, 101, 152.
+ num_stages=4, # number of stages,The feature maps generated by these states are used as the input for the subsequent neck and head.
+ out_indices=(3, ), # The output index of the output feature maps.
+ frozen_stages=-1, # the stage to be frozen, '-1' means not be forzen
+ style='pytorch'), # The style of backbone, 'pytorch' means that stride 2 layers are in 3x3 conv, 'caffe' means stride 2 layers are in 1x1 convs.
+ neck=dict(type='GlobalAveragePooling'), # neck network name
+ head=dict(
+ type='LinearClsHead', # linear classification head,
+ num_classes=1000, # The number of output categories, consistent with the number of categories in the dataset
+ in_channels=2048, # The number of input channels, consistent with the output channel of the neck
+ loss=dict(type='CrossEntropyLoss', loss_weight=1.0), # Loss function configuration information
+ topk=(1, 5), # Evaluation index, Top-k accuracy rate, here is the accuracy rate of top1 and top5
+ ))
+```
+
+### data
+
+The parameter `"data"` is a python dictionary in the configuration file, which mainly includes information to construct dataloader:
+
+- `samples_per_gpu` : the BatchSize of each GPU when building the dataloader
+- `workers_per_gpu` : the number of threads per GPU when building dataloader
+- `train | val | test` : config to construct dataset
+ - `type`: Dataset name, MMCls supports `ImageNet`, `Cifar` etc., refer to [API documentation](https://mmclassification.readthedocs.io/en/latest/api/datasets.html)
+ - `data_prefix` : Dataset root directory
+ - `pipeline` : Data processing pipeline, refer to related tutorial [CUSTOM DATA PIPELINES](https://mmclassification.readthedocs.io/en/latest/tutorials/data_pipeline.html)
+
+The parameter `evaluation` is also a dictionary, which is the configuration information of `evaluation hook`, mainly including evaluation interval, evaluation index, etc..
+
+```python
+# dataset settings
+dataset_type = 'ImageNet' # dataset name,
+img_norm_cfg = dict( # Image normalization config to normalize the input images
+ mean=[123.675, 116.28, 103.53], # Mean values used to pre-training the pre-trained backbone models
+ std=[58.395, 57.12, 57.375], # Standard variance used to pre-training the pre-trained backbone models
+ to_rgb=True) # Whether to invert the color channel, rgb2bgr or bgr2rgb.
+# train data pipeline
+train_pipeline = [
+ dict(type='LoadImageFromFile'), # First pipeline to load images from file path
+ dict(type='RandomResizedCrop', size=224), # RandomResizedCrop
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'), # Randomly flip the picture horizontally with a probability of 0.5
+ dict(type='Normalize', **img_norm_cfg), # normalization
+ dict(type='ImageToTensor', keys=['img']), # convert image from numpy into torch.Tensor
+ dict(type='ToTensor', keys=['gt_label']), # convert gt_label into torch.Tensor
+ dict(type='Collect', keys=['img', 'gt_label']) # Pipeline that decides which keys in the data should be passed to the detector
+]
+# test data pipeline
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='Resize', size=(256, -1)),
+ dict(type='CenterCrop', crop_size=224),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img']) # do not pass gt_label while testing
+]
+data = dict(
+ samples_per_gpu=32, # Batch size of a single GPU
+ workers_per_gpu=2, # Worker to pre-fetch data for each single GPU
+ train=dict( # Train dataset config
+ train=dict( # train data config
+ type=dataset_type, # dataset name
+ data_prefix='data/imagenet/train', # Dataset root, when ann_file does not exist, the category information is automatically obtained from the root folder
+ pipeline=train_pipeline), # train data pipeline
+ val=dict( # val data config
+ type=dataset_type,
+ data_prefix='data/imagenet/val',
+ ann_file='data/imagenet/meta/val.txt', # ann_file existes, the category information is obtained from file
+ pipeline=test_pipeline),
+ test=dict( # test data config
+ type=dataset_type,
+ data_prefix='data/imagenet/val',
+ ann_file='data/imagenet/meta/val.txt',
+ pipeline=test_pipeline))
+evaluation = dict( # The config to build the evaluation hook, refer to https://github.com/open-mmlab/mmdetection/blob/master/mmdet/core/evaluation/eval_hooks.py#L7 for more details.
+ interval=1, # Evaluation interval
+ metric='accuracy') # Metrics used during evaluation
+```
+
+### training schedule
+
+Mainly include optimizer settings, `optimizer hook` settings, learning rate schedule and `runner` settings:
+
+- `optimizer`: optimizer setting , support all optimizers in `pytorch`, refer to related [mmcv](https://mmcv.readthedocs.io/en/latest/_modules/mmcv/runner/optimizer/default_constructor.html#DefaultOptimizerConstructor) documentation.
+- `optimizer_config`: `optimizer hook` configuration file, such as setting gradient limit, refer to related [mmcv](https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/optimizer.py#L8) code.
+- `lr_config`: Learning rate scheduler, supports "CosineAnnealing", "Step", "Cyclic", etc. refer to related [mmcv](https://mmcv.readthedocs.io/en/latest/_modules/mmcv/runner/hooks/lr_updater.html#LrUpdaterHook) documentation for more options.
+- `runner`: For `runner`, please refer to `mmcv` for [`runner`](https://mmcv.readthedocs.io/en/latest/understand_mmcv/runner.html) introduction document.
+
+```python
+# he configuration file used to build the optimizer, support all optimizers in PyTorch.
+optimizer = dict(type='SGD', # Optimizer type
+ lr=0.1, # Learning rate of optimizers, see detail usages of the parameters in the documentation of PyTorch
+ momentum=0.9, # Momentum
+ weight_decay=0.0001) # Weight decay of SGD
+# Config used to build the optimizer hook, refer to https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/optimizer.py#L8 for implementation details.
+optimizer_config = dict(grad_clip=None) # Most of the methods do not use gradient clip
+# Learning rate scheduler config used to register LrUpdater hook
+lr_config = dict(policy='step', # The policy of scheduler, also support CosineAnnealing, Cyclic, etc. Refer to details of supported LrUpdater from https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/lr_updater.py#L9.
+ step=[30, 60, 90]) # Steps to decay the learning rate
+runner = dict(type='EpochBasedRunner', # Type of runner to use (i.e. IterBasedRunner or EpochBasedRunner)
+ max_epochs=100) # Runner that runs the workflow in total max_epochs. For IterBasedRunner use `max_iters`
+```
+
+### runtime setting
+
+This part mainly includes saving the checkpoint strategy, log configuration, training parameters, breakpoint weight path, working directory, etc..
+
+```python
+# Config to set the checkpoint hook, Refer to https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/checkpoint.py for implementation.
+checkpoint_config = dict(interval=1) # The save interval is 1
+# config to register logger hook
+log_config = dict(
+ interval=100, # Interval to print the log
+ hooks=[
+ dict(type='TextLoggerHook'), # The Tensorboard logger is also supported
+ # dict(type='TensorboardLoggerHook')
+ ])
+
+dist_params = dict(backend='nccl') # Parameters to setup distributed training, the port can also be set.
+log_level = 'INFO' # The output level of the log.
+resume_from = None # Resume checkpoints from a given path, the training will be resumed from the epoch when the checkpoint's is saved.
+workflow = [('train', 1)] # Workflow for runner. [('train', 1)] means there is only one workflow and the workflow named 'train' is executed once.
+work_dir = 'work_dir' # Directory to save the model checkpoints and logs for the current experiments.
+```
+
+## Inherit and Modify Config File
+
+For easy understanding, we recommend contributors to inherit from existing methods.
+
+For all configs under the same folder, it is recommended to have only **one** _primitive_ config. All other configs should inherit from the _primitive_ config. In this way, the maximum of inheritance level is 3.
+
+For example, if your config file is based on ResNet with some other modification, you can first inherit the basic ResNet structure, dataset and other training setting by specifying `_base_ ='./resnet50_8xb32_in1k.py'` (The path relative to your config file), and then modify the necessary parameters in the config file. A more specific example, now we want to use almost all configs in `configs/resnet/resnet50_8xb32_in1k.py`, but change the number of training epochs from 100 to 300, modify when to decay the learning rate, and modify the dataset path, you can create a new config file `configs/resnet/resnet50_8xb32-300e_in1k.py` with content as below:
+
+```python
+_base_ = './resnet50_8xb32_in1k.py'
+
+runner = dict(max_epochs=300)
+lr_config = dict(step=[150, 200, 250])
+
+data = dict(
+ train=dict(data_prefix='mydata/imagenet/train'),
+ val=dict(data_prefix='mydata/imagenet/train', ),
+ test=dict(data_prefix='mydata/imagenet/train', )
+)
+```
+
+### Use intermediate variables in configs
+
+Some intermediate variables are used in the configuration file. The intermediate variables make the configuration file clearer and easier to modify.
+
+For example, `train_pipeline` / `test_pipeline` is the intermediate variable of the data pipeline. We first need to define `train_pipeline` / `test_pipeline`, and then pass them to `data`. If you want to modify the size of the input image during training and testing, you need to modify the intermediate variables of `train_pipeline` / `test_pipeline`.
+
+```python
+img_norm_cfg = dict(
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='RandomResizedCrop', size=384, backend='pillow',),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='Collect', keys=['img', 'gt_label'])
+]
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='Resize', size=384, backend='pillow'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img'])
+]
+data = dict(
+ train=dict(pipeline=train_pipeline),
+ val=dict(pipeline=test_pipeline),
+ test=dict(pipeline=test_pipeline))
+```
+
+### Ignore some fields in the base configs
+
+Sometimes, you need to set `_delete_=True` to ignore some domain content in the basic configuration file. You can refer to [mmcv](https://mmcv.readthedocs.io/en/latest/understand_mmcv/config.html#inherit-from-base-config-with-ignored-fields) for more instructions.
+
+The following is an example. If you want to use cosine schedule in the above ResNet50 case, just using inheritance and directly modify it will report `get unexcepected keyword'step'` error, because the `'step'` field of the basic config in `lr_config` domain information is reserved, and you need to add `_delete_ =True` to ignore the content of `lr_config` related fields in the basic configuration file:
+
+```python
+_base_ = '../../configs/resnet/resnet50_8xb32_in1k.py'
+
+lr_config = dict(
+ _delete_=True,
+ policy='CosineAnnealing',
+ min_lr=0,
+ warmup='linear',
+ by_epoch=True,
+ warmup_iters=5,
+ warmup_ratio=0.1
+)
+```
+
+### Use some fields in the base configs
+
+Sometimes, you may refer to some fields in the `_base_` config, so as to avoid duplication of definitions. You can refer to [mmcv](https://mmcv.readthedocs.io/en/latest/understand_mmcv/config.html#reference-variables-from-base) for some more instructions.
+
+The following is an example of using auto augment in the training data preprocessing pipeline, refer to [`configs/_base_/datasets/imagenet_bs64_autoaug.py`](https://github.com/open-mmlab/mmclassification/blob/master/configs/_base_/datasets/imagenet_bs64_autoaug.py). When defining `train_pipeline`, just add the definition file name of auto augment to `_base_`, and then use `{{_base_.auto_increasing_policies}}` to reference the variables:
+
+```python
+_base_ = ['./pipelines/auto_aug.py']
+
+# dataset settings
+dataset_type = 'ImageNet'
+img_norm_cfg = dict(
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='RandomResizedCrop', size=224),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(type='AutoAugment', policies={{_base_.auto_increasing_policies}}),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='Collect', keys=['img', 'gt_label'])
+]
+test_pipeline = [...]
+data = dict(
+ samples_per_gpu=64,
+ workers_per_gpu=2,
+ train=dict(..., pipeline=train_pipeline),
+ val=dict(..., pipeline=test_pipeline))
+evaluation = dict(interval=1, metric='accuracy')
+```
+
+## Modify config through script arguments
+
+When users use the script "tools/train.py" or "tools/test.py" to submit tasks or use some other tools, they can directly modify the content of the configuration file used by specifying the `--cfg-options` parameter.
+
+- Update config keys of dict chains.
+
+ The config options can be specified following the order of the dict keys in the original config.
+ For example, `--cfg-options model.backbone.norm_eval=False` changes the all BN modules in model backbones to `train` mode.
+
+- Update keys inside a list of configs.
+
+ Some config dicts are composed as a list in your config. For example, the training pipeline `data.train.pipeline` is normally a list
+ e.g. `[dict(type='LoadImageFromFile'), dict(type='TopDownRandomFlip', flip_prob=0.5), ...]`. If you want to change `'flip_prob=0.5'` to `'flip_prob=0.0'` in the pipeline,
+ you may specify `--cfg-options data.train.pipeline.1.flip_prob=0.0`.
+
+- Update values of list/tuples.
+
+ If the value to be updated is a list or a tuple. For example, the config file normally sets `workflow=[('train', 1)]`. If you want to
+ change this key, you may specify `--cfg-options workflow="[(train,1),(val,1)]"`. Note that the quotation mark " is necessary to
+ support list/tuple data types, and that **NO** white space is allowed inside the quotation marks in the specified value.
+
+## Import user-defined modules
+
+```{note}
+This part may only be used when using MMClassification as a third party library to build your own project, and beginners can skip it.
+```
+
+After studying the follow-up tutorials [ADDING NEW DATASET](https://mmclassification.readthedocs.io/en/latest/tutorials/new_dataset.html), [CUSTOM DATA PIPELINES](https://mmclassification.readthedocs.io/en/latest/tutorials/data_pipeline.html), [ADDING NEW MODULES](https://mmclassification.readthedocs.io/en/latest/tutorials/new_modules.html). You may use MMClassification to complete your project and create new classes of datasets, models, data enhancements, etc. in the project. In order to streamline the code, you can use MMClassification as a third-party library, you just need to keep your own extra code and import your own custom module in the configuration files. For examples, you may refer to [OpenMMLab Algorithm Competition Project](https://github.com/zhangrui-wolf/openmmlab-competition-2021) .
+
+Add the following code to your own configuration files:
+
+```python
+custom_imports = dict(
+ imports=['your_dataset_class',
+ 'your_transforme_class',
+ 'your_model_class',
+ 'your_module_class'],
+ allow_failed_imports=False)
+```
+
+## FAQ
+
+- None
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/data_pipeline.md b/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/data_pipeline.md
new file mode 100644
index 0000000000000000000000000000000000000000..4b32280e2830174e8f78812a34ce4d88b63d92d8
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/data_pipeline.md
@@ -0,0 +1,150 @@
+# Tutorial 4: Custom Data Pipelines
+
+## Design of Data pipelines
+
+Following typical conventions, we use `Dataset` and `DataLoader` for data loading
+with multiple workers. Indexing `Dataset` returns a dict of data items corresponding to
+the arguments of models forward method.
+
+The data preparation pipeline and the dataset is decomposed. Usually a dataset
+defines how to process the annotations and a data pipeline defines all the steps to prepare a data dict.
+A pipeline consists of a sequence of operations. Each operation takes a dict as input and also output a dict for the next transform.
+
+The operations are categorized into data loading, pre-processing and formatting.
+
+Here is an pipeline example for ResNet-50 training on ImageNet.
+
+```python
+img_norm_cfg = dict(
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='RandomResizedCrop', size=224),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='Collect', keys=['img', 'gt_label'])
+]
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='Resize', size=256),
+ dict(type='CenterCrop', crop_size=224),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img'])
+]
+```
+
+For each operation, we list the related dict fields that are added/updated/removed.
+At the end of the pipeline, we use `Collect` to only retain the necessary items for forward computation.
+
+### Data loading
+
+`LoadImageFromFile`
+
+- add: img, img_shape, ori_shape
+
+By default, `LoadImageFromFile` loads images from disk but it may lead to IO bottleneck for efficient small models.
+Various backends are supported by mmcv to accelerate this process. For example, if the training machines have setup
+[memcached](https://memcached.org/), we can revise the config as follows.
+
+```
+memcached_root = '/mnt/xxx/memcached_client/'
+train_pipeline = [
+ dict(
+ type='LoadImageFromFile',
+ file_client_args=dict(
+ backend='memcached',
+ server_list_cfg=osp.join(memcached_root, 'server_list.conf'),
+ client_cfg=osp.join(memcached_root, 'client.conf'))),
+]
+```
+
+More supported backends can be found in [mmcv.fileio.FileClient](https://github.com/open-mmlab/mmcv/blob/master/mmcv/fileio/file_client.py).
+
+### Pre-processing
+
+`Resize`
+
+- add: scale, scale_idx, pad_shape, scale_factor, keep_ratio
+- update: img, img_shape
+
+`RandomFlip`
+
+- add: flip, flip_direction
+- update: img
+
+`RandomCrop`
+
+- update: img, pad_shape
+
+`Normalize`
+
+- add: img_norm_cfg
+- update: img
+
+### Formatting
+
+`ToTensor`
+
+- update: specified by `keys`.
+
+`ImageToTensor`
+
+- update: specified by `keys`.
+
+`Collect`
+
+- remove: all other keys except for those specified by `keys`
+
+For more information about other data transformation classes, please refer to [Data Transformations](../api/transforms.rst)
+
+## Extend and use custom pipelines
+
+1. Write a new pipeline in any file, e.g., `my_pipeline.py`, and place it in
+ the folder `mmcls/datasets/pipelines/`. The pipeline class needs to override
+ the `__call__` method which takes a dict as input and returns a dict.
+
+ ```python
+ from mmcls.datasets import PIPELINES
+
+ @PIPELINES.register_module()
+ class MyTransform(object):
+
+ def __call__(self, results):
+ # apply transforms on results['img']
+ return results
+ ```
+
+2. Import the new class in `mmcls/datasets/pipelines/__init__.py`.
+
+ ```python
+ ...
+ from .my_pipeline import MyTransform
+
+ __all__ = [
+ ..., 'MyTransform'
+ ]
+ ```
+
+3. Use it in config files.
+
+ ```python
+ img_norm_cfg = dict(
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
+ train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='RandomResizedCrop', size=224),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(type='MyTransform'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='Collect', keys=['img', 'gt_label'])
+ ]
+ ```
+
+## Pipeline visualization
+
+After designing data pipelines, you can use the [visualization tools](../tools/visualization.md) to view the performance.
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/finetune.md b/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/finetune.md
new file mode 100644
index 0000000000000000000000000000000000000000..98538fbf61bf03d6b2ccac15f077235fe7d46f09
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/finetune.md
@@ -0,0 +1,236 @@
+# Tutorial 2: Fine-tune Models
+
+Classification models pre-trained on the ImageNet dataset have been demonstrated to be effective for other datasets and other downstream tasks.
+This tutorial provides instructions for users to use the models provided in the [Model Zoo](../model_zoo.md) for other datasets to obtain better performance.
+
+There are two steps to fine-tune a model on a new dataset.
+
+- Add support for the new dataset following [Tutorial 3: Customize Dataset](new_dataset.md).
+- Modify the configs as will be discussed in this tutorial.
+
+Assume we have a ResNet-50 model pre-trained on the ImageNet-2012 dataset and want
+to take the fine-tuning on the CIFAR-10 dataset, we need to modify five parts in the
+config.
+
+## Inherit base configs
+
+At first, create a new config file
+`configs/tutorial/resnet50_finetune_cifar.py` to store our configs. Of course,
+the path can be customized by yourself.
+
+To reuse the common parts among different configs, we support inheriting
+configs from multiple existing configs. To fine-tune a ResNet-50 model, the new
+config needs to inherit `configs/_base_/models/resnet50.py` to build the basic
+structure of the model. To use the CIFAR-10 dataset, the new config can also
+simply inherit `configs/_base_/datasets/cifar10_bs16.py`. For runtime settings such as
+training schedules, the new config needs to inherit
+`configs/_base_/default_runtime.py`.
+
+To inherit all above configs, put the following code at the config file.
+
+```python
+_base_ = [
+ '../_base_/models/resnet50.py',
+ '../_base_/datasets/cifar10_bs16.py', '../_base_/default_runtime.py'
+]
+```
+
+Besides, you can also choose to write the whole contents rather than use inheritance,
+like [`configs/lenet/lenet5_mnist.py`](https://github.com/open-mmlab/mmclassification/blob/master/configs/lenet/lenet5_mnist.py).
+
+## Modify model
+
+When fine-tuning a model, usually we want to load the pre-trained backbone
+weights and train a new classification head.
+
+To load the pre-trained backbone, we need to change the initialization config
+of the backbone and use `Pretrained` initialization function. Besides, in the
+`init_cfg`, we use `prefix='backbone'` to tell the initialization
+function to remove the prefix of keys in the checkpoint, for example, it will
+change `backbone.conv1` to `conv1`. And here we use an online checkpoint, it
+will be downloaded during training, you can also download the model manually
+and use a local path.
+
+And then we need to modify the head according to the class numbers of the new
+datasets by just changing `num_classes` in the head.
+
+```python
+model = dict(
+ backbone=dict(
+ init_cfg=dict(
+ type='Pretrained',
+ checkpoint='https://download.openmmlab.com/mmclassification/v0/resnet/resnet50_8xb32_in1k_20210831-ea4938fc.pth',
+ prefix='backbone',
+ )),
+ head=dict(num_classes=10),
+)
+```
+
+```{tip}
+Here we only need to set the part of configs we want to modify, because the
+inherited configs will be merged and get the entire configs.
+```
+
+Sometimes, we want to freeze the first several layers' parameters of the
+backbone, that will help the network to keep ability to extract low-level
+information learnt from pre-trained model. In MMClassification, you can simply
+specify how many layers to freeze by `frozen_stages` argument. For example, to
+freeze the first two layers' parameters, just use the following config:
+
+```python
+model = dict(
+ backbone=dict(
+ frozen_stages=2,
+ init_cfg=dict(
+ type='Pretrained',
+ checkpoint='https://download.openmmlab.com/mmclassification/v0/resnet/resnet50_8xb32_in1k_20210831-ea4938fc.pth',
+ prefix='backbone',
+ )),
+ head=dict(num_classes=10),
+)
+```
+
+```{note}
+Not all backbones support the `frozen_stages` argument by now. Please check
+[the docs](https://mmclassification.readthedocs.io/en/latest/api/models.html#backbones)
+to confirm if your backbone supports it.
+```
+
+## Modify dataset
+
+When fine-tuning on a new dataset, usually we need to modify some dataset
+configs. Here, we need to modify the pipeline to resize the image from 32 to
+224 to fit the input size of the model pre-trained on ImageNet, and some other
+configs.
+
+```python
+img_norm_cfg = dict(
+ mean=[125.307, 122.961, 113.8575],
+ std=[51.5865, 50.847, 51.255],
+ to_rgb=False,
+)
+train_pipeline = [
+ dict(type='RandomCrop', size=32, padding=4),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(type='Resize', size=224),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='Collect', keys=['img', 'gt_label']),
+]
+test_pipeline = [
+ dict(type='Resize', size=224),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img']),
+]
+data = dict(
+ train=dict(pipeline=train_pipeline),
+ val=dict(pipeline=test_pipeline),
+ test=dict(pipeline=test_pipeline),
+)
+```
+
+## Modify training schedule
+
+The fine-tuning hyper parameters vary from the default schedule. It usually
+requires smaller learning rate and less training epochs.
+
+```python
+# lr is set for a batch size of 128
+optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)
+optimizer_config = dict(grad_clip=None)
+# learning policy
+lr_config = dict(policy='step', step=[15])
+runner = dict(type='EpochBasedRunner', max_epochs=200)
+log_config = dict(interval=100)
+```
+
+## Start Training
+
+Now, we have finished the fine-tuning config file as following:
+
+```python
+_base_ = [
+ '../_base_/models/resnet50.py',
+ '../_base_/datasets/cifar10_bs16.py', '../_base_/default_runtime.py'
+]
+
+# Model config
+model = dict(
+ backbone=dict(
+ frozen_stages=2,
+ init_cfg=dict(
+ type='Pretrained',
+ checkpoint='https://download.openmmlab.com/mmclassification/v0/resnet/resnet50_8xb32_in1k_20210831-ea4938fc.pth',
+ prefix='backbone',
+ )),
+ head=dict(num_classes=10),
+)
+
+# Dataset config
+img_norm_cfg = dict(
+ mean=[125.307, 122.961, 113.8575],
+ std=[51.5865, 50.847, 51.255],
+ to_rgb=False,
+)
+train_pipeline = [
+ dict(type='RandomCrop', size=32, padding=4),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(type='Resize', size=224),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='Collect', keys=['img', 'gt_label']),
+]
+test_pipeline = [
+ dict(type='Resize', size=224),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img']),
+]
+data = dict(
+ train=dict(pipeline=train_pipeline),
+ val=dict(pipeline=test_pipeline),
+ test=dict(pipeline=test_pipeline),
+)
+
+# Training schedule config
+# lr is set for a batch size of 128
+optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)
+optimizer_config = dict(grad_clip=None)
+# learning policy
+lr_config = dict(policy='step', step=[15])
+runner = dict(type='EpochBasedRunner', max_epochs=200)
+log_config = dict(interval=100)
+```
+
+Here we use 8 GPUs on your computer to train the model with the following
+command:
+
+```shell
+bash tools/dist_train.sh configs/tutorial/resnet50_finetune_cifar.py 8
+```
+
+Also, you can use only one GPU to train the model with the following command:
+
+```shell
+python tools/train.py configs/tutorial/resnet50_finetune_cifar.py
+```
+
+But wait, an important config need to be changed if using one GPU. We need to
+change the dataset config as following:
+
+```python
+data = dict(
+ samples_per_gpu=128,
+ train=dict(pipeline=train_pipeline),
+ val=dict(pipeline=test_pipeline),
+ test=dict(pipeline=test_pipeline),
+)
+```
+
+It's because our training schedule is for a batch size of 128. If using 8 GPUs,
+just use `samples_per_gpu=16` config in the base config file, and the total batch
+size will be 128. But if using one GPU, you need to change it to 128 manually to
+match the training schedule.
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/new_dataset.md b/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/new_dataset.md
new file mode 100644
index 0000000000000000000000000000000000000000..24e6fe9ecf86b39a59583d55350f3c2fc6279008
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/new_dataset.md
@@ -0,0 +1,239 @@
+# Tutorial 3: Customize Dataset
+
+We support many common public datasets for image classification task, you can find them in
+[this page](https://mmclassification.readthedocs.io/en/latest/api/datasets.html).
+
+In this section, we demonstrate how to [use your own dataset](#use-your-own-dataset)
+and [use dataset wrapper](#use-dataset-wrapper).
+
+## Use your own dataset
+
+### Reorganize dataset to existing format
+
+The simplest way to use your own dataset is to convert it to existing dataset formats.
+
+For multi-class classification task, we recommend to use the format of
+[`CustomDataset`](https://mmclassification.readthedocs.io/en/latest/api/datasets.html#mmcls.datasets.CustomDataset).
+
+The `CustomDataset` supports two kinds of format:
+
+1. An annotation file is provided, and each line indicates a sample image.
+
+ The sample images can be organized in any structure, like:
+
+ ```
+ train/
+ ├── folder_1
+ │ ├── xxx.png
+ │ ├── xxy.png
+ │ └── ...
+ ├── 123.png
+ ├── nsdf3.png
+ └── ...
+ ```
+
+ And an annotation file records all paths of samples and corresponding
+ category index. The first column is the image path relative to the folder
+ (in this example, `train`) and the second column is the index of category:
+
+ ```
+ folder_1/xxx.png 0
+ folder_1/xxy.png 1
+ 123.png 1
+ nsdf3.png 2
+ ...
+ ```
+
+ ```{note}
+ The value of the category indices should fall in range `[0, num_classes - 1]`.
+ ```
+
+2. The sample images are arranged in the special structure:
+
+ ```
+ train/
+ ├── cat
+ │ ├── xxx.png
+ │ ├── xxy.png
+ │ └── ...
+ │ └── xxz.png
+ ├── bird
+ │ ├── bird1.png
+ │ ├── bird2.png
+ │ └── ...
+ └── dog
+ ├── 123.png
+ ├── nsdf3.png
+ ├── ...
+ └── asd932_.png
+ ```
+
+ In this case, you don't need provide annotation file, and all images in the directory `cat` will be
+ recognized as samples of `cat`.
+
+Usually, we will split the whole dataset to three sub datasets: `train`, `val`
+and `test` for training, validation and test. And **every** sub dataset should
+be organized as one of the above structures.
+
+For example, the whole dataset is as below (using the first structure):
+
+```
+mmclassification
+└── data
+ └── my_dataset
+ ├── meta
+ │ ├── train.txt
+ │ ├── val.txt
+ │ └── test.txt
+ ├── train
+ ├── val
+ └── test
+```
+
+And in your config file, you can modify the `data` field as below:
+
+```python
+...
+dataset_type = 'CustomDataset'
+classes = ['cat', 'bird', 'dog'] # The category names of your dataset
+
+data = dict(
+ train=dict(
+ type=dataset_type,
+ data_prefix='data/my_dataset/train',
+ ann_file='data/my_dataset/meta/train.txt',
+ classes=classes,
+ pipeline=train_pipeline
+ ),
+ val=dict(
+ type=dataset_type,
+ data_prefix='data/my_dataset/val',
+ ann_file='data/my_dataset/meta/val.txt',
+ classes=classes,
+ pipeline=test_pipeline
+ ),
+ test=dict(
+ type=dataset_type,
+ data_prefix='data/my_dataset/test',
+ ann_file='data/my_dataset/meta/test.txt',
+ classes=classes,
+ pipeline=test_pipeline
+ )
+)
+...
+```
+
+### Create a new dataset class
+
+You can write a new dataset class inherited from `BaseDataset`, and overwrite `load_annotations(self)`,
+like [CIFAR10](https://github.com/open-mmlab/mmclassification/blob/master/mmcls/datasets/cifar.py) and
+[CustomDataset](https://github.com/open-mmlab/mmclassification/blob/master/mmcls/datasets/custom.py).
+
+Typically, this function returns a list, where each sample is a dict, containing necessary data information,
+e.g., `img` and `gt_label`.
+
+Assume we are going to implement a `Filelist` dataset, which takes filelists for both training and testing.
+The format of annotation list is as follows:
+
+```
+000001.jpg 0
+000002.jpg 1
+```
+
+We can create a new dataset in `mmcls/datasets/filelist.py` to load the data.
+
+```python
+import mmcv
+import numpy as np
+
+from .builder import DATASETS
+from .base_dataset import BaseDataset
+
+
+@DATASETS.register_module()
+class Filelist(BaseDataset):
+
+ def load_annotations(self):
+ assert isinstance(self.ann_file, str)
+
+ data_infos = []
+ with open(self.ann_file) as f:
+ samples = [x.strip().split(' ') for x in f.readlines()]
+ for filename, gt_label in samples:
+ info = {'img_prefix': self.data_prefix}
+ info['img_info'] = {'filename': filename}
+ info['gt_label'] = np.array(gt_label, dtype=np.int64)
+ data_infos.append(info)
+ return data_infos
+
+```
+
+And add this dataset class in `mmcls/datasets/__init__.py`
+
+```python
+from .base_dataset import BaseDataset
+...
+from .filelist import Filelist
+
+__all__ = [
+ 'BaseDataset', ... ,'Filelist'
+]
+```
+
+Then in the config, to use `Filelist` you can modify the config as the following
+
+```python
+train = dict(
+ type='Filelist',
+ ann_file='image_list.txt',
+ pipeline=train_pipeline
+)
+```
+
+## Use dataset wrapper
+
+The dataset wrapper is a kind of class to change the behavior of dataset class, such as repeat the dataset or
+re-balance the samples of different categories.
+
+### Repeat dataset
+
+We use `RepeatDataset` as wrapper to repeat the dataset. For example, suppose the original dataset is
+`Dataset_A`, to repeat it, the config looks like the following
+
+```python
+data = dict(
+ train = dict(
+ type='RepeatDataset',
+ times=N,
+ dataset=dict( # This is the original config of Dataset_A
+ type='Dataset_A',
+ ...
+ pipeline=train_pipeline
+ )
+ )
+ ...
+)
+```
+
+### Class balanced dataset
+
+We use `ClassBalancedDataset` as wrapper to repeat the dataset based on category frequency. The dataset to
+repeat needs to implement method `get_cat_ids(idx)` to support `ClassBalancedDataset`. For example, to repeat
+`Dataset_A` with `oversample_thr=1e-3`, the config looks like the following
+
+```python
+data = dict(
+ train = dict(
+ type='ClassBalancedDataset',
+ oversample_thr=1e-3,
+ dataset=dict( # This is the original config of Dataset_A
+ type='Dataset_A',
+ ...
+ pipeline=train_pipeline
+ )
+ )
+ ...
+)
+```
+
+You may refer to [API reference](https://mmclassification.readthedocs.io/en/latest/api/datasets.html#mmcls.datasets.ClassBalancedDataset) for details.
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/new_modules.md b/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/new_modules.md
new file mode 100644
index 0000000000000000000000000000000000000000..5ac89de36432aa8c319b51b65be13c6049610a2d
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/new_modules.md
@@ -0,0 +1,272 @@
+# Tutorial 5: Adding New Modules
+
+## Develop new components
+
+We basically categorize model components into 3 types.
+
+- backbone: usually an feature extraction network, e.g., ResNet, MobileNet.
+- neck: the component between backbones and heads, e.g., GlobalAveragePooling.
+- head: the component for specific tasks, e.g., classification or regression.
+
+### Add new backbones
+
+Here we show how to develop new components with an example of ResNet_CIFAR.
+As the input size of CIFAR is 32x32, this backbone replaces the `kernel_size=7, stride=2` to `kernel_size=3, stride=1` and remove the MaxPooling after stem, to avoid forwarding small feature maps to residual blocks.
+It inherits from ResNet and only modifies the stem layers.
+
+1. Create a new file `mmcls/models/backbones/resnet_cifar.py`.
+
+```python
+import torch.nn as nn
+
+from ..builder import BACKBONES
+from .resnet import ResNet
+
+
+@BACKBONES.register_module()
+class ResNet_CIFAR(ResNet):
+
+ """ResNet backbone for CIFAR.
+
+ short description of the backbone
+
+ Args:
+ depth(int): Network depth, from {18, 34, 50, 101, 152}.
+ ...
+ """
+
+ def __init__(self, depth, deep_stem, **kwargs):
+ # call ResNet init
+ super(ResNet_CIFAR, self).__init__(depth, deep_stem=deep_stem, **kwargs)
+ # other specific initialization
+ assert not self.deep_stem, 'ResNet_CIFAR do not support deep_stem'
+
+ def _make_stem_layer(self, in_channels, base_channels):
+ # override ResNet method to modify the network structure
+ self.conv1 = build_conv_layer(
+ self.conv_cfg,
+ in_channels,
+ base_channels,
+ kernel_size=3,
+ stride=1,
+ padding=1,
+ bias=False)
+ self.norm1_name, norm1 = build_norm_layer(
+ self.norm_cfg, base_channels, postfix=1)
+ self.add_module(self.norm1_name, norm1)
+ self.relu = nn.ReLU(inplace=True)
+
+ def forward(self, x): # should return a tuple
+ pass # implementation is ignored
+
+ def init_weights(self, pretrained=None):
+ pass # override ResNet init_weights if necessary
+
+ def train(self, mode=True):
+ pass # override ResNet train if necessary
+```
+
+2. Import the module in `mmcls/models/backbones/__init__.py`.
+
+```python
+...
+from .resnet_cifar import ResNet_CIFAR
+
+__all__ = [
+ ..., 'ResNet_CIFAR'
+]
+```
+
+3. Use it in your config file.
+
+```python
+model = dict(
+ ...
+ backbone=dict(
+ type='ResNet_CIFAR',
+ depth=18,
+ other_arg=xxx),
+ ...
+```
+
+### Add new necks
+
+Here we take `GlobalAveragePooling` as an example. It is a very simple neck without any arguments.
+To add a new neck, we mainly implement the `forward` function, which applies some operation on the output from backbone and forward the results to head.
+
+1. Create a new file in `mmcls/models/necks/gap.py`.
+
+ ```python
+ import torch.nn as nn
+
+ from ..builder import NECKS
+
+ @NECKS.register_module()
+ class GlobalAveragePooling(nn.Module):
+
+ def __init__(self):
+ self.gap = nn.AdaptiveAvgPool2d((1, 1))
+
+ def forward(self, inputs):
+ # we regard inputs as tensor for simplicity
+ outs = self.gap(inputs)
+ outs = outs.view(inputs.size(0), -1)
+ return outs
+ ```
+
+2. Import the module in `mmcls/models/necks/__init__.py`.
+
+ ```python
+ ...
+ from .gap import GlobalAveragePooling
+
+ __all__ = [
+ ..., 'GlobalAveragePooling'
+ ]
+ ```
+
+3. Modify the config file.
+
+ ```python
+ model = dict(
+ neck=dict(type='GlobalAveragePooling'),
+ )
+ ```
+
+### Add new heads
+
+Here we show how to develop a new head with the example of `LinearClsHead` as the following.
+To implement a new head, basically we need to implement `forward_train`, which takes the feature maps from necks or backbones as input and compute loss based on ground-truth labels.
+
+1. Create a new file in `mmcls/models/heads/linear_head.py`.
+
+ ```python
+ from ..builder import HEADS
+ from .cls_head import ClsHead
+
+
+ @HEADS.register_module()
+ class LinearClsHead(ClsHead):
+
+ def __init__(self,
+ num_classes,
+ in_channels,
+ loss=dict(type='CrossEntropyLoss', loss_weight=1.0),
+ topk=(1, )):
+ super(LinearClsHead, self).__init__(loss=loss, topk=topk)
+ self.in_channels = in_channels
+ self.num_classes = num_classes
+
+ if self.num_classes <= 0:
+ raise ValueError(
+ f'num_classes={num_classes} must be a positive integer')
+
+ self._init_layers()
+
+ def _init_layers(self):
+ self.fc = nn.Linear(self.in_channels, self.num_classes)
+
+ def init_weights(self):
+ normal_init(self.fc, mean=0, std=0.01, bias=0)
+
+ def forward_train(self, x, gt_label):
+ cls_score = self.fc(x)
+ losses = self.loss(cls_score, gt_label)
+ return losses
+
+ ```
+
+2. Import the module in `mmcls/models/heads/__init__.py`.
+
+ ```python
+ ...
+ from .linear_head import LinearClsHead
+
+ __all__ = [
+ ..., 'LinearClsHead'
+ ]
+ ```
+
+3. Modify the config file.
+
+Together with the added GlobalAveragePooling neck, an entire config for a model is as follows.
+
+```python
+model = dict(
+ type='ImageClassifier',
+ backbone=dict(
+ type='ResNet',
+ depth=50,
+ num_stages=4,
+ out_indices=(3, ),
+ style='pytorch'),
+ neck=dict(type='GlobalAveragePooling'),
+ head=dict(
+ type='LinearClsHead',
+ num_classes=1000,
+ in_channels=2048,
+ loss=dict(type='CrossEntropyLoss', loss_weight=1.0),
+ topk=(1, 5),
+ ))
+
+```
+
+### Add new loss
+
+To add a new loss function, we mainly implement the `forward` function in the loss module.
+In addition, it is helpful to leverage the decorator `weighted_loss` to weight the loss for each element.
+Assuming that we want to mimic a probabilistic distribution generated from another classification model, we implement a L1Loss to fulfil the purpose as below.
+
+1. Create a new file in `mmcls/models/losses/l1_loss.py`.
+
+ ```python
+ import torch
+ import torch.nn as nn
+
+ from ..builder import LOSSES
+ from .utils import weighted_loss
+
+ @weighted_loss
+ def l1_loss(pred, target):
+ assert pred.size() == target.size() and target.numel() > 0
+ loss = torch.abs(pred - target)
+ return loss
+
+ @LOSSES.register_module()
+ class L1Loss(nn.Module):
+
+ def __init__(self, reduction='mean', loss_weight=1.0):
+ super(L1Loss, self).__init__()
+ self.reduction = reduction
+ self.loss_weight = loss_weight
+
+ def forward(self,
+ pred,
+ target,
+ weight=None,
+ avg_factor=None,
+ reduction_override=None):
+ assert reduction_override in (None, 'none', 'mean', 'sum')
+ reduction = (
+ reduction_override if reduction_override else self.reduction)
+ loss = self.loss_weight * l1_loss(
+ pred, target, weight, reduction=reduction, avg_factor=avg_factor)
+ return loss
+ ```
+
+2. Import the module in `mmcls/models/losses/__init__.py`.
+
+ ```python
+ ...
+ from .l1_loss import L1Loss, l1_loss
+
+ __all__ = [
+ ..., 'L1Loss', 'l1_loss'
+ ]
+ ```
+
+3. Modify loss field in the config.
+
+ ```python
+ loss=dict(type='L1Loss', loss_weight=1.0))
+ ```
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/runtime.md b/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/runtime.md
new file mode 100644
index 0000000000000000000000000000000000000000..b2127448520a8614898aaec5123d188a359d7124
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/runtime.md
@@ -0,0 +1,257 @@
+# Tutorial 7: Customize Runtime Settings
+
+In this tutorial, we will introduce some methods about how to customize workflow and hooks when running your own settings for the project.
+
+
+
+- [Customize Workflow](#customize-workflow)
+- [Hooks](#hooks)
+ - [Default training hooks](#default-training-hooks)
+ - [Use other implemented hooks](#use-other-implemented-hooks)
+ - [Customize self-implemented hooks](#customize-self-implemented-hooks)
+- [FAQ](#faq)
+
+
+
+## Customize Workflow
+
+Workflow is a list of (phase, duration) to specify the running order and duration. The meaning of "duration" depends on the runner's type.
+
+For example, we use epoch-based runner by default, and the "duration" means how many epochs the phase to be executed in a cycle. Usually,
+we only want to execute training phase, just use the following config.
+
+```python
+workflow = [('train', 1)]
+```
+
+Sometimes we may want to check some metrics (e.g. loss, accuracy) about the model on the validate set.
+In such case, we can set the workflow as
+
+```python
+[('train', 1), ('val', 1)]
+```
+
+so that 1 epoch for training and 1 epoch for validation will be run iteratively.
+
+By default, we recommend using **`EvalHook`** to do evaluation after the training epoch, but you can still use `val` workflow as an alternative.
+
+```{note}
+1. The parameters of model will not be updated during the val epoch.
+2. Keyword `max_epochs` in the config only controls the number of training epochs and will not affect the validation workflow.
+3. Workflows `[('train', 1), ('val', 1)]` and `[('train', 1)]` will not change the behavior of `EvalHook` because `EvalHook` is called by `after_train_epoch` and validation workflow only affect hooks that are called through `after_val_epoch`.
+ Therefore, the only difference between `[('train', 1), ('val', 1)]` and ``[('train', 1)]`` is that the runner will calculate losses on the validation set after each training epoch.
+```
+
+## Hooks
+
+The hook mechanism is widely used in the OpenMMLab open-source algorithm library. Combined with the `Runner`, the entire life cycle of the training process can be managed easily. You can learn more about the hook through [related article](https://www.calltutors.com/blog/what-is-hook/).
+
+Hooks only work after being registered into the runner. At present, hooks are mainly divided into two categories:
+
+- default training hooks
+
+The default training hooks are registered by the runner by default. Generally, they are hooks for some basic functions, and have a certain priority, you don't need to modify the priority.
+
+- custom hooks
+
+The custom hooks are registered through `custom_hooks`. Generally, they are hooks with enhanced functions. The priority needs to be specified in the configuration file. If you do not specify the priority of the hook, it will be set to 'NORMAL' by default.
+
+**Priority list**
+
+| Level | Value |
+| :-------------: | :---: |
+| HIGHEST | 0 |
+| VERY_HIGH | 10 |
+| HIGH | 30 |
+| ABOVE_NORMAL | 40 |
+| NORMAL(default) | 50 |
+| BELOW_NORMAL | 60 |
+| LOW | 70 |
+| VERY_LOW | 90 |
+| LOWEST | 100 |
+
+The priority determines the execution order of the hooks. Before training, the log will print out the execution order of the hooks at each stage to facilitate debugging.
+
+### default training hooks
+
+Some common hooks are not registered through `custom_hooks`, they are
+
+| Hooks | Priority |
+| :-------------------: | :---------------: |
+| `LrUpdaterHook` | VERY_HIGH (10) |
+| `MomentumUpdaterHook` | HIGH (30) |
+| `OptimizerHook` | ABOVE_NORMAL (40) |
+| `CheckpointHook` | NORMAL (50) |
+| `IterTimerHook` | LOW (70) |
+| `EvalHook` | LOW (70) |
+| `LoggerHook(s)` | VERY_LOW (90) |
+
+`OptimizerHook`, `MomentumUpdaterHook` and `LrUpdaterHook` have been introduced in [sehedule strategy](./schedule.md).
+`IterTimerHook` is used to record elapsed time and does not support modification.
+
+Here we reveal how to customize `CheckpointHook`, `LoggerHooks`, and `EvalHook`.
+
+#### CheckpointHook
+
+The MMCV runner will use `checkpoint_config` to initialize [`CheckpointHook`](https://github.com/open-mmlab/mmcv/blob/9ecd6b0d5ff9d2172c49a182eaa669e9f27bb8e7/mmcv/runner/hooks/checkpoint.py).
+
+```python
+checkpoint_config = dict(interval=1)
+```
+
+We could set `max_keep_ckpts` to save only a small number of checkpoints or decide whether to store state dict of optimizer by `save_optimizer`.
+More details of the arguments are [here](https://mmcv.readthedocs.io/en/latest/api.html#mmcv.runner.CheckpointHook)
+
+#### LoggerHooks
+
+The `log_config` wraps multiple logger hooks and enables to set intervals. Now MMCV supports `TextLoggerHook`, `WandbLoggerHook`, `MlflowLoggerHook`, `NeptuneLoggerHook`, `DvcliveLoggerHook` and `TensorboardLoggerHook`.
+The detailed usages can be found in the [doc](https://mmcv.readthedocs.io/en/latest/api.html#mmcv.runner.LoggerHook).
+
+```python
+log_config = dict(
+ interval=50,
+ hooks=[
+ dict(type='TextLoggerHook'),
+ dict(type='TensorboardLoggerHook')
+ ])
+```
+
+#### EvalHook
+
+The config of `evaluation` will be used to initialize the [`EvalHook`](https://github.com/open-mmlab/mmclassification/blob/master/mmcls/core/evaluation/eval_hooks.py).
+
+The `EvalHook` has some reserved keys, such as `interval`, `save_best` and `start`, and the other arguments such as `metrics` will be passed to the `dataset.evaluate()`
+
+```python
+evaluation = dict(interval=1, metric='accuracy', metric_options={'topk': (1, )})
+```
+
+You can save the model weight when the best verification result is obtained by modifying the parameter `save_best`:
+
+```python
+# "auto" means automatically select the metrics to compare.
+# You can also use a specific key like "accuracy_top-1".
+evaluation = dict(interval=1, save_best="auto", metric='accuracy', metric_options={'topk': (1, )})
+```
+
+When running some large experiments, you can skip the validation step at the beginning of training by modifying the parameter `start` as below:
+
+```python
+evaluation = dict(interval=1, start=200, metric='accuracy', metric_options={'topk': (1, )})
+```
+
+This indicates that, before the 200th epoch, evaluations would not be executed. Since the 200th epoch, evaluations would be executed after the training process.
+
+```{note}
+In the default configuration files of MMClassification, the evaluation field is generally placed in the datasets configs.
+```
+
+### Use other implemented hooks
+
+Some hooks have been already implemented in MMCV and MMClassification, they are:
+
+- [EMAHook](https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/ema.py)
+- [SyncBuffersHook](https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/sync_buffer.py)
+- [EmptyCacheHook](https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/memory.py)
+- [ProfilerHook](https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/profiler.py)
+- ......
+
+If the hook is already implemented in MMCV, you can directly modify the config to use the hook as below
+
+```python
+mmcv_hooks = [
+ dict(type='MMCVHook', a=a_value, b=b_value, priority='NORMAL')
+]
+```
+
+such as using `EMAHook`, interval is 100 iters:
+
+```python
+custom_hooks = [
+ dict(type='EMAHook', interval=100, priority='HIGH')
+]
+```
+
+## Customize self-implemented hooks
+
+### 1. Implement a new hook
+
+Here we give an example of creating a new hook in MMClassification and using it in training.
+
+```python
+from mmcv.runner import HOOKS, Hook
+
+
+@HOOKS.register_module()
+class MyHook(Hook):
+
+ def __init__(self, a, b):
+ pass
+
+ def before_run(self, runner):
+ pass
+
+ def after_run(self, runner):
+ pass
+
+ def before_epoch(self, runner):
+ pass
+
+ def after_epoch(self, runner):
+ pass
+
+ def before_iter(self, runner):
+ pass
+
+ def after_iter(self, runner):
+ pass
+```
+
+Depending on the functionality of the hook, the users need to specify what the hook will do at each stage of the training in `before_run`, `after_run`, `before_epoch`, `after_epoch`, `before_iter`, and `after_iter`.
+
+### 2. Register the new hook
+
+Then we need to make `MyHook` imported. Assuming the file is in `mmcls/core/utils/my_hook.py` there are two ways to do that:
+
+- Modify `mmcls/core/utils/__init__.py` to import it.
+
+ The newly defined module should be imported in `mmcls/core/utils/__init__.py` so that the registry will
+ find the new module and add it:
+
+```python
+from .my_hook import MyHook
+```
+
+- Use `custom_imports` in the config to manually import it
+
+```python
+custom_imports = dict(imports=['mmcls.core.utils.my_hook'], allow_failed_imports=False)
+```
+
+### 3. Modify the config
+
+```python
+custom_hooks = [
+ dict(type='MyHook', a=a_value, b=b_value)
+]
+```
+
+You can also set the priority of the hook as below:
+
+```python
+custom_hooks = [
+ dict(type='MyHook', a=a_value, b=b_value, priority='ABOVE_NORMAL')
+]
+```
+
+By default, the hook's priority is set as `NORMAL` during registration.
+
+## FAQ
+
+### 1. `resume_from` and `load_from` and `init_cfg.Pretrained`
+
+- `load_from` : only imports model weights, which is mainly used to load pre-trained or trained models;
+
+- `resume_from` : not only import model weights, but also optimizer information, current epoch information, mainly used to continue training from the checkpoint.
+
+- `init_cfg.Pretrained` : Load weights during weight initialization, and you can specify which module to load. This is usually used when fine-tuning a model, refer to [Tutorial 2: Fine-tune Models](./finetune.md).
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/schedule.md b/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/schedule.md
new file mode 100644
index 0000000000000000000000000000000000000000..1afc4b7f35ec2061e713dd08f7453d67e73868da
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/en/tutorials/schedule.md
@@ -0,0 +1,341 @@
+# Tutorial 6: Customize Schedule
+
+In this tutorial, we will introduce some methods about how to construct optimizers, customize learning rate and momentum schedules, parameter-wise finely configuration, gradient clipping, gradient accumulation, and customize self-implemented methods for the project.
+
+
+
+- [Customize optimizer supported by PyTorch](#customize-optimizer-supported-by-pytorch)
+- [Customize learning rate schedules](#customize-learning-rate-schedules)
+ - [Learning rate decay](#learning-rate-decay)
+ - [Warmup strategy](#warmup-strategy)
+- [Customize momentum schedules](#customize-momentum-schedules)
+- [Parameter-wise finely configuration](#parameter-wise-finely-configuration)
+- [Gradient clipping and gradient accumulation](#gradient-clipping-and-gradient-accumulation)
+ - [Gradient clipping](#gradient-clipping)
+ - [Gradient accumulation](#gradient-accumulation)
+- [Customize self-implemented methods](#customize-self-implemented-methods)
+ - [Customize self-implemented optimizer](#customize-self-implemented-optimizer)
+ - [Customize optimizer constructor](#customize-optimizer-constructor)
+
+
+
+## Customize optimizer supported by PyTorch
+
+We already support to use all the optimizers implemented by PyTorch, and to use and modify them, please change the `optimizer` field of config files.
+
+For example, if you want to use `SGD`, the modification could be as the following.
+
+```python
+optimizer = dict(type='SGD', lr=0.0003, weight_decay=0.0001)
+```
+
+To modify the learning rate of the model, just modify the `lr` in the config of optimizer.
+You can also directly set other arguments according to the [API doc](https://pytorch.org/docs/stable/optim.html?highlight=optim#module-torch.optim) of PyTorch.
+
+For example, if you want to use `Adam` with the setting like `torch.optim.Adam(params, lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)` in PyTorch,
+the config should looks like.
+
+```python
+optimizer = dict(type='Adam', lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)
+```
+
+## Customize learning rate schedules
+
+### Learning rate decay
+
+Learning rate decay is widely used to improve performance. And to use learning rate decay, please set the `lr_confg` field in config files.
+
+For example, we use step policy as the default learning rate decay policy of ResNet, and the config is:
+
+```python
+lr_config = dict(policy='step', step=[100, 150])
+```
+
+Then during training, the program will call [`StepLRHook`](https://github.com/open-mmlab/mmcv/blob/f48241a65aebfe07db122e9db320c31b685dc674/mmcv/runner/hooks/lr_updater.py#L153) periodically to update the learning rate.
+
+We also support many other learning rate schedules [here](https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/lr_updater.py), such as `CosineAnnealing` and `Poly` schedule. Here are some examples
+
+- ConsineAnnealing schedule:
+
+ ```python
+ lr_config = dict(
+ policy='CosineAnnealing',
+ warmup='linear',
+ warmup_iters=1000,
+ warmup_ratio=1.0 / 10,
+ min_lr_ratio=1e-5)
+ ```
+
+- Poly schedule:
+
+ ```python
+ lr_config = dict(policy='poly', power=0.9, min_lr=1e-4, by_epoch=False)
+ ```
+
+### Warmup strategy
+
+In the early stage, training is easy to be volatile, and warmup is a technique
+to reduce volatility. With warmup, the learning rate will increase gradually
+from a minor value to the expected value.
+
+In MMClassification, we use `lr_config` to configure the warmup strategy, the main parameters are as follows:
+
+- `warmup`: The warmup curve type. Please choose one from 'constant', 'linear', 'exp' and `None`, and `None` means disable warmup.
+- `warmup_by_epoch` : if warmup by epoch or not, default to be True, if set to be False, warmup by iter.
+- `warmup_iters` : the number of warm-up iterations, when `warmup_by_epoch=True`, the unit is epoch; when `warmup_by_epoch=False`, the unit is the number of iterations (iter).
+- `warmup_ratio` : warm-up initial learning rate will calculate as `lr = lr * warmup_ratio`。
+
+Here are some examples
+
+1. linear & warmup by iter
+
+ ```python
+ lr_config = dict(
+ policy='CosineAnnealing',
+ by_epoch=False,
+ min_lr_ratio=1e-2,
+ warmup='linear',
+ warmup_ratio=1e-3,
+ warmup_iters=20 * 1252,
+ warmup_by_epoch=False)
+ ```
+
+2. exp & warmup by epoch
+
+ ```python
+ lr_config = dict(
+ policy='CosineAnnealing',
+ min_lr=0,
+ warmup='exp',
+ warmup_iters=5,
+ warmup_ratio=0.1,
+ warmup_by_epoch=True)
+ ```
+
+```{tip}
+After completing your configuration file,you could use [learning rate visualization tool](https://mmclassification.readthedocs.io/en/latest/tools/visualization.html#learning-rate-schedule-visualization) to draw the corresponding learning rate adjustment curve.
+```
+
+## Customize momentum schedules
+
+We support the momentum scheduler to modify the model's momentum according to learning rate, which could make the model converge in a faster way.
+
+Momentum scheduler is usually used with LR scheduler, for example, the following config is used to accelerate convergence.
+For more details, please refer to the implementation of [CyclicLrUpdater](https://github.com/open-mmlab/mmcv/blob/f48241a65aebfe07db122e9db320c31b685dc674/mmcv/runner/hooks/lr_updater.py#L327)
+and [CyclicMomentumUpdater](https://github.com/open-mmlab/mmcv/blob/f48241a65aebfe07db122e9db320c31b685dc674/mmcv/runner/hooks/momentum_updater.py#L130).
+
+Here is an example
+
+```python
+lr_config = dict(
+ policy='cyclic',
+ target_ratio=(10, 1e-4),
+ cyclic_times=1,
+ step_ratio_up=0.4,
+)
+momentum_config = dict(
+ policy='cyclic',
+ target_ratio=(0.85 / 0.95, 1),
+ cyclic_times=1,
+ step_ratio_up=0.4,
+)
+```
+
+## Parameter-wise finely configuration
+
+Some models may have some parameter-specific settings for optimization, for example, no weight decay to the BatchNorm layer or using different learning rates for different network layers.
+To finely configuration them, we can use the `paramwise_cfg` option in `optimizer`.
+
+We provide some examples here and more usages refer to [DefaultOptimizerConstructor](https://mmcv.readthedocs.io/en/latest/_modules/mmcv/runner/optimizer/default_constructor.html#DefaultOptimizerConstructor).
+
+- Using specified options
+
+ The `DefaultOptimizerConstructor` provides options including `bias_lr_mult`, `bias_decay_mult`, `norm_decay_mult`, `dwconv_decay_mult`, `dcn_offset_lr_mult` and `bypass_duplicate` to configure special optimizer behaviors of bias, normalization, depth-wise convolution, deformable convolution and duplicated parameter. E.g:
+
+ 1. No weight decay to the BatchNorm layer
+
+ ```python
+ optimizer = dict(
+ type='SGD',
+ lr=0.8,
+ weight_decay=1e-4,
+ paramwise_cfg=dict(norm_decay_mult=0.))
+ ```
+
+- Using `custom_keys` dict
+
+ MMClassification can use `custom_keys` to specify different parameters to use different learning rates or weight decays, for example:
+
+ 1. No weight decay for specific parameters
+
+ ```python
+ paramwise_cfg = dict(
+ custom_keys={
+ 'backbone.cls_token': dict(decay_mult=0.0),
+ 'backbone.pos_embed': dict(decay_mult=0.0)
+ })
+
+ optimizer = dict(
+ type='SGD',
+ lr=0.8,
+ weight_decay=1e-4,
+ paramwise_cfg=paramwise_cfg)
+ ```
+
+ 2. Using a smaller learning rate and a weight decay for the backbone layers
+
+ ```python
+ optimizer = dict(
+ type='SGD',
+ lr=0.8,
+ weight_decay=1e-4,
+ # 'lr' for backbone and 'weight_decay' are 0.1 * lr and 0.9 * weight_decay
+ paramwise_cfg=dict(
+ custom_keys={'backbone': dict(lr_mult=0.1, decay_mult=0.9)}))
+ ```
+
+## Gradient clipping and gradient accumulation
+
+Besides the basic function of PyTorch optimizers, we also provide some enhancement functions, such as gradient clipping, gradient accumulation, etc., refer to [MMCV](https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/optimizer.py).
+
+### Gradient clipping
+
+During the training process, the loss function may get close to a cliffy region and cause gradient explosion. And gradient clipping is helpful to stabilize the training process. More introduction can be found in [this page](https://paperswithcode.com/method/gradient-clipping).
+
+Currently we support `grad_clip` option in `optimizer_config`, and the arguments refer to [PyTorch Documentation](https://pytorch.org/docs/stable/generated/torch.nn.utils.clip_grad_norm_.html).
+
+Here is an example:
+
+```python
+optimizer_config = dict(grad_clip=dict(max_norm=35, norm_type=2))
+# norm_type: type of the used p-norm, here norm_type is 2.
+```
+
+When inheriting from base and modifying configs, if `grad_clip=None` in base, `_delete_=True` is needed. For more details about `_delete_` you can refer to [TUTORIAL 1: LEARN ABOUT CONFIGS](https://mmclassification.readthedocs.io/en/latest/tutorials/config.html#ignore-some-fields-in-the-base-configs). For example,
+
+```python
+_base_ = [./_base_/schedules/imagenet_bs256_coslr.py]
+
+optimizer_config = dict(grad_clip=dict(max_norm=35, norm_type=2), _delete_=True, type='OptimizerHook')
+# you can ignore type if type is 'OptimizerHook', otherwise you must add "type='xxxxxOptimizerHook'" here
+```
+
+### Gradient accumulation
+
+When computing resources are lacking, the batch size can only be set to a small value, which may affect the performance of models. Gradient accumulation can be used to solve this problem.
+
+Here is an example:
+
+```python
+data = dict(samples_per_gpu=64)
+optimizer_config = dict(type="GradientCumulativeOptimizerHook", cumulative_iters=4)
+```
+
+Indicates that during training, back-propagation is performed every 4 iters. And the above is equivalent to:
+
+```python
+data = dict(samples_per_gpu=256)
+optimizer_config = dict(type="OptimizerHook")
+```
+
+```{note}
+When the optimizer hook type is not specified in `optimizer_config`, `OptimizerHook` is used by default.
+```
+
+## Customize self-implemented methods
+
+In academic research and industrial practice, it may be necessary to use optimization methods not implemented by MMClassification, and you can add them through the following methods.
+
+```{note}
+This part will modify the MMClassification source code or add code to the MMClassification framework, beginners can skip it.
+```
+
+### Customize self-implemented optimizer
+
+#### 1. Define a new optimizer
+
+A customized optimizer could be defined as below.
+
+Assume you want to add an optimizer named `MyOptimizer`, which has arguments `a`, `b`, and `c`.
+You need to create a new directory named `mmcls/core/optimizer`.
+And then implement the new optimizer in a file, e.g., in `mmcls/core/optimizer/my_optimizer.py`:
+
+```python
+from mmcv.runner import OPTIMIZERS
+from torch.optim import Optimizer
+
+
+@OPTIMIZERS.register_module()
+class MyOptimizer(Optimizer):
+
+ def __init__(self, a, b, c):
+
+```
+
+#### 2. Add the optimizer to registry
+
+To find the above module defined above, this module should be imported into the main namespace at first. There are two ways to achieve it.
+
+- Modify `mmcls/core/optimizer/__init__.py` to import it into `optimizer` package, and then modify `mmcls/core/__init__.py` to import the new `optimizer` package.
+
+ Create the `mmcls/core/optimizer` folder and the `mmcls/core/optimizer/__init__.py` file if they don't exist. The newly defined module should be imported in `mmcls/core/optimizer/__init__.py` and `mmcls/core/__init__.py` so that the registry will find the new module and add it:
+
+```python
+# In mmcls/core/optimizer/__init__.py
+from .my_optimizer import MyOptimizer # MyOptimizer maybe other class name
+
+__all__ = ['MyOptimizer']
+```
+
+```python
+# In mmcls/core/__init__.py
+...
+from .optimizer import * # noqa: F401, F403
+```
+
+- Use `custom_imports` in the config to manually import it
+
+```python
+custom_imports = dict(imports=['mmcls.core.optimizer.my_optimizer'], allow_failed_imports=False)
+```
+
+The module `mmcls.core.optimizer.my_optimizer` will be imported at the beginning of the program and the class `MyOptimizer` is then automatically registered.
+Note that only the package containing the class `MyOptimizer` should be imported. `mmcls.core.optimizer.my_optimizer.MyOptimizer` **cannot** be imported directly.
+
+#### 3. Specify the optimizer in the config file
+
+Then you can use `MyOptimizer` in `optimizer` field of config files.
+In the configs, the optimizers are defined by the field `optimizer` like the following:
+
+```python
+optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)
+```
+
+To use your own optimizer, the field can be changed to
+
+```python
+optimizer = dict(type='MyOptimizer', a=a_value, b=b_value, c=c_value)
+```
+
+### Customize optimizer constructor
+
+Some models may have some parameter-specific settings for optimization, e.g. weight decay for BatchNorm layers.
+
+Although our `DefaultOptimizerConstructor` is powerful, it may still not cover your need. If that, you can do those fine-grained parameter tuning through customizing optimizer constructor.
+
+```python
+from mmcv.runner.optimizer import OPTIMIZER_BUILDERS
+
+
+@OPTIMIZER_BUILDERS.register_module()
+class MyOptimizerConstructor:
+
+ def __init__(self, optimizer_cfg, paramwise_cfg=None):
+ pass
+
+ def __call__(self, model):
+ ... # Construct your optimzier here.
+ return my_optimizer
+```
+
+The default optimizer constructor is implemented [here](https://github.com/open-mmlab/mmcv/blob/9ecd6b0d5ff9d2172c49a182eaa669e9f27bb8e7/mmcv/runner/optimizer/default_constructor.py#L11), which could also serve as a template for new optimizer constructor.
diff --git a/openmmlab_test/mmclassification-speed-benchmark/docs_zh-CN/Makefile b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/Makefile
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/docs_zh-CN/Makefile
rename to openmmlab_test/mmclassification-0.24.1/docs/zh_CN/Makefile
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/_static/css/readthedocs.css b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/_static/css/readthedocs.css
new file mode 100644
index 0000000000000000000000000000000000000000..577a67a88fa6693c9256d9d971a1ffe3eb6460e7
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/_static/css/readthedocs.css
@@ -0,0 +1,27 @@
+.header-logo {
+ background-image: url("../image/mmcls-logo.png");
+ background-size: 204px 40px;
+ height: 40px;
+ width: 204px;
+}
+
+pre {
+ white-space: pre;
+}
+
+article.pytorch-article section code {
+ padding: .2em .4em;
+ background-color: #f3f4f7;
+ border-radius: 5px;
+}
+
+/* Disable the change in tables */
+article.pytorch-article section table code {
+ padding: unset;
+ background-color: unset;
+ border-radius: unset;
+}
+
+table.autosummary td {
+ width: 50%
+}
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/_static/image/mmcls-logo.png b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/_static/image/mmcls-logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..6e65420ab9b63bc8080d1156372e6822e0efe15a
Binary files /dev/null and b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/_static/image/mmcls-logo.png differ
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/_static/image/tools/analysis/analyze_log.jpg b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/_static/image/tools/analysis/analyze_log.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..8eb1a27d6464d255b84b23a7460a5f622f51712f
Binary files /dev/null and b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/_static/image/tools/analysis/analyze_log.jpg differ
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/_static/image/tools/visualization/lr_schedule1.png b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/_static/image/tools/visualization/lr_schedule1.png
new file mode 100644
index 0000000000000000000000000000000000000000..31fca35bb525280af6f83b755aef3f2495f07ed2
Binary files /dev/null and b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/_static/image/tools/visualization/lr_schedule1.png differ
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/_static/image/tools/visualization/lr_schedule2.png b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/_static/image/tools/visualization/lr_schedule2.png
new file mode 100644
index 0000000000000000000000000000000000000000..8c6231db8db2a60c3be70d0e4388f5565bcd915b
Binary files /dev/null and b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/_static/image/tools/visualization/lr_schedule2.png differ
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/_static/js/custom.js b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/_static/js/custom.js
new file mode 100644
index 0000000000000000000000000000000000000000..44a4057dc20cd44442c2d7a0869e864bf30f4e46
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/_static/js/custom.js
@@ -0,0 +1 @@
+var collapsedSections = ['Model zoo'];
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/community/CONTRIBUTING.md b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/community/CONTRIBUTING.md
new file mode 100644
index 0000000000000000000000000000000000000000..5554800822f3efc352a7c2b9d2baf2ec966cbd85
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/community/CONTRIBUTING.md
@@ -0,0 +1,62 @@
+# 参与贡献 OpenMMLab
+
+欢迎任何类型的贡献,包括但不限于
+
+- 修改拼写错误或代码错误
+- 添加文档或将文档翻译成其他语言
+- 添加新功能和新组件
+
+## 工作流程
+
+1. fork 并 pull 最新的 OpenMMLab 仓库 (MMClassification)
+2. 签出到一个新分支(不要使用 master 分支提交 PR)
+3. 进行修改并提交至 fork 出的自己的远程仓库
+4. 在我们的仓库中创建一个 PR
+
+```{note}
+如果你计划添加一些新的功能,并引入大量改动,请尽量首先创建一个 issue 来进行讨论。
+```
+
+## 代码风格
+
+### Python
+
+我们采用 [PEP8](https://www.python.org/dev/peps/pep-0008/) 作为统一的代码风格。
+
+我们使用下列工具来进行代码风格检查与格式化:
+
+- [flake8](https://github.com/PyCQA/flake8): Python 官方发布的代码规范检查工具,是多个检查工具的封装
+- [isort](https://github.com/timothycrosley/isort): 自动调整模块导入顺序的工具
+- [yapf](https://github.com/google/yapf): 一个 Python 文件的格式化工具。
+- [codespell](https://github.com/codespell-project/codespell): 检查单词拼写是否有误
+- [mdformat](https://github.com/executablebooks/mdformat): 检查 markdown 文件的工具
+- [docformatter](https://github.com/myint/docformatter): 一个 docstring 格式化工具。
+
+yapf 和 isort 的格式设置位于 [setup.cfg](https://github.com/open-mmlab/mmclassification/blob/master/setup.cfg)
+
+我们使用 [pre-commit hook](https://pre-commit.com/) 来保证每次提交时自动进行代
+码检查和格式化,启用的功能包括 `flake8`, `yapf`, `isort`, `trailing whitespaces`, `markdown files`, 修复 `end-of-files`, `double-quoted-strings`,
+`python-encoding-pragma`, `mixed-line-ending`, 对 `requirments.txt`的排序等。
+pre-commit hook 的配置文件位于 [.pre-commit-config](https://github.com/open-mmlab/mmclassification/blob/master/.pre-commit-config.yaml)
+
+在你克隆仓库后,你需要按照如下步骤安装并初始化 pre-commit hook。
+
+```shell
+pip install -U pre-commit
+```
+
+在仓库文件夹中执行
+
+```shell
+pre-commit install
+```
+
+在此之后,每次提交,代码规范检查和格式化工具都将被强制执行。
+
+```{important}
+在创建 PR 之前,请确保你的代码完成了代码规范检查,并经过了 yapf 的格式化。
+```
+
+### C++ 和 CUDA
+
+C++ 和 CUDA 的代码规范遵从 [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html)
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/compatibility.md b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/compatibility.md
new file mode 100644
index 0000000000000000000000000000000000000000..178e555b6df48794bdefb98c118d1323d1671bba
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/compatibility.md
@@ -0,0 +1,7 @@
+# 0.x 相关兼容性问题
+
+## MMClassification 0.20.1
+
+### MMCV 兼容性
+
+在 Twins 骨干网络中,我们使用了 MMCV 提供的 `PatchEmbed` 模块,该模块是在 MMCV 1.4.2 版本加入的,因此我们需要将 MMCV 依赖版本升至 1.4.2。
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/conf.py b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/conf.py
new file mode 100644
index 0000000000000000000000000000000000000000..71daf28b028c5054057c0ffa2d2ad18b39b266f4
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/conf.py
@@ -0,0 +1,226 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# This file only contains a selection of the most common options. For a full
+# list see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+import os
+import subprocess
+import sys
+
+import pytorch_sphinx_theme
+from sphinx.builders.html import StandaloneHTMLBuilder
+
+sys.path.insert(0, os.path.abspath('../..'))
+
+# -- Project information -----------------------------------------------------
+
+project = 'MMClassification'
+copyright = '2020, OpenMMLab'
+author = 'MMClassification Authors'
+
+# The full version, including alpha/beta/rc tags
+version_file = '../../mmcls/version.py'
+
+
+def get_version():
+ with open(version_file, 'r') as f:
+ exec(compile(f.read(), version_file, 'exec'))
+ return locals()['__version__']
+
+
+release = get_version()
+
+# -- General configuration ---------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+ 'sphinx.ext.autodoc',
+ 'sphinx.ext.autosummary',
+ 'sphinx.ext.intersphinx',
+ 'sphinx.ext.napoleon',
+ 'sphinx.ext.viewcode',
+ 'myst_parser',
+ 'sphinx_copybutton',
+]
+
+autodoc_mock_imports = ['mmcv._ext', 'matplotlib']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+#
+source_suffix = {
+ '.rst': 'restructuredtext',
+ '.md': 'markdown',
+}
+
+language = 'zh_CN'
+
+# The master toctree document.
+master_doc = 'index'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'pytorch_sphinx_theme'
+html_theme_path = [pytorch_sphinx_theme.get_html_theme_path()]
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#
+html_theme_options = {
+ 'logo_url':
+ 'https://mmclassification.readthedocs.io/zh_CN/latest/',
+ 'menu': [
+ {
+ 'name': 'GitHub',
+ 'url': 'https://github.com/open-mmlab/mmclassification'
+ },
+ {
+ 'name':
+ 'Colab 教程',
+ 'children': [
+ {
+ 'name':
+ '用命令行工具训练和推理',
+ 'url':
+ 'https://colab.research.google.com/github/'
+ 'open-mmlab/mmclassification/blob/master/docs/zh_CN/'
+ 'tutorials/MMClassification_tools_cn.ipynb',
+ },
+ {
+ 'name':
+ '用 Python API 训练和推理',
+ 'url':
+ 'https://colab.research.google.com/github/'
+ 'open-mmlab/mmclassification/blob/master/docs/zh_CN/'
+ 'tutorials/MMClassification_python_cn.ipynb',
+ },
+ ]
+ },
+ ],
+ # Specify the language of shared menu
+ 'menu_lang':
+ 'cn',
+}
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+html_css_files = ['css/readthedocs.css']
+html_js_files = ['js/custom.js']
+
+# -- Options for HTMLHelp output ---------------------------------------------
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'mmclsdoc'
+
+# -- Options for LaTeX output ------------------------------------------------
+
+latex_elements = {
+ # The paper size ('letterpaper' or 'a4paper').
+ #
+ # 'papersize': 'letterpaper',
+
+ # The font size ('10pt', '11pt' or '12pt').
+ #
+ # 'pointsize': '10pt',
+
+ # Additional stuff for the LaTeX preamble.
+ #
+ # 'preamble': '',
+
+ # Latex figure (float) alignment
+ #
+ # 'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+latex_documents = [
+ (master_doc, 'mmcls.tex', 'MMClassification Documentation', author,
+ 'manual'),
+]
+
+# -- Options for manual page output ------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [(master_doc, 'mmcls', 'MMClassification Documentation', [author],
+ 1)]
+
+# -- Options for Texinfo output ----------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+texinfo_documents = [
+ (master_doc, 'mmcls', 'MMClassification Documentation', author, 'mmcls',
+ 'OpenMMLab image classification toolbox and benchmark.', 'Miscellaneous'),
+]
+
+# -- Options for Epub output -------------------------------------------------
+
+# Bibliographic Dublin Core info.
+epub_title = project
+
+# The unique identifier of the text. This can be a ISBN number
+# or the project homepage.
+#
+# epub_identifier = ''
+
+# A unique identification for the text.
+#
+# epub_uid = ''
+
+# A list of files that should not be packed into the epub file.
+epub_exclude_files = ['search.html']
+
+# set priority when building html
+StandaloneHTMLBuilder.supported_image_types = [
+ 'image/svg+xml', 'image/gif', 'image/png', 'image/jpeg'
+]
+
+# -- Extension configuration -------------------------------------------------
+# Ignore >>> when copying code
+copybutton_prompt_text = r'>>> |\.\.\. '
+copybutton_prompt_is_regexp = True
+# Auto-generated header anchors
+myst_heading_anchors = 3
+# Configuration for intersphinx
+intersphinx_mapping = {
+ 'python': ('https://docs.python.org/3', None),
+ 'numpy': ('https://numpy.org/doc/stable', None),
+ 'torch': ('https://pytorch.org/docs/stable/', None),
+ 'mmcv': ('https://mmcv.readthedocs.io/zh_CN/latest/', None),
+}
+
+
+def builder_inited_handler(app):
+ subprocess.run(['./stat.py'])
+
+
+def setup(app):
+ app.add_config_value('no_underscore_emphasis', False, 'env')
+ app.connect('builder-inited', builder_inited_handler)
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/device/npu.md b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/device/npu.md
new file mode 100644
index 0000000000000000000000000000000000000000..7adcf0e514430853b0c6d0dcbdf15f257bb812a2
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/device/npu.md
@@ -0,0 +1,34 @@
+# NPU (华为昇腾)
+
+## 使用方法
+
+首先,请参考 {external+mmcv:doc}`教程 ` 安装带有 NPU 支持的 MMCV。
+
+使用如下命令,可以利用 8 个 NPU 在机器上训练模型(以 ResNet 为例):
+
+```shell
+bash tools/dist_train.sh configs/cspnet/resnet50_8xb32_in1k.py 8 --device npu
+```
+
+或者,使用如下命令,在一个 NPU 上训练模型(以 ResNet 为例):
+
+```shell
+python tools/train.py configs/cspnet/resnet50_8xb32_in1k.py --device npu
+```
+
+## 经过验证的模型
+
+| 模型 | Top-1 (%) | Top-5 (%) | 配置文件 | 相关下载 |
+| :--------------------------------------------------------: | :-------: | :-------: | :------------------------------------------------------------: | :------------------------------------------------------------: |
+| [CSPResNeXt50](../papers/cspnet.md) | 77.10 | 93.55 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/cspnet/cspresnext50_8xb32_in1k.py) | [model](<>) \| [log](https://download.openmmlab.com/mmclassification/v0/device/npu/cspresnext50_8xb32_in1k.log.json) |
+| [DenseNet121](../papers/densenet.md) | 72.62 | 91.04 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/densenet/densenet121_4xb256_in1k.py) | [model](<>) \| [log](https://download.openmmlab.com/mmclassification/v0/device/npu/densenet121_4xb256_in1k.log.json) |
+| [EfficientNet-B4(AA + AdvProp)](../papers/efficientnet.md) | 75.55 | 92.86 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/efficientnet/efficientnet-b4_8xb32-01norm_in1k.py) | [model](<>) \| [log](https://download.openmmlab.com/mmclassification/v0/device/npu/efficientnet-b4_8xb32-01norm_in1k.log.json) |
+| [HRNet-W18](../papers/hrnet.md) | 77.01 | 93.46 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/hrnet/hrnet-w18_4xb32_in1k.py) | [model](<>) \| [log](https://download.openmmlab.com/mmclassification/v0/device/npu/hrnet-w18_4xb32_in1k.log.json) |
+| [ResNetV1D-152](../papers/resnet.md) | 77.11 | 94.54 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnet/resnetv1d152_8xb32_in1k.py) | [model](<>) \| [log](<>) |
+| [ResNet-50](../papers/resnet.md) | 76.40 | - | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnet/resnet50_8xb32_in1k.py) | [model](<>) \| [log](<>) |
+| [ResNetXt-32x4d-50](../papers/resnext.md) | 77.55 | 93.75 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnext/resnext50-32x4d_8xb32_in1k.py) | [model](<>) \| [log](https://download.openmmlab.com/mmclassification/v0/device/npu/resnext50-32x4d_8xb32_in1k.log.json) |
+| [SE-ResNet-50](../papers/seresnet.md) | 77.64 | 93.76 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/seresnet/seresnet50_8xb32_in1k.py) | [model](<>) \| [log](https://download.openmmlab.com/mmclassification/v0/device/npu/seresnet50_8xb32_in1k.log.json) |
+| [VGG-11](../papers/vgg.md) | 68.92 | 88.83 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/vgg/vgg11_8xb32_in1k.py) | [model](<>) \| [log](https://download.openmmlab.com/mmclassification/v0/device/npu/vgg11_8xb32_in1k.log.json) |
+| [ShuffleNetV2 1.0x](../papers/shufflenet_v2.md) | 69.53 | 88.82 | [config](https://github.com/open-mmlab/mmclassification/blob/master/configs/shufflenet_v2/shufflenet-v2-1x_16xb64_in1k.py) | [model](<>) \| [log](<>) |
+
+**以上所有模型权重及训练日志均由华为昇腾团队提供**
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/docutils.conf b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/docutils.conf
new file mode 100644
index 0000000000000000000000000000000000000000..0c00c84688701117f231fd0c8ec295fb747b7d8f
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/docutils.conf
@@ -0,0 +1,2 @@
+[html writers]
+table_style: colwidths-auto
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/faq.md b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/faq.md
new file mode 100644
index 0000000000000000000000000000000000000000..4f9572226ac88ae7b0d4fccdf571e51c4a920ae1
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/faq.md
@@ -0,0 +1,73 @@
+# 常见问题
+
+我们在这里列出了一些常见问题及其相应的解决方案。如果您发现任何常见问题并有方法
+帮助解决,欢迎随时丰富列表。如果这里的内容没有涵盖您的问题,请按照
+[提问模板](https://github.com/open-mmlab/mmclassification/issues/new/choose)
+在 GitHub 上提出问题,并补充模板中需要的信息。
+
+## 安装
+
+- MMCV 与 MMClassification 的兼容问题。如遇到
+ "AssertionError: MMCV==xxx is used but incompatible. Please install mmcv>=xxx, \<=xxx."
+
+ 这里我们列举了各版本 MMClassification 对 MMCV 版本的依赖,请选择合适的 MMCV
+ 版本来避免安装和使用中的问题。
+
+ | MMClassification version | MMCV version |
+ | :----------------------: | :--------------------: |
+ | dev | mmcv>=1.7.0, \<1.9.0 |
+ | 0.24.1 (master) | mmcv>=1.4.2, \<1.9.0 |
+ | 0.23.2 | mmcv>=1.4.2, \<1.7.0 |
+ | 0.22.1 | mmcv>=1.4.2, \<1.6.0 |
+ | 0.21.0 | mmcv>=1.4.2, \<=1.5.0 |
+ | 0.20.1 | mmcv>=1.4.2, \<=1.5.0 |
+ | 0.19.0 | mmcv>=1.3.16, \<=1.5.0 |
+ | 0.18.0 | mmcv>=1.3.16, \<=1.5.0 |
+ | 0.17.0 | mmcv>=1.3.8, \<=1.5.0 |
+ | 0.16.0 | mmcv>=1.3.8, \<=1.5.0 |
+ | 0.15.0 | mmcv>=1.3.8, \<=1.5.0 |
+ | 0.15.0 | mmcv>=1.3.8, \<=1.5.0 |
+ | 0.14.0 | mmcv>=1.3.8, \<=1.5.0 |
+ | 0.13.0 | mmcv>=1.3.8, \<=1.5.0 |
+ | 0.12.0 | mmcv>=1.3.1, \<=1.5.0 |
+ | 0.11.1 | mmcv>=1.3.1, \<=1.5.0 |
+ | 0.11.0 | mmcv>=1.3.0 |
+ | 0.10.0 | mmcv>=1.3.0 |
+ | 0.9.0 | mmcv>=1.1.4 |
+ | 0.8.0 | mmcv>=1.1.4 |
+ | 0.7.0 | mmcv>=1.1.4 |
+ | 0.6.0 | mmcv>=1.1.4 |
+
+ ```{note}
+ 由于 `dev` 分支处于频繁开发中,MMCV 版本依赖可能不准确。如果您在使用
+ `dev` 分支时遇到问题,请尝试更新 MMCV 到最新版。
+ ```
+
+- 使用 Albumentations
+
+ 如果你希望使用 `albumentations` 相关的功能,我们建议使用 `pip install -r requirements/optional.txt` 或者
+ `pip install -U albumentations>=0.3.2 --no-binary qudida,albumentations` 命令进行安装。
+
+ 如果你直接使用 `pip install albumentations>=0.3.2` 来安装,它会同时安装 `opencv-python-headless`
+ (即使你已经安装了 `opencv-python`)。具体细节可参阅
+ [官方文档](https://albumentations.ai/docs/getting_started/installation/#note-on-opencv-dependencies)。
+
+## 开发
+
+- 如果我对源码进行了改动,需要重新安装以使改动生效吗?
+
+ 如果你遵照[最佳实践](install.md)的指引,从源码安装 mmcls,那么任何本地修改都不需要重新安装即可生效。
+
+- 如何在多个 MMClassification 版本下进行开发?
+
+ 通常来说,我们推荐通过不同虚拟环境来管理多个开发目录下的 MMClassification。
+ 但如果你希望在不同目录(如 mmcls-0.21, mmcls-0.23 等)使用同一个环境进行开发,
+ 我们提供的训练和测试 shell 脚本会自动使用当前目录的 mmcls,其他 Python 脚本
+ 则可以在命令前添加 `` PYTHONPATH=`pwd` `` 来使用当前目录的代码。
+
+ 反过来,如果你希望 shell 脚本使用环境中安装的 MMClassification,而不是当前目录的,
+ 则可以去掉 shell 脚本中如下一行代码:
+
+ ```shell
+ PYTHONPATH="$(dirname $0)/..":$PYTHONPATH
+ ```
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/getting_started.md b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/getting_started.md
new file mode 100644
index 0000000000000000000000000000000000000000..d3e98997a5693e053fadca59ef8e44fc5b12263d
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/getting_started.md
@@ -0,0 +1,266 @@
+# 基础教程
+
+本文档提供 MMClassification 相关用法的基本教程。
+
+## 准备数据集
+
+MMClassification 建议用户将数据集根目录链接到 `$MMCLASSIFICATION/data` 下。
+如果用户的文件夹结构与默认结构不同,则需要在配置文件中进行对应路径的修改。
+
+```
+mmclassification
+├── mmcls
+├── tools
+├── configs
+├── docs
+├── data
+│ ├── imagenet
+│ │ ├── meta
+│ │ ├── train
+│ │ ├── val
+│ ├── cifar
+│ │ ├── cifar-10-batches-py
+│ ├── mnist
+│ │ ├── train-images-idx3-ubyte
+│ │ ├── train-labels-idx1-ubyte
+│ │ ├── t10k-images-idx3-ubyte
+│ │ ├── t10k-labels-idx1-ubyte
+
+```
+
+对于 ImageNet,其存在多个版本,但最为常用的一个是 [ILSVRC 2012](http://www.image-net.org/challenges/LSVRC/2012/),可以通过以下步骤获取该数据集。
+
+1. 注册账号并登录 [下载页面](http://www.image-net.org/download-images)
+2. 获取 ILSVRC2012 下载链接并下载以下文件
+ - ILSVRC2012_img_train.tar (~138GB)
+ - ILSVRC2012_img_val.tar (~6.3GB)
+3. 解压下载的文件
+4. 使用 [该脚本](https://github.com/BVLC/caffe/blob/master/data/ilsvrc12/get_ilsvrc_aux.sh) 获取元数据
+
+对于 MNIST,CIFAR10 和 CIFAR100,程序将会在需要的时候自动下载数据集。
+
+对于用户自定义数据集的准备,请参阅 [教程 3:如何自定义数据集
+](tutorials/new_dataset.md)
+
+## 使用预训练模型进行推理
+
+MMClassification 提供了一些脚本用于进行单张图像的推理、数据集的推理和数据集的测试(如 ImageNet 等)
+
+### 单张图像的推理
+
+```shell
+python demo/image_demo.py ${IMAGE_FILE} ${CONFIG_FILE} ${CHECKPOINT_FILE}
+
+# Example
+python demo/image_demo.py demo/demo.JPEG configs/resnet/resnet50_8xb32_in1k.py \
+ https://download.openmmlab.com/mmclassification/v0/resnet/resnet50_8xb32_in1k_20210831-ea4938fc.pth
+```
+
+### 数据集的推理与测试
+
+- 支持单 GPU
+- 支持 CPU
+- 支持单节点多 GPU
+- 支持多节点
+
+用户可使用以下命令进行数据集的推理:
+
+```shell
+# 单 GPU
+python tools/test.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--metrics ${METRICS}] [--out ${RESULT_FILE}]
+
+# CPU: 禁用 GPU 并运行单 GPU 测试脚本
+export CUDA_VISIBLE_DEVICES=-1
+python tools/test.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--metrics ${METRICS}] [--out ${RESULT_FILE}]
+
+# 多 GPU
+./tools/dist_test.sh ${CONFIG_FILE} ${CHECKPOINT_FILE} ${GPU_NUM} [--metrics ${METRICS}] [--out ${RESULT_FILE}]
+
+# 基于 slurm 分布式环境的多节点
+python tools/test.py ${CONFIG_FILE} ${CHECKPOINT_FILE} [--metrics ${METRICS}] [--out ${RESULT_FILE}] --launcher slurm
+```
+
+可选参数:
+
+- `RESULT_FILE`:输出结果的文件名。如果未指定,结果将不会保存到文件中。支持 json, yaml, pickle 格式。
+- `METRICS`:数据集测试指标,如准确率 (accuracy), 精确率 (precision), 召回率 (recall) 等
+
+例子:
+
+在 ImageNet 验证集上,使用 ResNet-50 进行推理并获得预测标签及其对应的预测得分。
+
+```shell
+python tools/test.py configs/resnet/resnet50_8xb16_cifar10.py \
+ https://download.openmmlab.com/mmclassification/v0/resnet/resnet50_b16x8_cifar10_20210528-f54bfad9.pth \
+ --out result.pkl
+```
+
+## 模型训练
+
+MMClassification 使用 `MMDistributedDataParallel` 进行分布式训练,使用 `MMDataParallel` 进行非分布式训练。
+
+所有的输出(日志文件和模型权重文件)会被将保存到工作目录下。工作目录通过配置文件中的参数 `work_dir` 指定。
+
+默认情况下,MMClassification 在每个周期后会在验证集上评估模型,可以通过在训练配置中修改 `interval` 参数来更改评估间隔
+
+```python
+evaluation = dict(interval=12) # 每进行 12 轮训练后评估一次模型
+```
+
+### 使用单个 GPU 进行训练
+
+```shell
+python tools/train.py ${CONFIG_FILE} [optional arguments]
+```
+
+如果用户想在命令中指定工作目录,则需要增加参数 `--work-dir ${YOUR_WORK_DIR}`
+
+### 使用 CPU 训练
+
+使用 CPU 训练的流程和使用单 GPU 训练的流程一致,我们仅需要在训练流程开始前禁用 GPU。
+
+```shell
+export CUDA_VISIBLE_DEVICES=-1
+```
+
+之后运行单 GPU 训练脚本即可。
+
+```{warning}
+我们不推荐用户使用 CPU 进行训练,这太过缓慢。我们支持这个功能是为了方便用户在没有 GPU 的机器上进行调试。
+```
+
+### 使用单台机器多个 GPU 进行训练
+
+```shell
+./tools/dist_train.sh ${CONFIG_FILE} ${GPU_NUM} [optional arguments]
+```
+
+可选参数为:
+
+- `--no-validate` (**不建议**): 默认情况下,程序将会在训练期间的每 k (默认为 1) 个周期进行一次验证。要禁用这一功能,使用 `--no-validate`
+- `--work-dir ${WORK_DIR}`:覆盖配置文件中指定的工作目录。
+- `--resume-from ${CHECKPOINT_FILE}`:从以前的模型权重文件恢复训练。
+
+`resume-from` 和 `load-from` 的不同点:
+`resume-from` 加载模型参数和优化器状态,并且保留检查点所在的周期数,常被用于恢复意外被中断的训练。
+`load-from` 只加载模型参数,但周期数从 0 开始计数,常被用于微调模型。
+
+### 使用多台机器进行训练
+
+如果您想使用由 ethernet 连接起来的多台机器, 您可以使用以下命令:
+
+在第一台机器上:
+
+```shell
+NNODES=2 NODE_RANK=0 PORT=$MASTER_PORT MASTER_ADDR=$MASTER_ADDR sh tools/dist_train.sh $CONFIG $GPUS
+```
+
+在第二台机器上:
+
+```shell
+NNODES=2 NODE_RANK=1 PORT=$MASTER_PORT MASTER_ADDR=$MASTER_ADDR sh tools/dist_train.sh $CONFIG $GPUS
+```
+
+但是,如果您不使用高速网路连接这几台机器的话,训练将会非常慢。
+
+如果用户在 [slurm](https://slurm.schedmd.com/) 集群上运行 MMClassification,可使用 `slurm_train.sh` 脚本。(该脚本也支持单台机器上进行训练)
+
+```shell
+[GPUS=${GPUS}] ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} ${CONFIG_FILE} ${WORK_DIR}
+```
+
+用户可以在 [slurm_train.sh](https://github.com/open-mmlab/mmclassification/blob/master/tools/slurm_train.sh) 中检查所有的参数和环境变量
+
+如果用户的多台机器通过 Ethernet 连接,则可以参考 pytorch [launch utility](https://pytorch.org/docs/stable/distributed.html#launch-utility)。如果用户没有高速网络,如 InfiniBand,速度将会非常慢。
+
+### 使用单台机器启动多个任务
+
+如果用使用单台机器启动多个任务,如在有 8 块 GPU 的单台机器上启动 2 个需要 4 块 GPU 的训练任务,则需要为每个任务指定不同端口,以避免通信冲突。
+
+如果用户使用 `dist_train.sh` 脚本启动训练任务,则可以通过以下命令指定端口
+
+```shell
+CUDA_VISIBLE_DEVICES=0,1,2,3 PORT=29500 ./tools/dist_train.sh ${CONFIG_FILE} 4
+CUDA_VISIBLE_DEVICES=4,5,6,7 PORT=29501 ./tools/dist_train.sh ${CONFIG_FILE} 4
+```
+
+如果用户在 slurm 集群下启动多个训练任务,则需要修改配置文件中的 `dist_params` 变量,以设置不同的通信端口。
+
+在 `config1.py` 中,
+
+```python
+dist_params = dict(backend='nccl', port=29500)
+```
+
+在 `config2.py` 中,
+
+```python
+dist_params = dict(backend='nccl', port=29501)
+```
+
+之后便可启动两个任务,分别对应 `config1.py` 和 `config2.py`。
+
+```shell
+CUDA_VISIBLE_DEVICES=0,1,2,3 GPUS=4 ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config1.py ${WORK_DIR}
+CUDA_VISIBLE_DEVICES=4,5,6,7 GPUS=4 ./tools/slurm_train.sh ${PARTITION} ${JOB_NAME} config2.py ${WORK_DIR}
+```
+
+## 实用工具
+
+我们在 `tools/` 目录下提供的一些对训练和测试十分有用的工具
+
+### 计算 FLOPs 和参数量(试验性的)
+
+我们根据 [flops-counter.pytorch](https://github.com/sovrasov/flops-counter.pytorch) 提供了一个脚本用于计算给定模型的 FLOPs 和参数量
+
+```shell
+python tools/analysis_tools/get_flops.py ${CONFIG_FILE} [--shape ${INPUT_SHAPE}]
+```
+
+用户将获得如下结果:
+
+```
+==============================
+Input shape: (3, 224, 224)
+Flops: 4.12 GFLOPs
+Params: 25.56 M
+==============================
+```
+
+```{warning}
+此工具仍处于试验阶段,我们不保证该数字正确无误。您最好将结果用于简单比较,但在技术报告或论文中采用该结果之前,请仔细检查。
+- FLOPs 与输入的尺寸有关,而参数量与输入尺寸无关。默认输入尺寸为 (1, 3, 224, 224)
+- 一些运算不会被计入 FLOPs 的统计中,例如 GN 和自定义运算。详细信息请参考 [`mmcv.cnn.get_model_complexity_info()`](https://github.com/open-mmlab/mmcv/blob/master/mmcv/cnn/utils/flops_counter.py)
+```
+
+### 模型发布
+
+在发布模型之前,你也许会需要
+
+1. 转换模型权重至 CPU 张量
+2. 删除优化器状态
+3. 计算模型权重文件的哈希值,并添加至文件名之后
+
+```shell
+python tools/convert_models/publish_model.py ${INPUT_FILENAME} ${OUTPUT_FILENAME}
+```
+
+例如:
+
+```shell
+python tools/convert_models/publish_model.py work_dirs/resnet50/latest.pth imagenet_resnet50.pth
+```
+
+最终输出的文件名将会是 `imagenet_resnet50_{date}-{hash id}.pth`
+
+## 详细教程
+
+目前,MMClassification 提供以下几种更详细的教程:
+
+- [如何编写配置文件](tutorials/config.md)
+- [如何微调模型](tutorials/finetune.md)
+- [如何增加新数据集](tutorials/new_dataset.md)
+- [如何设计数据处理流程](tutorials/data_pipeline.md)
+- [如何增加新模块](tutorials/new_modules.md)
+- [如何自定义优化策略](tutorials/schedule.md)
+- [如何自定义运行参数](tutorials/runtime.md)。
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/imgs/qq_group_qrcode.jpg b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/imgs/qq_group_qrcode.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..7c6b04f561da283ae622f4219ea9b8cabf8f301a
Binary files /dev/null and b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/imgs/qq_group_qrcode.jpg differ
diff --git a/openmmlab_test/mmclassification-speed-benchmark/docs/imgs/zhihu_qrcode.jpg b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/imgs/zhihu_qrcode.jpg
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/docs/imgs/zhihu_qrcode.jpg
rename to openmmlab_test/mmclassification-0.24.1/docs/zh_CN/imgs/zhihu_qrcode.jpg
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/index.rst b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..1de4b829b824d7432e27e809156f738812a0b0a4
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/index.rst
@@ -0,0 +1,99 @@
+欢迎来到 MMClassification 中文教程!
+==========================================
+
+You can switch between Chinese and English documentation in the lower-left corner of the layout.
+
+您可以在页面左下角切换中英文文档。
+
+.. toctree::
+ :maxdepth: 1
+ :caption: 开始你的第一步
+
+ install.md
+ getting_started.md
+
+
+.. toctree::
+ :maxdepth: 1
+ :caption: 教程
+
+ tutorials/config.md
+ tutorials/finetune.md
+ tutorials/new_dataset.md
+ tutorials/data_pipeline.md
+ tutorials/new_modules.md
+ tutorials/schedule.md
+ tutorials/runtime.md
+
+
+.. toctree::
+ :maxdepth: 1
+ :caption: 模型库
+ :glob:
+
+ modelzoo_statistics.md
+ model_zoo.md
+ papers/*
+
+
+.. toctree::
+ :maxdepth: 1
+ :caption: 实用工具
+
+ tools/pytorch2onnx.md
+ tools/onnx2tensorrt.md
+ tools/pytorch2torchscript.md
+ tools/model_serving.md
+ tools/visualization.md
+ tools/analysis.md
+ tools/miscellaneous.md
+
+
+.. toctree::
+ :maxdepth: 1
+ :caption: 社区
+
+ community/CONTRIBUTING.md
+
+
+.. toctree::
+ :caption: API 参考文档
+
+ mmcls.apis
+ mmcls.core
+ mmcls.models
+ mmcls.models.utils
+ mmcls.datasets
+ 数据转换
+ 批数据增强
+ mmcls.utils
+
+
+.. toctree::
+ :maxdepth: 1
+ :caption: 其他说明
+
+ changelog.md
+ compatibility.md
+ faq.md
+
+
+.. toctree::
+ :maxdepth: 1
+ :caption: 设备支持
+
+ device/npu.md
+
+
+.. toctree::
+ :caption: 语言切换
+
+ English
+ 简体中文
+
+
+索引与表格
+==================
+
+* :ref:`genindex`
+* :ref:`search`
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/install.md b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/install.md
new file mode 100644
index 0000000000000000000000000000000000000000..e88158660e370c5d684b4e8b74d09d85ab4e6807
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/install.md
@@ -0,0 +1,210 @@
+# 依赖环境
+
+在本节中,我们将演示如何准备 PyTorch 相关的依赖环境。
+
+MMClassification 适用于 Linux、Windows 和 macOS。它需要 Python 3.6+、CUDA 9.2+ 和 PyTorch 1.5+。
+
+```{note}
+如果你对配置 PyTorch 环境已经很熟悉,并且已经完成了配置,可以直接进入[下一节](#安装)。
+否则的话,请依照以下步骤完成配置。
+```
+
+**第 1 步** 从[官网](https://docs.conda.io/en/latest/miniconda.html)下载并安装 Miniconda。
+
+**第 2 步** 创建一个 conda 虚拟环境并激活它。
+
+```shell
+conda create --name openmmlab python=3.8 -y
+conda activate openmmlab
+```
+
+**第 3 步** 按照[官方指南](https://pytorch.org/get-started/locally/)安装 PyTorch。例如:
+
+在 GPU 平台:
+
+```shell
+conda install pytorch torchvision -c pytorch
+```
+
+```{warning}
+以上命令会自动安装最新版的 PyTorch 与对应的 cudatoolkit,请检查它们是否与你的环境匹配。
+```
+
+在 CPU 平台:
+
+```shell
+conda install pytorch torchvision cpuonly -c pytorch
+```
+
+# 安装
+
+我们推荐用户按照我们的最佳实践来安装 MMClassification。但除此之外,如果你想根据
+你的习惯完成安装流程,也可以参见[自定义安装](#自定义安装)一节来获取更多信息。
+
+## 最佳实践
+
+**第 1 步** 使用 [MIM](https://github.com/open-mmlab/mim) 安装 [MMCV](https://github.com/open-mmlab/mmcv)
+
+```shell
+pip install -U openmim
+mim install mmcv-full
+```
+
+**第 2 步** 安装 MMClassification
+
+根据具体需求,我们支持两种安装模式:
+
+- [从源码安装(推荐)](#从源码安装):希望基于 MMClassification 框架开发自己的图像分类任务,需要添加新的功能,比如新的模型或是数据集,或者使用我们提供的各种工具。
+- [作为 Python 包安装](#作为-python-包安装):只是希望调用 MMClassification 的 API 接口,或者在自己的项目中导入 MMClassification 中的模块。
+
+### 从源码安装
+
+这种情况下,从源码按如下方式安装 mmcls:
+
+```shell
+git clone https://github.com/open-mmlab/mmclassification.git
+cd mmclassification
+pip install -v -e .
+# "-v" 表示输出更多安装相关的信息
+# "-e" 表示以可编辑形式安装,这样可以在不重新安装的情况下,让本地修改直接生效
+```
+
+另外,如果你希望向 MMClassification 贡献代码,或者使用试验中的功能,请签出到 `dev` 分支。
+
+```shell
+git checkout dev
+```
+
+### 作为 Python 包安装
+
+直接使用 pip 安装即可。
+
+```shell
+pip install mmcls
+```
+
+## 验证安装
+
+为了验证 MMClassification 的安装是否正确,我们提供了一些示例代码来执行模型推理。
+
+**第 1 步** 我们需要下载配置文件和模型权重文件
+
+```shell
+mim download mmcls --config resnet50_8xb32_in1k --dest .
+```
+
+**第 2 步** 验证示例的推理流程
+
+如果你是**从源码安装**的 mmcls,那么直接运行以下命令进行验证:
+
+```shell
+python demo/image_demo.py demo/demo.JPEG resnet50_8xb32_in1k.py resnet50_8xb32_in1k_20210831-ea4938fc.pth --device cpu
+```
+
+你可以看到命令行中输出了结果字典,包括 `pred_label`,`pred_score` 和 `pred_class` 三个字段。另外如果你拥有图形
+界面(而不是使用远程终端),那么可以启用 `--show` 选项,将示例图像和对应的预测结果在窗口中进行显示。
+
+如果你是**作为 PyThon 包安装**,那么可以打开你的 Python 解释器,并粘贴如下代码:
+
+```python
+from mmcls.apis import init_model, inference_model
+
+config_file = 'resnet50_8xb32_in1k.py'
+checkpoint_file = 'resnet50_8xb32_in1k_20210831-ea4938fc.pth'
+model = init_model(config_file, checkpoint_file, device='cpu') # 或者 device='cuda:0'
+inference_model(model, 'demo/demo.JPEG')
+```
+
+你会看到输出一个字典,包含预测的标签、得分及类别名。
+
+## 自定义安装
+
+### CUDA 版本
+
+安装 PyTorch 时,需要指定 CUDA 版本。如果您不清楚选择哪个,请遵循我们的建议:
+
+- 对于 Ampere 架构的 NVIDIA GPU,例如 GeForce 30 series 以及 NVIDIA A100,CUDA 11 是必需的。
+- 对于更早的 NVIDIA GPU,CUDA 11 是向前兼容的,但 CUDA 10.2 能够提供更好的兼容性,也更加轻量。
+
+请确保你的 GPU 驱动版本满足最低的版本需求,参阅[这张表](https://docs.nvidia.com/cuda/cuda-toolkit-release-notes/index.html#cuda-major-component-versions__table-cuda-toolkit-driver-versions)。
+
+```{note}
+如果按照我们的最佳实践进行安装,CUDA 运行时库就足够了,因为我们提供相关 CUDA 代码的预编译,你不需要进行本地编译。
+但如果你希望从源码进行 MMCV 的编译,或是进行其他 CUDA 算子的开发,那么就必须安装完整的 CUDA 工具链,参见
+[NVIDIA 官网](https://developer.nvidia.com/cuda-downloads),另外还需要确保该 CUDA 工具链的版本与 PyTorch 安装时
+的配置相匹配(如用 `conda install` 安装 PyTorch 时指定的 cudatoolkit 版本)。
+```
+
+### 不使用 MIM 安装 MMCV
+
+MMCV 包含 C++ 和 CUDA 扩展,因此其对 PyTorch 的依赖比较复杂。MIM 会自动解析这些
+依赖,选择合适的 MMCV 预编译包,使安装更简单,但它并不是必需的。
+
+要使用 pip 而不是 MIM 来安装 MMCV,请遵照 [MMCV 安装指南](https://mmcv.readthedocs.io/zh_CN/latest/get_started/installation.html)。
+它需要你用指定 url 的形式手动指定对应的 PyTorch 和 CUDA 版本。
+
+举个例子,如下命令将会安装基于 PyTorch 1.10.x 和 CUDA 11.3 编译的 mmcv-full。
+
+```shell
+pip install mmcv-full -f https://download.openmmlab.com/mmcv/dist/cu113/torch1.10/index.html
+```
+
+### 在 CPU 环境中安装
+
+MMClassification 可以仅在 CPU 环境中安装,在 CPU 模式下,你可以完成训练(需要 MMCV 版本 >= 1.4.4)、测试和模型推理等所有操作。
+
+在 CPU 模式下,MMCV 的部分功能将不可用,通常是一些 GPU 编译的算子。不过不用担心,
+MMClassification 中几乎所有的模型都不会依赖这些算子。
+
+### 在 Google Colab 中安装
+
+[Google Colab](https://research.google.com/) 通常已经包含了 PyTorch 环境,因此我们只需要安装 MMCV 和 MMClassification 即可,命令如下:
+
+**第 1 步** 使用 [MIM](https://github.com/open-mmlab/mim) 安装 [MMCV](https://github.com/open-mmlab/mmcv)
+
+```shell
+!pip3 install openmim
+!mim install mmcv-full
+```
+
+**第 2 步** 从源码安装 MMClassification
+
+```shell
+!git clone https://github.com/open-mmlab/mmclassification.git
+%cd mmclassification
+!pip install -e .
+```
+
+**第 3 步** 验证
+
+```python
+import mmcls
+print(mmcls.__version__)
+# 预期输出: 0.23.0 或更新的版本号
+```
+
+```{note}
+在 Jupyter 中,感叹号 `!` 用于执行外部命令,而 `%cd` 是一个[魔术命令](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-cd),用于切换 Python 的工作路径。
+```
+
+### 通过 Docker 使用 MMClassification
+
+MMClassification 提供 [Dockerfile](https://github.com/open-mmlab/mmclassification/blob/master/docker/Dockerfile)
+用于构建镜像。请确保你的 [Docker 版本](https://docs.docker.com/engine/install/) >=19.03。
+
+```shell
+# 构建默认的 PyTorch 1.8.1,CUDA 10.2 版本镜像
+# 如果你希望使用其他版本,请修改 Dockerfile
+docker build -t mmclassification docker/
+```
+
+用以下命令运行 Docker 镜像:
+
+```shell
+docker run --gpus all --shm-size=8g -it -v {DATA_DIR}:/mmclassification/data mmclassification
+```
+
+## 故障解决
+
+如果你在安装过程中遇到了什么问题,请先查阅[常见问题](faq.md)。如果没有找到解决方法,可以在 GitHub
+上[提出 issue](https://github.com/open-mmlab/mmclassification/issues/new/choose)。
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/stat.py b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/stat.py
new file mode 100644
index 0000000000000000000000000000000000000000..f6d5b3ab63656b0c3e845a04b919328451813232
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/stat.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python
+import functools as func
+import glob
+import os
+import re
+from pathlib import Path
+
+import numpy as np
+
+MMCLS_ROOT = Path(__file__).absolute().parents[1]
+url_prefix = 'https://github.com/open-mmlab/mmclassification/blob/master/'
+
+papers_root = Path('papers')
+papers_root.mkdir(exist_ok=True)
+files = [Path(f) for f in sorted(glob.glob('../../configs/*/README.md'))]
+
+stats = []
+titles = []
+num_ckpts = 0
+num_configs = 0
+
+for f in files:
+ with open(f, 'r') as content_file:
+ content = content_file.read()
+
+ # Extract checkpoints
+ ckpts = set(x.lower().strip()
+ for x in re.findall(r'\[model\]\((https?.*)\)', content))
+ if len(ckpts) == 0:
+ continue
+ num_ckpts += len(ckpts)
+
+ # Extract paper title
+ match_res = list(re.finditer(r'> \[(.*)\]\((.*)\)', content))
+ if len(match_res) > 0:
+ title, paperlink = match_res[0].groups()
+ else:
+ title = content.split('\n')[0].replace('# ', '').strip()
+ paperlink = None
+ titles.append(title)
+
+ # Replace paper link to a button
+ if paperlink is not None:
+ start = match_res[0].start()
+ end = match_res[0].end()
+ link_button = f'[{title}]({paperlink})'
+ content = content[:start] + link_button + content[end:]
+
+ # Extract paper type
+ _papertype = [x for x in re.findall(r'\[([A-Z]+)\]', content)]
+ assert len(_papertype) > 0
+ papertype = _papertype[0]
+ paper = set([(papertype, title)])
+
+ # Write a copy of README
+ copy = papers_root / (f.parent.name + '.md')
+ if copy.exists():
+ os.remove(copy)
+
+ def replace_link(matchobj):
+ # Replace relative link to GitHub link.
+ name = matchobj.group(1)
+ link = matchobj.group(2)
+ if not link.startswith('http') and (f.parent / link).exists():
+ rel_link = (f.parent / link).absolute().relative_to(MMCLS_ROOT)
+ link = url_prefix + str(rel_link)
+ return f'[{name}]({link})'
+
+ content = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', replace_link, content)
+
+ with open(copy, 'w') as copy_file:
+ copy_file.write(content)
+
+ statsmsg = f"""
+\t* [{papertype}] [{title}]({copy}) ({len(ckpts)} ckpts)
+"""
+ stats.append(dict(paper=paper, ckpts=ckpts, statsmsg=statsmsg, copy=copy))
+
+allpapers = func.reduce(lambda a, b: a.union(b),
+ [stat['paper'] for stat in stats])
+msglist = '\n'.join(stat['statsmsg'] for stat in stats)
+
+papertypes, papercounts = np.unique([t for t, _ in allpapers],
+ return_counts=True)
+countstr = '\n'.join(
+ [f' - {t}: {c}' for t, c in zip(papertypes, papercounts)])
+
+modelzoo = f"""
+# 模型库统计
+
+* 论文数量: {len(set(titles))}
+{countstr}
+
+* 模型权重文件数量: {num_ckpts}
+{msglist}
+"""
+
+with open('modelzoo_statistics.md', 'w') as f:
+ f.write(modelzoo)
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/tools/analysis.md b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/tools/analysis.md
new file mode 100644
index 0000000000000000000000000000000000000000..840ff39cb70ecacbcaa93f03deab662ef701ab04
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/tools/analysis.md
@@ -0,0 +1,211 @@
+# 分析
+
+
+
+- [日志分析](#日志分析)
+ - [绘制曲线图](#绘制曲线图)
+ - [统计训练时间](#统计训练时间)
+- [结果分析](#结果分析)
+ - [评估结果](#查看典型结果)
+ - [查看典型结果](#查看典型结果)
+- [模型复杂度分析](#模型复杂度分析)
+- [常见问题](#常见问题)
+
+
+
+## 日志分析
+
+### 绘制曲线图
+
+指定一个训练日志文件,可通过 `tools/analysis_tools/analyze_logs.py` 脚本绘制指定键值的变化曲线
+
+
"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/tutorials/config.md b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/tutorials/config.md
new file mode 100644
index 0000000000000000000000000000000000000000..9e9c87e86490db7b3f1c2e9abe7b64b7f83063ae
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/tutorials/config.md
@@ -0,0 +1,417 @@
+# 教程 1:如何编写配置文件
+
+MMClassification 主要使用 python 文件作为配置文件。其配置文件系统的设计将模块化与继承整合进来,方便用户进行各种实验。所有配置文件都放置在 `configs` 文件夹下,主要包含 `_base_` 原始配置文件夹 以及 `resnet`, `swin_transformer`,`vision_transformer` 等诸多算法文件夹。
+
+可以使用 `python tools/misc/print_config.py /PATH/TO/CONFIG` 命令来查看完整的配置信息,从而方便检查所对应的配置文件。
+
+
+
+- [配置文件以及权重命名规则](#配置文件以及权重命名规则)
+- [配置文件结构](#配置文件结构)
+- [继承并修改配置文件](#继承并修改配置文件)
+ - [使用配置文件里的中间变量](#使用配置文件里的中间变量)
+ - [忽略基础配置文件里的部分内容](#忽略基础配置文件里的部分内容)
+ - [引用基础配置文件里的变量](#引用基础配置文件里的变量)
+- [通过命令行参数修改配置信息](#通过命令行参数修改配置信息)
+- [导入用户自定义模块](#导入用户自定义模块)
+- [常见问题](#常见问题)
+
+
+
+## 配置文件以及权重命名规则
+
+MMClassification 按照以下风格进行配置文件命名,代码库的贡献者需要遵循相同的命名规则。文件名总体分为四部分:算法信息,模块信息,训练信息和数据信息。逻辑上属于不同部分的单词之间用下划线 `'_'` 连接,同一部分有多个单词用短横线 `'-'` 连接。
+
+```
+{algorithm info}_{module info}_{training info}_{data info}.py
+```
+
+- `algorithm info`:算法信息,算法名称或者网络架构,如 resnet 等;
+- `module info`: 模块信息,因任务而异,用以表示一些特殊的 neck、head 和 pretrain 信息;
+- `training info`:一些训练信息,训练策略设置,包括 batch size,schedule 数据增强等;
+- `data info`:数据信息,数据集名称、模态、输入尺寸等,如 imagenet, cifar 等;
+
+### 算法信息
+
+指论文中的算法名称缩写,以及相应的分支架构信息。例如:
+
+- `resnet50`
+- `mobilenet-v3-large`
+- `vit-small-patch32` : `patch32` 表示 `ViT` 切分的分块大小
+- `seresnext101-32x4d` : `SeResNet101` 基本网络结构,`32x4d` 表示在 `Bottleneck` 中 `groups` 和 `width_per_group` 分别为32和4
+
+### 模块信息
+
+指一些特殊的 `neck` 、`head` 或者 `pretrain` 的信息, 在分类中常见为预训练信息,比如:
+
+- `in21k-pre` : 在 `ImageNet21k` 上预训练
+- `in21k-pre-3rd-party` : 在 `ImageNet21k` 上预训练,其权重来自其他仓库
+
+### 训练信息
+
+训练策略的一些设置,包括训练类型、 `batch size`、 `lr schedule`、 数据增强以及特殊的损失函数等等,比如:
+Batch size 信息:
+
+- 格式为`{gpu x batch_per_gpu}`, 如 `8xb32`
+
+训练类型(主要见于 transformer 网络,如 `ViT` 算法,这类算法通常分为预训练和微调两种模式):
+
+- `ft` : Finetune config,用于微调的配置文件
+- `pt` : Pretrain config,用于预训练的配置文件
+
+训练策略信息,训练策略以复现配置文件为基础,此基础不必标注训练策略。但如果在此基础上进行改进,则需注明训练策略,按照应用点位顺序排列,如:`{pipeline aug}-{train aug}-{loss trick}-{scheduler}-{epochs}`
+
+- `coslr-200e` : 使用 cosine scheduler, 训练 200 个 epoch
+- `autoaug-mixup-lbs-coslr-50e` : 使用了 `autoaug`、`mixup`、`label smooth`、`cosine scheduler`, 训练了 50 个轮次
+
+### 数据信息
+
+- `in1k` : `ImageNet1k` 数据集,默认使用 `224x224` 大小的图片
+- `in21k` : `ImageNet21k` 数据集,有些地方也称为 `ImageNet22k` 数据集,默认使用 `224x224` 大小的图片
+- `in1k-384px` : 表示训练的输出图片大小为 `384x384`
+- `cifar100`
+
+### 配置文件命名案例:
+
+```
+repvgg-D2se_deploy_4xb64-autoaug-lbs-mixup-coslr-200e_in1k.py
+```
+
+- `repvgg-D2se`: 算法信息
+ - `repvgg`: 主要算法名称。
+ - `D2se`: 模型的结构。
+- `deploy`:模块信息,该模型为推理状态。
+- `4xb64-autoaug-lbs-mixup-coslr-200e`: 训练信息
+ - `4xb64`: 使用4块 GPU 并且 每块 GPU 的批大小为64。
+ - `autoaug`: 使用 `AutoAugment` 数据增强方法。
+ - `lbs`: 使用 `label smoothing` 损失函数。
+ - `mixup`: 使用 `mixup` 训练增强方法。
+ - `coslr`: 使用 `cosine scheduler` 优化策略。
+ - `200e`: 训练 200 轮次。
+- `in1k`: 数据信息。 配置文件用于 `ImageNet1k` 数据集上使用 `224x224` 大小图片训练。
+
+```{note}
+部分配置文件目前还没有遵循此命名规范,相关文件命名近期会更新。
+```
+
+### 权重命名规则
+
+权重的命名主要包括配置文件名,日期和哈希值。
+
+```
+{config_name}_{date}-{hash}.pth
+```
+
+## 配置文件结构
+
+在 `configs/_base_` 文件夹下有 4 个基本组件类型,分别是:
+
+- [模型(model)](https://github.com/open-mmlab/mmclassification/tree/master/configs/_base_/models)
+- [数据(data)](https://github.com/open-mmlab/mmclassification/tree/master/configs/_base_/datasets)
+- [训练策略(schedule)](https://github.com/open-mmlab/mmclassification/tree/master/configs/_base_/schedules)
+- [运行设置(runtime)](https://github.com/open-mmlab/mmclassification/blob/master/configs/_base_/default_runtime.py)
+
+你可以通过继承一些基本配置文件轻松构建自己的训练配置文件。由来自`_base_` 的组件组成的配置称为 _primitive_。
+
+为了帮助用户对 MMClassification 检测系统中的完整配置和模块有一个基本的了解,我们使用 [ResNet50 原始配置文件](https://github.com/open-mmlab/mmclassification/blob/master/configs/resnet/resnet50_8xb32_in1k.py) 作为案例进行说明并注释每一行含义。更详细的用法和各个模块对应的替代方案,请参考 API 文档。
+
+```python
+_base_ = [
+ '../_base_/models/resnet50.py', # 模型
+ '../_base_/datasets/imagenet_bs32.py', # 数据
+ '../_base_/schedules/imagenet_bs256.py', # 训练策略
+ '../_base_/default_runtime.py' # 默认运行设置
+]
+```
+
+下面对这四个部分分别进行说明,仍然以上述 ResNet50 原始配置文件作为案例。
+
+### 模型
+
+模型参数 `model` 在配置文件中为一个 `python` 字典,主要包括网络结构、损失函数等信息:
+
+- `type` : 分类器名称, 目前 MMClassification 只支持 `ImageClassifier`, 参考 [API 文档](https://mmclassification.readthedocs.io/zh_CN/latest/api/models.html#classifier)。
+- `backbone` : 主干网类型,可用选项参考 [API 文档](https://mmclassification.readthedocs.io/zh_CN/latest/api/models.html#backbones)。
+- `neck` : 颈网络类型,目前 MMClassification 只支持 `GlobalAveragePooling`, 参考 [API 文档](https://mmclassification.readthedocs.io/zh_CN/latest/api/models.html#necks)。
+- `head` : 头网络类型, 包括单标签分类与多标签分类头网络,可用选项参考 [API 文档](https://mmclassification.readthedocs.io/zh_CN/latest/api/models.html#heads)。
+ - `loss` : 损失函数类型, 支持 `CrossEntropyLoss`, [`LabelSmoothLoss`](https://github.com/open-mmlab/mmclassification/blob/master/configs/_base_/models/resnet50_label_smooth.py) 等,可用选项参考 [API 文档](https://mmclassification.readthedocs.io/zh_CN/latest/api/models.html#losses)。
+- `train_cfg` :训练配置, 支持 [`mixup`](https://github.com/open-mmlab/mmclassification/blob/master/configs/_base_/models/resnet50_mixup.py), [`cutmix`](https://github.com/open-mmlab/mmclassification/blob/master/configs/_base_/models/resnet50_cutmix.py) 等训练增强。
+
+```{note}
+配置文件中的 'type' 不是构造时的参数,而是类名。
+```
+
+```python
+model = dict(
+ type='ImageClassifier', # 分类器类型
+ backbone=dict(
+ type='ResNet', # 主干网络类型
+ depth=50, # 主干网网络深度, ResNet 一般有18, 34, 50, 101, 152 可以选择
+ num_stages=4, # 主干网络状态(stages)的数目,这些状态产生的特征图作为后续的 head 的输入。
+ out_indices=(3, ), # 输出的特征图输出索引。越远离输入图像,索引越大
+ frozen_stages=-1, # 网络微调时,冻结网络的stage(训练时不执行反相传播算法),若num_stages=4,backbone包含stem 与 4 个 stages。frozen_stages为-1时,不冻结网络; 为0时,冻结 stem; 为1时,冻结 stem 和 stage1; 为4时,冻结整个backbone
+ style='pytorch'), # 主干网络的风格,'pytorch' 意思是步长为2的层为 3x3 卷积, 'caffe' 意思是步长为2的层为 1x1 卷积。
+ neck=dict(type='GlobalAveragePooling'), # 颈网络类型
+ head=dict(
+ type='LinearClsHead', # 线性分类头,
+ num_classes=1000, # 输出类别数,这与数据集的类别数一致
+ in_channels=2048, # 输入通道数,这与 neck 的输出通道一致
+ loss=dict(type='CrossEntropyLoss', loss_weight=1.0), # 损失函数配置信息
+ topk=(1, 5), # 评估指标,Top-k 准确率, 这里为 top1 与 top5 准确率
+ ))
+```
+
+### 数据
+
+数据参数 `data` 在配置文件中为一个 `python` 字典,主要包含构造数据集加载器(dataloader)配置信息:
+
+- `samples_per_gpu` : 构建 dataloader 时,每个 GPU 的 Batch Size
+- `workers_per_gpu` : 构建 dataloader 时,每个 GPU 的 线程数
+- `train | val | test` : 构造数据集
+ - `type` : 数据集类型, MMClassification 支持 `ImageNet`、 `Cifar` 等 ,参考[API 文档](https://mmclassification.readthedocs.io/zh_CN/latest/api/datasets.html)
+ - `data_prefix` : 数据集根目录
+ - `pipeline` : 数据处理流水线,参考相关教程文档 [如何设计数据处理流水线](https://mmclassification.readthedocs.io/zh_CN/latest/tutorials/data_pipeline.html)
+
+评估参数 `evaluation` 也是一个字典, 为 `evaluation hook` 的配置信息, 主要包括评估间隔、评估指标等。
+
+```python
+# dataset settings
+dataset_type = 'ImageNet' # 数据集名称,
+img_norm_cfg = dict( #图像归一化配置,用来归一化输入的图像。
+ mean=[123.675, 116.28, 103.53], # 预训练里用于预训练主干网络模型的平均值。
+ std=[58.395, 57.12, 57.375], # 预训练里用于预训练主干网络模型的标准差。
+ to_rgb=True) # 是否反转通道,使用 cv2, mmcv 读取图片默认为 BGR 通道顺序,这里 Normalize 均值方差数组的数值是以 RGB 通道顺序, 因此需要反转通道顺序。
+# 训练数据流水线
+train_pipeline = [
+ dict(type='LoadImageFromFile'), # 读取图片
+ dict(type='RandomResizedCrop', size=224), # 随机缩放抠图
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'), # 以概率为0.5随机水平翻转图片
+ dict(type='Normalize', **img_norm_cfg), # 归一化
+ dict(type='ImageToTensor', keys=['img']), # image 转为 torch.Tensor
+ dict(type='ToTensor', keys=['gt_label']), # gt_label 转为 torch.Tensor
+ dict(type='Collect', keys=['img', 'gt_label']) # 决定数据中哪些键应该传递给检测器的流程
+]
+# 测试数据流水线
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='Resize', size=(256, -1)),
+ dict(type='CenterCrop', crop_size=224),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img']) # test 时不传递 gt_label
+]
+data = dict(
+ samples_per_gpu=32, # 单个 GPU 的 Batch size
+ workers_per_gpu=2, # 单个 GPU 的 线程数
+ train=dict( # 训练数据信息
+ type=dataset_type, # 数据集名称
+ data_prefix='data/imagenet/train', # 数据集目录,当不存在 ann_file 时,类别信息从文件夹自动获取
+ pipeline=train_pipeline), # 数据集需要经过的 数据流水线
+ val=dict( # 验证数据集信息
+ type=dataset_type,
+ data_prefix='data/imagenet/val',
+ ann_file='data/imagenet/meta/val.txt', # 标注文件路径,存在 ann_file 时,不通过文件夹自动获取类别信息
+ pipeline=test_pipeline),
+ test=dict( # 测试数据集信息
+ type=dataset_type,
+ data_prefix='data/imagenet/val',
+ ann_file='data/imagenet/meta/val.txt',
+ pipeline=test_pipeline))
+evaluation = dict( # evaluation hook 的配置
+ interval=1, # 验证期间的间隔,单位为 epoch 或者 iter, 取决于 runner 类型。
+ metric='accuracy') # 验证期间使用的指标。
+```
+
+### 训练策略
+
+主要包含 优化器设置、 `optimizer hook` 设置、学习率策略和 `runner`设置:
+
+- `optimizer` : 优化器设置信息, 支持 `pytorch` 所有的优化器,参考相关 [mmcv](https://mmcv.readthedocs.io/zh_CN/latest/_modules/mmcv/runner/optimizer/default_constructor.html#DefaultOptimizerConstructor) 文档
+- `optimizer_config` : `optimizer hook` 的配置文件,如设置梯度限制,参考相关 [mmcv](https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/optimizer.py#L8) 代码
+- `lr_config` : 学习率策略,支持 "CosineAnnealing"、 "Step"、 "Cyclic" 等等,参考相关 [mmcv](https://mmcv.readthedocs.io/zh_CN/latest/_modules/mmcv/runner/hooks/lr_updater.html#LrUpdaterHook) 文档
+- `runner` : 有关 `runner` 可以参考 `mmcv` 对于 [`runner`](https://mmcv.readthedocs.io/zh_CN/latest/understand_mmcv/runner.html) 介绍文档
+
+```python
+# 用于构建优化器的配置文件。支持 PyTorch 中的所有优化器,同时它们的参数与 PyTorch 里的优化器参数一致。
+optimizer = dict(type='SGD', # 优化器类型
+ lr=0.1, # 优化器的学习率,参数的使用细节请参照对应的 PyTorch 文档。
+ momentum=0.9, # 动量(Momentum)
+ weight_decay=0.0001) # 权重衰减系数(weight decay)。
+ # optimizer hook 的配置文件
+optimizer_config = dict(grad_clip=None) # 大多数方法不使用梯度限制(grad_clip)。
+# 学习率调整配置,用于注册 LrUpdater hook。
+lr_config = dict(policy='step', # 调度流程(scheduler)的策略,也支持 CosineAnnealing, Cyclic, 等。
+ step=[30, 60, 90]) # 在 epoch 为 30, 60, 90 时, lr 进行衰减
+runner = dict(type='EpochBasedRunner', # 将使用的 runner 的类别,如 IterBasedRunner 或 EpochBasedRunner。
+ max_epochs=100) # runner 总回合数, 对于 IterBasedRunner 使用 `max_iters`
+```
+
+### 运行设置
+
+本部分主要包括保存权重策略、日志配置、训练参数、断点权重路径和工作目录等等。
+
+```python
+# Checkpoint hook 的配置文件。
+checkpoint_config = dict(interval=1) # 保存的间隔是 1,单位会根据 runner 不同变动,可以为 epoch 或者 iter。
+# 日志配置信息。
+log_config = dict(
+ interval=100, # 打印日志的间隔, 单位 iters
+ hooks=[
+ dict(type='TextLoggerHook'), # 用于记录训练过程的文本记录器(logger)。
+ # dict(type='TensorboardLoggerHook') # 同样支持 Tensorboard 日志
+ ])
+
+dist_params = dict(backend='nccl') # 用于设置分布式训练的参数,端口也同样可被设置。
+log_level = 'INFO' # 日志的输出级别。
+resume_from = None # 从给定路径里恢复检查点(checkpoints),训练模式将从检查点保存的轮次开始恢复训练。
+workflow = [('train', 1)] # runner 的工作流程,[('train', 1)] 表示只有一个工作流且工作流仅执行一次。
+work_dir = 'work_dir' # 用于保存当前实验的模型检查点和日志的目录文件地址。
+```
+
+## 继承并修改配置文件
+
+为了精简代码、更快的修改配置文件以及便于理解,我们建议继承现有方法。
+
+对于在同一算法文件夹下的所有配置文件,MMClassification 推荐只存在 **一个** 对应的 _原始配置_ 文件。
+所有其他的配置文件都应该继承 _原始配置_ 文件,这样就能保证配置文件的最大继承深度为 3。
+
+例如,如果在 ResNet 的基础上做了一些修改,用户首先可以通过指定 `_base_ = './resnet50_8xb32_in1k.py'`(相对于你的配置文件的路径),来继承基础的 ResNet 结构、数据集以及其他训练配置信息,然后修改配置文件中的必要参数以完成继承。如想在基础 resnet50 的基础上将训练轮数由 100 改为 300 和修改学习率衰减轮数,同时修改数据集路径,可以建立新的配置文件 `configs/resnet/resnet50_8xb32-300e_in1k.py`, 文件中写入以下内容:
+
+```python
+_base_ = './resnet50_8xb32_in1k.py'
+
+runner = dict(max_epochs=300)
+lr_config = dict(step=[150, 200, 250])
+
+data = dict(
+ train=dict(data_prefix='mydata/imagenet/train'),
+ val=dict(data_prefix='mydata/imagenet/train', ),
+ test=dict(data_prefix='mydata/imagenet/train', )
+)
+```
+
+### 使用配置文件里的中间变量
+
+用一些中间变量,中间变量让配置文件更加清晰,也更容易修改。
+
+例如数据集里的 `train_pipeline` / `test_pipeline` 是作为数据流水线的中间变量。我们首先要定义 `train_pipeline` / `test_pipeline`,然后将它们传递到 `data` 中。如果想修改训练或测试时输入图片的大小,就需要修改 `train_pipeline` / `test_pipeline` 这些中间变量。
+
+```python
+img_norm_cfg = dict(
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='RandomResizedCrop', size=384, backend='pillow',),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='Collect', keys=['img', 'gt_label'])
+]
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='Resize', size=384, backend='pillow'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img'])
+]
+data = dict(
+ train=dict(pipeline=train_pipeline),
+ val=dict(pipeline=test_pipeline),
+ test=dict(pipeline=test_pipeline))
+```
+
+### 忽略基础配置文件里的部分内容
+
+有时,您需要设置 `_delete_=True` 去忽略基础配置文件里的一些域内容。 可以参照 [mmcv](https://mmcv.readthedocs.io/zh_CN/latest/understand_mmcv/config.html#inherit-from-base-config-with-ignored-fields) 来获得一些简单的指导。
+
+以下是一个简单应用案例。 如果在上述 ResNet50 案例中 使用 cosine schedule ,使用继承并直接修改会报 `get unexcepected keyword 'step'` 错, 因为基础配置文件 lr_config 域信息的 `'step'` 字段被保留下来了,需要加入 `_delete_=True` 去忽略基础配置文件里的 `lr_config` 相关域内容:
+
+```python
+_base_ = '../../configs/resnet/resnet50_8xb32_in1k.py'
+
+lr_config = dict(
+ _delete_=True,
+ policy='CosineAnnealing',
+ min_lr=0,
+ warmup='linear',
+ by_epoch=True,
+ warmup_iters=5,
+ warmup_ratio=0.1
+)
+```
+
+### 引用基础配置文件里的变量
+
+有时,您可以引用 `_base_` 配置信息的一些域内容,这样可以避免重复定义。 可以参照 [mmcv](https://mmcv.readthedocs.io/zh_CN/latest/understand_mmcv/config.html#reference-variables-from-base) 来获得一些简单的指导。
+
+以下是一个简单应用案例,在训练数据预处理流水线中使用 auto augment 数据增强,参考配置文件 [`configs/_base_/datasets/imagenet_bs64_autoaug.py`](https://github.com/open-mmlab/mmclassification/blob/master/configs/_base_/datasets/imagenet_bs64_autoaug.py)。 在定义 `train_pipeline` 时,可以直接在 `_base_` 中加入定义 auto augment 数据增强的文件命名,再通过 `{{_base_.auto_increasing_policies}}` 引用变量:
+
+```python
+_base_ = ['./pipelines/auto_aug.py']
+
+# dataset settings
+dataset_type = 'ImageNet'
+img_norm_cfg = dict(
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='RandomResizedCrop', size=224),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(type='AutoAugment', policies={{_base_.auto_increasing_policies}}),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='Collect', keys=['img', 'gt_label'])
+]
+test_pipeline = [...]
+data = dict(
+ samples_per_gpu=64,
+ workers_per_gpu=2,
+ train=dict(..., pipeline=train_pipeline),
+ val=dict(..., pipeline=test_pipeline))
+evaluation = dict(interval=1, metric='accuracy')
+```
+
+## 通过命令行参数修改配置信息
+
+当用户使用脚本 "tools/train.py" 或者 "tools/test.py" 提交任务,以及使用一些工具脚本时,可以通过指定 `--cfg-options` 参数来直接修改所使用的配置文件内容。
+
+- 更新配置文件内的字典
+
+ 可以按照原始配置文件中字典的键的顺序指定配置选项。
+ 例如,`--cfg-options model.backbone.norm_eval=False` 将主干网络中的所有 BN 模块更改为 `train` 模式。
+
+- 更新配置文件内列表的键
+
+ 一些配置字典在配置文件中会形成一个列表。例如,训练流水线 `data.train.pipeline` 通常是一个列表。
+ 例如,`[dict(type='LoadImageFromFile'), dict(type='TopDownRandomFlip', flip_prob=0.5), ...]` 。如果要将流水线中的 `'flip_prob=0.5'` 更改为 `'flip_prob=0.0'`,您可以这样指定 `--cfg-options data.train.pipeline.1.flip_prob=0.0` 。
+
+- 更新列表/元组的值。
+
+ 当配置文件中需要更新的是一个列表或者元组,例如,配置文件通常会设置 `workflow=[('train', 1)]`,用户如果想更改,
+ 需要指定 `--cfg-options workflow="[(train,1),(val,1)]"`。注意这里的引号 " 对于列表以及元组数据类型的修改是必要的,
+ 并且 **不允许** 引号内所指定的值的书写存在空格。
+
+## 导入用户自定义模块
+
+```{note}
+本部分仅在当将 MMClassification 当作库构建自己项目时可能用到,初学者可跳过。
+```
+
+在学习完后续教程 [如何添加新数据集](https://mmclassification.readthedocs.io/zh_CN/latest/tutorials/new_dataset.html)、[如何设计数据处理流程](https://mmclassification.readthedocs.io/zh_CN/latest/tutorials/data_pipeline.html) 、[如何增加新模块](https://mmclassification.readthedocs.io/zh_CN/latest/tutorials/new_modules.html) 后,您可能使用 MMClassification 完成自己的项目并在项目中自定义了数据集、模型、数据增强等。为了精简代码,可以将 MMClassification 作为一个第三方库,只需要保留自己的额外的代码,并在配置文件中导入自定义的模块。案例可以参考 [OpenMMLab 算法大赛项目](https://github.com/zhangrui-wolf/openmmlab-competition-2021)。
+
+只需要在你的配置文件中添加以下代码:
+
+```python
+custom_imports = dict(
+ imports=['your_dataset_class',
+ 'your_transforme_class',
+ 'your_model_class',
+ 'your_module_class'],
+ allow_failed_imports=False)
+```
+
+## 常见问题
+
+- 无
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/tutorials/data_pipeline.md b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/tutorials/data_pipeline.md
new file mode 100644
index 0000000000000000000000000000000000000000..bbcf9d58de96fcceb0adcb751626f0e493090d73
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/tutorials/data_pipeline.md
@@ -0,0 +1,148 @@
+# 教程 4:如何设计数据处理流程
+
+## 设计数据流水线
+
+按照典型的用法,我们通过 `Dataset` 和 `DataLoader` 来使用多个 worker 进行数据加
+载。对 `Dataset` 的索引操作将返回一个与模型的 `forward` 方法的参数相对应的字典。
+
+数据流水线和数据集在这里是解耦的。通常,数据集定义如何处理标注文件,而数据流水
+线定义所有准备数据字典的步骤。流水线由一系列操作组成。每个操作都将一个字典作为
+输入,并输出一个字典。
+
+这些操作分为数据加载,预处理和格式化。
+
+这里使用 ResNet-50 在 ImageNet 数据集上的数据流水线作为示例。
+
+```python
+img_norm_cfg = dict(
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
+train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='RandomResizedCrop', size=224),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='Collect', keys=['img', 'gt_label'])
+]
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='Resize', size=256),
+ dict(type='CenterCrop', crop_size=224),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img'])
+]
+```
+
+对于每个操作,我们列出了添加、更新、删除的相关字典字段。在流水线的最后,我们使
+用 `Collect` 仅保留进行模型 `forward` 方法所需的项。
+
+### 数据加载
+
+`LoadImageFromFile` - 从文件中加载图像
+
+- 添加:img, img_shape, ori_shape
+
+默认情况下,`LoadImageFromFile` 将会直接从硬盘加载图像,但对于一些效率较高、规
+模较小的模型,这可能会导致 IO 瓶颈。MMCV 支持多种数据加载后端来加速这一过程。例
+如,如果训练设备上配置了 [memcached](https://memcached.org/),那么我们按照如下
+方式修改配置文件。
+
+```
+memcached_root = '/mnt/xxx/memcached_client/'
+train_pipeline = [
+ dict(
+ type='LoadImageFromFile',
+ file_client_args=dict(
+ backend='memcached',
+ server_list_cfg=osp.join(memcached_root, 'server_list.conf'),
+ client_cfg=osp.join(memcached_root, 'client.conf'))),
+]
+```
+
+更多支持的数据加载后端,可以参见 [mmcv.fileio.FileClient](https://github.com/open-mmlab/mmcv/blob/master/mmcv/fileio/file_client.py)。
+
+### 预处理
+
+`Resize` - 缩放图像尺寸
+
+- 添加:scale, scale_idx, pad_shape, scale_factor, keep_ratio
+- 更新:img, img_shape
+
+`RandomFlip` - 随机翻转图像
+
+- 添加:flip, flip_direction
+- 更新:img
+
+`RandomCrop` - 随机裁剪图像
+
+- 更新:img, pad_shape
+
+`Normalize` - 图像数据归一化
+
+- 添加:img_norm_cfg
+- 更新:img
+
+### 格式化
+
+`ToTensor` - 转换(标签)数据至 `torch.Tensor`
+
+- 更新:根据参数 `keys` 指定
+
+`ImageToTensor` - 转换图像数据至 `torch.Tensor`
+
+- 更新:根据参数 `keys` 指定
+
+`Collect` - 保留指定键值
+
+- 删除:除了参数 `keys` 指定以外的所有键值对
+
+## 扩展及使用自定义流水线
+
+1. 编写一个新的数据处理操作,并放置在 `mmcls/datasets/pipelines/` 目录下的任何
+ 一个文件中,例如 `my_pipeline.py`。这个类需要重载 `__call__` 方法,接受一个
+ 字典作为输入,并返回一个字典。
+
+ ```python
+ from mmcls.datasets import PIPELINES
+
+ @PIPELINES.register_module()
+ class MyTransform(object):
+
+ def __call__(self, results):
+ # 对 results['img'] 进行变换操作
+ return results
+ ```
+
+2. 在 `mmcls/datasets/pipelines/__init__.py` 中导入这个新的类。
+
+ ```python
+ ...
+ from .my_pipeline import MyTransform
+
+ __all__ = [
+ ..., 'MyTransform'
+ ]
+ ```
+
+3. 在数据流水线的配置中添加这一操作。
+
+ ```python
+ img_norm_cfg = dict(
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
+ train_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(type='RandomResizedCrop', size=224),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(type='MyTransform'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='Collect', keys=['img', 'gt_label'])
+ ]
+ ```
+
+## 流水线可视化
+
+设计好数据流水线后,可以使用[可视化工具](../tools/visualization.md)查看具体的效果。
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/tutorials/finetune.md b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/tutorials/finetune.md
new file mode 100644
index 0000000000000000000000000000000000000000..efaa88f35761c6dabae5b55a8be0ec4c7ccbdf6f
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/tutorials/finetune.md
@@ -0,0 +1,222 @@
+# 教程 2:如何微调模型
+
+已经证明,在 ImageNet 数据集上预先训练的分类模型对于其他数据集和其他下游任务有很好的效果。
+
+该教程提供了如何将 [Model Zoo](https://github.com/open-mmlab/mmclassification/blob/master/docs/model_zoo.md) 中提供的预训练模型用于其他数据集,已获得更好的效果。
+
+在新数据集上微调模型分为两步:
+
+- 按照 [教程 3:如何自定义数据集](new_dataset.md) 添加对新数据集的支持。
+- 按照本教程中讨论的内容修改配置文件
+
+假设我们现在有一个在 ImageNet-2012 数据集上训练好的 ResNet-50 模型,并且希望在
+CIFAR-10 数据集上进行模型微调,我们需要修改配置文件中的五个部分。
+
+## 继承基础配置
+
+首先,创建一个新的配置文件 `configs/tutorial/resnet50_finetune_cifar.py` 来保存我们的配置,当然,这个文件名可以自由设定。
+
+为了重用不同配置之间的通用部分,我们支持从多个现有配置中继承配置。要微调
+ResNet-50 模型,新配置需要继承 `_base_/models/resnet50.py` 来搭建模型的基本结构。
+为了使用 CIFAR10 数据集,新的配置文件可以直接继承 `_base_/datasets/cifar10.py`。
+而为了保留运行相关设置,比如训练调整器,新的配置文件需要继承
+`_base_/default_runtime.py`。
+
+要继承以上这些配置文件,只需要把下面一段代码放在我们的配置文件开头。
+
+```python
+_base_ = [
+ '../_base_/models/resnet50.py',
+ '../_base_/datasets/cifar10.py', '../_base_/default_runtime.py'
+]
+```
+
+除此之外,你也可以不使用继承,直接编写完整的配置文件,例如
+[`configs/lenet/lenet5_mnist.py`](https://github.com/open-mmlab/mmclassification/blob/master/configs/lenet/lenet5_mnist.py)。
+
+## 修改模型
+
+在进行模型微调是,我们通常希望在主干网络(backbone)加载预训练模型,再用我们的数据集训练一个新的分类头(head)。
+
+为了在主干网络加载预训练模型,我们需要修改主干网络的初始化设置,使用
+`Pretrained` 类型的初始化函数。另外,在初始化设置中,我们使用
+`prefix='backbone'` 来告诉初始化函数移除权重文件中键值名称的前缀,比如把
+`backbone.conv1` 变成 `conv1`。方便起见,我们这里使用一个在线的权重文件链接,它
+会在训练前自动下载对应的文件,你也可以提前下载这个模型,然后使用本地路径。
+
+接下来,新的配置文件需要按照新数据集的类别数目来修改分类头的配置。只需要修改分
+类头中的 `num_classes` 设置即可。
+
+```python
+model = dict(
+ backbone=dict(
+ init_cfg=dict(
+ type='Pretrained',
+ checkpoint='https://download.openmmlab.com/mmclassification/v0/resnet/resnet50_8xb32_in1k_20210831-ea4938fc.pth',
+ prefix='backbone',
+ )),
+ head=dict(num_classes=10),
+)
+```
+
+```{tip}
+这里我们只需要设定我们想要修改的部分配置,其他配置将会自动从我们的父配置文件中获取。
+```
+
+另外,有时我们在进行微调时会希望冻结主干网络前面几层的参数,这么做有助于在后续
+训练中,保持网络从预训练权重中获得的提取低阶特征的能力。在 MMClassification 中,
+这一功能可以通过简单的一个 `frozen_stages` 参数来实现。比如我们需要冻结前两层网
+络的参数,只需要在上面的配置中添加一行:
+
+```python
+model = dict(
+ backbone=dict(
+ frozen_stages=2,
+ init_cfg=dict(
+ type='Pretrained',
+ checkpoint='https://download.openmmlab.com/mmclassification/v0/resnet/resnet50_8xb32_in1k_20210831-ea4938fc.pth',
+ prefix='backbone',
+ )),
+ head=dict(num_classes=10),
+)
+```
+
+```{note}
+目前还不是所有的网络都支持 `frozen_stages` 参数,在使用之前,请先检查
+[文档](https://mmclassification.readthedocs.io/zh_CN/latest/api/models.html#backbones)
+以确认你所使用的主干网络是否支持。
+```
+
+## 修改数据集
+
+当针对一个新的数据集进行微调时,我们通常都需要修改一些数据集相关的配置。比如这
+里,我们就需要把 CIFAR-10 数据集中的图像大小从 32 缩放到 224 来配合 ImageNet 上
+预训练模型的输入。这一需要可以通过修改数据集的预处理流水线(pipeline)来实现。
+
+```python
+img_norm_cfg = dict(
+ mean=[125.307, 122.961, 113.8575],
+ std=[51.5865, 50.847, 51.255],
+ to_rgb=False,
+)
+train_pipeline = [
+ dict(type='RandomCrop', size=32, padding=4),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(type='Resize', size=224),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='Collect', keys=['img', 'gt_label']),
+]
+test_pipeline = [
+ dict(type='Resize', size=224),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img']),
+]
+data = dict(
+ train=dict(pipeline=train_pipeline),
+ val=dict(pipeline=test_pipeline),
+ test=dict(pipeline=test_pipeline),
+)
+```
+
+## 修改训练策略设置
+
+用于微调任务的超参数与默认配置不同,通常只需要较小的学习率和较少的训练时间。
+
+```python
+# 用于批大小为 128 的优化器学习率
+optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)
+optimizer_config = dict(grad_clip=None)
+# 学习率衰减策略
+lr_config = dict(policy='step', step=[15])
+runner = dict(type='EpochBasedRunner', max_epochs=200)
+log_config = dict(interval=100)
+```
+
+## 开始训练
+
+现在,我们完成了用于微调的配置文件,完整的文件如下:
+
+```python
+_base_ = [
+ '../_base_/models/resnet50.py',
+ '../_base_/datasets/cifar10_bs16.py', '../_base_/default_runtime.py'
+]
+
+# 模型设置
+model = dict(
+ backbone=dict(
+ frozen_stages=2,
+ init_cfg=dict(
+ type='Pretrained',
+ checkpoint='https://download.openmmlab.com/mmclassification/v0/resnet/resnet50_8xb32_in1k_20210831-ea4938fc.pth',
+ prefix='backbone',
+ )),
+ head=dict(num_classes=10),
+)
+
+# 数据集设置
+img_norm_cfg = dict(
+ mean=[125.307, 122.961, 113.8575],
+ std=[51.5865, 50.847, 51.255],
+ to_rgb=False,
+)
+train_pipeline = [
+ dict(type='RandomCrop', size=32, padding=4),
+ dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'),
+ dict(type='Resize', size=224),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='ToTensor', keys=['gt_label']),
+ dict(type='Collect', keys=['img', 'gt_label']),
+]
+test_pipeline = [
+ dict(type='Resize', size=224),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img']),
+]
+data = dict(
+ train=dict(pipeline=train_pipeline),
+ val=dict(pipeline=test_pipeline),
+ test=dict(pipeline=test_pipeline),
+)
+
+# 训练策略设置
+# 用于批大小为 128 的优化器学习率
+optimizer = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)
+optimizer_config = dict(grad_clip=None)
+# 学习率衰减策略
+lr_config = dict(policy='step', step=[15])
+runner = dict(type='EpochBasedRunner', max_epochs=200)
+log_config = dict(interval=100)
+```
+
+接下来,我们使用一台 8 张 GPU 的电脑来训练我们的模型,指令如下:
+
+```shell
+bash tools/dist_train.sh configs/tutorial/resnet50_finetune_cifar.py 8
+```
+
+当然,我们也可以使用单张 GPU 来进行训练,使用如下命令:
+
+```shell
+python tools/train.py configs/tutorial/resnet50_finetune_cifar.py
+```
+
+但是如果我们使用单张 GPU 进行训练的话,需要在数据集设置部分作如下修改:
+
+```python
+data = dict(
+ samples_per_gpu=128,
+ train=dict(pipeline=train_pipeline),
+ val=dict(pipeline=test_pipeline),
+ test=dict(pipeline=test_pipeline),
+)
+```
+
+这是因为我们的训练策略是针对批次大小(batch size)为 128 设置的。在父配置文件中,
+设置了 `samples_per_gpu=16`,如果使用 8 张 GPU,总的批次大小就是 128。而如果使
+用单张 GPU,就必须手动修改 `samples_per_gpu=128` 来匹配训练策略。
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/tutorials/new_dataset.md b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/tutorials/new_dataset.md
new file mode 100644
index 0000000000000000000000000000000000000000..86782a13b02d318d5c49d5ab99aa6145c22807ad
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/tutorials/new_dataset.md
@@ -0,0 +1,230 @@
+# 教程 3:如何自定义数据集
+
+我们支持许多常用的图像分类领域公开数据集,你可以在
+[此页面](https://mmclassification.readthedocs.io/zh_CN/latest/api/datasets.html)中找到它们。
+
+在本节中,我们将介绍如何[使用自己的数据集](#使用自己的数据集)以及如何[使用数据集包装](#使用数据集包装)。
+
+## 使用自己的数据集
+
+### 将数据集重新组织为已有格式
+
+想要使用自己的数据集,最简单的方法就是将数据集转换为现有的数据集格式。
+
+对于多分类任务,我们推荐使用 [`CustomDataset`](https://mmclassification.readthedocs.io/zh_CN/latest/api/datasets.html#mmcls.datasets.CustomDataset) 格式。
+
+`CustomDataset` 支持两种类型的数据格式:
+
+1. 提供一个标注文件,其中每一行表示一张样本图片。
+
+ 样本图片可以以任意的结构进行组织,比如:
+
+ ```
+ train/
+ ├── folder_1
+ │ ├── xxx.png
+ │ ├── xxy.png
+ │ └── ...
+ ├── 123.png
+ ├── nsdf3.png
+ └── ...
+ ```
+
+ 而标注文件则记录了所有样本图片的文件路径以及相应的类别序号。其中第一列表示图像
+ 相对于主目录(本例中为 `train` 目录)的路径,第二列表示类别序号:
+
+ ```
+ folder_1/xxx.png 0
+ folder_1/xxy.png 1
+ 123.png 1
+ nsdf3.png 2
+ ...
+ ```
+
+ ```{note}
+ 类别序号的值应当属于 `[0, num_classes - 1]` 范围。
+ ```
+
+2. 将所有样本文件按如下结构进行组织:
+
+ ```
+ train/
+ ├── cat
+ │ ├── xxx.png
+ │ ├── xxy.png
+ │ └── ...
+ │ └── xxz.png
+ ├── bird
+ │ ├── bird1.png
+ │ ├── bird2.png
+ │ └── ...
+ └── dog
+ ├── 123.png
+ ├── nsdf3.png
+ ├── ...
+ └── asd932_.png
+ ```
+
+ 这种情况下,你不需要提供标注文件,所有位于 `cat` 目录下的图片文件都会被视为 `cat` 类别的样本。
+
+通常而言,我们会将整个数据集分为三个子数据集:`train`,`val` 和 `test`,分别用于训练、验证和测试。**每一个**子
+数据集都需要被组织成如上的一种结构。
+
+举个例子,完整的数据集结构如下所示(使用第一种组织结构):
+
+```
+mmclassification
+└── data
+ └── my_dataset
+ ├── meta
+ │ ├── train.txt
+ │ ├── val.txt
+ │ └── test.txt
+ ├── train
+ ├── val
+ └── test
+```
+
+之后在你的配置文件中,可以修改其中的 `data` 字段为如下格式:
+
+```python
+...
+dataset_type = 'CustomDataset'
+classes = ['cat', 'bird', 'dog'] # 数据集中各类别的名称
+
+data = dict(
+ train=dict(
+ type=dataset_type,
+ data_prefix='data/my_dataset/train',
+ ann_file='data/my_dataset/meta/train.txt',
+ classes=classes,
+ pipeline=train_pipeline
+ ),
+ val=dict(
+ type=dataset_type,
+ data_prefix='data/my_dataset/val',
+ ann_file='data/my_dataset/meta/val.txt',
+ classes=classes,
+ pipeline=test_pipeline
+ ),
+ test=dict(
+ type=dataset_type,
+ data_prefix='data/my_dataset/test',
+ ann_file='data/my_dataset/meta/test.txt',
+ classes=classes,
+ pipeline=test_pipeline
+ )
+)
+...
+```
+
+### 创建一个新的数据集类
+
+用户可以编写一个继承自 `BasesDataset` 的新数据集类,并重载 `load_annotations(self)` 方法,
+类似 [CIFAR10](https://github.com/open-mmlab/mmclassification/blob/master/mmcls/datasets/cifar.py)
+和 [ImageNet](https://github.com/open-mmlab/mmclassification/blob/master/mmcls/datasets/imagenet.py)。
+
+通常,此方法返回一个包含所有样本的列表,其中的每个样本都是一个字典。字典中包含了必要的数据信息,例如 `img` 和 `gt_label`。
+
+假设我们将要实现一个 `Filelist` 数据集,该数据集将使用文件列表进行训练和测试。注释列表的格式如下:
+
+```
+000001.jpg 0
+000002.jpg 1
+```
+
+我们可以在 `mmcls/datasets/filelist.py` 中创建一个新的数据集类以加载数据。
+
+```python
+import mmcv
+import numpy as np
+
+from .builder import DATASETS
+from .base_dataset import BaseDataset
+
+
+@DATASETS.register_module()
+class Filelist(BaseDataset):
+
+ def load_annotations(self):
+ assert isinstance(self.ann_file, str)
+
+ data_infos = []
+ with open(self.ann_file) as f:
+ samples = [x.strip().split(' ') for x in f.readlines()]
+ for filename, gt_label in samples:
+ info = {'img_prefix': self.data_prefix}
+ info['img_info'] = {'filename': filename}
+ info['gt_label'] = np.array(gt_label, dtype=np.int64)
+ data_infos.append(info)
+ return data_infos
+
+```
+
+将新的数据集类加入到 `mmcls/datasets/__init__.py` 中:
+
+```python
+from .base_dataset import BaseDataset
+...
+from .filelist import Filelist
+
+__all__ = [
+ 'BaseDataset', ... ,'Filelist'
+]
+```
+
+然后在配置文件中,为了使用 `Filelist`,用户可以按以下方式修改配置
+
+```python
+train = dict(
+ type='Filelist',
+ ann_file = 'image_list.txt',
+ pipeline=train_pipeline
+)
+```
+
+## 使用数据集包装
+
+数据集包装是一种可以改变数据集类行为的类,比如将数据集中的样本进行重复,或是将不同类别的数据进行再平衡。
+
+### 重复数据集
+
+我们使用 `RepeatDataset` 作为一个重复数据集的封装。举个例子,假设原始数据集是 `Dataset_A`,为了重复它,我们需要如下的配置文件:
+
+```python
+data = dict(
+ train=dict(
+ type='RepeatDataset',
+ times=N,
+ dataset=dict( # 这里是 Dataset_A 的原始配置
+ type='Dataset_A',
+ ...
+ pipeline=train_pipeline
+ )
+ )
+ ...
+)
+```
+
+### 类别平衡数据集
+
+我们使用 `ClassBalancedDataset` 作为根据类别频率对数据集进行重复采样的封装类。进行重复采样的数据集需要实现函数 `self.get_cat_ids(idx)` 以支持 `ClassBalancedDataset`。
+
+举个例子,按照 `oversample_thr=1e-3` 对 `Dataset_A` 进行重复采样,需要如下的配置文件:
+
+```python
+data = dict(
+ train = dict(
+ type='ClassBalancedDataset',
+ oversample_thr=1e-3,
+ dataset=dict( # 这里是 Dataset_A 的原始配置
+ type='Dataset_A',
+ ...
+ pipeline=train_pipeline
+ )
+ )
+ ...
+)
+```
+
+更加具体的细节,请参考 [API 文档](https://mmclassification.readthedocs.io/zh_CN/latest/api/datasets.html#mmcls.datasets.ClassBalancedDataset)。
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/tutorials/new_modules.md b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/tutorials/new_modules.md
new file mode 100644
index 0000000000000000000000000000000000000000..14ee32c1677ea4588cb11a8847bb50dfea003f08
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/tutorials/new_modules.md
@@ -0,0 +1,280 @@
+# 教程 5:如何增加新模块
+
+## 开发新组件
+
+我们基本上将模型组件分为 3 种类型。
+
+- 主干网络:通常是一个特征提取网络,例如 ResNet、MobileNet
+- 颈部:用于连接主干网络和头部的组件,例如 GlobalAveragePooling
+- 头部:用于执行特定任务的组件,例如分类和回归
+
+### 添加新的主干网络
+
+这里,我们以 ResNet_CIFAR 为例,展示了如何开发一个新的主干网络组件。
+
+ResNet_CIFAR 针对 CIFAR 32x32 的图像输入,将 ResNet 中 `kernel_size=7, stride=2` 的设置替换为 `kernel_size=3, stride=1`,并移除了 stem 层之后的
+`MaxPooling`,以避免传递过小的特征图到残差块中。
+
+它继承自 `ResNet` 并只修改了 stem 层。
+
+1. 创建一个新文件 `mmcls/models/backbones/resnet_cifar.py`。
+
+```python
+import torch.nn as nn
+
+from ..builder import BACKBONES
+from .resnet import ResNet
+
+
+@BACKBONES.register_module()
+class ResNet_CIFAR(ResNet):
+
+ """ResNet backbone for CIFAR.
+
+ (对这个主干网络的简短描述)
+
+ Args:
+ depth(int): Network depth, from {18, 34, 50, 101, 152}.
+ ...
+ (参数文档)
+ """
+
+ def __init__(self, depth, deep_stem=False, **kwargs):
+ # 调用基类 ResNet 的初始化函数
+ super(ResNet_CIFAR, self).__init__(depth, deep_stem=deep_stem **kwargs)
+ # 其他特殊的初始化流程
+ assert not self.deep_stem, 'ResNet_CIFAR do not support deep_stem'
+
+ def _make_stem_layer(self, in_channels, base_channels):
+ # 重载基类的方法,以实现对网络结构的修改
+ self.conv1 = build_conv_layer(
+ self.conv_cfg,
+ in_channels,
+ base_channels,
+ kernel_size=3,
+ stride=1,
+ padding=1,
+ bias=False)
+ self.norm1_name, norm1 = build_norm_layer(
+ self.norm_cfg, base_channels, postfix=1)
+ self.add_module(self.norm1_name, norm1)
+ self.relu = nn.ReLU(inplace=True)
+
+ def forward(self, x): # 需要返回一个元组
+ pass # 此处省略了网络的前向实现
+
+ def init_weights(self, pretrained=None):
+ pass # 如果有必要的话,重载基类 ResNet 的参数初始化函数
+
+ def train(self, mode=True):
+ pass # 如果有必要的话,重载基类 ResNet 的训练状态函数
+```
+
+2. 在 `mmcls/models/backbones/__init__.py` 中导入新模块
+
+```python
+...
+from .resnet_cifar import ResNet_CIFAR
+
+__all__ = [
+ ..., 'ResNet_CIFAR'
+]
+```
+
+3. 在配置文件中使用新的主干网络
+
+```python
+model = dict(
+ ...
+ backbone=dict(
+ type='ResNet_CIFAR',
+ depth=18,
+ other_arg=xxx),
+ ...
+```
+
+### 添加新的颈部组件
+
+这里我们以 `GlobalAveragePooling` 为例。这是一个非常简单的颈部组件,没有任何参数。
+
+要添加新的颈部组件,我们主要需要实现 `forward` 函数,该函数对主干网络的输出进行
+一些操作并将结果传递到头部。
+
+1. 创建一个新文件 `mmcls/models/necks/gap.py`
+
+ ```python
+ import torch.nn as nn
+
+ from ..builder import NECKS
+
+ @NECKS.register_module()
+ class GlobalAveragePooling(nn.Module):
+
+ def __init__(self):
+ self.gap = nn.AdaptiveAvgPool2d((1, 1))
+
+ def forward(self, inputs):
+ # 简单起见,我们默认输入是一个张量
+ outs = self.gap(inputs)
+ outs = outs.view(inputs.size(0), -1)
+ return outs
+ ```
+
+2. 在 `mmcls/models/necks/__init__.py` 中导入新模块
+
+ ```python
+ ...
+ from .gap import GlobalAveragePooling
+
+ __all__ = [
+ ..., 'GlobalAveragePooling'
+ ]
+ ```
+
+3. 修改配置文件以使用新的颈部组件
+
+ ```python
+ model = dict(
+ neck=dict(type='GlobalAveragePooling'),
+ )
+ ```
+
+### 添加新的头部组件
+
+在此,我们以 `LinearClsHead` 为例,说明如何开发新的头部组件。
+
+要添加一个新的头部组件,基本上我们需要实现 `forward_train` 函数,它接受来自颈部
+或主干网络的特征图作为输入,并基于真实标签计算。
+
+1. 创建一个文件 `mmcls/models/heads/linear_head.py`.
+
+ ```python
+ from ..builder import HEADS
+ from .cls_head import ClsHead
+
+
+ @HEADS.register_module()
+ class LinearClsHead(ClsHead):
+
+ def __init__(self,
+ num_classes,
+ in_channels,
+ loss=dict(type='CrossEntropyLoss', loss_weight=1.0),
+ topk=(1, )):
+ super(LinearClsHead, self).__init__(loss=loss, topk=topk)
+ self.in_channels = in_channels
+ self.num_classes = num_classes
+
+ if self.num_classes <= 0:
+ raise ValueError(
+ f'num_classes={num_classes} must be a positive integer')
+
+ self._init_layers()
+
+ def _init_layers(self):
+ self.fc = nn.Linear(self.in_channels, self.num_classes)
+
+ def init_weights(self):
+ normal_init(self.fc, mean=0, std=0.01, bias=0)
+
+ def forward_train(self, x, gt_label):
+ cls_score = self.fc(x)
+ losses = self.loss(cls_score, gt_label)
+ return losses
+
+ ```
+
+2. 在 `mmcls/models/heads/__init__.py` 中导入这个模块
+
+ ```python
+ ...
+ from .linear_head import LinearClsHead
+
+ __all__ = [
+ ..., 'LinearClsHead'
+ ]
+ ```
+
+3. 修改配置文件以使用新的头部组件。
+
+连同 `GlobalAveragePooling` 颈部组件,完整的模型配置如下:
+
+```python
+model = dict(
+ type='ImageClassifier',
+ backbone=dict(
+ type='ResNet',
+ depth=50,
+ num_stages=4,
+ out_indices=(3, ),
+ style='pytorch'),
+ neck=dict(type='GlobalAveragePooling'),
+ head=dict(
+ type='LinearClsHead',
+ num_classes=1000,
+ in_channels=2048,
+ loss=dict(type='CrossEntropyLoss', loss_weight=1.0),
+ topk=(1, 5),
+ ))
+
+```
+
+### 添加新的损失函数
+
+要添加新的损失函数,我们主要需要在损失函数模块中 `forward` 函数。另外,利用装饰器 `weighted_loss` 可以方便的实现对每个元素的损失进行加权平均。
+
+假设我们要模拟从另一个分类模型生成的概率分布,需要添加 `L1loss` 来实现该目的。
+
+1. 创建一个新文件 `mmcls/models/losses/l1_loss.py`
+
+ ```python
+ import torch
+ import torch.nn as nn
+
+ from ..builder import LOSSES
+ from .utils import weighted_loss
+
+ @weighted_loss
+ def l1_loss(pred, target):
+ assert pred.size() == target.size() and target.numel() > 0
+ loss = torch.abs(pred - target)
+ return loss
+
+ @LOSSES.register_module()
+ class L1Loss(nn.Module):
+
+ def __init__(self, reduction='mean', loss_weight=1.0):
+ super(L1Loss, self).__init__()
+ self.reduction = reduction
+ self.loss_weight = loss_weight
+
+ def forward(self,
+ pred,
+ target,
+ weight=None,
+ avg_factor=None,
+ reduction_override=None):
+ assert reduction_override in (None, 'none', 'mean', 'sum')
+ reduction = (
+ reduction_override if reduction_override else self.reduction)
+ loss = self.loss_weight * l1_loss(
+ pred, target, weight, reduction=reduction, avg_factor=avg_factor)
+ return loss
+ ```
+
+2. 在文件 `mmcls/models/losses/__init__.py` 中导入这个模块
+
+ ```python
+ ...
+ from .l1_loss import L1Loss, l1_loss
+
+ __all__ = [
+ ..., 'L1Loss', 'l1_loss'
+ ]
+ ```
+
+3. 修改配置文件中的 `loss` 字段以使用新的损失函数
+
+ ```python
+ loss=dict(type='L1Loss', loss_weight=1.0))
+ ```
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/tutorials/runtime.md b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/tutorials/runtime.md
new file mode 100644
index 0000000000000000000000000000000000000000..0be7999eefede474c6f2bdc253404ccac2f08bf2
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/tutorials/runtime.md
@@ -0,0 +1,260 @@
+# 教程 7:如何自定义模型运行参数
+
+在本教程中,我们将介绍如何在运行自定义模型时,进行自定义工作流和钩子的方法。
+
+
+
+- [定制工作流](#定制工作流)
+- [钩子](#钩子)
+ - [默认训练钩子](#默认训练钩子)
+ - [使用内置钩子](#使用内置钩子)
+ - [自定义钩子](#自定义钩子)
+- [常见问题](#常见问题)
+
+
+
+## 定制工作流
+
+工作流是一个形如 (任务名,周期数) 的列表,用于指定运行顺序和周期。这里“周期数”的单位由执行器的类型来决定。
+
+比如在 MMClassification 中,我们默认使用基于**轮次**的执行器(`EpochBasedRunner`),那么“周期数”指的就是对应的任务在一个周期中
+要执行多少个轮次。通常,我们只希望执行训练任务,那么只需要使用以下设置:
+
+```python
+workflow = [('train', 1)]
+```
+
+有时我们可能希望在训练过程中穿插检查模型在验证集上的一些指标(例如,损失,准确性)。
+
+在这种情况下,可以将工作流程设置为:
+
+```python
+[('train', 1), ('val', 1)]
+```
+
+这样一来,程序会一轮训练一轮测试地反复执行。
+
+需要注意的是,默认情况下,我们并不推荐用这种方式来进行模型验证,而是推荐在训练中使用 **`EvalHook`** 进行模型验证。使用上述工作流的方式进行模型验证只是一个替代方案。
+
+```{note}
+1. 在验证周期时不会更新模型参数。
+2. 配置文件内的关键词 `max_epochs` 控制训练时期数,并且不会影响验证工作流程。
+3. 工作流 `[('train', 1), ('val', 1)]` 和 `[('train', 1)]` 不会改变 `EvalHook` 的行为。
+ 因为 `EvalHook` 由 `after_train_epoch` 调用,而验证工作流只会影响 `after_val_epoch` 调用的钩子。
+ 因此,`[('train', 1), ('val', 1)]` 和 ``[('train', 1)]`` 的区别在于,runner 在完成每一轮训练后,会计算验证集上的损失。
+```
+
+## 钩子
+
+钩子机制在 OpenMMLab 开源算法库中应用非常广泛,结合执行器可以实现对训练过程的整个生命周期进行管理,可以通过[相关文章](https://zhuanlan.zhihu.com/p/355272220)进一步理解钩子。
+
+钩子只有在构造器中被注册才起作用,目前钩子主要分为两类:
+
+- 默认训练钩子
+
+默认训练钩子由运行器默认注册,一般为一些基础型功能的钩子,已经有确定的优先级,一般不需要修改优先级。
+
+- 定制钩子
+
+定制钩子通过 `custom_hooks` 注册,一般为一些增强型功能的钩子,需要在配置文件中指定优先级,不指定该钩子的优先级将默被设定为 'NORMAL'。
+
+**优先级列表**
+
+| Level | Value |
+| :-------------: | :---: |
+| HIGHEST | 0 |
+| VERY_HIGH | 10 |
+| HIGH | 30 |
+| ABOVE_NORMAL | 40 |
+| NORMAL(default) | 50 |
+| BELOW_NORMAL | 60 |
+| LOW | 70 |
+| VERY_LOW | 90 |
+| LOWEST | 100 |
+
+优先级确定钩子的执行顺序,每次训练前,日志会打印出各个阶段钩子的执行顺序,方便调试。
+
+### 默认训练钩子
+
+有一些常见的钩子未通过 `custom_hooks` 注册,但会在运行器(`Runner`)中默认注册,它们是:
+
+| Hooks | Priority |
+| :-------------------: | :---------------: |
+| `LrUpdaterHook` | VERY_HIGH (10) |
+| `MomentumUpdaterHook` | HIGH (30) |
+| `OptimizerHook` | ABOVE_NORMAL (40) |
+| `CheckpointHook` | NORMAL (50) |
+| `IterTimerHook` | LOW (70) |
+| `EvalHook` | LOW (70) |
+| `LoggerHook(s)` | VERY_LOW (90) |
+
+`OptimizerHook`,`MomentumUpdaterHook`和 `LrUpdaterHook` 在 [优化策略](./schedule.md) 部分进行了介绍,
+`IterTimerHook` 用于记录所用时间,目前不支持修改;
+
+下面介绍如何使用去定制 `CheckpointHook`、`LoggerHooks` 以及 `EvalHook`。
+
+#### 权重文件钩子(CheckpointHook)
+
+MMCV 的 runner 使用 `checkpoint_config` 来初始化 [`CheckpointHook`](https://github.com/open-mmlab/mmcv/blob/9ecd6b0d5ff9d2172c49a182eaa669e9f27bb8e7/mmcv/runner/hooks/checkpoint.py#L9)。
+
+```python
+checkpoint_config = dict(interval=1)
+```
+
+用户可以设置 “max_keep_ckpts” 来仅保存少量模型权重文件,或者通过 “save_optimizer” 决定是否存储优化器的状态字典。
+更多细节可参考 [这里](https://mmcv.readthedocs.io/zh_CN/latest/api.html#mmcv.runner.CheckpointHook)。
+
+#### 日志钩子(LoggerHooks)
+
+`log_config` 包装了多个记录器钩子,并可以设置间隔。
+目前,MMCV 支持 `TextLoggerHook`、 `WandbLoggerHook`、`MlflowLoggerHook` 和 `TensorboardLoggerHook`。
+更多细节可参考[这里](https://mmcv.readthedocs.io/zh_CN/latest/api.html#mmcv.runner.LoggerHook)。
+
+```python
+log_config = dict(
+ interval=50,
+ hooks=[
+ dict(type='TextLoggerHook'),
+ dict(type='TensorboardLoggerHook')
+ ])
+```
+
+#### 验证钩子(EvalHook)
+
+配置中的 `evaluation` 字段将用于初始化 [`EvalHook`](https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/evaluation.py)。
+
+`EvalHook` 有一些保留参数,如 `interval`,`save_best` 和 `start` 等。其他的参数,如“metrics”将被传递给 `dataset.evaluate()`。
+
+```python
+evaluation = dict(interval=1, metric='accuracy', metric_options={'topk': (1, )})
+```
+
+我们可以通过参数 `save_best` 保存取得最好验证结果时的模型权重:
+
+```python
+# "auto" 表示自动选择指标来进行模型的比较。也可以指定一个特定的 key 比如 "accuracy_top-1"。
+evaluation = dict(interval=1, save_best=True, metric='accuracy', metric_options={'topk': (1, )})
+```
+
+在跑一些大型实验时,可以通过修改参数 `start` 跳过训练靠前轮次时的验证步骤,以节约时间。如下:
+
+```python
+evaluation = dict(interval=1, start=200, metric='accuracy', metric_options={'topk': (1, )})
+```
+
+表示在第 200 轮之前,只执行训练流程,不执行验证;从轮次 200 开始,在每一轮训练之后进行验证。
+
+```{note}
+在 MMClassification 的默认配置文件中,evaluation 字段一般被放在 datasets 基础配置文件中。
+```
+
+### 使用内置钩子
+
+一些钩子已在 MMCV 和 MMClassification 中实现:
+
+- [EMAHook](https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/ema.py)
+- [SyncBuffersHook](https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/sync_buffer.py)
+- [EmptyCacheHook](https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/memory.py)
+- [ProfilerHook](https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/profiler.py)
+- ......
+
+可以直接修改配置以使用该钩子,如下格式:
+
+```python
+custom_hooks = [
+ dict(type='MMCVHook', a=a_value, b=b_value, priority='NORMAL')
+]
+```
+
+例如使用 `EMAHook`,进行一次 EMA 的间隔是100个迭代:
+
+```python
+custom_hooks = [
+ dict(type='EMAHook', interval=100, priority='HIGH')
+]
+```
+
+## 自定义钩子
+
+### 创建一个新钩子
+
+这里举一个在 MMClassification 中创建一个新钩子,并在训练中使用它的示例:
+
+```python
+from mmcv.runner import HOOKS, Hook
+
+
+@HOOKS.register_module()
+class MyHook(Hook):
+
+ def __init__(self, a, b):
+ pass
+
+ def before_run(self, runner):
+ pass
+
+ def after_run(self, runner):
+ pass
+
+ def before_epoch(self, runner):
+ pass
+
+ def after_epoch(self, runner):
+ pass
+
+ def before_iter(self, runner):
+ pass
+
+ def after_iter(self, runner):
+ pass
+```
+
+根据钩子的功能,用户需要指定钩子在训练的每个阶段将要执行的操作,比如 `before_run`,`after_run`,`before_epoch`,`after_epoch`,`before_iter` 和 `after_iter`。
+
+### 注册新钩子
+
+之后,需要导入 `MyHook`。假设该文件在 `mmcls/core/utils/my_hook.py`,有两种办法导入它:
+
+- 修改 `mmcls/core/utils/__init__.py` 进行导入
+
+ 新定义的模块应导入到 `mmcls/core/utils/__init__py` 中,以便注册器能找到并添加新模块:
+
+```python
+from .my_hook import MyHook
+
+__all__ = ['MyHook']
+```
+
+- 使用配置文件中的 `custom_imports` 变量手动导入
+
+```python
+custom_imports = dict(imports=['mmcls.core.utils.my_hook'], allow_failed_imports=False)
+```
+
+### 修改配置
+
+```python
+custom_hooks = [
+ dict(type='MyHook', a=a_value, b=b_value)
+]
+```
+
+还可通过 `priority` 参数设置钩子优先级,如下所示:
+
+```python
+custom_hooks = [
+ dict(type='MyHook', a=a_value, b=b_value, priority='NORMAL')
+]
+```
+
+默认情况下,在注册过程中,钩子的优先级设置为“NORMAL”。
+
+## 常见问题
+
+### 1. resume_from, load_from,init_cfg.Pretrained 区别
+
+- `load_from` :仅仅加载模型权重,主要用于加载预训练或者训练好的模型;
+
+- `resume_from` :不仅导入模型权重,还会导入优化器信息,当前轮次(epoch)信息,主要用于从断点继续训练。
+
+- `init_cfg.Pretrained` :在权重初始化期间加载权重,您可以指定要加载的模块。 这通常在微调模型时使用,请参阅[教程 2:如何微调模型](./finetune.md)
diff --git a/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/tutorials/schedule.md b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/tutorials/schedule.md
new file mode 100644
index 0000000000000000000000000000000000000000..931edd09e8764cfb876195d2044c4a3dc8b85060
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/docs/zh_CN/tutorials/schedule.md
@@ -0,0 +1,333 @@
+# 教程 6:如何自定义优化策略
+
+在本教程中,我们将介绍如何在运行自定义模型时,进行构造优化器、定制学习率及动量调整策略、梯度裁剪、梯度累计以及用户自定义优化方法等。
+
+
+
+- [构造 PyTorch 内置优化器](#构造-pytorch-内置优化器)
+- [定制学习率调整策略](#定制学习率调整策略)
+ - [学习率衰减曲线](#定制学习率衰减曲线)
+ - [学习率预热策略](#定制学习率预热策略)
+- [定制动量调整策略](#定制动量调整策略)
+- [参数化精细配置](#参数化精细配置)
+- [梯度裁剪与梯度累计](#梯度裁剪与梯度累计)
+ - [梯度裁剪](#梯度裁剪)
+ - [梯度累计](#梯度累计)
+- [用户自定义优化方法](#用户自定义优化方法)
+ - [自定义优化器](#自定义优化器)
+ - [自定义优化器构造器](#自定义优化器构造器)
+
+
+
+## 构造 PyTorch 内置优化器
+
+MMClassification 支持 PyTorch 实现的所有优化器,仅需在配置文件中,指定 “optimizer” 字段。
+例如,如果要使用 “SGD”,则修改如下。
+
+```python
+optimizer = dict(type='SGD', lr=0.0003, weight_decay=0.0001)
+```
+
+要修改模型的学习率,只需要在优化器的配置中修改 `lr` 即可。
+要配置其他参数,可直接根据 [PyTorch API 文档](https://pytorch.org/docs/stable/optim.html?highlight=optim#module-torch.optim) 进行。
+
+```{note}
+配置文件中的 'type' 不是构造时的参数,而是 PyTorch 内置优化器的类名。
+```
+
+例如,如果想使用 `Adam` 并设置参数为 `torch.optim.Adam(params, lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)`,
+则需要进行如下修改
+
+```python
+optimizer = dict(type='Adam', lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)
+```
+
+## 定制学习率调整策略
+
+### 定制学习率衰减曲线
+
+深度学习研究中,广泛应用学习率衰减来提高网络的性能。要使用学习率衰减,可以在配置中设置 `lr_confg` 字段。
+
+比如在默认的 ResNet 网络训练中,我们使用阶梯式的学习率衰减策略,配置文件为:
+
+```python
+lr_config = dict(policy='step', step=[100, 150])
+```
+
+在训练过程中,程序会周期性地调用 MMCV 中的 [`StepLRHook`](https://github.com/open-mmlab/mmcv/blob/f48241a65aebfe07db122e9db320c31b685dc674/mmcv/runner/hooks/lr_updater.py#L153) 来进行学习率更新。
+
+此外,我们也支持其他学习率调整方法,如 `CosineAnnealing` 和 `Poly` 等。详情可见 [这里](https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/lr_updater.py)
+
+- ConsineAnnealing:
+
+ ```python
+ lr_config = dict(policy='CosineAnnealing', min_lr_ratio=1e-5)
+ ```
+
+- Poly:
+
+ ```python
+ lr_config = dict(policy='poly', power=0.9, min_lr=1e-4, by_epoch=False)
+ ```
+
+### 定制学习率预热策略
+
+在训练的早期阶段,网络容易不稳定,而学习率的预热就是为了减少这种不稳定性。通过预热,学习率将会从一个很小的值逐步提高到预定值。
+
+在 MMClassification 中,我们同样使用 `lr_config` 配置学习率预热策略,主要的参数有以下几个:
+
+- `warmup` : 学习率预热曲线类别,必须为 'constant'、 'linear', 'exp' 或者 `None` 其一, 如果为 `None`, 则不使用学习率预热策略。
+- `warmup_by_epoch` : 是否以轮次(epoch)为单位进行预热。
+- `warmup_iters` : 预热的迭代次数,当 `warmup_by_epoch=True` 时,单位为轮次(epoch);当 `warmup_by_epoch=False` 时,单位为迭代次数(iter)。
+- `warmup_ratio` : 预测的初始学习率 `lr = lr * warmup_ratio`。
+
+例如:
+
+1. 逐**迭代次数**地**线性**预热
+
+ ```python
+ lr_config = dict(
+ policy='CosineAnnealing',
+ by_epoch=False,
+ min_lr_ratio=1e-2,
+ warmup='linear',
+ warmup_ratio=1e-3,
+ warmup_iters=20 * 1252,
+ warmup_by_epoch=False)
+ ```
+
+2. 逐**轮次**地**指数**预热
+
+ ```python
+ lr_config = dict(
+ policy='CosineAnnealing',
+ min_lr=0,
+ warmup='exp',
+ warmup_iters=5,
+ warmup_ratio=0.1,
+ warmup_by_epoch=True)
+ ```
+
+```{tip}
+配置完成后,可以使用 MMClassification 提供的 [学习率可视化工具](https://mmclassification.readthedocs.io/zh_CN/latest/tools/visualization.html#id3) 画出对应学习率调整曲线。
+```
+
+## 定制动量调整策略
+
+MMClassification 支持动量调整器根据学习率修改模型的动量,从而使模型收敛更快。
+
+动量调整程序通常与学习率调整器一起使用,例如,以下配置用于加速收敛。
+更多细节可参考 [CyclicLrUpdater](https://github.com/open-mmlab/mmcv/blob/f48241a65aebfe07db122e9db320c31b685dc674/mmcv/runner/hooks/lr_updater.py#L327) 和 [CyclicMomentumUpdater](https://github.com/open-mmlab/mmcv/blob/f48241a65aebfe07db122e9db320c31b685dc674/mmcv/runner/hooks/momentum_updater.py#L130)。
+
+这里是一个用例:
+
+```python
+lr_config = dict(
+ policy='cyclic',
+ target_ratio=(10, 1e-4),
+ cyclic_times=1,
+ step_ratio_up=0.4,
+)
+momentum_config = dict(
+ policy='cyclic',
+ target_ratio=(0.85 / 0.95, 1),
+ cyclic_times=1,
+ step_ratio_up=0.4,
+)
+```
+
+## 参数化精细配置
+
+一些模型可能具有一些特定于参数的设置以进行优化,例如 BatchNorm 层不添加权重衰减或者对不同的网络层使用不同的学习率。
+在 MMClassification 中,我们通过 `optimizer` 的 `paramwise_cfg` 参数进行配置,可以参考[MMCV](https://mmcv.readthedocs.io/en/latest/_modules/mmcv/runner/optimizer/default_constructor.html#DefaultOptimizerConstructor)。
+
+- 使用指定选项
+
+ MMClassification 提供了包括 `bias_lr_mult`、 `bias_decay_mult`、 `norm_decay_mult`、 `dwconv_decay_mult`、 `dcn_offset_lr_mult` 和 `bypass_duplicate` 选项,指定相关所有的 `bais`、 `norm`、 `dwconv`、 `dcn` 和 `bypass` 参数。例如令模型中所有的 BN 不进行参数衰减:
+
+ ```python
+ optimizer = dict(
+ type='SGD',
+ lr=0.8,
+ weight_decay=1e-4,
+ paramwise_cfg=dict(norm_decay_mult=0.)
+ )
+ ```
+
+- 使用 `custom_keys` 指定参数
+
+ MMClassification 可通过 `custom_keys` 指定不同的参数使用不同的学习率或者权重衰减,例如对特定的参数不使用权重衰减:
+
+ ```python
+ paramwise_cfg = dict(
+ custom_keys={
+ 'backbone.cls_token': dict(decay_mult=0.0),
+ 'backbone.pos_embed': dict(decay_mult=0.0)
+ })
+
+ optimizer = dict(
+ type='SGD',
+ lr=0.8,
+ weight_decay=1e-4,
+ paramwise_cfg=paramwise_cfg)
+ ```
+
+ 对 backbone 使用更小的学习率与衰减系数:
+
+ ```python
+ optimizer = dict(
+ type='SGD',
+ lr=0.8,
+ weight_decay=1e-4,
+ # backbone 的 'lr' and 'weight_decay' 分别为 0.1 * lr 和 0.9 * weight_decay
+ paramwise_cfg = dict(custom_keys={'backbone': dict(lr_mult=0.1, decay_mult=0.9)}))
+ ```
+
+## 梯度裁剪与梯度累计
+
+除了 PyTorch 优化器的基本功能,我们还提供了一些对优化器的增强功能,例如梯度裁剪、梯度累计等,参考 [MMCV](https://github.com/open-mmlab/mmcv/blob/master/mmcv/runner/hooks/optimizer.py)。
+
+### 梯度裁剪
+
+在训练过程中,损失函数可能接近于一些异常陡峭的区域,从而导致梯度爆炸。而梯度裁剪可以帮助稳定训练过程,更多介绍可以参见[该页面](https://paperswithcode.com/method/gradient-clipping)。
+
+目前我们支持在 `optimizer_config` 字段中添加 `grad_clip` 参数来进行梯度裁剪,更详细的参数可参考 [PyTorch 文档](https://pytorch.org/docs/stable/generated/torch.nn.utils.clip_grad_norm_.html)。
+
+用例如下:
+
+```python
+# norm_type: 使用的范数类型,此处使用范数2。
+optimizer_config = dict(grad_clip=dict(max_norm=35, norm_type=2))
+```
+
+当使用继承并修改基础配置方式时,如果基础配置中 `grad_clip=None`,需要添加 `_delete_=True`。有关 `_delete_` 可以参考[教程 1:如何编写配置文件](https://mmclassification.readthedocs.io/zh_CN/latest/tutorials/config.html#id16)。案例如下:
+
+```python
+_base_ = [./_base_/schedules/imagenet_bs256_coslr.py]
+
+optimizer_config = dict(grad_clip=dict(max_norm=35, norm_type=2), _delete_=True, type='OptimizerHook')
+# 当 type 为 'OptimizerHook',可以省略 type;其他情况下,此处必须指明 type='xxxOptimizerHook'。
+```
+
+### 梯度累计
+
+计算资源缺乏缺乏时,每个训练批次的大小(batch size)只能设置为较小的值,这可能会影响模型的性能。
+
+可以使用梯度累计来规避这一问题。
+
+用例如下:
+
+```python
+data = dict(samples_per_gpu=64)
+optimizer_config = dict(type="GradientCumulativeOptimizerHook", cumulative_iters=4)
+```
+
+表示训练时,每 4 个 iter 执行一次反向传播。由于此时单张 GPU 上的批次大小为 64,也就等价于单张 GPU 上一次迭代的批次大小为 256,也即:
+
+```python
+data = dict(samples_per_gpu=256)
+optimizer_config = dict(type="OptimizerHook")
+```
+
+```{note}
+当在 `optimizer_config` 不指定优化器钩子类型时,默认使用 `OptimizerHook`。
+```
+
+## 用户自定义优化方法
+
+在学术研究和工业实践中,可能需要使用 MMClassification 未实现的优化方法,可以通过以下方法添加。
+
+```{note}
+本部分将修改 MMClassification 源码或者向 MMClassification 框架添加代码,初学者可跳过。
+```
+
+### 自定义优化器
+
+#### 1. 定义一个新的优化器
+
+一个自定义的优化器可根据如下规则进行定制
+
+假设我们想添加一个名为 `MyOptimzer` 的优化器,其拥有参数 `a`, `b` 和 `c`。
+可以创建一个名为 `mmcls/core/optimizer` 的文件夹,并在目录下的一个文件,如 `mmcls/core/optimizer/my_optimizer.py` 中实现该自定义优化器:
+
+```python
+from mmcv.runner import OPTIMIZERS
+from torch.optim import Optimizer
+
+
+@OPTIMIZERS.register_module()
+class MyOptimizer(Optimizer):
+
+ def __init__(self, a, b, c):
+
+```
+
+#### 2. 注册优化器
+
+要注册上面定义的上述模块,首先需要将此模块导入到主命名空间中。有两种方法可以实现它。
+
+- 修改 `mmcls/core/optimizer/__init__.py`,将其导入至 `optimizer` 包;再修改 `mmcls/core/__init__.py` 以导入 `optimizer` 包
+
+ 创建 `mmcls/core/optimizer/__init__.py` 文件。
+ 新定义的模块应导入到 `mmcls/core/optimizer/__init__.py` 中,以便注册器能找到新模块并将其添加:
+
+```python
+# 在 mmcls/core/optimizer/__init__.py 中
+from .my_optimizer import MyOptimizer # MyOptimizer 是我们自定义的优化器的名字
+
+__all__ = ['MyOptimizer']
+```
+
+```python
+# 在 mmcls/core/__init__.py 中
+...
+from .optimizer import * # noqa: F401, F403
+```
+
+- 在配置中使用 `custom_imports` 手动导入
+
+```python
+custom_imports = dict(imports=['mmcls.core.optimizer.my_optimizer'], allow_failed_imports=False)
+```
+
+`mmcls.core.optimizer.my_optimizer` 模块将会在程序开始阶段被导入,`MyOptimizer` 类会随之自动被注册。
+注意,只有包含 `MyOptmizer` 类的包会被导入。`mmcls.core.optimizer.my_optimizer.MyOptimizer` **不会** 被直接导入。
+
+#### 3. 在配置文件中指定优化器
+
+之后,用户便可在配置文件的 `optimizer` 域中使用 `MyOptimizer`。
+在配置中,优化器由 “optimizer” 字段定义,如下所示:
+
+```python
+optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)
+```
+
+要使用自定义的优化器,可以将该字段更改为
+
+```python
+optimizer = dict(type='MyOptimizer', a=a_value, b=b_value, c=c_value)
+```
+
+### 自定义优化器构造器
+
+某些模型可能具有一些特定于参数的设置以进行优化,例如 BatchNorm 层的权重衰减。
+
+虽然我们的 `DefaultOptimizerConstructor` 已经提供了这些强大的功能,但可能仍然无法覆盖需求。
+此时我们可以通过自定义优化器构造函数来进行其他细粒度的参数调整。
+
+```python
+from mmcv.runner.optimizer import OPTIMIZER_BUILDERS
+
+
+@OPTIMIZER_BUILDERS.register_module()
+class MyOptimizerConstructor:
+
+ def __init__(self, optimizer_cfg, paramwise_cfg=None):
+ pass
+
+ def __call__(self, model):
+ ... # 在这里实现自己的优化器构造器。
+ return my_optimizer
+```
+
+[这里](https://github.com/open-mmlab/mmcv/blob/9ecd6b0d5ff9d2172c49a182eaa669e9f27bb8e7/mmcv/runner/optimizer/default_constructor.py#L11)是我们默认的优化器构造器的实现,可以作为新优化器构造器实现的模板。
diff --git a/openmmlab_test/mmclassification-0.24.1/hostfile b/openmmlab_test/mmclassification-0.24.1/hostfile
new file mode 100644
index 0000000000000000000000000000000000000000..2c50542622e5c480830198e9d3685397ad89b278
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/hostfile
@@ -0,0 +1,2 @@
+a03r3n15 slots=4
+a03r1n12 slots=4
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/__init__.py b/openmmlab_test/mmclassification-0.24.1/mmcls/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..097c8fef4c5615afbe81add86b38ca80dbd5661c
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/__init__.py
@@ -0,0 +1,60 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import warnings
+
+import mmcv
+from packaging.version import parse
+
+from .version import __version__
+
+
+def digit_version(version_str: str, length: int = 4):
+ """Convert a version string into a tuple of integers.
+
+ This method is usually used for comparing two versions. For pre-release
+ versions: alpha < beta < rc.
+
+ Args:
+ version_str (str): The version string.
+ length (int): The maximum number of version levels. Default: 4.
+
+ Returns:
+ tuple[int]: The version info in digits (integers).
+ """
+ version = parse(version_str)
+ assert version.release, f'failed to parse version {version_str}'
+ release = list(version.release)
+ release = release[:length]
+ if len(release) < length:
+ release = release + [0] * (length - len(release))
+ if version.is_prerelease:
+ mapping = {'a': -3, 'b': -2, 'rc': -1}
+ val = -4
+ # version.pre can be None
+ if version.pre:
+ if version.pre[0] not in mapping:
+ warnings.warn(f'unknown prerelease version {version.pre[0]}, '
+ 'version checking may go wrong')
+ else:
+ val = mapping[version.pre[0]]
+ release.extend([val, version.pre[-1]])
+ else:
+ release.extend([val, 0])
+
+ elif version.is_postrelease:
+ release.extend([1, version.post])
+ else:
+ release.extend([0, 0])
+ return tuple(release)
+
+
+mmcv_minimum_version = '1.4.2'
+mmcv_maximum_version = '1.9.0'
+mmcv_version = digit_version(mmcv.__version__)
+
+
+assert (mmcv_version >= digit_version(mmcv_minimum_version)
+ and mmcv_version <= digit_version(mmcv_maximum_version)), \
+ f'MMCV=={mmcv.__version__} is used but incompatible. ' \
+ f'Please install mmcv>={mmcv_minimum_version}, <={mmcv_maximum_version}.'
+
+__all__ = ['__version__', 'digit_version']
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/apis/__init__.py b/openmmlab_test/mmclassification-0.24.1/mmcls/apis/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..b632f2a9fff9372eed74ba7cd1dfddc060fb72e3
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/apis/__init__.py
@@ -0,0 +1,10 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from .inference import inference_model, init_model, show_result_pyplot
+from .test import multi_gpu_test, single_gpu_test
+from .train import init_random_seed, set_random_seed, train_model
+
+__all__ = [
+ 'set_random_seed', 'train_model', 'init_model', 'inference_model',
+ 'multi_gpu_test', 'single_gpu_test', 'show_result_pyplot',
+ 'init_random_seed'
+]
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/apis/inference.py b/openmmlab_test/mmclassification-0.24.1/mmcls/apis/inference.py
similarity index 82%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/apis/inference.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/apis/inference.py
index 5483c86a49ca39f117aedbbf8206870c3d347327..09e004183dc603ce44bd079c87cae683c10d6d5a 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/apis/inference.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/apis/inference.py
@@ -1,6 +1,6 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import warnings
-import matplotlib.pyplot as plt
import mmcv
import numpy as np
import torch
@@ -34,8 +34,9 @@ def init_model(config, checkpoint=None, device='cuda:0', options=None):
config.model.pretrained = None
model = build_classifier(config.model)
if checkpoint is not None:
- map_loc = 'cpu' if device == 'cpu' else None
- checkpoint = load_checkpoint(model, checkpoint, map_location=map_loc)
+ # Mapping the weights to GPU may cause unexpected video memory leak
+ # which refers to https://github.com/open-mmlab/mmdetection/pull/6405
+ checkpoint = load_checkpoint(model, checkpoint, map_location='cpu')
if 'CLASSES' in checkpoint.get('meta', {}):
model.CLASSES = checkpoint['meta']['CLASSES']
else:
@@ -89,7 +90,12 @@ def inference_model(model, img):
return result
-def show_result_pyplot(model, img, result, fig_size=(15, 10)):
+def show_result_pyplot(model,
+ img,
+ result,
+ fig_size=(15, 10),
+ title='result',
+ wait_time=0):
"""Visualize the classification results on the image.
Args:
@@ -97,10 +103,18 @@ def show_result_pyplot(model, img, result, fig_size=(15, 10)):
img (str or np.ndarray): Image filename or loaded image.
result (list): The classification result.
fig_size (tuple): Figure size of the pyplot figure.
+ Defaults to (15, 10).
+ title (str): Title of the pyplot figure.
+ Defaults to 'result'.
+ wait_time (int): How many seconds to display the image.
+ Defaults to 0.
"""
if hasattr(model, 'module'):
model = model.module
- img = model.show_result(img, result, show=False)
- plt.figure(figsize=fig_size)
- plt.imshow(mmcv.bgr2rgb(img))
- plt.show()
+ model.show_result(
+ img,
+ result,
+ show=True,
+ fig_size=fig_size,
+ win_name=title,
+ wait_time=wait_time)
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/apis/test.py b/openmmlab_test/mmclassification-0.24.1/mmcls/apis/test.py
new file mode 100644
index 0000000000000000000000000000000000000000..dba3f8f0224c876664c1abbab97e02ada38ff15d
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/apis/test.py
@@ -0,0 +1,230 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import os.path as osp
+import pickle
+import shutil
+import tempfile
+import time
+
+import mmcv
+import numpy as np
+import torch
+import torch.distributed as dist
+from mmcv.image import tensor2imgs
+from mmcv.runner import get_dist_info
+
+
+def single_gpu_test(model,
+ data_loader,
+ show=False,
+ out_dir=None,
+ **show_kwargs):
+ """Test model with local single gpu.
+
+ This method tests model with a single gpu and supports showing results.
+
+ Args:
+ model (:obj:`torch.nn.Module`): Model to be tested.
+ data_loader (:obj:`torch.utils.data.DataLoader`): Pytorch data loader.
+ show (bool): Whether to show the test results. Defaults to False.
+ out_dir (str): The output directory of result plots of all samples.
+ Defaults to None, which means not to write output files.
+ **show_kwargs: Any other keyword arguments for showing results.
+
+ Returns:
+ list: The prediction results.
+ """
+ model.eval()
+ results = []
+ dataset = data_loader.dataset
+
+ for i, data in enumerate(data_loader):
+ if i<100:
+ with torch.no_grad():
+ result = model(return_loss=False, **data)
+
+ prog_bar = mmcv.ProgressBar(len(dataset))
+ for i, data in enumerate(data_loader):
+ with torch.no_grad():
+ result = model(return_loss=False, **data)
+
+ batch_size = len(result)
+ #print("batch size api_test-------:",batch_size)
+ results.extend(result)
+
+ if show or out_dir:
+ scores = np.vstack(result)
+ pred_score = np.max(scores, axis=1)
+ pred_label = np.argmax(scores, axis=1)
+ pred_class = [model.CLASSES[lb] for lb in pred_label]
+
+ img_metas = data['img_metas'].data[0]
+ imgs = tensor2imgs(data['img'], **img_metas[0]['img_norm_cfg'])
+ assert len(imgs) == len(img_metas)
+
+ for i, (img, img_meta) in enumerate(zip(imgs, img_metas)):
+ h, w, _ = img_meta['img_shape']
+ img_show = img[:h, :w, :]
+
+ ori_h, ori_w = img_meta['ori_shape'][:-1]
+ img_show = mmcv.imresize(img_show, (ori_w, ori_h))
+
+ if out_dir:
+ out_file = osp.join(out_dir, img_meta['ori_filename'])
+ else:
+ out_file = None
+
+ result_show = {
+ 'pred_score': pred_score[i],
+ 'pred_label': pred_label[i],
+ 'pred_class': pred_class[i]
+ }
+ model.module.show_result(
+ img_show,
+ result_show,
+ show=show,
+ out_file=out_file,
+ **show_kwargs)
+
+ batch_size = data['img'].size(0)
+ #print("batch size api_test:",batch_size)
+ prog_bar.update(batch_size)
+ return results
+
+
+def multi_gpu_test(model, data_loader, tmpdir=None, gpu_collect=False):
+ """Test model with multiple gpus.
+
+ This method tests model with multiple gpus and collects the results
+ under two different modes: gpu and cpu modes. By setting 'gpu_collect=True'
+ it encodes results to gpu tensors and use gpu communication for results
+ collection. On cpu mode it saves the results on different gpus to 'tmpdir'
+ and collects them by the rank 0 worker.
+
+ Args:
+ model (nn.Module): Model to be tested.
+ data_loader (nn.Dataloader): Pytorch data loader.
+ tmpdir (str): Path of directory to save the temporary results from
+ different gpus under cpu mode.
+ gpu_collect (bool): Option to use either gpu or cpu to collect results.
+
+ Returns:
+ list: The prediction results.
+ """
+ model.eval()
+ results = []
+ dataset = data_loader.dataset
+ rank, world_size = get_dist_info()
+
+ for i, data in enumerate(data_loader):
+ if i<100:
+ #print("warm up................")
+ with torch.no_grad():
+ result = model(return_loss=False, **data)
+
+ #print("warm up end ................")
+ if rank == 0:
+ # Check if tmpdir is valid for cpu_collect
+ if (not gpu_collect) and (tmpdir is not None and osp.exists(tmpdir)):
+ raise OSError((f'The tmpdir {tmpdir} already exists.',
+ ' Since tmpdir will be deleted after testing,',
+ ' please make sure you specify an empty one.'))
+ prog_bar = mmcv.ProgressBar(len(dataset))
+ time.sleep(2)
+ dist.barrier()
+ for i, data in enumerate(data_loader):
+ with torch.no_grad():
+ result = model(return_loss=False, **data)
+ if isinstance(result, list):
+ results.extend(result)
+ else:
+ results.append(result)
+
+ if rank == 0:
+ batch_size = data['img'].size(0)
+ #print("batch size api_test-----:",batch_size * world_size)
+ for _ in range(batch_size * world_size):
+ prog_bar.update()
+
+ # collect results from all ranks
+ if gpu_collect:
+ results = collect_results_gpu(results, len(dataset))
+ else:
+ results = collect_results_cpu(results, len(dataset), tmpdir)
+ return results
+
+
+def collect_results_cpu(result_part, size, tmpdir=None):
+ rank, world_size = get_dist_info()
+ # create a tmp dir if it is not specified
+ if tmpdir is None:
+ MAX_LEN = 512
+ # 32 is whitespace
+ dir_tensor = torch.full((MAX_LEN, ),
+ 32,
+ dtype=torch.uint8,
+ device='cuda')
+ if rank == 0:
+ mmcv.mkdir_or_exist('.dist_test')
+ tmpdir = tempfile.mkdtemp(dir='.dist_test')
+ tmpdir = torch.tensor(
+ bytearray(tmpdir.encode()), dtype=torch.uint8, device='cuda')
+ dir_tensor[:len(tmpdir)] = tmpdir
+ dist.broadcast(dir_tensor, 0)
+ tmpdir = dir_tensor.cpu().numpy().tobytes().decode().rstrip()
+ else:
+ mmcv.mkdir_or_exist(tmpdir)
+ # dump the part result to the dir
+ mmcv.dump(result_part, osp.join(tmpdir, f'part_{rank}.pkl'))
+ dist.barrier()
+ # collect all parts
+ if rank != 0:
+ return None
+ else:
+ # load results of all parts from tmp dir
+ part_list = []
+ for i in range(world_size):
+ part_file = osp.join(tmpdir, f'part_{i}.pkl')
+ part_result = mmcv.load(part_file)
+ part_list.append(part_result)
+ # sort the results
+ ordered_results = []
+ for res in zip(*part_list):
+ ordered_results.extend(list(res))
+ # the dataloader may pad some samples
+ ordered_results = ordered_results[:size]
+ # remove tmp dir
+ shutil.rmtree(tmpdir)
+ return ordered_results
+
+
+def collect_results_gpu(result_part, size):
+ rank, world_size = get_dist_info()
+ # dump result part to tensor with pickle
+ part_tensor = torch.tensor(
+ bytearray(pickle.dumps(result_part)), dtype=torch.uint8, device='cuda')
+ # gather all result part tensor shape
+ shape_tensor = torch.tensor(part_tensor.shape, device='cuda')
+ shape_list = [shape_tensor.clone() for _ in range(world_size)]
+ dist.all_gather(shape_list, shape_tensor)
+ # padding result part tensor to max length
+ shape_max = torch.tensor(shape_list).max()
+ part_send = torch.zeros(shape_max, dtype=torch.uint8, device='cuda')
+ part_send[:shape_tensor[0]] = part_tensor
+ part_recv_list = [
+ part_tensor.new_zeros(shape_max) for _ in range(world_size)
+ ]
+ # gather all result part
+ dist.all_gather(part_recv_list, part_send)
+
+ if rank == 0:
+ part_list = []
+ for recv, shape in zip(part_recv_list, shape_list):
+ part_result = pickle.loads(recv[:shape[0]].cpu().numpy().tobytes())
+ part_list.append(part_result)
+ # sort the results
+ ordered_results = []
+ for res in zip(*part_list):
+ ordered_results.extend(list(res))
+ # the dataloader may pad some samples
+ ordered_results = ordered_results[:size]
+ return ordered_results
\ No newline at end of file
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/apis/test_old.py b/openmmlab_test/mmclassification-0.24.1/mmcls/apis/test_old.py
new file mode 100644
index 0000000000000000000000000000000000000000..c15ad2d0cdf08fa6dd258ced925b187b23dcbf6e
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/apis/test_old.py
@@ -0,0 +1,228 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import os.path as osp
+import pickle
+import shutil
+import tempfile
+import time
+
+import mmcv
+import numpy as np
+import torch
+import torch.distributed as dist
+from mmcv.image import tensor2imgs
+from mmcv.runner import get_dist_info
+
+
+def single_gpu_test(model,
+ data_loader,
+ show=False,
+ out_dir=None,
+ **show_kwargs):
+ """Test model with local single gpu.
+
+ This method tests model with a single gpu and supports showing results.
+
+ Args:
+ model (:obj:`torch.nn.Module`): Model to be tested.
+ data_loader (:obj:`torch.utils.data.DataLoader`): Pytorch data loader.
+ show (bool): Whether to show the test results. Defaults to False.
+ out_dir (str): The output directory of result plots of all samples.
+ Defaults to None, which means not to write output files.
+ **show_kwargs: Any other keyword arguments for showing results.
+
+ Returns:
+ list: The prediction results.
+ """
+
+ #dummy = torch.rand(1, 3, 608, 608).cuda()
+ #model = torch.jit.script(model).eval()
+ #model = torch_blade.optimize(model, allow_tracing=True,model_inputs=(dummy,))
+ model.eval()
+ results = []
+ start=0
+ end=0
+ dataset = data_loader.dataset
+ prog_bar = mmcv.ProgressBar(len(dataset))
+ for i, data in enumerate(data_loader):
+ with torch.no_grad():
+ #dummy = torch.rand(32, 3, 224, 224).cuda()
+ #print("-------------------:",data['img'].shape)
+ #model = torch.jit.script(model).eval()
+ #model = torch_blade.optimize(model, allow_tracing=True,model_inputs=data['img'])
+ #print("------------------------:",data['img'].shape)
+ start=time.time()
+ result = model(return_loss=False, **data)
+ end=time.time()
+ #print("====================:",end-start)
+
+ batch_size = len(result)
+ #print("=============:",batch_size)
+ results.extend(result)
+
+ if show or out_dir:
+ scores = np.vstack(result)
+ pred_score = np.max(scores, axis=1)
+ pred_label = np.argmax(scores, axis=1)
+ pred_class = [model.CLASSES[lb] for lb in pred_label]
+
+ img_metas = data['img_metas'].data[0]
+ imgs = tensor2imgs(data['img'], **img_metas[0]['img_norm_cfg'])
+ assert len(imgs) == len(img_metas)
+
+ for i, (img, img_meta) in enumerate(zip(imgs, img_metas)):
+ h, w, _ = img_meta['img_shape']
+ img_show = img[:h, :w, :]
+
+ ori_h, ori_w = img_meta['ori_shape'][:-1]
+ img_show = mmcv.imresize(img_show, (ori_w, ori_h))
+
+ if out_dir:
+ out_file = osp.join(out_dir, img_meta['ori_filename'])
+ else:
+ out_file = None
+
+ result_show = {
+ 'pred_score': pred_score[i],
+ 'pred_label': pred_label[i],
+ 'pred_class': pred_class[i]
+ }
+ model.module.show_result(
+ img_show,
+ result_show,
+ show=show,
+ out_file=out_file,
+ **show_kwargs)
+
+ batch_size = data['img'].size(0)
+ prog_bar.update(batch_size)
+ return results
+
+
+def multi_gpu_test(model, data_loader, tmpdir=None, gpu_collect=False):
+ """Test model with multiple gpus.
+
+ This method tests model with multiple gpus and collects the results
+ under two different modes: gpu and cpu modes. By setting 'gpu_collect=True'
+ it encodes results to gpu tensors and use gpu communication for results
+ collection. On cpu mode it saves the results on different gpus to 'tmpdir'
+ and collects them by the rank 0 worker.
+
+ Args:
+ model (nn.Module): Model to be tested.
+ data_loader (nn.Dataloader): Pytorch data loader.
+ tmpdir (str): Path of directory to save the temporary results from
+ different gpus under cpu mode.
+ gpu_collect (bool): Option to use either gpu or cpu to collect results.
+
+ Returns:
+ list: The prediction results.
+ """
+ model.eval()
+ results = []
+ dataset = data_loader.dataset
+ rank, world_size = get_dist_info()
+ if rank == 0:
+ # Check if tmpdir is valid for cpu_collect
+ if (not gpu_collect) and (tmpdir is not None and osp.exists(tmpdir)):
+ raise OSError((f'The tmpdir {tmpdir} already exists.',
+ ' Since tmpdir will be deleted after testing,',
+ ' please make sure you specify an empty one.'))
+ prog_bar = mmcv.ProgressBar(len(dataset))
+ time.sleep(2)
+ dist.barrier()
+ for i, data in enumerate(data_loader):
+ with torch.no_grad():
+ result = model(return_loss=False, **data)
+ if isinstance(result, list):
+ results.extend(result)
+ else:
+ results.append(result)
+
+ if rank == 0:
+ batch_size = data['img'].size(0)
+ for _ in range(batch_size * world_size):
+ prog_bar.update()
+
+ # collect results from all ranks
+ if gpu_collect:
+ results = collect_results_gpu(results, len(dataset))
+ else:
+ results = collect_results_cpu(results, len(dataset), tmpdir)
+ return results
+
+
+def collect_results_cpu(result_part, size, tmpdir=None):
+ rank, world_size = get_dist_info()
+ # create a tmp dir if it is not specified
+ if tmpdir is None:
+ MAX_LEN = 512
+ # 32 is whitespace
+ dir_tensor = torch.full((MAX_LEN, ),
+ 32,
+ dtype=torch.uint8,
+ device='cuda')
+ if rank == 0:
+ mmcv.mkdir_or_exist('.dist_test')
+ tmpdir = tempfile.mkdtemp(dir='.dist_test')
+ tmpdir = torch.tensor(
+ bytearray(tmpdir.encode()), dtype=torch.uint8, device='cuda')
+ dir_tensor[:len(tmpdir)] = tmpdir
+ dist.broadcast(dir_tensor, 0)
+ tmpdir = dir_tensor.cpu().numpy().tobytes().decode().rstrip()
+ else:
+ mmcv.mkdir_or_exist(tmpdir)
+ # dump the part result to the dir
+ mmcv.dump(result_part, osp.join(tmpdir, f'part_{rank}.pkl'))
+ dist.barrier()
+ # collect all parts
+ if rank != 0:
+ return None
+ else:
+ # load results of all parts from tmp dir
+ part_list = []
+ for i in range(world_size):
+ part_file = osp.join(tmpdir, f'part_{i}.pkl')
+ part_result = mmcv.load(part_file)
+ part_list.append(part_result)
+ # sort the results
+ ordered_results = []
+ for res in zip(*part_list):
+ ordered_results.extend(list(res))
+ # the dataloader may pad some samples
+ ordered_results = ordered_results[:size]
+ # remove tmp dir
+ shutil.rmtree(tmpdir)
+ return ordered_results
+
+
+def collect_results_gpu(result_part, size):
+ rank, world_size = get_dist_info()
+ # dump result part to tensor with pickle
+ part_tensor = torch.tensor(
+ bytearray(pickle.dumps(result_part)), dtype=torch.uint8, device='cuda')
+ # gather all result part tensor shape
+ shape_tensor = torch.tensor(part_tensor.shape, device='cuda')
+ shape_list = [shape_tensor.clone() for _ in range(world_size)]
+ dist.all_gather(shape_list, shape_tensor)
+ # padding result part tensor to max length
+ shape_max = torch.tensor(shape_list).max()
+ part_send = torch.zeros(shape_max, dtype=torch.uint8, device='cuda')
+ part_send[:shape_tensor[0]] = part_tensor
+ part_recv_list = [
+ part_tensor.new_zeros(shape_max) for _ in range(world_size)
+ ]
+ # gather all result part
+ dist.all_gather(part_recv_list, part_send)
+
+ if rank == 0:
+ part_list = []
+ for recv, shape in zip(part_recv_list, shape_list):
+ part_result = pickle.loads(recv[:shape[0]].cpu().numpy().tobytes())
+ part_list.append(part_result)
+ # sort the results
+ ordered_results = []
+ for res in zip(*part_list):
+ ordered_results.extend(list(res))
+ # the dataloader may pad some samples
+ ordered_results = ordered_results[:size]
+ return ordered_results
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/apis/test_time.py b/openmmlab_test/mmclassification-0.24.1/mmcls/apis/test_time.py
new file mode 100644
index 0000000000000000000000000000000000000000..7a7f5dad0b9a0c990201089bdfdf97ac2feeccb2
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/apis/test_time.py
@@ -0,0 +1,257 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import os.path as osp
+import pickle
+import shutil
+import tempfile
+import time
+
+import mmcv
+import numpy as np
+import torch
+import torch.distributed as dist
+from mmcv.image import tensor2imgs
+from mmcv.runner import get_dist_info
+
+
+def single_gpu_test(model,
+ data_loader,
+ show=False,
+ out_dir=None,
+ **show_kwargs):
+ """Test model with local single gpu.
+
+ This method tests model with a single gpu and supports showing results.
+
+ Args:
+ model (:obj:`torch.nn.Module`): Model to be tested.
+ data_loader (:obj:`torch.utils.data.DataLoader`): Pytorch data loader.
+ show (bool): Whether to show the test results. Defaults to False.
+ out_dir (str): The output directory of result plots of all samples.
+ Defaults to None, which means not to write output files.
+ **show_kwargs: Any other keyword arguments for showing results.
+
+ Returns:
+ list: The prediction results.
+ """
+
+ #dummy = torch.rand(1, 3, 608, 608).cuda()
+ #model = torch.jit.script(model).eval()
+ #model = torch_blade.optimize(model, allow_tracing=True,model_inputs=(dummy,))
+ model.eval()
+ results = []
+ start=0
+ end=0
+ dataset = data_loader.dataset
+ #prog_bar = mmcv.ProgressBar(len(dataset))
+ ips_num=0
+ j=0
+ start=time.time()
+
+ for i, data in enumerate(data_loader):
+ end_time1=time.time()
+ time1=end_time1-start
+ with torch.no_grad():
+ result = model(return_loss=False, **data)
+ end_time2=time.time()
+ time2=end_time2-end_time1
+
+ batch_size = len(result)
+
+ ips=batch_size/time2
+ if i >0:
+ ips_num=ips_num+ips
+ j=j+1
+ print("=============batch_size1:",batch_size)
+ results.extend(result)
+
+ if show or out_dir:
+ scores = np.vstack(result)
+ pred_score = np.max(scores, axis=1)
+ pred_label = np.argmax(scores, axis=1)
+ pred_class = [model.CLASSES[lb] for lb in pred_label]
+
+ img_metas = data['img_metas'].data[0]
+ imgs = tensor2imgs(data['img'], **img_metas[0]['img_norm_cfg'])
+ assert len(imgs) == len(img_metas)
+
+ for i, (img, img_meta) in enumerate(zip(imgs, img_metas)):
+ h, w, _ = img_meta['img_shape']
+ img_show = img[:h, :w, :]
+
+ ori_h, ori_w = img_meta['ori_shape'][:-1]
+ img_show = mmcv.imresize(img_show, (ori_w, ori_h))
+
+ if out_dir:
+ out_file = osp.join(out_dir, img_meta['ori_filename'])
+ else:
+ out_file = None
+
+ result_show = {
+ 'pred_score': pred_score[i],
+ 'pred_label': pred_label[i],
+ 'pred_class': pred_class[i]
+ }
+ model.module.show_result(
+ img_show,
+ result_show,
+ show=show,
+ out_file=out_file,
+ **show_kwargs)
+
+ batch_size = data['img'].size(0)
+ print("=============batch_size2:",batch_size)
+ #prog_bar.update(batch_size)
+ print("batch size is %d ,data load cost time: %f s model cost time: %f s,ips: %f" % (batch_size,time1,time2,(batch_size/time2)))
+
+ start=time.time()
+ ips_avg=ips_num/j
+ print("Avg ips is %f" %ips_avg)
+ return results
+
+
+def multi_gpu_test(model, data_loader, tmpdir=None, gpu_collect=False):
+ """Test model with multiple gpus.
+
+ This method tests model with multiple gpus and collects the results
+ under two different modes: gpu and cpu modes. By setting 'gpu_collect=True'
+ it encodes results to gpu tensors and use gpu communication for results
+ collection. On cpu mode it saves the results on different gpus to 'tmpdir'
+ and collects them by the rank 0 worker.
+
+ Args:
+ model (nn.Module): Model to be tested.
+ data_loader (nn.Dataloader): Pytorch data loader.
+ tmpdir (str): Path of directory to save the temporary results from
+ different gpus under cpu mode.
+ gpu_collect (bool): Option to use either gpu or cpu to collect results.
+
+ Returns:
+ list: The prediction results.
+ """
+ model.eval()
+ results = []
+ dataset = data_loader.dataset
+ rank, world_size = get_dist_info()
+
+ if rank == 0:
+ # Check if tmpdir is valid for cpu_collect
+ if (not gpu_collect) and (tmpdir is not None and osp.exists(tmpdir)):
+ raise OSError((f'The tmpdir {tmpdir} already exists.',
+ ' Since tmpdir will be deleted after testing,',
+ ' please make sure you specify an empty one.'))
+ prog_bar = mmcv.ProgressBar(len(dataset))
+
+
+ time.sleep(2)
+ #dist.barrier()
+ ips_num=0
+ j_num=0
+ satrt=time.time()
+ for i, data in enumerate(data_loader):
+ end_time1=time.time()
+ time1=end_time1-satrt
+ with torch.no_grad():
+ result = model(return_loss=False, **data)
+ end_time2=time.time()
+ time2=end_time2-end_time1
+ if isinstance(result, list):
+ results.extend(result)
+ else:
+ results.append(result)
+
+ if rank == 0:
+ batch_size = data['img'].size(0)
+ for _ in range(batch_size * world_size):
+ prog_bar.update()
+ batch_size_global=batch_size * world_size
+ ips=batch_size_global/time2
+ #print("samples_per_gpu is %d ,data load cost time %f s,ips:%f" % (batch_size,time1,ips))
+ if i>0:
+ ips_num=ips_num+ips
+ j_num=j_num+1
+ # collect results from all ranks
+ if gpu_collect:
+ results = collect_results_gpu(results, len(dataset))
+ else:
+ results = collect_results_cpu(results, len(dataset), tmpdir)
+ if rank == 0:
+ ips_avg=ips_num/j_num
+ print("Avg IPS is %f " % ips_avg)
+ return results
+
+
+def collect_results_cpu(result_part, size, tmpdir=None):
+ rank, world_size = get_dist_info()
+ # create a tmp dir if it is not specified
+ if tmpdir is None:
+ MAX_LEN = 512
+ # 32 is whitespace
+ dir_tensor = torch.full((MAX_LEN, ),
+ 32,
+ dtype=torch.uint8,
+ device='cuda')
+ if rank == 0:
+ mmcv.mkdir_or_exist('.dist_test')
+ tmpdir = tempfile.mkdtemp(dir='.dist_test')
+ tmpdir = torch.tensor(
+ bytearray(tmpdir.encode()), dtype=torch.uint8, device='cuda')
+ dir_tensor[:len(tmpdir)] = tmpdir
+ dist.broadcast(dir_tensor, 0)
+ tmpdir = dir_tensor.cpu().numpy().tobytes().decode().rstrip()
+ else:
+ mmcv.mkdir_or_exist(tmpdir)
+ # dump the part result to the dir
+ mmcv.dump(result_part, osp.join(tmpdir, f'part_{rank}.pkl'))
+ dist.barrier()
+ # collect all parts
+ if rank != 0:
+ return None
+ else:
+ # load results of all parts from tmp dir
+ part_list = []
+ for i in range(world_size):
+ part_file = osp.join(tmpdir, f'part_{i}.pkl')
+ part_result = mmcv.load(part_file)
+ part_list.append(part_result)
+ # sort the results
+ ordered_results = []
+ for res in zip(*part_list):
+ ordered_results.extend(list(res))
+ # the dataloader may pad some samples
+ ordered_results = ordered_results[:size]
+ # remove tmp dir
+ shutil.rmtree(tmpdir)
+ return ordered_results
+
+
+def collect_results_gpu(result_part, size):
+ rank, world_size = get_dist_info()
+ # dump result part to tensor with pickle
+ part_tensor = torch.tensor(
+ bytearray(pickle.dumps(result_part)), dtype=torch.uint8, device='cuda')
+ # gather all result part tensor shape
+ shape_tensor = torch.tensor(part_tensor.shape, device='cuda')
+ shape_list = [shape_tensor.clone() for _ in range(world_size)]
+ dist.all_gather(shape_list, shape_tensor)
+ # padding result part tensor to max length
+ shape_max = torch.tensor(shape_list).max()
+ part_send = torch.zeros(shape_max, dtype=torch.uint8, device='cuda')
+ part_send[:shape_tensor[0]] = part_tensor
+ part_recv_list = [
+ part_tensor.new_zeros(shape_max) for _ in range(world_size)
+ ]
+ # gather all result part
+ dist.all_gather(part_recv_list, part_send)
+
+ if rank == 0:
+ part_list = []
+ for recv, shape in zip(part_recv_list, shape_list):
+ part_result = pickle.loads(recv[:shape[0]].cpu().numpy().tobytes())
+ part_list.append(part_result)
+ # sort the results
+ ordered_results = []
+ for res in zip(*part_list):
+ ordered_results.extend(list(res))
+ # the dataloader may pad some samples
+ ordered_results = ordered_results[:size]
+ return ordered_results
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/apis/train.py b/openmmlab_test/mmclassification-0.24.1/mmcls/apis/train.py
new file mode 100644
index 0000000000000000000000000000000000000000..909b116d1635c0f36072a1e977e30eb9f003492f
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/apis/train.py
@@ -0,0 +1,232 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import random
+import warnings
+
+import numpy as np
+import torch
+import torch.distributed as dist
+from mmcv.runner import (DistSamplerSeedHook, Fp16OptimizerHook,
+ build_optimizer, build_runner, get_dist_info)
+
+from mmcls.core import DistEvalHook, DistOptimizerHook, EvalHook
+from mmcls.datasets import build_dataloader, build_dataset
+from mmcls.utils import (get_root_logger, wrap_distributed_model,
+ wrap_non_distributed_model)
+
+
+def init_random_seed(seed=None, device='cuda'):
+ """Initialize random seed.
+
+ If the seed is not set, the seed will be automatically randomized,
+ and then broadcast to all processes to prevent some potential bugs.
+
+ Args:
+ seed (int, Optional): The seed. Default to None.
+ device (str): The device where the seed will be put on.
+ Default to 'cuda'.
+
+ Returns:
+ int: Seed to be used.
+ """
+ if seed is not None:
+ return seed
+
+ # Make sure all ranks share the same random seed to prevent
+ # some potential bugs. Please refer to
+ # https://github.com/open-mmlab/mmdetection/issues/6339
+ rank, world_size = get_dist_info()
+ seed = np.random.randint(2**31)
+ if world_size == 1:
+ return seed
+
+ if rank == 0:
+ random_num = torch.tensor(seed, dtype=torch.int32, device=device)
+ else:
+ random_num = torch.tensor(0, dtype=torch.int32, device=device)
+ dist.broadcast(random_num, src=0)
+ return random_num.item()
+
+
+def set_random_seed(seed, deterministic=False):
+ """Set random seed.
+
+ Args:
+ seed (int): Seed to be used.
+ deterministic (bool): Whether to set the deterministic option for
+ CUDNN backend, i.e., set `torch.backends.cudnn.deterministic`
+ to True and `torch.backends.cudnn.benchmark` to False.
+ Default: False.
+ """
+ random.seed(seed)
+ np.random.seed(seed)
+ torch.manual_seed(seed)
+ torch.cuda.manual_seed_all(seed)
+ if deterministic:
+ torch.backends.cudnn.deterministic = True
+ torch.backends.cudnn.benchmark = False
+
+
+def train_model(model,
+ dataset,
+ cfg,
+ distributed=False,
+ validate=False,
+ timestamp=None,
+ device=None,
+ meta=None):
+ """Train a model.
+
+ This method will build dataloaders, wrap the model and build a runner
+ according to the provided config.
+
+ Args:
+ model (:obj:`torch.nn.Module`): The model to be run.
+ dataset (:obj:`mmcls.datasets.BaseDataset` | List[BaseDataset]):
+ The dataset used to train the model. It can be a single dataset,
+ or a list of dataset with the same length as workflow.
+ cfg (:obj:`mmcv.utils.Config`): The configs of the experiment.
+ distributed (bool): Whether to train the model in a distributed
+ environment. Defaults to False.
+ validate (bool): Whether to do validation with
+ :obj:`mmcv.runner.EvalHook`. Defaults to False.
+ timestamp (str, optional): The timestamp string to auto generate the
+ name of log files. Defaults to None.
+ device (str, optional): TODO
+ meta (dict, optional): A dict records some import information such as
+ environment info and seed, which will be logged in logger hook.
+ Defaults to None.
+ """
+ logger = get_root_logger()
+
+ # prepare data loaders
+ dataset = dataset if isinstance(dataset, (list, tuple)) else [dataset]
+
+ # The default loader config
+ loader_cfg = dict(
+ # cfg.gpus will be ignored if distributed
+ num_gpus=cfg.ipu_replicas if device == 'ipu' else len(cfg.gpu_ids),
+ dist=distributed,
+ round_up=True,
+ seed=cfg.get('seed'),
+ sampler_cfg=cfg.get('sampler', None),
+ )
+ # The overall dataloader settings
+ loader_cfg.update({
+ k: v
+ for k, v in cfg.data.items() if k not in [
+ 'train', 'val', 'test', 'train_dataloader', 'val_dataloader',
+ 'test_dataloader'
+ ]
+ })
+ # The specific dataloader settings
+ train_loader_cfg = {**loader_cfg, **cfg.data.get('train_dataloader', {})}
+
+ data_loaders = [build_dataloader(ds, **train_loader_cfg) for ds in dataset]
+
+ # put model on gpus
+ if distributed:
+ find_unused_parameters = cfg.get('find_unused_parameters', False)
+ # Sets the `find_unused_parameters` parameter in
+ # torch.nn.parallel.DistributedDataParallel
+ model = wrap_distributed_model(
+ model,
+ cfg.device,
+ broadcast_buffers=False,
+ find_unused_parameters=find_unused_parameters)
+ else:
+ model = wrap_non_distributed_model(
+ model, cfg.device, device_ids=cfg.gpu_ids)
+
+ # build runner
+ optimizer = build_optimizer(model, cfg.optimizer)
+
+ if cfg.get('runner') is None:
+ cfg.runner = {
+ 'type': 'EpochBasedRunner',
+ 'max_epochs': cfg.total_epochs
+ }
+ warnings.warn(
+ 'config is now expected to have a `runner` section, '
+ 'please set `runner` in your config.', UserWarning)
+
+ if device == 'ipu':
+ if not cfg.runner['type'].startswith('IPU'):
+ cfg.runner['type'] = 'IPU' + cfg.runner['type']
+ if 'options_cfg' not in cfg.runner:
+ cfg.runner['options_cfg'] = {}
+ cfg.runner['options_cfg']['replicationFactor'] = cfg.ipu_replicas
+ cfg.runner['fp16_cfg'] = cfg.get('fp16', None)
+
+ runner = build_runner(
+ cfg.runner,
+ default_args=dict(
+ model=model,
+ batch_processor=None,
+ optimizer=optimizer,
+ work_dir=cfg.work_dir,
+ logger=logger,
+ meta=meta))
+
+ # an ugly walkaround to make the .log and .log.json filenames the same
+ runner.timestamp = timestamp
+
+ # fp16 setting
+ fp16_cfg = cfg.get('fp16', None)
+
+ if fp16_cfg is None and device == 'npu':
+ fp16_cfg = {'loss_scale': 'dynamic'}
+
+ if fp16_cfg is not None:
+ if device == 'ipu':
+ from mmcv.device.ipu import IPUFp16OptimizerHook
+ optimizer_config = IPUFp16OptimizerHook(
+ **cfg.optimizer_config,
+ loss_scale=fp16_cfg['loss_scale'],
+ distributed=distributed)
+ else:
+ optimizer_config = Fp16OptimizerHook(
+ **cfg.optimizer_config,
+ loss_scale=fp16_cfg['loss_scale'],
+ distributed=distributed)
+ elif distributed and 'type' not in cfg.optimizer_config:
+ optimizer_config = DistOptimizerHook(**cfg.optimizer_config)
+ else:
+ optimizer_config = cfg.optimizer_config
+
+ # register hooks
+ runner.register_training_hooks(
+ cfg.lr_config,
+ optimizer_config,
+ cfg.checkpoint_config,
+ cfg.log_config,
+ cfg.get('momentum_config', None),
+ custom_hooks_config=cfg.get('custom_hooks', None))
+ if distributed and cfg.runner['type'] == 'EpochBasedRunner':
+ runner.register_hook(DistSamplerSeedHook())
+
+ # register eval hooks
+ if validate:
+ val_dataset = build_dataset(cfg.data.val, dict(test_mode=True))
+ # The specific dataloader settings
+ val_loader_cfg = {
+ **loader_cfg,
+ 'shuffle': False, # Not shuffle by default
+ 'sampler_cfg': None, # Not use sampler by default
+ 'drop_last': False, # Not drop last by default
+ **cfg.data.get('val_dataloader', {}),
+ }
+ val_dataloader = build_dataloader(val_dataset, **val_loader_cfg)
+ eval_cfg = cfg.get('evaluation', {})
+ eval_cfg['by_epoch'] = cfg.runner['type'] != 'IterBasedRunner'
+ eval_hook = DistEvalHook if distributed else EvalHook
+ # `EvalHook` needs to be executed after `IterTimerHook`.
+ # Otherwise, it will cause a bug if use `IterBasedRunner`.
+ # Refers to https://github.com/open-mmlab/mmcv/issues/1261
+ runner.register_hook(
+ eval_hook(val_dataloader, **eval_cfg), priority='LOW')
+
+ if cfg.resume_from:
+ runner.resume(cfg.resume_from)
+ elif cfg.load_from:
+ runner.load_checkpoint(cfg.load_from)
+ runner.run(data_loaders, cfg.workflow)
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/core/__init__.py b/openmmlab_test/mmclassification-0.24.1/mmcls/core/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..dd10803296cf98cac18b38001162502a1a099648
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/core/__init__.py
@@ -0,0 +1,5 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from .evaluation import * # noqa: F401, F403
+from .hook import * # noqa: F401, F403
+from .optimizers import * # noqa: F401, F403
+from .utils import * # noqa: F401, F403
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/core/evaluation/__init__.py b/openmmlab_test/mmclassification-0.24.1/mmcls/core/evaluation/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..dd4e57ccf0a2c3a0ba21cd2d144c6c4bef449e70
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/core/evaluation/__init__.py
@@ -0,0 +1,12 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from .eval_hooks import DistEvalHook, EvalHook
+from .eval_metrics import (calculate_confusion_matrix, f1_score, precision,
+ precision_recall_f1, recall, support)
+from .mean_ap import average_precision, mAP
+from .multilabel_eval_metrics import average_performance
+
+__all__ = [
+ 'precision', 'recall', 'f1_score', 'support', 'average_precision', 'mAP',
+ 'average_performance', 'calculate_confusion_matrix', 'precision_recall_f1',
+ 'EvalHook', 'DistEvalHook'
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/core/evaluation/eval_hooks.py b/openmmlab_test/mmclassification-0.24.1/mmcls/core/evaluation/eval_hooks.py
new file mode 100644
index 0000000000000000000000000000000000000000..412eab4fa9a70b6d2402d7a36b7e31aae864b780
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/core/evaluation/eval_hooks.py
@@ -0,0 +1,78 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import os.path as osp
+
+import torch.distributed as dist
+from mmcv.runner import DistEvalHook as BaseDistEvalHook
+from mmcv.runner import EvalHook as BaseEvalHook
+from torch.nn.modules.batchnorm import _BatchNorm
+
+
+class EvalHook(BaseEvalHook):
+ """Non-Distributed evaluation hook.
+
+ Comparing with the ``EvalHook`` in MMCV, this hook will save the latest
+ evaluation results as an attribute for other hooks to use (like
+ `MMClsWandbHook`).
+ """
+
+ def __init__(self, dataloader, **kwargs):
+ super(EvalHook, self).__init__(dataloader, **kwargs)
+ self.latest_results = None
+
+ def _do_evaluate(self, runner):
+ """perform evaluation and save ckpt."""
+ results = self.test_fn(runner.model, self.dataloader)
+ self.latest_results = results
+ runner.log_buffer.output['eval_iter_num'] = len(self.dataloader)
+ key_score = self.evaluate(runner, results)
+ # the key_score may be `None` so it needs to skip the action to save
+ # the best checkpoint
+ if self.save_best and key_score:
+ self._save_ckpt(runner, key_score)
+
+
+class DistEvalHook(BaseDistEvalHook):
+ """Non-Distributed evaluation hook.
+
+ Comparing with the ``EvalHook`` in MMCV, this hook will save the latest
+ evaluation results as an attribute for other hooks to use (like
+ `MMClsWandbHook`).
+ """
+
+ def __init__(self, dataloader, **kwargs):
+ super(DistEvalHook, self).__init__(dataloader, **kwargs)
+ self.latest_results = None
+
+ def _do_evaluate(self, runner):
+ """perform evaluation and save ckpt."""
+ # Synchronization of BatchNorm's buffer (running_mean
+ # and running_var) is not supported in the DDP of pytorch,
+ # which may cause the inconsistent performance of models in
+ # different ranks, so we broadcast BatchNorm's buffers
+ # of rank 0 to other ranks to avoid this.
+ if self.broadcast_bn_buffer:
+ model = runner.model
+ for name, module in model.named_modules():
+ if isinstance(module,
+ _BatchNorm) and module.track_running_stats:
+ dist.broadcast(module.running_var, 0)
+ dist.broadcast(module.running_mean, 0)
+
+ tmpdir = self.tmpdir
+ if tmpdir is None:
+ tmpdir = osp.join(runner.work_dir, '.eval_hook')
+
+ results = self.test_fn(
+ runner.model,
+ self.dataloader,
+ tmpdir=tmpdir,
+ gpu_collect=self.gpu_collect)
+ self.latest_results = results
+ if runner.rank == 0:
+ print('\n')
+ runner.log_buffer.output['eval_iter_num'] = len(self.dataloader)
+ key_score = self.evaluate(runner, results)
+ # the key_score may be `None` so it needs to skip the action to
+ # save the best checkpoint
+ if self.save_best and key_score:
+ self._save_ckpt(runner, key_score)
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/core/evaluation/eval_metrics.py b/openmmlab_test/mmclassification-0.24.1/mmcls/core/evaluation/eval_metrics.py
new file mode 100644
index 0000000000000000000000000000000000000000..365b40883e732ab082bde1e26f1148701e29c328
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/core/evaluation/eval_metrics.py
@@ -0,0 +1,259 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from numbers import Number
+
+import numpy as np
+import torch
+from torch.nn.functional import one_hot
+
+
+def calculate_confusion_matrix(pred, target):
+ """Calculate confusion matrix according to the prediction and target.
+
+ Args:
+ pred (torch.Tensor | np.array): The model prediction with shape (N, C).
+ target (torch.Tensor | np.array): The target of each prediction with
+ shape (N, 1) or (N,).
+
+ Returns:
+ torch.Tensor: Confusion matrix
+ The shape is (C, C), where C is the number of classes.
+ """
+
+ if isinstance(pred, np.ndarray):
+ pred = torch.from_numpy(pred)
+ if isinstance(target, np.ndarray):
+ target = torch.from_numpy(target)
+ assert (
+ isinstance(pred, torch.Tensor) and isinstance(target, torch.Tensor)), \
+ (f'pred and target should be torch.Tensor or np.ndarray, '
+ f'but got {type(pred)} and {type(target)}.')
+
+ # Modified from PyTorch-Ignite
+ num_classes = pred.size(1)
+ pred_label = torch.argmax(pred, dim=1).flatten()
+ target_label = target.flatten()
+ assert len(pred_label) == len(target_label)
+
+ with torch.no_grad():
+ indices = num_classes * target_label + pred_label
+ matrix = torch.bincount(indices, minlength=num_classes**2)
+ matrix = matrix.reshape(num_classes, num_classes)
+ return matrix
+
+
+def precision_recall_f1(pred, target, average_mode='macro', thrs=0.):
+ """Calculate precision, recall and f1 score according to the prediction and
+ target.
+
+ Args:
+ pred (torch.Tensor | np.array): The model prediction with shape (N, C).
+ target (torch.Tensor | np.array): The target of each prediction with
+ shape (N, 1) or (N,).
+ average_mode (str): The type of averaging performed on the result.
+ Options are 'macro' and 'none'. If 'none', the scores for each
+ class are returned. If 'macro', calculate metrics for each class,
+ and find their unweighted mean.
+ Defaults to 'macro'.
+ thrs (Number | tuple[Number], optional): Predictions with scores under
+ the thresholds are considered negative. Default to 0.
+
+ Returns:
+ tuple: tuple containing precision, recall, f1 score.
+
+ The type of precision, recall, f1 score is one of the following:
+
+ +----------------------------+--------------------+-------------------+
+ | Args | ``thrs`` is number | ``thrs`` is tuple |
+ +============================+====================+===================+
+ | ``average_mode`` = "macro" | float | list[float] |
+ +----------------------------+--------------------+-------------------+
+ | ``average_mode`` = "none" | np.array | list[np.array] |
+ +----------------------------+--------------------+-------------------+
+ """
+
+ allowed_average_mode = ['macro', 'none']
+ if average_mode not in allowed_average_mode:
+ raise ValueError(f'Unsupport type of averaging {average_mode}.')
+
+ if isinstance(pred, np.ndarray):
+ pred = torch.from_numpy(pred)
+ assert isinstance(pred, torch.Tensor), \
+ (f'pred should be torch.Tensor or np.ndarray, but got {type(pred)}.')
+ if isinstance(target, np.ndarray):
+ target = torch.from_numpy(target).long()
+ assert isinstance(target, torch.Tensor), \
+ f'target should be torch.Tensor or np.ndarray, ' \
+ f'but got {type(target)}.'
+
+ if isinstance(thrs, Number):
+ thrs = (thrs, )
+ return_single = True
+ elif isinstance(thrs, tuple):
+ return_single = False
+ else:
+ raise TypeError(
+ f'thrs should be a number or tuple, but got {type(thrs)}.')
+
+ num_classes = pred.size(1)
+ pred_score, pred_label = torch.topk(pred, k=1)
+ pred_score = pred_score.flatten()
+ pred_label = pred_label.flatten()
+
+ gt_positive = one_hot(target.flatten(), num_classes)
+
+ precisions = []
+ recalls = []
+ f1_scores = []
+ for thr in thrs:
+ # Only prediction values larger than thr are counted as positive
+ pred_positive = one_hot(pred_label, num_classes)
+ if thr is not None:
+ pred_positive[pred_score <= thr] = 0
+ class_correct = (pred_positive & gt_positive).sum(0)
+ precision = class_correct / np.maximum(pred_positive.sum(0), 1.) * 100
+ recall = class_correct / np.maximum(gt_positive.sum(0), 1.) * 100
+ f1_score = 2 * precision * recall / np.maximum(
+ precision + recall,
+ torch.finfo(torch.float32).eps)
+ if average_mode == 'macro':
+ precision = float(precision.mean())
+ recall = float(recall.mean())
+ f1_score = float(f1_score.mean())
+ elif average_mode == 'none':
+ precision = precision.detach().cpu().numpy()
+ recall = recall.detach().cpu().numpy()
+ f1_score = f1_score.detach().cpu().numpy()
+ else:
+ raise ValueError(f'Unsupport type of averaging {average_mode}.')
+ precisions.append(precision)
+ recalls.append(recall)
+ f1_scores.append(f1_score)
+
+ if return_single:
+ return precisions[0], recalls[0], f1_scores[0]
+ else:
+ return precisions, recalls, f1_scores
+
+
+def precision(pred, target, average_mode='macro', thrs=0.):
+ """Calculate precision according to the prediction and target.
+
+ Args:
+ pred (torch.Tensor | np.array): The model prediction with shape (N, C).
+ target (torch.Tensor | np.array): The target of each prediction with
+ shape (N, 1) or (N,).
+ average_mode (str): The type of averaging performed on the result.
+ Options are 'macro' and 'none'. If 'none', the scores for each
+ class are returned. If 'macro', calculate metrics for each class,
+ and find their unweighted mean.
+ Defaults to 'macro'.
+ thrs (Number | tuple[Number], optional): Predictions with scores under
+ the thresholds are considered negative. Default to 0.
+
+ Returns:
+ float | np.array | list[float | np.array]: Precision.
+
+ +----------------------------+--------------------+-------------------+
+ | Args | ``thrs`` is number | ``thrs`` is tuple |
+ +============================+====================+===================+
+ | ``average_mode`` = "macro" | float | list[float] |
+ +----------------------------+--------------------+-------------------+
+ | ``average_mode`` = "none" | np.array | list[np.array] |
+ +----------------------------+--------------------+-------------------+
+ """
+ precisions, _, _ = precision_recall_f1(pred, target, average_mode, thrs)
+ return precisions
+
+
+def recall(pred, target, average_mode='macro', thrs=0.):
+ """Calculate recall according to the prediction and target.
+
+ Args:
+ pred (torch.Tensor | np.array): The model prediction with shape (N, C).
+ target (torch.Tensor | np.array): The target of each prediction with
+ shape (N, 1) or (N,).
+ average_mode (str): The type of averaging performed on the result.
+ Options are 'macro' and 'none'. If 'none', the scores for each
+ class are returned. If 'macro', calculate metrics for each class,
+ and find their unweighted mean.
+ Defaults to 'macro'.
+ thrs (Number | tuple[Number], optional): Predictions with scores under
+ the thresholds are considered negative. Default to 0.
+
+ Returns:
+ float | np.array | list[float | np.array]: Recall.
+
+ +----------------------------+--------------------+-------------------+
+ | Args | ``thrs`` is number | ``thrs`` is tuple |
+ +============================+====================+===================+
+ | ``average_mode`` = "macro" | float | list[float] |
+ +----------------------------+--------------------+-------------------+
+ | ``average_mode`` = "none" | np.array | list[np.array] |
+ +----------------------------+--------------------+-------------------+
+ """
+ _, recalls, _ = precision_recall_f1(pred, target, average_mode, thrs)
+ return recalls
+
+
+def f1_score(pred, target, average_mode='macro', thrs=0.):
+ """Calculate F1 score according to the prediction and target.
+
+ Args:
+ pred (torch.Tensor | np.array): The model prediction with shape (N, C).
+ target (torch.Tensor | np.array): The target of each prediction with
+ shape (N, 1) or (N,).
+ average_mode (str): The type of averaging performed on the result.
+ Options are 'macro' and 'none'. If 'none', the scores for each
+ class are returned. If 'macro', calculate metrics for each class,
+ and find their unweighted mean.
+ Defaults to 'macro'.
+ thrs (Number | tuple[Number], optional): Predictions with scores under
+ the thresholds are considered negative. Default to 0.
+
+ Returns:
+ float | np.array | list[float | np.array]: F1 score.
+
+ +----------------------------+--------------------+-------------------+
+ | Args | ``thrs`` is number | ``thrs`` is tuple |
+ +============================+====================+===================+
+ | ``average_mode`` = "macro" | float | list[float] |
+ +----------------------------+--------------------+-------------------+
+ | ``average_mode`` = "none" | np.array | list[np.array] |
+ +----------------------------+--------------------+-------------------+
+ """
+ _, _, f1_scores = precision_recall_f1(pred, target, average_mode, thrs)
+ return f1_scores
+
+
+def support(pred, target, average_mode='macro'):
+ """Calculate the total number of occurrences of each label according to the
+ prediction and target.
+
+ Args:
+ pred (torch.Tensor | np.array): The model prediction with shape (N, C).
+ target (torch.Tensor | np.array): The target of each prediction with
+ shape (N, 1) or (N,).
+ average_mode (str): The type of averaging performed on the result.
+ Options are 'macro' and 'none'. If 'none', the scores for each
+ class are returned. If 'macro', calculate metrics for each class,
+ and find their unweighted sum.
+ Defaults to 'macro'.
+
+ Returns:
+ float | np.array: Support.
+
+ - If the ``average_mode`` is set to macro, the function returns
+ a single float.
+ - If the ``average_mode`` is set to none, the function returns
+ a np.array with shape C.
+ """
+ confusion_matrix = calculate_confusion_matrix(pred, target)
+ with torch.no_grad():
+ res = confusion_matrix.sum(1)
+ if average_mode == 'macro':
+ res = float(res.sum().numpy())
+ elif average_mode == 'none':
+ res = res.numpy()
+ else:
+ raise ValueError(f'Unsupport type of averaging {average_mode}.')
+ return res
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/core/evaluation/mean_ap.py b/openmmlab_test/mmclassification-0.24.1/mmcls/core/evaluation/mean_ap.py
similarity index 92%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/core/evaluation/mean_ap.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/core/evaluation/mean_ap.py
index 2255ce2208adea8bf294b8bcfa24f6e53acb7e27..2771a2acd72d84d6a569548ac16f094bd58f88a9 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/core/evaluation/mean_ap.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/core/evaluation/mean_ap.py
@@ -1,15 +1,16 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import numpy as np
import torch
def average_precision(pred, target):
- """Calculate the average precision for a single class.
+ r"""Calculate the average precision for a single class.
AP summarizes a precision-recall curve as the weighted mean of maximum
precisions obtained for any r'>r, where r is the recall:
- ..math::
- \\text{AP} = \\sum_n (R_n - R_{n-1}) P_n
+ .. math::
+ \text{AP} = \sum_n (R_n - R_{n-1}) P_n
Note that no approximation is involved since the curve is piecewise
constant.
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/core/evaluation/multilabel_eval_metrics.py b/openmmlab_test/mmclassification-0.24.1/mmcls/core/evaluation/multilabel_eval_metrics.py
similarity index 98%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/core/evaluation/multilabel_eval_metrics.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/core/evaluation/multilabel_eval_metrics.py
index e8fcfc1111d646f0ccdda7a635cb0a4968a9be53..1d34e2b081250066413a7c4283511cdd2b3d1be1 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/core/evaluation/multilabel_eval_metrics.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/core/evaluation/multilabel_eval_metrics.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import warnings
import numpy as np
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/core/export/__init__.py b/openmmlab_test/mmclassification-0.24.1/mmcls/core/export/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1c6ec1b9bc306498b63d80e0b52b29441d2242fd
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/core/export/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from .test import ONNXRuntimeClassifier, TensorRTClassifier
+
+__all__ = ['ONNXRuntimeClassifier', 'TensorRTClassifier']
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/core/export/test.py b/openmmlab_test/mmclassification-0.24.1/mmcls/core/export/test.py
new file mode 100644
index 0000000000000000000000000000000000000000..f7caed6e024943715f6a2a043e12584d62e09001
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/core/export/test.py
@@ -0,0 +1,96 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import warnings
+
+import numpy as np
+import onnxruntime as ort
+import torch
+
+from mmcls.models.classifiers import BaseClassifier
+
+
+class ONNXRuntimeClassifier(BaseClassifier):
+ """Wrapper for classifier's inference with ONNXRuntime."""
+
+ def __init__(self, onnx_file, class_names, device_id):
+ super(ONNXRuntimeClassifier, self).__init__()
+ sess = ort.InferenceSession(onnx_file)
+
+ providers = ['CPUExecutionProvider']
+ options = [{}]
+ is_cuda_available = ort.get_device() == 'GPU'
+ if is_cuda_available:
+ providers.insert(0, 'CUDAExecutionProvider')
+ options.insert(0, {'device_id': device_id})
+ sess.set_providers(providers, options)
+
+ self.sess = sess
+ self.CLASSES = class_names
+ self.device_id = device_id
+ self.io_binding = sess.io_binding()
+ self.output_names = [_.name for _ in sess.get_outputs()]
+ self.is_cuda_available = is_cuda_available
+
+ def simple_test(self, img, img_metas, **kwargs):
+ raise NotImplementedError('This method is not implemented.')
+
+ def extract_feat(self, imgs):
+ raise NotImplementedError('This method is not implemented.')
+
+ def forward_train(self, imgs, **kwargs):
+ raise NotImplementedError('This method is not implemented.')
+
+ def forward_test(self, imgs, img_metas, **kwargs):
+ input_data = imgs
+ # set io binding for inputs/outputs
+ device_type = 'cuda' if self.is_cuda_available else 'cpu'
+ if not self.is_cuda_available:
+ input_data = input_data.cpu()
+ self.io_binding.bind_input(
+ name='input',
+ device_type=device_type,
+ device_id=self.device_id,
+ element_type=np.float32,
+ shape=input_data.shape,
+ buffer_ptr=input_data.data_ptr())
+
+ for name in self.output_names:
+ self.io_binding.bind_output(name)
+ # run session to get outputs
+ self.sess.run_with_iobinding(self.io_binding)
+ results = self.io_binding.copy_outputs_to_cpu()[0]
+ return list(results)
+
+
+class TensorRTClassifier(BaseClassifier):
+
+ def __init__(self, trt_file, class_names, device_id):
+ super(TensorRTClassifier, self).__init__()
+ from mmcv.tensorrt import TRTWraper, load_tensorrt_plugin
+ try:
+ load_tensorrt_plugin()
+ except (ImportError, ModuleNotFoundError):
+ warnings.warn('If input model has custom op from mmcv, \
+ you may have to build mmcv with TensorRT from source.')
+ model = TRTWraper(
+ trt_file, input_names=['input'], output_names=['probs'])
+
+ self.model = model
+ self.device_id = device_id
+ self.CLASSES = class_names
+
+ def simple_test(self, img, img_metas, **kwargs):
+ raise NotImplementedError('This method is not implemented.')
+
+ def extract_feat(self, imgs):
+ raise NotImplementedError('This method is not implemented.')
+
+ def forward_train(self, imgs, **kwargs):
+ raise NotImplementedError('This method is not implemented.')
+
+ def forward_test(self, imgs, img_metas, **kwargs):
+ input_data = imgs
+ with torch.cuda.device(self.device_id), torch.no_grad():
+ results = self.model({'input': input_data})['probs']
+ results = results.detach().cpu().numpy()
+
+ return list(results)
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/core/hook/__init__.py b/openmmlab_test/mmclassification-0.24.1/mmcls/core/hook/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..4212dcf9ccb0956399afddc2de42a9036a117061
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/core/hook/__init__.py
@@ -0,0 +1,10 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from .class_num_check_hook import ClassNumCheckHook
+from .lr_updater import CosineAnnealingCooldownLrUpdaterHook
+from .precise_bn_hook import PreciseBNHook
+from .wandblogger_hook import MMClsWandbHook
+
+__all__ = [
+ 'ClassNumCheckHook', 'PreciseBNHook',
+ 'CosineAnnealingCooldownLrUpdaterHook', 'MMClsWandbHook'
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/core/hook/class_num_check_hook.py b/openmmlab_test/mmclassification-0.24.1/mmcls/core/hook/class_num_check_hook.py
new file mode 100644
index 0000000000000000000000000000000000000000..52c2c9a59d09ca4c3810b382e142c178c8656d83
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/core/hook/class_num_check_hook.py
@@ -0,0 +1,73 @@
+# Copyright (c) OpenMMLab. All rights reserved
+from mmcv.runner import IterBasedRunner
+from mmcv.runner.hooks import HOOKS, Hook
+from mmcv.utils import is_seq_of
+
+
+@HOOKS.register_module()
+class ClassNumCheckHook(Hook):
+
+ def _check_head(self, runner, dataset):
+ """Check whether the `num_classes` in head matches the length of
+ `CLASSES` in `dataset`.
+
+ Args:
+ runner (obj:`EpochBasedRunner`, `IterBasedRunner`): runner object.
+ dataset (obj: `BaseDataset`): the dataset to check.
+ """
+ model = runner.model
+ if dataset.CLASSES is None:
+ runner.logger.warning(
+ f'Please set `CLASSES` '
+ f'in the {dataset.__class__.__name__} and'
+ f'check if it is consistent with the `num_classes` '
+ f'of head')
+ else:
+ assert is_seq_of(dataset.CLASSES, str), \
+ (f'`CLASSES` in {dataset.__class__.__name__}'
+ f'should be a tuple of str.')
+ for name, module in model.named_modules():
+ if hasattr(module, 'num_classes'):
+ assert module.num_classes == len(dataset.CLASSES), \
+ (f'The `num_classes` ({module.num_classes}) in '
+ f'{module.__class__.__name__} of '
+ f'{model.__class__.__name__} does not matches '
+ f'the length of `CLASSES` '
+ f'{len(dataset.CLASSES)}) in '
+ f'{dataset.__class__.__name__}')
+
+ def before_train_iter(self, runner):
+ """Check whether the training dataset is compatible with head.
+
+ Args:
+ runner (obj: `IterBasedRunner`): Iter based Runner.
+ """
+ if not isinstance(runner, IterBasedRunner):
+ return
+ self._check_head(runner, runner.data_loader._dataloader.dataset)
+
+ def before_val_iter(self, runner):
+ """Check whether the eval dataset is compatible with head.
+
+ Args:
+ runner (obj:`IterBasedRunner`): Iter based Runner.
+ """
+ if not isinstance(runner, IterBasedRunner):
+ return
+ self._check_head(runner, runner.data_loader._dataloader.dataset)
+
+ def before_train_epoch(self, runner):
+ """Check whether the training dataset is compatible with head.
+
+ Args:
+ runner (obj:`EpochBasedRunner`): Epoch based Runner.
+ """
+ self._check_head(runner, runner.data_loader.dataset)
+
+ def before_val_epoch(self, runner):
+ """Check whether the eval dataset is compatible with head.
+
+ Args:
+ runner (obj:`EpochBasedRunner`): Epoch based Runner.
+ """
+ self._check_head(runner, runner.data_loader.dataset)
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/core/hook/lr_updater.py b/openmmlab_test/mmclassification-0.24.1/mmcls/core/hook/lr_updater.py
new file mode 100644
index 0000000000000000000000000000000000000000..021f66b57385280a83dac30b16f4f39ebd82142a
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/core/hook/lr_updater.py
@@ -0,0 +1,83 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from math import cos, pi
+
+from mmcv.runner.hooks import HOOKS, LrUpdaterHook
+
+
+@HOOKS.register_module()
+class CosineAnnealingCooldownLrUpdaterHook(LrUpdaterHook):
+ """Cosine annealing learning rate scheduler with cooldown.
+
+ Args:
+ min_lr (float, optional): The minimum learning rate after annealing.
+ Defaults to None.
+ min_lr_ratio (float, optional): The minimum learning ratio after
+ nnealing. Defaults to None.
+ cool_down_ratio (float): The cooldown ratio. Defaults to 0.1.
+ cool_down_time (int): The cooldown time. Defaults to 10.
+ by_epoch (bool): If True, the learning rate changes epoch by epoch. If
+ False, the learning rate changes iter by iter. Defaults to True.
+ warmup (string, optional): Type of warmup used. It can be None (use no
+ warmup), 'constant', 'linear' or 'exp'. Defaults to None.
+ warmup_iters (int): The number of iterations or epochs that warmup
+ lasts. Defaults to 0.
+ warmup_ratio (float): LR used at the beginning of warmup equals to
+ ``warmup_ratio * initial_lr``. Defaults to 0.1.
+ warmup_by_epoch (bool): If True, the ``warmup_iters``
+ means the number of epochs that warmup lasts, otherwise means the
+ number of iteration that warmup lasts. Defaults to False.
+
+ Note:
+ You need to set one and only one of ``min_lr`` and ``min_lr_ratio``.
+ """
+
+ def __init__(self,
+ min_lr=None,
+ min_lr_ratio=None,
+ cool_down_ratio=0.1,
+ cool_down_time=10,
+ **kwargs):
+ assert (min_lr is None) ^ (min_lr_ratio is None)
+ self.min_lr = min_lr
+ self.min_lr_ratio = min_lr_ratio
+ self.cool_down_time = cool_down_time
+ self.cool_down_ratio = cool_down_ratio
+ super(CosineAnnealingCooldownLrUpdaterHook, self).__init__(**kwargs)
+
+ def get_lr(self, runner, base_lr):
+ if self.by_epoch:
+ progress = runner.epoch
+ max_progress = runner.max_epochs
+ else:
+ progress = runner.iter
+ max_progress = runner.max_iters
+
+ if self.min_lr_ratio is not None:
+ target_lr = base_lr * self.min_lr_ratio
+ else:
+ target_lr = self.min_lr
+
+ if progress > max_progress - self.cool_down_time:
+ return target_lr * self.cool_down_ratio
+ else:
+ max_progress = max_progress - self.cool_down_time
+
+ return annealing_cos(base_lr, target_lr, progress / max_progress)
+
+
+def annealing_cos(start, end, factor, weight=1):
+ """Calculate annealing cos learning rate.
+
+ Cosine anneal from `weight * start + (1 - weight) * end` to `end` as
+ percentage goes from 0.0 to 1.0.
+
+ Args:
+ start (float): The starting learning rate of the cosine annealing.
+ end (float): The ending learing rate of the cosine annealing.
+ factor (float): The coefficient of `pi` when calculating the current
+ percentage. Range from 0.0 to 1.0.
+ weight (float, optional): The combination factor of `start` and `end`
+ when calculating the actual starting learning rate. Default to 1.
+ """
+ cos_out = cos(pi * factor) + 1
+ return end + 0.5 * weight * (start - end) * cos_out
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/core/hook/precise_bn_hook.py b/openmmlab_test/mmclassification-0.24.1/mmcls/core/hook/precise_bn_hook.py
new file mode 100644
index 0000000000000000000000000000000000000000..e6d45980130025a4d4f298b0957586014ecc9f11
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/core/hook/precise_bn_hook.py
@@ -0,0 +1,180 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+# Adapted from https://github.com/facebookresearch/pycls/blob/f8cd962737e33ce9e19b3083a33551da95c2d9c0/pycls/core/net.py # noqa: E501
+# Original licence: Copyright (c) 2019 Facebook, Inc under the Apache License 2.0 # noqa: E501
+
+import itertools
+import logging
+from typing import List, Optional
+
+import mmcv
+import torch
+import torch.nn as nn
+from mmcv.runner import EpochBasedRunner, get_dist_info
+from mmcv.runner.hooks import HOOKS, Hook
+from mmcv.utils import print_log
+from torch.functional import Tensor
+from torch.nn import GroupNorm
+from torch.nn.modules.batchnorm import _BatchNorm
+from torch.nn.modules.instancenorm import _InstanceNorm
+from torch.utils.data import DataLoader
+
+
+def scaled_all_reduce(tensors: List[Tensor], num_gpus: int) -> List[Tensor]:
+ """Performs the scaled all_reduce operation on the provided tensors.
+
+ The input tensors are modified in-place. Currently supports only the sum
+ reduction operator. The reduced values are scaled by the inverse size of
+ the process group.
+
+ Args:
+ tensors (List[torch.Tensor]): The tensors to process.
+ num_gpus (int): The number of gpus to use
+ Returns:
+ List[torch.Tensor]: The processed tensors.
+ """
+ # There is no need for reduction in the single-proc case
+ if num_gpus == 1:
+ return tensors
+ # Queue the reductions
+ reductions = []
+ for tensor in tensors:
+ reduction = torch.distributed.all_reduce(tensor, async_op=True)
+ reductions.append(reduction)
+ # Wait for reductions to finish
+ for reduction in reductions:
+ reduction.wait()
+ # Scale the results
+ for tensor in tensors:
+ tensor.mul_(1.0 / num_gpus)
+ return tensors
+
+
+@torch.no_grad()
+def update_bn_stats(model: nn.Module,
+ loader: DataLoader,
+ num_samples: int = 8192,
+ logger: Optional[logging.Logger] = None) -> None:
+ """Computes precise BN stats on training data.
+
+ Args:
+ model (nn.module): The model whose bn stats will be recomputed.
+ loader (DataLoader): PyTorch dataloader._dataloader
+ num_samples (int): The number of samples to update the bn stats.
+ Defaults to 8192.
+ logger (:obj:`logging.Logger` | None): Logger for logging.
+ Default: None.
+ """
+ # get dist info
+ rank, world_size = get_dist_info()
+ # Compute the number of mini-batches to use, if the size of dataloader is
+ # less than num_iters, use all the samples in dataloader.
+ num_iter = num_samples // (loader.batch_size * world_size)
+ num_iter = min(num_iter, len(loader))
+ # Retrieve the BN layers
+ bn_layers = [
+ m for m in model.modules()
+ if m.training and isinstance(m, (_BatchNorm))
+ ]
+
+ if len(bn_layers) == 0:
+ print_log('No BN found in model', logger=logger, level=logging.WARNING)
+ return
+ print_log(
+ f'{len(bn_layers)} BN found, run {num_iter} iters...', logger=logger)
+
+ # Finds all the other norm layers with training=True.
+ other_norm_layers = [
+ m for m in model.modules()
+ if m.training and isinstance(m, (_InstanceNorm, GroupNorm))
+ ]
+ if len(other_norm_layers) > 0:
+ print_log(
+ 'IN/GN stats will not be updated in PreciseHook.',
+ logger=logger,
+ level=logging.INFO)
+
+ # Initialize BN stats storage for computing
+ # mean(mean(batch)) and mean(var(batch))
+ running_means = [torch.zeros_like(bn.running_mean) for bn in bn_layers]
+ running_vars = [torch.zeros_like(bn.running_var) for bn in bn_layers]
+ # Remember momentum values
+ momentums = [bn.momentum for bn in bn_layers]
+ # Set momentum to 1.0 to compute BN stats that reflect the current batch
+ for bn in bn_layers:
+ bn.momentum = 1.0
+ # Average the BN stats for each BN layer over the batches
+ if rank == 0:
+ prog_bar = mmcv.ProgressBar(num_iter)
+
+ for data in itertools.islice(loader, num_iter):
+ model.train_step(data)
+ for i, bn in enumerate(bn_layers):
+ running_means[i] += bn.running_mean / num_iter
+ running_vars[i] += bn.running_var / num_iter
+ if rank == 0:
+ prog_bar.update()
+
+ # Sync BN stats across GPUs (no reduction if 1 GPU used)
+ running_means = scaled_all_reduce(running_means, world_size)
+ running_vars = scaled_all_reduce(running_vars, world_size)
+ # Set BN stats and restore original momentum values
+ for i, bn in enumerate(bn_layers):
+ bn.running_mean = running_means[i]
+ bn.running_var = running_vars[i]
+ bn.momentum = momentums[i]
+
+
+@HOOKS.register_module()
+class PreciseBNHook(Hook):
+ """Precise BN hook.
+
+ Recompute and update the batch norm stats to make them more precise. During
+ training both BN stats and the weight are changing after every iteration,
+ so the running average can not precisely reflect the actual stats of the
+ current model.
+
+ With this hook, the BN stats are recomputed with fixed weights, to make the
+ running average more precise. Specifically, it computes the true average of
+ per-batch mean/variance instead of the running average. See Sec. 3 of the
+ paper `Rethinking Batch in BatchNorm `
+ for details.
+
+ This hook will update BN stats, so it should be executed before
+ ``CheckpointHook`` and ``EMAHook``, generally set its priority to
+ "ABOVE_NORMAL".
+
+ Args:
+ num_samples (int): The number of samples to update the bn stats.
+ Defaults to 8192.
+ interval (int): Perform precise bn interval. Defaults to 1.
+ """
+
+ def __init__(self, num_samples: int = 8192, interval: int = 1) -> None:
+ assert interval > 0 and num_samples > 0
+
+ self.interval = interval
+ self.num_samples = num_samples
+
+ def _perform_precise_bn(self, runner: EpochBasedRunner) -> None:
+ print_log(
+ f'Running Precise BN for {self.num_samples} items...',
+ logger=runner.logger)
+ update_bn_stats(
+ runner.model,
+ runner.data_loader,
+ self.num_samples,
+ logger=runner.logger)
+ print_log('Finish Precise BN, BN stats updated.', logger=runner.logger)
+
+ def after_train_epoch(self, runner: EpochBasedRunner) -> None:
+ """Calculate prcise BN and broadcast BN stats across GPUs.
+
+ Args:
+ runner (obj:`EpochBasedRunner`): runner object.
+ """
+ assert isinstance(runner, EpochBasedRunner), \
+ 'PreciseBN only supports `EpochBasedRunner` by now'
+
+ # if by epoch, do perform precise every `self.interval` epochs;
+ if self.every_n_epochs(runner, self.interval):
+ self._perform_precise_bn(runner)
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/core/hook/wandblogger_hook.py b/openmmlab_test/mmclassification-0.24.1/mmcls/core/hook/wandblogger_hook.py
new file mode 100644
index 0000000000000000000000000000000000000000..61ccfe90d6e6ac18b43cec9ad0e8a06b2cb8db5e
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/core/hook/wandblogger_hook.py
@@ -0,0 +1,340 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import os.path as osp
+
+import numpy as np
+from mmcv.runner import HOOKS, BaseRunner
+from mmcv.runner.dist_utils import master_only
+from mmcv.runner.hooks.checkpoint import CheckpointHook
+from mmcv.runner.hooks.evaluation import DistEvalHook, EvalHook
+from mmcv.runner.hooks.logger.wandb import WandbLoggerHook
+
+
+@HOOKS.register_module()
+class MMClsWandbHook(WandbLoggerHook):
+ """Enhanced Wandb logger hook for classification.
+
+ Comparing with the :cls:`mmcv.runner.WandbLoggerHook`, this hook can not
+ only automatically log all information in ``log_buffer`` but also log
+ the following extra information.
+
+ - **Checkpoints**: If ``log_checkpoint`` is True, the checkpoint saved at
+ every checkpoint interval will be saved as W&B Artifacts. This depends on
+ the : class:`mmcv.runner.CheckpointHook` whose priority is higher than
+ this hook. Please refer to
+ https://docs.wandb.ai/guides/artifacts/model-versioning to learn more
+ about model versioning with W&B Artifacts.
+
+ - **Checkpoint Metadata**: If ``log_checkpoint_metadata`` is True, every
+ checkpoint artifact will have a metadata associated with it. The metadata
+ contains the evaluation metrics computed on validation data with that
+ checkpoint along with the current epoch/iter. It depends on
+ :class:`EvalHook` whose priority is higher than this hook.
+
+ - **Evaluation**: At every interval, this hook logs the model prediction as
+ interactive W&B Tables. The number of samples logged is given by
+ ``num_eval_images``. Currently, this hook logs the predicted labels along
+ with the ground truth at every evaluation interval. This depends on the
+ :class:`EvalHook` whose priority is higher than this hook. Also note that
+ the data is just logged once and subsequent evaluation tables uses
+ reference to the logged data to save memory usage. Please refer to
+ https://docs.wandb.ai/guides/data-vis to learn more about W&B Tables.
+
+ Here is a config example:
+
+ .. code:: python
+
+ checkpoint_config = dict(interval=10)
+
+ # To log checkpoint metadata, the interval of checkpoint saving should
+ # be divisible by the interval of evaluation.
+ evaluation = dict(interval=5)
+
+ log_config = dict(
+ ...
+ hooks=[
+ ...
+ dict(type='MMClsWandbHook',
+ init_kwargs={
+ 'entity': "YOUR_ENTITY",
+ 'project': "YOUR_PROJECT_NAME"
+ },
+ log_checkpoint=True,
+ log_checkpoint_metadata=True,
+ num_eval_images=100)
+ ])
+
+ Args:
+ init_kwargs (dict): A dict passed to wandb.init to initialize
+ a W&B run. Please refer to https://docs.wandb.ai/ref/python/init
+ for possible key-value pairs.
+ interval (int): Logging interval (every k iterations). Defaults to 10.
+ log_checkpoint (bool): Save the checkpoint at every checkpoint interval
+ as W&B Artifacts. Use this for model versioning where each version
+ is a checkpoint. Defaults to False.
+ log_checkpoint_metadata (bool): Log the evaluation metrics computed
+ on the validation data with the checkpoint, along with current
+ epoch as a metadata to that checkpoint.
+ Defaults to True.
+ num_eval_images (int): The number of validation images to be logged.
+ If zero, the evaluation won't be logged. Defaults to 100.
+ """
+
+ def __init__(self,
+ init_kwargs=None,
+ interval=10,
+ log_checkpoint=False,
+ log_checkpoint_metadata=False,
+ num_eval_images=100,
+ **kwargs):
+ super(MMClsWandbHook, self).__init__(init_kwargs, interval, **kwargs)
+
+ self.log_checkpoint = log_checkpoint
+ self.log_checkpoint_metadata = (
+ log_checkpoint and log_checkpoint_metadata)
+ self.num_eval_images = num_eval_images
+ self.log_evaluation = (num_eval_images > 0)
+ self.ckpt_hook: CheckpointHook = None
+ self.eval_hook: EvalHook = None
+
+ @master_only
+ def before_run(self, runner: BaseRunner):
+ super(MMClsWandbHook, self).before_run(runner)
+
+ # Inspect CheckpointHook and EvalHook
+ for hook in runner.hooks:
+ if isinstance(hook, CheckpointHook):
+ self.ckpt_hook = hook
+ if isinstance(hook, (EvalHook, DistEvalHook)):
+ self.eval_hook = hook
+
+ # Check conditions to log checkpoint
+ if self.log_checkpoint:
+ if self.ckpt_hook is None:
+ self.log_checkpoint = False
+ self.log_checkpoint_metadata = False
+ runner.logger.warning(
+ 'To log checkpoint in MMClsWandbHook, `CheckpointHook` is'
+ 'required, please check hooks in the runner.')
+ else:
+ self.ckpt_interval = self.ckpt_hook.interval
+
+ # Check conditions to log evaluation
+ if self.log_evaluation or self.log_checkpoint_metadata:
+ if self.eval_hook is None:
+ self.log_evaluation = False
+ self.log_checkpoint_metadata = False
+ runner.logger.warning(
+ 'To log evaluation or checkpoint metadata in '
+ 'MMClsWandbHook, `EvalHook` or `DistEvalHook` in mmcls '
+ 'is required, please check whether the validation '
+ 'is enabled.')
+ else:
+ self.eval_interval = self.eval_hook.interval
+ self.val_dataset = self.eval_hook.dataloader.dataset
+ if (self.log_evaluation
+ and self.num_eval_images > len(self.val_dataset)):
+ self.num_eval_images = len(self.val_dataset)
+ runner.logger.warning(
+ f'The num_eval_images ({self.num_eval_images}) is '
+ 'greater than the total number of validation samples '
+ f'({len(self.val_dataset)}). The complete validation '
+ 'dataset will be logged.')
+
+ # Check conditions to log checkpoint metadata
+ if self.log_checkpoint_metadata:
+ assert self.ckpt_interval % self.eval_interval == 0, \
+ 'To log checkpoint metadata in MMClsWandbHook, the interval ' \
+ f'of checkpoint saving ({self.ckpt_interval}) should be ' \
+ 'divisible by the interval of evaluation ' \
+ f'({self.eval_interval}).'
+
+ # Initialize evaluation table
+ if self.log_evaluation:
+ # Initialize data table
+ self._init_data_table()
+ # Add ground truth to the data table
+ self._add_ground_truth()
+ # Log ground truth data
+ self._log_data_table()
+
+ @master_only
+ def after_train_epoch(self, runner):
+ super(MMClsWandbHook, self).after_train_epoch(runner)
+
+ if not self.by_epoch:
+ return
+
+ # Save checkpoint and metadata
+ if (self.log_checkpoint
+ and self.every_n_epochs(runner, self.ckpt_interval)
+ or (self.ckpt_hook.save_last and self.is_last_epoch(runner))):
+ if self.log_checkpoint_metadata and self.eval_hook:
+ metadata = {
+ 'epoch': runner.epoch + 1,
+ **self._get_eval_results()
+ }
+ else:
+ metadata = None
+ aliases = [f'epoch_{runner.epoch+1}', 'latest']
+ model_path = osp.join(self.ckpt_hook.out_dir,
+ f'epoch_{runner.epoch+1}.pth')
+ self._log_ckpt_as_artifact(model_path, aliases, metadata)
+
+ # Save prediction table
+ if self.log_evaluation and self.eval_hook._should_evaluate(runner):
+ results = self.eval_hook.latest_results
+ # Initialize evaluation table
+ self._init_pred_table()
+ # Add predictions to evaluation table
+ self._add_predictions(results, runner.epoch + 1)
+ # Log the evaluation table
+ self._log_eval_table(runner.epoch + 1)
+
+ @master_only
+ def after_train_iter(self, runner):
+ if self.get_mode(runner) == 'train':
+ # An ugly patch. The iter-based eval hook will call the
+ # `after_train_iter` method of all logger hooks before evaluation.
+ # Use this trick to skip that call.
+ # Don't call super method at first, it will clear the log_buffer
+ return super(MMClsWandbHook, self).after_train_iter(runner)
+ else:
+ super(MMClsWandbHook, self).after_train_iter(runner)
+
+ if self.by_epoch:
+ return
+
+ # Save checkpoint and metadata
+ if (self.log_checkpoint
+ and self.every_n_iters(runner, self.ckpt_interval)
+ or (self.ckpt_hook.save_last and self.is_last_iter(runner))):
+ if self.log_checkpoint_metadata and self.eval_hook:
+ metadata = {
+ 'iter': runner.iter + 1,
+ **self._get_eval_results()
+ }
+ else:
+ metadata = None
+ aliases = [f'iter_{runner.iter+1}', 'latest']
+ model_path = osp.join(self.ckpt_hook.out_dir,
+ f'iter_{runner.iter+1}.pth')
+ self._log_ckpt_as_artifact(model_path, aliases, metadata)
+
+ # Save prediction table
+ if self.log_evaluation and self.eval_hook._should_evaluate(runner):
+ results = self.eval_hook.latest_results
+ # Initialize evaluation table
+ self._init_pred_table()
+ # Log predictions
+ self._add_predictions(results, runner.iter + 1)
+ # Log the table
+ self._log_eval_table(runner.iter + 1)
+
+ @master_only
+ def after_run(self, runner):
+ self.wandb.finish()
+
+ def _log_ckpt_as_artifact(self, model_path, aliases, metadata=None):
+ """Log model checkpoint as W&B Artifact.
+
+ Args:
+ model_path (str): Path of the checkpoint to log.
+ aliases (list): List of the aliases associated with this artifact.
+ metadata (dict, optional): Metadata associated with this artifact.
+ """
+ model_artifact = self.wandb.Artifact(
+ f'run_{self.wandb.run.id}_model', type='model', metadata=metadata)
+ model_artifact.add_file(model_path)
+ self.wandb.log_artifact(model_artifact, aliases=aliases)
+
+ def _get_eval_results(self):
+ """Get model evaluation results."""
+ results = self.eval_hook.latest_results
+ eval_results = self.val_dataset.evaluate(
+ results, logger='silent', **self.eval_hook.eval_kwargs)
+ return eval_results
+
+ def _init_data_table(self):
+ """Initialize the W&B Tables for validation data."""
+ columns = ['image_name', 'image', 'ground_truth']
+ self.data_table = self.wandb.Table(columns=columns)
+
+ def _init_pred_table(self):
+ """Initialize the W&B Tables for model evaluation."""
+ columns = ['epoch'] if self.by_epoch else ['iter']
+ columns += ['image_name', 'image', 'ground_truth', 'prediction'
+ ] + list(self.val_dataset.CLASSES)
+ self.eval_table = self.wandb.Table(columns=columns)
+
+ def _add_ground_truth(self):
+ # Get image loading pipeline
+ from mmcls.datasets.pipelines import LoadImageFromFile
+ img_loader = None
+ for t in self.val_dataset.pipeline.transforms:
+ if isinstance(t, LoadImageFromFile):
+ img_loader = t
+
+ CLASSES = self.val_dataset.CLASSES
+ self.eval_image_indexs = np.arange(len(self.val_dataset))
+ # Set seed so that same validation set is logged each time.
+ np.random.seed(42)
+ np.random.shuffle(self.eval_image_indexs)
+ self.eval_image_indexs = self.eval_image_indexs[:self.num_eval_images]
+
+ for idx in self.eval_image_indexs:
+ img_info = self.val_dataset.data_infos[idx]
+ if img_loader is not None:
+ img_info = img_loader(img_info)
+ # Get image and convert from BGR to RGB
+ image = img_info['img'][..., ::-1]
+ else:
+ # For CIFAR dataset.
+ image = img_info['img']
+ image_name = img_info.get('filename', f'img_{idx}')
+ gt_label = img_info.get('gt_label').item()
+
+ self.data_table.add_data(image_name, self.wandb.Image(image),
+ CLASSES[gt_label])
+
+ def _add_predictions(self, results, idx):
+ table_idxs = self.data_table_ref.get_index()
+ assert len(table_idxs) == len(self.eval_image_indexs)
+
+ for ndx, eval_image_index in enumerate(self.eval_image_indexs):
+ result = results[eval_image_index]
+
+ self.eval_table.add_data(
+ idx, self.data_table_ref.data[ndx][0],
+ self.data_table_ref.data[ndx][1],
+ self.data_table_ref.data[ndx][2],
+ self.val_dataset.CLASSES[np.argmax(result)], *tuple(result))
+
+ def _log_data_table(self):
+ """Log the W&B Tables for validation data as artifact and calls
+ `use_artifact` on it so that the evaluation table can use the reference
+ of already uploaded images.
+
+ This allows the data to be uploaded just once.
+ """
+ data_artifact = self.wandb.Artifact('val', type='dataset')
+ data_artifact.add(self.data_table, 'val_data')
+
+ self.wandb.run.use_artifact(data_artifact)
+ data_artifact.wait()
+
+ self.data_table_ref = data_artifact.get('val_data')
+
+ def _log_eval_table(self, idx):
+ """Log the W&B Tables for model evaluation.
+
+ The table will be logged multiple times creating new version. Use this
+ to compare models at different intervals interactively.
+ """
+ pred_artifact = self.wandb.Artifact(
+ f'run_{self.wandb.run.id}_pred', type='evaluation')
+ pred_artifact.add(self.eval_table, 'eval_data')
+ if self.by_epoch:
+ aliases = ['latest', f'epoch_{idx}']
+ else:
+ aliases = ['latest', f'iter_{idx}']
+ self.wandb.run.log_artifact(pred_artifact, aliases=aliases)
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/core/optimizers/__init__.py b/openmmlab_test/mmclassification-0.24.1/mmcls/core/optimizers/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..aa9cc43e57268bf06abbf6deea1099663f62b9fe
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/core/optimizers/__init__.py
@@ -0,0 +1,6 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from .lamb import Lamb
+
+__all__ = [
+ 'Lamb',
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/core/optimizers/lamb.py b/openmmlab_test/mmclassification-0.24.1/mmcls/core/optimizers/lamb.py
new file mode 100644
index 0000000000000000000000000000000000000000..c65fbae27499e7bbc23f5afad04a8f89c347d7c8
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/core/optimizers/lamb.py
@@ -0,0 +1,227 @@
+"""PyTorch Lamb optimizer w/ behaviour similar to NVIDIA FusedLamb.
+
+This optimizer code was adapted from the following (starting with latest)
+* https://github.com/HabanaAI/Model-References/blob/
+2b435114fe8e31f159b1d3063b8280ae37af7423/PyTorch/nlp/bert/pretraining/lamb.py
+* https://github.com/NVIDIA/DeepLearningExamples/blob/master/PyTorch/
+LanguageModeling/Transformer-XL/pytorch/lamb.py
+* https://github.com/cybertronai/pytorch-lamb
+
+Use FusedLamb if you can (GPU). The reason for including this variant of Lamb
+is to have a version that is
+similar in behaviour to APEX FusedLamb if you aren't using NVIDIA GPUs or
+cannot install/use APEX.
+
+In addition to some cleanup, this Lamb impl has been modified to support
+PyTorch XLA and has been tested on TPU.
+
+Original copyrights for above sources are below.
+
+Modifications Copyright 2021 Ross Wightman
+"""
+# Copyright (c) 2021, Habana Labs Ltd. All rights reserved.
+
+# Copyright (c) 2019-2020, NVIDIA CORPORATION. All rights reserved.
+#
+# 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.
+
+# MIT License
+#
+# Copyright (c) 2019 cybertronai
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+import math
+
+import torch
+from mmcv.runner import OPTIMIZERS
+from torch.optim import Optimizer
+
+
+@OPTIMIZERS.register_module()
+class Lamb(Optimizer):
+ """A pure pytorch variant of FuseLAMB (NvLamb variant) optimizer.
+
+ This class is copied from `timm`_. The LAMB was proposed in `Large Batch
+ Optimization for Deep Learning - Training BERT in 76 minutes`_.
+
+ .. _timm:
+ https://github.com/rwightman/pytorch-image-models/blob/master/timm/optim/lamb.py
+ .. _Large Batch Optimization for Deep Learning - Training BERT in 76 minutes:
+ https://arxiv.org/abs/1904.00962
+
+ Arguments:
+ params (iterable): iterable of parameters to optimize or dicts defining
+ parameter groups.
+ lr (float, optional): learning rate. (default: 1e-3)
+ betas (Tuple[float, float], optional): coefficients used for computing
+ running averages of gradient and its norm. (default: (0.9, 0.999))
+ eps (float, optional): term added to the denominator to improve
+ numerical stability. (default: 1e-8)
+ weight_decay (float, optional): weight decay (L2 penalty) (default: 0)
+ grad_averaging (bool, optional): whether apply (1-beta2) to grad when
+ calculating running averages of gradient. (default: True)
+ max_grad_norm (float, optional): value used to clip global grad norm
+ (default: 1.0)
+ trust_clip (bool): enable LAMBC trust ratio clipping (default: False)
+ always_adapt (boolean, optional): Apply adaptive learning rate to 0.0
+ weight decay parameter (default: False)
+ """ # noqa: E501
+
+ def __init__(self,
+ params,
+ lr=1e-3,
+ bias_correction=True,
+ betas=(0.9, 0.999),
+ eps=1e-6,
+ weight_decay=0.01,
+ grad_averaging=True,
+ max_grad_norm=1.0,
+ trust_clip=False,
+ always_adapt=False):
+ defaults = dict(
+ lr=lr,
+ bias_correction=bias_correction,
+ betas=betas,
+ eps=eps,
+ weight_decay=weight_decay,
+ grad_averaging=grad_averaging,
+ max_grad_norm=max_grad_norm,
+ trust_clip=trust_clip,
+ always_adapt=always_adapt)
+ super().__init__(params, defaults)
+
+ @torch.no_grad()
+ def step(self, closure=None):
+ """Performs a single optimization step.
+
+ Arguments:
+ closure (callable, optional): A closure that reevaluates the model
+ and returns the loss.
+ """
+ loss = None
+ if closure is not None:
+ with torch.enable_grad():
+ loss = closure()
+
+ device = self.param_groups[0]['params'][0].device
+ one_tensor = torch.tensor(
+ 1.0, device=device
+ ) # because torch.where doesn't handle scalars correctly
+ global_grad_norm = torch.zeros(1, device=device)
+ for group in self.param_groups:
+ for p in group['params']:
+ if p.grad is None:
+ continue
+ grad = p.grad
+ if grad.is_sparse:
+ raise RuntimeError(
+ 'Lamb does not support sparse gradients, consider '
+ 'SparseAdam instead.')
+ global_grad_norm.add_(grad.pow(2).sum())
+
+ global_grad_norm = torch.sqrt(global_grad_norm)
+ # FIXME it'd be nice to remove explicit tensor conversion of scalars
+ # when torch.where promotes
+ # scalar types properly https://github.com/pytorch/pytorch/issues/9190
+ max_grad_norm = torch.tensor(
+ self.defaults['max_grad_norm'], device=device)
+ clip_global_grad_norm = torch.where(global_grad_norm > max_grad_norm,
+ global_grad_norm / max_grad_norm,
+ one_tensor)
+
+ for group in self.param_groups:
+ bias_correction = 1 if group['bias_correction'] else 0
+ beta1, beta2 = group['betas']
+ grad_averaging = 1 if group['grad_averaging'] else 0
+ beta3 = 1 - beta1 if grad_averaging else 1.0
+
+ # assume same step across group now to simplify things
+ # per parameter step can be easily support by making it tensor, or
+ # pass list into kernel
+ if 'step' in group:
+ group['step'] += 1
+ else:
+ group['step'] = 1
+
+ if bias_correction:
+ bias_correction1 = 1 - beta1**group['step']
+ bias_correction2 = 1 - beta2**group['step']
+ else:
+ bias_correction1, bias_correction2 = 1.0, 1.0
+
+ for p in group['params']:
+ if p.grad is None:
+ continue
+ grad = p.grad.div_(clip_global_grad_norm)
+ state = self.state[p]
+
+ # State initialization
+ if len(state) == 0:
+ # Exponential moving average of gradient valuesa
+ state['exp_avg'] = torch.zeros_like(p)
+ # Exponential moving average of squared gradient values
+ state['exp_avg_sq'] = torch.zeros_like(p)
+
+ exp_avg, exp_avg_sq = state['exp_avg'], state['exp_avg_sq']
+
+ # Decay the first and second moment running average coefficient
+ exp_avg.mul_(beta1).add_(grad, alpha=beta3) # m_t
+ exp_avg_sq.mul_(beta2).addcmul_(
+ grad, grad, value=1 - beta2) # v_t
+
+ denom = (exp_avg_sq.sqrt() / math.sqrt(bias_correction2)).add_(
+ group['eps'])
+ update = (exp_avg / bias_correction1).div_(denom)
+
+ weight_decay = group['weight_decay']
+ if weight_decay != 0:
+ update.add_(p, alpha=weight_decay)
+
+ if weight_decay != 0 or group['always_adapt']:
+ # Layer-wise LR adaptation. By default, skip adaptation on
+ # parameters that are
+ # excluded from weight decay, unless always_adapt == True,
+ # then always enabled.
+ w_norm = p.norm(2.0)
+ g_norm = update.norm(2.0)
+ # FIXME nested where required since logical and/or not
+ # working in PT XLA
+ trust_ratio = torch.where(
+ w_norm > 0,
+ torch.where(g_norm > 0, w_norm / g_norm, one_tensor),
+ one_tensor,
+ )
+ if group['trust_clip']:
+ # LAMBC trust clipping, upper bound fixed at one
+ trust_ratio = torch.minimum(trust_ratio, one_tensor)
+ update.mul_(trust_ratio)
+
+ p.add_(update, alpha=-group['lr'])
+
+ return loss
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/core/utils/__init__.py b/openmmlab_test/mmclassification-0.24.1/mmcls/core/utils/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..7170f232d3f3cd56fac36712b24704f62caaf439
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/core/utils/__init__.py
@@ -0,0 +1,7 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from .dist_utils import DistOptimizerHook, allreduce_grads, sync_random_seed
+from .misc import multi_apply
+
+__all__ = [
+ 'allreduce_grads', 'DistOptimizerHook', 'multi_apply', 'sync_random_seed'
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/core/utils/dist_utils.py b/openmmlab_test/mmclassification-0.24.1/mmcls/core/utils/dist_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..8912cea435aa99107b6fbca0ee1c4a4ae1b4885d
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/core/utils/dist_utils.py
@@ -0,0 +1,98 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from collections import OrderedDict
+
+import numpy as np
+import torch
+import torch.distributed as dist
+from mmcv.runner import OptimizerHook, get_dist_info
+from torch._utils import (_flatten_dense_tensors, _take_tensors,
+ _unflatten_dense_tensors)
+
+
+def _allreduce_coalesced(tensors, world_size, bucket_size_mb=-1):
+ if bucket_size_mb > 0:
+ bucket_size_bytes = bucket_size_mb * 1024 * 1024
+ buckets = _take_tensors(tensors, bucket_size_bytes)
+ else:
+ buckets = OrderedDict()
+ for tensor in tensors:
+ tp = tensor.type()
+ if tp not in buckets:
+ buckets[tp] = []
+ buckets[tp].append(tensor)
+ buckets = buckets.values()
+
+ for bucket in buckets:
+ flat_tensors = _flatten_dense_tensors(bucket)
+ dist.all_reduce(flat_tensors)
+ flat_tensors.div_(world_size)
+ for tensor, synced in zip(
+ bucket, _unflatten_dense_tensors(flat_tensors, bucket)):
+ tensor.copy_(synced)
+
+
+def allreduce_grads(params, coalesce=True, bucket_size_mb=-1):
+ grads = [
+ param.grad.data for param in params
+ if param.requires_grad and param.grad is not None
+ ]
+ world_size = dist.get_world_size()
+ if coalesce:
+ _allreduce_coalesced(grads, world_size, bucket_size_mb)
+ else:
+ for tensor in grads:
+ dist.all_reduce(tensor.div_(world_size))
+
+
+class DistOptimizerHook(OptimizerHook):
+
+ def __init__(self, grad_clip=None, coalesce=True, bucket_size_mb=-1):
+ self.grad_clip = grad_clip
+ self.coalesce = coalesce
+ self.bucket_size_mb = bucket_size_mb
+
+ def after_train_iter(self, runner):
+ runner.optimizer.zero_grad()
+ runner.outputs['loss'].backward()
+ if self.grad_clip is not None:
+ self.clip_grads(runner.model.parameters())
+ runner.optimizer.step()
+
+
+def sync_random_seed(seed=None, device='cuda'):
+ """Make sure different ranks share the same seed.
+
+ All workers must call this function, otherwise it will deadlock.
+ This method is generally used in `DistributedSampler`,
+ because the seed should be identical across all processes
+ in the distributed group.
+
+ In distributed sampling, different ranks should sample non-overlapped
+ data in the dataset. Therefore, this function is used to make sure that
+ each rank shuffles the data indices in the same order based
+ on the same seed. Then different ranks could use different indices
+ to select non-overlapped data from the same data list.
+
+ Args:
+ seed (int, Optional): The seed. Default to None.
+ device (str): The device where the seed will be put on.
+ Default to 'cuda'.
+
+ Returns:
+ int: Seed to be used.
+ """
+ if seed is None:
+ seed = np.random.randint(2**31)
+ assert isinstance(seed, int)
+
+ rank, world_size = get_dist_info()
+
+ if world_size == 1:
+ return seed
+
+ if rank == 0:
+ random_num = torch.tensor(seed, dtype=torch.int32, device=device)
+ else:
+ random_num = torch.tensor(0, dtype=torch.int32, device=device)
+ dist.broadcast(random_num, src=0)
+ return random_num.item()
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/core/utils/misc.py b/openmmlab_test/mmclassification-0.24.1/mmcls/core/utils/misc.py
similarity index 81%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/core/utils/misc.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/core/utils/misc.py
index be6a0b668835f77e9df707d3e1d21106fd6f0f06..31f846377da8fa2e43115dbef51b39cf47c7b8e5 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/core/utils/misc.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/core/utils/misc.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
from functools import partial
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/core/visualization/__init__.py b/openmmlab_test/mmclassification-0.24.1/mmcls/core/visualization/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..bdd0c1893b5a2199657c26fadc5c85bd96a8989a
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/core/visualization/__init__.py
@@ -0,0 +1,8 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from .image import (BaseFigureContextManager, ImshowInfosContextManager,
+ color_val_matplotlib, imshow_infos)
+
+__all__ = [
+ 'BaseFigureContextManager', 'ImshowInfosContextManager', 'imshow_infos',
+ 'color_val_matplotlib'
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/core/visualization/image.py b/openmmlab_test/mmclassification-0.24.1/mmcls/core/visualization/image.py
new file mode 100644
index 0000000000000000000000000000000000000000..d0169748d1d486800fa68a52543efd3809421dad
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/core/visualization/image.py
@@ -0,0 +1,343 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import matplotlib.pyplot as plt
+import mmcv
+import numpy as np
+from matplotlib.backend_bases import CloseEvent
+
+# A small value
+EPS = 1e-2
+
+
+def color_val_matplotlib(color):
+ """Convert various input in BGR order to normalized RGB matplotlib color
+ tuples,
+
+ Args:
+ color (:obj:`mmcv.Color`/str/tuple/int/ndarray): Color inputs
+
+ Returns:
+ tuple[float]: A tuple of 3 normalized floats indicating RGB channels.
+ """
+ color = mmcv.color_val(color)
+ color = [color / 255 for color in color[::-1]]
+ return tuple(color)
+
+
+class BaseFigureContextManager:
+ """Context Manager to reuse matplotlib figure.
+
+ It provides a figure for saving and a figure for showing to support
+ different settings.
+
+ Args:
+ axis (bool): Whether to show the axis lines.
+ fig_save_cfg (dict): Keyword parameters of figure for saving.
+ Defaults to empty dict.
+ fig_show_cfg (dict): Keyword parameters of figure for showing.
+ Defaults to empty dict.
+ """
+
+ def __init__(self, axis=False, fig_save_cfg={}, fig_show_cfg={}) -> None:
+ self.is_inline = 'inline' in plt.get_backend()
+
+ # Because save and show need different figure size
+ # We set two figure and axes to handle save and show
+ self.fig_save: plt.Figure = None
+ self.fig_save_cfg = fig_save_cfg
+ self.ax_save: plt.Axes = None
+
+ self.fig_show: plt.Figure = None
+ self.fig_show_cfg = fig_show_cfg
+ self.ax_show: plt.Axes = None
+
+ self.axis = axis
+
+ def __enter__(self):
+ if not self.is_inline:
+ # If use inline backend, we cannot control which figure to show,
+ # so disable the interactive fig_show, and put the initialization
+ # of fig_save to `prepare` function.
+ self._initialize_fig_save()
+ self._initialize_fig_show()
+ return self
+
+ def _initialize_fig_save(self):
+ fig = plt.figure(**self.fig_save_cfg)
+ ax = fig.add_subplot()
+
+ # remove white edges by set subplot margin
+ fig.subplots_adjust(left=0, right=1, bottom=0, top=1)
+
+ self.fig_save, self.ax_save = fig, ax
+
+ def _initialize_fig_show(self):
+ # fig_save will be resized to image size, only fig_show needs fig_size.
+ fig = plt.figure(**self.fig_show_cfg)
+ ax = fig.add_subplot()
+
+ # remove white edges by set subplot margin
+ fig.subplots_adjust(left=0, right=1, bottom=0, top=1)
+
+ self.fig_show, self.ax_show = fig, ax
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ if self.is_inline:
+ # If use inline backend, whether to close figure depends on if
+ # users want to show the image.
+ return
+
+ plt.close(self.fig_save)
+ plt.close(self.fig_show)
+
+ def prepare(self):
+ if self.is_inline:
+ # if use inline backend, just rebuild the fig_save.
+ self._initialize_fig_save()
+ self.ax_save.cla()
+ self.ax_save.axis(self.axis)
+ return
+
+ # If users force to destroy the window, rebuild fig_show.
+ if not plt.fignum_exists(self.fig_show.number):
+ self._initialize_fig_show()
+
+ # Clear all axes
+ self.ax_save.cla()
+ self.ax_save.axis(self.axis)
+ self.ax_show.cla()
+ self.ax_show.axis(self.axis)
+
+ def wait_continue(self, timeout=0, continue_key=' ') -> int:
+ """Show the image and wait for the user's input.
+
+ This implementation refers to
+ https://github.com/matplotlib/matplotlib/blob/v3.5.x/lib/matplotlib/_blocking_input.py
+
+ Args:
+ timeout (int): If positive, continue after ``timeout`` seconds.
+ Defaults to 0.
+ continue_key (str): The key for users to continue. Defaults to
+ the space key.
+
+ Returns:
+ int: If zero, means time out or the user pressed ``continue_key``,
+ and if one, means the user closed the show figure.
+ """ # noqa: E501
+ if self.is_inline:
+ # If use inline backend, interactive input and timeout is no use.
+ return
+
+ if self.fig_show.canvas.manager:
+ # Ensure that the figure is shown
+ self.fig_show.show()
+
+ while True:
+
+ # Connect the events to the handler function call.
+ event = None
+
+ def handler(ev):
+ # Set external event variable
+ nonlocal event
+ # Qt backend may fire two events at the same time,
+ # use a condition to avoid missing close event.
+ event = ev if not isinstance(event, CloseEvent) else event
+ self.fig_show.canvas.stop_event_loop()
+
+ cids = [
+ self.fig_show.canvas.mpl_connect(name, handler)
+ for name in ('key_press_event', 'close_event')
+ ]
+
+ try:
+ self.fig_show.canvas.start_event_loop(timeout)
+ finally: # Run even on exception like ctrl-c.
+ # Disconnect the callbacks.
+ for cid in cids:
+ self.fig_show.canvas.mpl_disconnect(cid)
+
+ if isinstance(event, CloseEvent):
+ return 1 # Quit for close.
+ elif event is None or event.key == continue_key:
+ return 0 # Quit for continue.
+
+
+class ImshowInfosContextManager(BaseFigureContextManager):
+ """Context Manager to reuse matplotlib figure and put infos on images.
+
+ Args:
+ fig_size (tuple[int]): Size of the figure to show image.
+
+ Examples:
+ >>> import mmcv
+ >>> from mmcls.core import visualization as vis
+ >>> img1 = mmcv.imread("./1.png")
+ >>> info1 = {'class': 'cat', 'label': 0}
+ >>> img2 = mmcv.imread("./2.png")
+ >>> info2 = {'class': 'dog', 'label': 1}
+ >>> with vis.ImshowInfosContextManager() as manager:
+ ... # Show img1
+ ... manager.put_img_infos(img1, info1)
+ ... # Show img2 on the same figure and save output image.
+ ... manager.put_img_infos(
+ ... img2, info2, out_file='./2_out.png')
+ """
+
+ def __init__(self, fig_size=(15, 10)):
+ super().__init__(
+ axis=False,
+ # A proper dpi for image save with default font size.
+ fig_save_cfg=dict(frameon=False, dpi=36),
+ fig_show_cfg=dict(frameon=False, figsize=fig_size))
+
+ def _put_text(self, ax, text, x, y, text_color, font_size):
+ ax.text(
+ x,
+ y,
+ f'{text}',
+ bbox={
+ 'facecolor': 'black',
+ 'alpha': 0.7,
+ 'pad': 0.2,
+ 'edgecolor': 'none',
+ 'boxstyle': 'round'
+ },
+ color=text_color,
+ fontsize=font_size,
+ family='monospace',
+ verticalalignment='top',
+ horizontalalignment='left')
+
+ def put_img_infos(self,
+ img,
+ infos,
+ text_color='white',
+ font_size=26,
+ row_width=20,
+ win_name='',
+ show=True,
+ wait_time=0,
+ out_file=None):
+ """Show image with extra information.
+
+ Args:
+ img (str | ndarray): The image to be displayed.
+ infos (dict): Extra infos to display in the image.
+ text_color (:obj:`mmcv.Color`/str/tuple/int/ndarray): Extra infos
+ display color. Defaults to 'white'.
+ font_size (int): Extra infos display font size. Defaults to 26.
+ row_width (int): width between each row of results on the image.
+ win_name (str): The image title. Defaults to ''
+ show (bool): Whether to show the image. Defaults to True.
+ wait_time (int): How many seconds to display the image.
+ Defaults to 0.
+ out_file (Optional[str]): The filename to write the image.
+ Defaults to None.
+
+ Returns:
+ np.ndarray: The image with extra infomations.
+ """
+ self.prepare()
+
+ text_color = color_val_matplotlib(text_color)
+ img = mmcv.imread(img).astype(np.uint8)
+
+ x, y = 3, row_width // 2
+ img = mmcv.bgr2rgb(img)
+ width, height = img.shape[1], img.shape[0]
+ img = np.ascontiguousarray(img)
+
+ # add a small EPS to avoid precision lost due to matplotlib's
+ # truncation (https://github.com/matplotlib/matplotlib/issues/15363)
+ dpi = self.fig_save.get_dpi()
+ self.fig_save.set_size_inches((width + EPS) / dpi,
+ (height + EPS) / dpi)
+
+ for k, v in infos.items():
+ if isinstance(v, float):
+ v = f'{v:.2f}'
+ label_text = f'{k}: {v}'
+ self._put_text(self.ax_save, label_text, x, y, text_color,
+ font_size)
+ if show and not self.is_inline:
+ self._put_text(self.ax_show, label_text, x, y, text_color,
+ font_size)
+ y += row_width
+
+ self.ax_save.imshow(img)
+ stream, _ = self.fig_save.canvas.print_to_buffer()
+ buffer = np.frombuffer(stream, dtype='uint8')
+ img_rgba = buffer.reshape(height, width, 4)
+ rgb, _ = np.split(img_rgba, [3], axis=2)
+ img_save = rgb.astype('uint8')
+ img_save = mmcv.rgb2bgr(img_save)
+
+ if out_file is not None:
+ mmcv.imwrite(img_save, out_file)
+
+ ret = 0
+ if show and not self.is_inline:
+ # Reserve some space for the tip.
+ self.ax_show.set_title(win_name)
+ self.ax_show.set_ylim(height + 20)
+ self.ax_show.text(
+ width // 2,
+ height + 18,
+ 'Press SPACE to continue.',
+ ha='center',
+ fontsize=font_size)
+ self.ax_show.imshow(img)
+
+ # Refresh canvas, necessary for Qt5 backend.
+ self.fig_show.canvas.draw()
+
+ ret = self.wait_continue(timeout=wait_time)
+ elif (not show) and self.is_inline:
+ # If use inline backend, we use fig_save to show the image
+ # So we need to close it if users don't want to show.
+ plt.close(self.fig_save)
+
+ return ret, img_save
+
+
+def imshow_infos(img,
+ infos,
+ text_color='white',
+ font_size=26,
+ row_width=20,
+ win_name='',
+ show=True,
+ fig_size=(15, 10),
+ wait_time=0,
+ out_file=None):
+ """Show image with extra information.
+
+ Args:
+ img (str | ndarray): The image to be displayed.
+ infos (dict): Extra infos to display in the image.
+ text_color (:obj:`mmcv.Color`/str/tuple/int/ndarray): Extra infos
+ display color. Defaults to 'white'.
+ font_size (int): Extra infos display font size. Defaults to 26.
+ row_width (int): width between each row of results on the image.
+ win_name (str): The image title. Defaults to ''
+ show (bool): Whether to show the image. Defaults to True.
+ fig_size (tuple): Image show figure size. Defaults to (15, 10).
+ wait_time (int): How many seconds to display the image. Defaults to 0.
+ out_file (Optional[str]): The filename to write the image.
+ Defaults to None.
+
+ Returns:
+ np.ndarray: The image with extra infomations.
+ """
+ with ImshowInfosContextManager(fig_size=fig_size) as manager:
+ _, img = manager.put_img_infos(
+ img,
+ infos,
+ text_color=text_color,
+ font_size=font_size,
+ row_width=row_width,
+ win_name=win_name,
+ show=show,
+ wait_time=wait_time,
+ out_file=out_file)
+ return img
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/__init__.py b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..095077e2321a827e61ff61a4f8d21e22da901e15
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/__init__.py
@@ -0,0 +1,25 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from .base_dataset import BaseDataset
+from .builder import (DATASETS, PIPELINES, SAMPLERS, build_dataloader,
+ build_dataset, build_sampler)
+from .cifar import CIFAR10, CIFAR100
+from .cub import CUB
+from .custom import CustomDataset
+from .dataset_wrappers import (ClassBalancedDataset, ConcatDataset,
+ KFoldDataset, RepeatDataset)
+from .imagenet import ImageNet
+from .imagenet21k import ImageNet21k
+from .mnist import MNIST, FashionMNIST
+from .multi_label import MultiLabelDataset
+from .samplers import DistributedSampler, RepeatAugSampler
+from .stanford_cars import StanfordCars
+from .voc import VOC
+
+__all__ = [
+ 'BaseDataset', 'ImageNet', 'CIFAR10', 'CIFAR100', 'MNIST', 'FashionMNIST',
+ 'VOC', 'MultiLabelDataset', 'build_dataloader', 'build_dataset',
+ 'DistributedSampler', 'ConcatDataset', 'RepeatDataset',
+ 'ClassBalancedDataset', 'DATASETS', 'PIPELINES', 'ImageNet21k', 'SAMPLERS',
+ 'build_sampler', 'RepeatAugSampler', 'KFoldDataset', 'CUB',
+ 'CustomDataset', 'StanfordCars'
+]
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/base_dataset.py b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/base_dataset.py
similarity index 81%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/base_dataset.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/datasets/base_dataset.py
index 5ccea9ffc48094e98e7430922f86f674e845a176..fb6578ab18198dca8288d7e061b69d20f41d6a0f 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/base_dataset.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/base_dataset.py
@@ -1,5 +1,9 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import copy
+import os.path as osp
from abc import ABCMeta, abstractmethod
+from os import PathLike
+from typing import List
import mmcv
import numpy as np
@@ -10,6 +14,13 @@ from mmcls.models.losses import accuracy
from .pipelines import Compose
+def expanduser(path):
+ if isinstance(path, (str, PathLike)):
+ return osp.expanduser(path)
+ else:
+ return path
+
+
class BaseDataset(Dataset, metaclass=ABCMeta):
"""Base dataset.
@@ -32,12 +43,11 @@ class BaseDataset(Dataset, metaclass=ABCMeta):
ann_file=None,
test_mode=False):
super(BaseDataset, self).__init__()
-
- self.ann_file = ann_file
- self.data_prefix = data_prefix
- self.test_mode = test_mode
+ self.data_prefix = expanduser(data_prefix)
self.pipeline = Compose(pipeline)
self.CLASSES = self.get_classes(classes)
+ self.ann_file = expanduser(ann_file)
+ self.test_mode = test_mode
self.data_infos = self.load_annotations()
@abstractmethod
@@ -58,23 +68,23 @@ class BaseDataset(Dataset, metaclass=ABCMeta):
"""Get all ground-truth labels (categories).
Returns:
- list[int]: categories for all images.
+ np.ndarray: categories for all images.
"""
gt_labels = np.array([data['gt_label'] for data in self.data_infos])
return gt_labels
- def get_cat_ids(self, idx):
+ def get_cat_ids(self, idx: int) -> List[int]:
"""Get category id by index.
Args:
idx (int): Index of data.
Returns:
- int: Image category of specified index.
+ cat_ids (List[int]): Image category of specified index.
"""
- return self.data_infos[idx]['gt_label'].astype(np.int)
+ return [int(self.data_infos[idx]['gt_label'])]
def prepare_data(self, idx):
results = copy.deepcopy(self.data_infos[idx])
@@ -89,6 +99,7 @@ class BaseDataset(Dataset, metaclass=ABCMeta):
@classmethod
def get_classes(cls, classes=None):
"""Get class names of current dataset.
+
Args:
classes (Sequence[str] | str | None): If classes is None, use
default CLASSES defined by builtin dataset. If classes is a
@@ -104,7 +115,7 @@ class BaseDataset(Dataset, metaclass=ABCMeta):
if isinstance(classes, str):
# take it as a file path
- class_names = mmcv.list_from_file(classes)
+ class_names = mmcv.list_from_file(expanduser(classes))
elif isinstance(classes, (tuple, list)):
class_names = classes
else:
@@ -116,6 +127,7 @@ class BaseDataset(Dataset, metaclass=ABCMeta):
results,
metric='accuracy',
metric_options=None,
+ indices=None,
logger=None):
"""Evaluate the dataset.
@@ -126,6 +138,8 @@ class BaseDataset(Dataset, metaclass=ABCMeta):
metric_options (dict, optional): Options for calculating metrics.
Allowed keys are 'topk', 'thrs' and 'average_mode'.
Defaults to None.
+ indices (list, optional): The indices of samples corresponding to
+ the results. Defaults to None.
logger (logging.Logger | str, optional): Logger used for printing
related information during evaluation. Defaults to None.
Returns:
@@ -143,20 +157,25 @@ class BaseDataset(Dataset, metaclass=ABCMeta):
eval_results = {}
results = np.vstack(results)
gt_labels = self.get_gt_labels()
+ if indices is not None:
+ gt_labels = gt_labels[indices]
num_imgs = len(results)
assert len(gt_labels) == num_imgs, 'dataset testing results should '\
'be of the same length as gt_labels.'
invalid_metrics = set(metrics) - set(allowed_metrics)
if len(invalid_metrics) != 0:
- raise ValueError(f'metirc {invalid_metrics} is not supported.')
+ raise ValueError(f'metric {invalid_metrics} is not supported.')
topk = metric_options.get('topk', (1, 5))
thrs = metric_options.get('thrs')
average_mode = metric_options.get('average_mode', 'macro')
if 'accuracy' in metrics:
- acc = accuracy(results, gt_labels, topk=topk, thrs=thrs)
+ if thrs is not None:
+ acc = accuracy(results, gt_labels, topk=topk, thrs=thrs)
+ else:
+ acc = accuracy(results, gt_labels, topk=topk)
if isinstance(topk, tuple):
eval_results_ = {
f'accuracy_top-{k}': a
@@ -182,8 +201,12 @@ class BaseDataset(Dataset, metaclass=ABCMeta):
precision_recall_f1_keys = ['precision', 'recall', 'f1_score']
if len(set(metrics) & set(precision_recall_f1_keys)) != 0:
- precision_recall_f1_values = precision_recall_f1(
- results, gt_labels, average_mode=average_mode, thrs=thrs)
+ if thrs is not None:
+ precision_recall_f1_values = precision_recall_f1(
+ results, gt_labels, average_mode=average_mode, thrs=thrs)
+ else:
+ precision_recall_f1_values = precision_recall_f1(
+ results, gt_labels, average_mode=average_mode)
for key, values in zip(precision_recall_f1_keys,
precision_recall_f1_values):
if key in metrics:
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/builder.py b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/builder.py
new file mode 100644
index 0000000000000000000000000000000000000000..1b626b4a12b02a1a80b51fd31e6e4c2857427504
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/builder.py
@@ -0,0 +1,183 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import copy
+import platform
+import random
+from functools import partial
+
+import numpy as np
+import torch
+from mmcv.parallel import collate
+from mmcv.runner import get_dist_info
+from mmcv.utils import Registry, build_from_cfg, digit_version
+from torch.utils.data import DataLoader
+
+try:
+ from mmcv.utils import IS_IPU_AVAILABLE
+except ImportError:
+ IS_IPU_AVAILABLE = False
+
+if platform.system() != 'Windows':
+ # https://github.com/pytorch/pytorch/issues/973
+ import resource
+ rlimit = resource.getrlimit(resource.RLIMIT_NOFILE)
+ hard_limit = rlimit[1]
+ soft_limit = min(4096, hard_limit)
+ resource.setrlimit(resource.RLIMIT_NOFILE, (soft_limit, hard_limit))
+
+DATASETS = Registry('dataset')
+PIPELINES = Registry('pipeline')
+SAMPLERS = Registry('sampler')
+
+
+def build_dataset(cfg, default_args=None):
+ from .dataset_wrappers import (ClassBalancedDataset, ConcatDataset,
+ KFoldDataset, RepeatDataset)
+ if isinstance(cfg, (list, tuple)):
+ dataset = ConcatDataset([build_dataset(c, default_args) for c in cfg])
+ elif cfg['type'] == 'ConcatDataset':
+ dataset = ConcatDataset(
+ [build_dataset(c, default_args) for c in cfg['datasets']],
+ separate_eval=cfg.get('separate_eval', True))
+ elif cfg['type'] == 'RepeatDataset':
+ dataset = RepeatDataset(
+ build_dataset(cfg['dataset'], default_args), cfg['times'])
+ elif cfg['type'] == 'ClassBalancedDataset':
+ dataset = ClassBalancedDataset(
+ build_dataset(cfg['dataset'], default_args), cfg['oversample_thr'])
+ elif cfg['type'] == 'KFoldDataset':
+ cp_cfg = copy.deepcopy(cfg)
+ if cp_cfg.get('test_mode', None) is None:
+ cp_cfg['test_mode'] = (default_args or {}).pop('test_mode', False)
+ cp_cfg['dataset'] = build_dataset(cp_cfg['dataset'], default_args)
+ cp_cfg.pop('type')
+ dataset = KFoldDataset(**cp_cfg)
+ else:
+ dataset = build_from_cfg(cfg, DATASETS, default_args)
+
+ return dataset
+
+
+def build_dataloader(dataset,
+ samples_per_gpu,
+ workers_per_gpu,
+ num_gpus=1,
+ dist=True,
+ shuffle=True,
+ round_up=True,
+ seed=None,
+ pin_memory=True,
+ persistent_workers=True,
+ sampler_cfg=None,
+ **kwargs):
+ """Build PyTorch DataLoader.
+
+ In distributed training, each GPU/process has a dataloader.
+ In non-distributed training, there is only one dataloader for all GPUs.
+
+ Args:
+ dataset (Dataset): A PyTorch dataset.
+ samples_per_gpu (int): Number of training samples on each GPU, i.e.,
+ batch size of each GPU.
+ workers_per_gpu (int): How many subprocesses to use for data loading
+ for each GPU.
+ num_gpus (int): Number of GPUs. Only used in non-distributed training.
+ dist (bool): Distributed training/test or not. Default: True.
+ shuffle (bool): Whether to shuffle the data at every epoch.
+ Default: True.
+ round_up (bool): Whether to round up the length of dataset by adding
+ extra samples to make it evenly divisible. Default: True.
+ pin_memory (bool): Whether to use pin_memory in DataLoader.
+ Default: True
+ persistent_workers (bool): If True, the data loader will not shutdown
+ the worker processes after a dataset has been consumed once.
+ This allows to maintain the workers Dataset instances alive.
+ The argument also has effect in PyTorch>=1.7.0.
+ Default: True
+ sampler_cfg (dict): sampler configuration to override the default
+ sampler
+ kwargs: any keyword argument to be used to initialize DataLoader
+
+ Returns:
+ DataLoader: A PyTorch dataloader.
+ """
+ rank, world_size = get_dist_info()
+
+ # Custom sampler logic
+ if sampler_cfg:
+ # shuffle=False when val and test
+ sampler_cfg.update(shuffle=shuffle)
+ sampler = build_sampler(
+ sampler_cfg,
+ default_args=dict(
+ dataset=dataset, num_replicas=world_size, rank=rank,
+ seed=seed))
+ # Default sampler logic
+ elif dist:
+ sampler = build_sampler(
+ dict(
+ type='DistributedSampler',
+ dataset=dataset,
+ num_replicas=world_size,
+ rank=rank,
+ shuffle=shuffle,
+ round_up=round_up,
+ seed=seed))
+ else:
+ sampler = None
+
+ # If sampler exists, turn off dataloader shuffle
+ if sampler is not None:
+ shuffle = False
+
+ if dist:
+ batch_size = samples_per_gpu
+ num_workers = workers_per_gpu
+ else:
+ batch_size = num_gpus * samples_per_gpu
+ num_workers = num_gpus * workers_per_gpu
+
+ init_fn = partial(
+ worker_init_fn, num_workers=num_workers, rank=rank,
+ seed=seed) if seed is not None else None
+
+ if digit_version(torch.__version__) >= digit_version('1.8.0'):
+ kwargs['persistent_workers'] = persistent_workers
+ if IS_IPU_AVAILABLE:
+ from mmcv.device.ipu import IPUDataLoader
+ data_loader = IPUDataLoader(
+ dataset,
+ None,
+ batch_size=samples_per_gpu,
+ num_workers=num_workers,
+ shuffle=shuffle,
+ worker_init_fn=init_fn,
+ **kwargs)
+ else:
+ data_loader = DataLoader(
+ dataset,
+ batch_size=batch_size,
+ sampler=sampler,
+ num_workers=num_workers,
+ collate_fn=partial(collate, samples_per_gpu=samples_per_gpu),
+ pin_memory=pin_memory,
+ shuffle=shuffle,
+ worker_init_fn=init_fn,
+ **kwargs)
+
+ return data_loader
+
+
+def worker_init_fn(worker_id, num_workers, rank, seed):
+ # The seed of each worker equals to
+ # num_worker * rank + worker_id + user_seed
+ worker_seed = num_workers * rank + worker_id + seed
+ np.random.seed(worker_seed)
+ random.seed(worker_seed)
+ torch.manual_seed(worker_seed)
+
+
+def build_sampler(cfg, default_args=None):
+ if cfg is None:
+ return None
+ else:
+ return build_from_cfg(cfg, SAMPLERS, default_args=default_args)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/cifar.py b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/cifar.py
similarity index 76%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/cifar.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/datasets/cifar.py
index f3159ea8efa4c7e61c4a21575295cf0d4cecca98..453b8d9d95f72de77719a6c85b5e3451b405c4dd 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/cifar.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/cifar.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import os
import os.path
import pickle
@@ -16,8 +17,8 @@ class CIFAR10(BaseDataset):
"""`CIFAR10 `_ Dataset.
This implementation is modified from
- https://github.com/pytorch/vision/blob/master/torchvision/datasets/cifar.py # noqa: E501
- """
+ https://github.com/pytorch/vision/blob/master/torchvision/datasets/cifar.py
+ """ # noqa: E501
base_folder = 'cifar-10-batches-py'
url = 'https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz'
@@ -39,6 +40,10 @@ class CIFAR10(BaseDataset):
'key': 'label_names',
'md5': '5ff9c542aee3614f3951f8cda6e48888',
}
+ CLASSES = [
+ 'airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog',
+ 'horse', 'ship', 'truck'
+ ]
def load_annotations(self):
@@ -130,3 +135,21 @@ class CIFAR100(CIFAR10):
'key': 'fine_label_names',
'md5': '7973b15100ade9c7d40fb424638fde48',
}
+ CLASSES = [
+ 'apple', 'aquarium_fish', 'baby', 'bear', 'beaver', 'bed', 'bee',
+ 'beetle', 'bicycle', 'bottle', 'bowl', 'boy', 'bridge', 'bus',
+ 'butterfly', 'camel', 'can', 'castle', 'caterpillar', 'cattle',
+ 'chair', 'chimpanzee', 'clock', 'cloud', 'cockroach', 'couch', 'crab',
+ 'crocodile', 'cup', 'dinosaur', 'dolphin', 'elephant', 'flatfish',
+ 'forest', 'fox', 'girl', 'hamster', 'house', 'kangaroo', 'keyboard',
+ 'lamp', 'lawn_mower', 'leopard', 'lion', 'lizard', 'lobster', 'man',
+ 'maple_tree', 'motorcycle', 'mountain', 'mouse', 'mushroom',
+ 'oak_tree', 'orange', 'orchid', 'otter', 'palm_tree', 'pear',
+ 'pickup_truck', 'pine_tree', 'plain', 'plate', 'poppy', 'porcupine',
+ 'possum', 'rabbit', 'raccoon', 'ray', 'road', 'rocket', 'rose', 'sea',
+ 'seal', 'shark', 'shrew', 'skunk', 'skyscraper', 'snail', 'snake',
+ 'spider', 'squirrel', 'streetcar', 'sunflower', 'sweet_pepper',
+ 'table', 'tank', 'telephone', 'television', 'tiger', 'tractor',
+ 'train', 'trout', 'tulip', 'turtle', 'wardrobe', 'whale',
+ 'willow_tree', 'wolf', 'woman', 'worm'
+ ]
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/cub.py b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/cub.py
new file mode 100644
index 0000000000000000000000000000000000000000..6199bc7a27290056bfe8da43161539390e384037
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/cub.py
@@ -0,0 +1,129 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import numpy as np
+
+from .base_dataset import BaseDataset
+from .builder import DATASETS
+
+
+@DATASETS.register_module()
+class CUB(BaseDataset):
+ """The CUB-200-2011 Dataset.
+
+ Support the `CUB-200-2011 `_ Dataset.
+ Comparing with the `CUB-200 `_ Dataset,
+ there are much more pictures in `CUB-200-2011`.
+
+ Args:
+ ann_file (str): the annotation file.
+ images.txt in CUB.
+ image_class_labels_file (str): the label file.
+ image_class_labels.txt in CUB.
+ train_test_split_file (str): the split file.
+ train_test_split_file.txt in CUB.
+ """ # noqa: E501
+
+ CLASSES = [
+ 'Black_footed_Albatross', 'Laysan_Albatross', 'Sooty_Albatross',
+ 'Groove_billed_Ani', 'Crested_Auklet', 'Least_Auklet',
+ 'Parakeet_Auklet', 'Rhinoceros_Auklet', 'Brewer_Blackbird',
+ 'Red_winged_Blackbird', 'Rusty_Blackbird', 'Yellow_headed_Blackbird',
+ 'Bobolink', 'Indigo_Bunting', 'Lazuli_Bunting', 'Painted_Bunting',
+ 'Cardinal', 'Spotted_Catbird', 'Gray_Catbird', 'Yellow_breasted_Chat',
+ 'Eastern_Towhee', 'Chuck_will_Widow', 'Brandt_Cormorant',
+ 'Red_faced_Cormorant', 'Pelagic_Cormorant', 'Bronzed_Cowbird',
+ 'Shiny_Cowbird', 'Brown_Creeper', 'American_Crow', 'Fish_Crow',
+ 'Black_billed_Cuckoo', 'Mangrove_Cuckoo', 'Yellow_billed_Cuckoo',
+ 'Gray_crowned_Rosy_Finch', 'Purple_Finch', 'Northern_Flicker',
+ 'Acadian_Flycatcher', 'Great_Crested_Flycatcher', 'Least_Flycatcher',
+ 'Olive_sided_Flycatcher', 'Scissor_tailed_Flycatcher',
+ 'Vermilion_Flycatcher', 'Yellow_bellied_Flycatcher', 'Frigatebird',
+ 'Northern_Fulmar', 'Gadwall', 'American_Goldfinch',
+ 'European_Goldfinch', 'Boat_tailed_Grackle', 'Eared_Grebe',
+ 'Horned_Grebe', 'Pied_billed_Grebe', 'Western_Grebe', 'Blue_Grosbeak',
+ 'Evening_Grosbeak', 'Pine_Grosbeak', 'Rose_breasted_Grosbeak',
+ 'Pigeon_Guillemot', 'California_Gull', 'Glaucous_winged_Gull',
+ 'Heermann_Gull', 'Herring_Gull', 'Ivory_Gull', 'Ring_billed_Gull',
+ 'Slaty_backed_Gull', 'Western_Gull', 'Anna_Hummingbird',
+ 'Ruby_throated_Hummingbird', 'Rufous_Hummingbird', 'Green_Violetear',
+ 'Long_tailed_Jaeger', 'Pomarine_Jaeger', 'Blue_Jay', 'Florida_Jay',
+ 'Green_Jay', 'Dark_eyed_Junco', 'Tropical_Kingbird', 'Gray_Kingbird',
+ 'Belted_Kingfisher', 'Green_Kingfisher', 'Pied_Kingfisher',
+ 'Ringed_Kingfisher', 'White_breasted_Kingfisher',
+ 'Red_legged_Kittiwake', 'Horned_Lark', 'Pacific_Loon', 'Mallard',
+ 'Western_Meadowlark', 'Hooded_Merganser', 'Red_breasted_Merganser',
+ 'Mockingbird', 'Nighthawk', 'Clark_Nutcracker',
+ 'White_breasted_Nuthatch', 'Baltimore_Oriole', 'Hooded_Oriole',
+ 'Orchard_Oriole', 'Scott_Oriole', 'Ovenbird', 'Brown_Pelican',
+ 'White_Pelican', 'Western_Wood_Pewee', 'Sayornis', 'American_Pipit',
+ 'Whip_poor_Will', 'Horned_Puffin', 'Common_Raven',
+ 'White_necked_Raven', 'American_Redstart', 'Geococcyx',
+ 'Loggerhead_Shrike', 'Great_Grey_Shrike', 'Baird_Sparrow',
+ 'Black_throated_Sparrow', 'Brewer_Sparrow', 'Chipping_Sparrow',
+ 'Clay_colored_Sparrow', 'House_Sparrow', 'Field_Sparrow',
+ 'Fox_Sparrow', 'Grasshopper_Sparrow', 'Harris_Sparrow',
+ 'Henslow_Sparrow', 'Le_Conte_Sparrow', 'Lincoln_Sparrow',
+ 'Nelson_Sharp_tailed_Sparrow', 'Savannah_Sparrow', 'Seaside_Sparrow',
+ 'Song_Sparrow', 'Tree_Sparrow', 'Vesper_Sparrow',
+ 'White_crowned_Sparrow', 'White_throated_Sparrow',
+ 'Cape_Glossy_Starling', 'Bank_Swallow', 'Barn_Swallow',
+ 'Cliff_Swallow', 'Tree_Swallow', 'Scarlet_Tanager', 'Summer_Tanager',
+ 'Artic_Tern', 'Black_Tern', 'Caspian_Tern', 'Common_Tern',
+ 'Elegant_Tern', 'Forsters_Tern', 'Least_Tern', 'Green_tailed_Towhee',
+ 'Brown_Thrasher', 'Sage_Thrasher', 'Black_capped_Vireo',
+ 'Blue_headed_Vireo', 'Philadelphia_Vireo', 'Red_eyed_Vireo',
+ 'Warbling_Vireo', 'White_eyed_Vireo', 'Yellow_throated_Vireo',
+ 'Bay_breasted_Warbler', 'Black_and_white_Warbler',
+ 'Black_throated_Blue_Warbler', 'Blue_winged_Warbler', 'Canada_Warbler',
+ 'Cape_May_Warbler', 'Cerulean_Warbler', 'Chestnut_sided_Warbler',
+ 'Golden_winged_Warbler', 'Hooded_Warbler', 'Kentucky_Warbler',
+ 'Magnolia_Warbler', 'Mourning_Warbler', 'Myrtle_Warbler',
+ 'Nashville_Warbler', 'Orange_crowned_Warbler', 'Palm_Warbler',
+ 'Pine_Warbler', 'Prairie_Warbler', 'Prothonotary_Warbler',
+ 'Swainson_Warbler', 'Tennessee_Warbler', 'Wilson_Warbler',
+ 'Worm_eating_Warbler', 'Yellow_Warbler', 'Northern_Waterthrush',
+ 'Louisiana_Waterthrush', 'Bohemian_Waxwing', 'Cedar_Waxwing',
+ 'American_Three_toed_Woodpecker', 'Pileated_Woodpecker',
+ 'Red_bellied_Woodpecker', 'Red_cockaded_Woodpecker',
+ 'Red_headed_Woodpecker', 'Downy_Woodpecker', 'Bewick_Wren',
+ 'Cactus_Wren', 'Carolina_Wren', 'House_Wren', 'Marsh_Wren',
+ 'Rock_Wren', 'Winter_Wren', 'Common_Yellowthroat'
+ ]
+
+ def __init__(self, *args, ann_file, image_class_labels_file,
+ train_test_split_file, **kwargs):
+ self.image_class_labels_file = image_class_labels_file
+ self.train_test_split_file = train_test_split_file
+ super(CUB, self).__init__(*args, ann_file=ann_file, **kwargs)
+
+ def load_annotations(self):
+ with open(self.ann_file) as f:
+ samples = [x.strip().split(' ')[1] for x in f.readlines()]
+
+ with open(self.image_class_labels_file) as f:
+ gt_labels = [
+ # in the official CUB-200-2011 dataset, labels in
+ # image_class_labels_file are started from 1, so
+ # here we need to '- 1' to let them start from 0.
+ int(x.strip().split(' ')[1]) - 1 for x in f.readlines()
+ ]
+
+ with open(self.train_test_split_file) as f:
+ splits = [int(x.strip().split(' ')[1]) for x in f.readlines()]
+
+ assert len(samples) == len(gt_labels) == len(splits),\
+ f'samples({len(samples)}), gt_labels({len(gt_labels)}) and ' \
+ f'splits({len(splits)}) should have same length.'
+
+ data_infos = []
+ for filename, gt_label, split in zip(samples, gt_labels, splits):
+ if split and self.test_mode:
+ # skip train samples when test_mode=True
+ continue
+ elif not split and not self.test_mode:
+ # skip test samples when test_mode=False
+ continue
+ info = {'img_prefix': self.data_prefix}
+ info['img_info'] = {'filename': filename}
+ info['gt_label'] = np.array(gt_label, dtype=np.int64)
+ data_infos.append(info)
+ return data_infos
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/custom.py b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/custom.py
new file mode 100644
index 0000000000000000000000000000000000000000..61458f63bac778ed793a6dc2d2b80079de6cb502
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/custom.py
@@ -0,0 +1,229 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import warnings
+from typing import Callable, Dict, List, Optional, Sequence, Tuple, Union
+
+import mmcv
+import numpy as np
+from mmcv import FileClient
+
+from .base_dataset import BaseDataset
+from .builder import DATASETS
+
+
+def find_folders(root: str,
+ file_client: FileClient) -> Tuple[List[str], Dict[str, int]]:
+ """Find classes by folders under a root.
+
+ Args:
+ root (string): root directory of folders
+
+ Returns:
+ Tuple[List[str], Dict[str, int]]:
+
+ - folders: The name of sub folders under the root.
+ - folder_to_idx: The map from folder name to class idx.
+ """
+ folders = list(
+ file_client.list_dir_or_file(
+ root,
+ list_dir=True,
+ list_file=False,
+ recursive=False,
+ ))
+ folders.sort()
+ folder_to_idx = {folders[i]: i for i in range(len(folders))}
+ return folders, folder_to_idx
+
+
+def get_samples(root: str, folder_to_idx: Dict[str, int],
+ is_valid_file: Callable, file_client: FileClient):
+ """Make dataset by walking all images under a root.
+
+ Args:
+ root (string): root directory of folders
+ folder_to_idx (dict): the map from class name to class idx
+ is_valid_file (Callable): A function that takes path of a file
+ and check if the file is a valid sample file.
+
+ Returns:
+ Tuple[list, set]:
+
+ - samples: a list of tuple where each element is (image, class_idx)
+ - empty_folders: The folders don't have any valid files.
+ """
+ samples = []
+ available_classes = set()
+
+ for folder_name in sorted(list(folder_to_idx.keys())):
+ _dir = file_client.join_path(root, folder_name)
+ files = list(
+ file_client.list_dir_or_file(
+ _dir,
+ list_dir=False,
+ list_file=True,
+ recursive=True,
+ ))
+ for file in sorted(list(files)):
+ if is_valid_file(file):
+ path = file_client.join_path(folder_name, file)
+ item = (path, folder_to_idx[folder_name])
+ samples.append(item)
+ available_classes.add(folder_name)
+
+ empty_folders = set(folder_to_idx.keys()) - available_classes
+
+ return samples, empty_folders
+
+
+@DATASETS.register_module()
+class CustomDataset(BaseDataset):
+ """Custom dataset for classification.
+
+ The dataset supports two kinds of annotation format.
+
+ 1. An annotation file is provided, and each line indicates a sample:
+
+ The sample files: ::
+
+ data_prefix/
+ ├── folder_1
+ │ ├── xxx.png
+ │ ├── xxy.png
+ │ └── ...
+ └── folder_2
+ ├── 123.png
+ ├── nsdf3.png
+ └── ...
+
+ The annotation file (the first column is the image path and the second
+ column is the index of category): ::
+
+ folder_1/xxx.png 0
+ folder_1/xxy.png 1
+ folder_2/123.png 5
+ folder_2/nsdf3.png 3
+ ...
+
+ Please specify the name of categories by the argument ``classes``.
+
+ 2. The samples are arranged in the specific way: ::
+
+ data_prefix/
+ ├── class_x
+ │ ├── xxx.png
+ │ ├── xxy.png
+ │ └── ...
+ │ └── xxz.png
+ └── class_y
+ ├── 123.png
+ ├── nsdf3.png
+ ├── ...
+ └── asd932_.png
+
+ If the ``ann_file`` is specified, the dataset will be generated by the
+ first way, otherwise, try the second way.
+
+ Args:
+ data_prefix (str): The path of data directory.
+ pipeline (Sequence[dict]): A list of dict, where each element
+ represents a operation defined in :mod:`mmcls.datasets.pipelines`.
+ Defaults to an empty tuple.
+ classes (str | Sequence[str], optional): Specify names of classes.
+
+ - If is string, it should be a file path, and the every line of
+ the file is a name of a class.
+ - If is a sequence of string, every item is a name of class.
+ - If is None, use ``cls.CLASSES`` or the names of sub folders
+ (If use the second way to arrange samples).
+
+ Defaults to None.
+ ann_file (str, optional): The annotation file. If is string, read
+ samples paths from the ann_file. If is None, find samples in
+ ``data_prefix``. Defaults to None.
+ extensions (Sequence[str]): A sequence of allowed extensions. Defaults
+ to ('.jpg', '.jpeg', '.png', '.ppm', '.bmp', '.pgm', '.tif').
+ test_mode (bool): In train mode or test mode. It's only a mark and
+ won't be used in this class. Defaults to False.
+ file_client_args (dict, optional): Arguments to instantiate a
+ FileClient. See :class:`mmcv.fileio.FileClient` for details.
+ If None, automatically inference from the specified path.
+ Defaults to None.
+ """
+
+ def __init__(self,
+ data_prefix: str,
+ pipeline: Sequence = (),
+ classes: Union[str, Sequence[str], None] = None,
+ ann_file: Optional[str] = None,
+ extensions: Sequence[str] = ('.jpg', '.jpeg', '.png', '.ppm',
+ '.bmp', '.pgm', '.tif'),
+ test_mode: bool = False,
+ file_client_args: Optional[dict] = None):
+ self.extensions = tuple(set([i.lower() for i in extensions]))
+ self.file_client_args = file_client_args
+
+ super().__init__(
+ data_prefix=data_prefix,
+ pipeline=pipeline,
+ classes=classes,
+ ann_file=ann_file,
+ test_mode=test_mode)
+
+ def _find_samples(self):
+ """find samples from ``data_prefix``."""
+ file_client = FileClient.infer_client(self.file_client_args,
+ self.data_prefix)
+ classes, folder_to_idx = find_folders(self.data_prefix, file_client)
+ samples, empty_classes = get_samples(
+ self.data_prefix,
+ folder_to_idx,
+ is_valid_file=self.is_valid_file,
+ file_client=file_client,
+ )
+
+ if len(samples) == 0:
+ raise RuntimeError(
+ f'Found 0 files in subfolders of: {self.data_prefix}. '
+ f'Supported extensions are: {",".join(self.extensions)}')
+
+ if self.CLASSES is not None:
+ assert len(self.CLASSES) == len(classes), \
+ f"The number of subfolders ({len(classes)}) doesn't match " \
+ f'the number of specified classes ({len(self.CLASSES)}). ' \
+ 'Please check the data folder.'
+ else:
+ self.CLASSES = classes
+
+ if empty_classes:
+ warnings.warn(
+ 'Found no valid file in the folder '
+ f'{", ".join(empty_classes)}. '
+ f"Supported extensions are: {', '.join(self.extensions)}",
+ UserWarning)
+
+ self.folder_to_idx = folder_to_idx
+
+ return samples
+
+ def load_annotations(self):
+ """Load image paths and gt_labels."""
+ if self.ann_file is None:
+ samples = self._find_samples()
+ elif isinstance(self.ann_file, str):
+ lines = mmcv.list_from_file(
+ self.ann_file, file_client_args=self.file_client_args)
+ samples = [x.strip().rsplit(' ', 1) for x in lines]
+ else:
+ raise TypeError('ann_file must be a str or None')
+
+ data_infos = []
+ for filename, gt_label in samples:
+ info = {'img_prefix': self.data_prefix}
+ info['img_info'] = {'filename': filename}
+ info['gt_label'] = np.array(gt_label, dtype=np.int64)
+ data_infos.append(info)
+ return data_infos
+
+ def is_valid_file(self, filename: str) -> bool:
+ """Check if a file is a valid sample."""
+ return filename.lower().endswith(self.extensions)
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/dataset_wrappers.py b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/dataset_wrappers.py
new file mode 100644
index 0000000000000000000000000000000000000000..93de60f60840efd4ab6e174d02a9070d3733ec98
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/dataset_wrappers.py
@@ -0,0 +1,329 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import bisect
+import math
+from collections import defaultdict
+
+import numpy as np
+from mmcv.utils import print_log
+from torch.utils.data.dataset import ConcatDataset as _ConcatDataset
+
+from .builder import DATASETS
+
+
+@DATASETS.register_module()
+class ConcatDataset(_ConcatDataset):
+ """A wrapper of concatenated dataset.
+
+ Same as :obj:`torch.utils.data.dataset.ConcatDataset`, but
+ add `get_cat_ids` function.
+
+ Args:
+ datasets (list[:obj:`BaseDataset`]): A list of datasets.
+ separate_eval (bool): Whether to evaluate the results
+ separately if it is used as validation dataset.
+ Defaults to True.
+ """
+
+ def __init__(self, datasets, separate_eval=True):
+ super(ConcatDataset, self).__init__(datasets)
+ self.separate_eval = separate_eval
+
+ self.CLASSES = datasets[0].CLASSES
+
+ if not separate_eval:
+ if len(set([type(ds) for ds in datasets])) != 1:
+ raise NotImplementedError(
+ 'To evaluate a concat dataset non-separately, '
+ 'all the datasets should have same types')
+
+ def get_cat_ids(self, idx):
+ if idx < 0:
+ if -idx > len(self):
+ raise ValueError(
+ 'absolute value of index should not exceed dataset length')
+ idx = len(self) + idx
+ dataset_idx = bisect.bisect_right(self.cumulative_sizes, idx)
+ if dataset_idx == 0:
+ sample_idx = idx
+ else:
+ sample_idx = idx - self.cumulative_sizes[dataset_idx - 1]
+ return self.datasets[dataset_idx].get_cat_ids(sample_idx)
+
+ def evaluate(self, results, *args, indices=None, logger=None, **kwargs):
+ """Evaluate the results.
+
+ Args:
+ results (list[list | tuple]): Testing results of the dataset.
+ indices (list, optional): The indices of samples corresponding to
+ the results. It's unavailable on ConcatDataset.
+ Defaults to None.
+ logger (logging.Logger | str, optional): Logger used for printing
+ related information during evaluation. Defaults to None.
+
+ Returns:
+ dict[str: float]: AP results of the total dataset or each separate
+ dataset if `self.separate_eval=True`.
+ """
+ if indices is not None:
+ raise NotImplementedError(
+ 'Use indices to evaluate speific samples in a ConcatDataset '
+ 'is not supported by now.')
+
+ assert len(results) == len(self), \
+ ('Dataset and results have different sizes: '
+ f'{len(self)} v.s. {len(results)}')
+
+ # Check whether all the datasets support evaluation
+ for dataset in self.datasets:
+ assert hasattr(dataset, 'evaluate'), \
+ f"{type(dataset)} haven't implemented the evaluate function."
+
+ if self.separate_eval:
+ total_eval_results = dict()
+ for dataset_idx, dataset in enumerate(self.datasets):
+ start_idx = 0 if dataset_idx == 0 else \
+ self.cumulative_sizes[dataset_idx-1]
+ end_idx = self.cumulative_sizes[dataset_idx]
+
+ results_per_dataset = results[start_idx:end_idx]
+ print_log(
+ f'Evaluateing dataset-{dataset_idx} with '
+ f'{len(results_per_dataset)} images now',
+ logger=logger)
+
+ eval_results_per_dataset = dataset.evaluate(
+ results_per_dataset, *args, logger=logger, **kwargs)
+ for k, v in eval_results_per_dataset.items():
+ total_eval_results.update({f'{dataset_idx}_{k}': v})
+
+ return total_eval_results
+ else:
+ original_data_infos = self.datasets[0].data_infos
+ self.datasets[0].data_infos = sum(
+ [dataset.data_infos for dataset in self.datasets], [])
+ eval_results = self.datasets[0].evaluate(
+ results, logger=logger, **kwargs)
+ self.datasets[0].data_infos = original_data_infos
+ return eval_results
+
+
+@DATASETS.register_module()
+class RepeatDataset(object):
+ """A wrapper of repeated dataset.
+
+ The length of repeated dataset will be `times` larger than the original
+ dataset. This is useful when the data loading time is long but the dataset
+ is small. Using RepeatDataset can reduce the data loading time between
+ epochs.
+
+ Args:
+ dataset (:obj:`BaseDataset`): The dataset to be repeated.
+ times (int): Repeat times.
+ """
+
+ def __init__(self, dataset, times):
+ self.dataset = dataset
+ self.times = times
+ self.CLASSES = dataset.CLASSES
+
+ self._ori_len = len(self.dataset)
+
+ def __getitem__(self, idx):
+ return self.dataset[idx % self._ori_len]
+
+ def get_cat_ids(self, idx):
+ return self.dataset.get_cat_ids(idx % self._ori_len)
+
+ def __len__(self):
+ return self.times * self._ori_len
+
+ def evaluate(self, *args, **kwargs):
+ raise NotImplementedError(
+ 'evaluate results on a repeated dataset is weird. '
+ 'Please inference and evaluate on the original dataset.')
+
+ def __repr__(self):
+ """Print the number of instance number."""
+ dataset_type = 'Test' if self.test_mode else 'Train'
+ result = (
+ f'\n{self.__class__.__name__} ({self.dataset.__class__.__name__}) '
+ f'{dataset_type} dataset with total number of samples {len(self)}.'
+ )
+ return result
+
+
+# Modified from https://github.com/facebookresearch/detectron2/blob/41d475b75a230221e21d9cac5d69655e3415e3a4/detectron2/data/samplers/distributed_sampler.py#L57 # noqa
+@DATASETS.register_module()
+class ClassBalancedDataset(object):
+ r"""A wrapper of repeated dataset with repeat factor.
+
+ Suitable for training on class imbalanced datasets like LVIS. Following the
+ sampling strategy in `this paper`_, in each epoch, an image may appear
+ multiple times based on its "repeat factor".
+
+ .. _this paper: https://arxiv.org/pdf/1908.03195.pdf
+
+ The repeat factor for an image is a function of the frequency the rarest
+ category labeled in that image. The "frequency of category c" in [0, 1]
+ is defined by the fraction of images in the training set (without repeats)
+ in which category c appears.
+
+ The dataset needs to implement :func:`self.get_cat_ids` to support
+ ClassBalancedDataset.
+
+ The repeat factor is computed as followed.
+
+ 1. For each category c, compute the fraction :math:`f(c)` of images that
+ contain it.
+ 2. For each category c, compute the category-level repeat factor
+
+ .. math::
+ r(c) = \max(1, \sqrt{\frac{t}{f(c)}})
+
+ 3. For each image I and its labels :math:`L(I)`, compute the image-level
+ repeat factor
+
+ .. math::
+ r(I) = \max_{c \in L(I)} r(c)
+
+ Args:
+ dataset (:obj:`BaseDataset`): The dataset to be repeated.
+ oversample_thr (float): frequency threshold below which data is
+ repeated. For categories with ``f_c`` >= ``oversample_thr``, there
+ is no oversampling. For categories with ``f_c`` <
+ ``oversample_thr``, the degree of oversampling following the
+ square-root inverse frequency heuristic above.
+ """
+
+ def __init__(self, dataset, oversample_thr):
+ self.dataset = dataset
+ self.oversample_thr = oversample_thr
+ self.CLASSES = dataset.CLASSES
+
+ repeat_factors = self._get_repeat_factors(dataset, oversample_thr)
+ repeat_indices = []
+ for dataset_index, repeat_factor in enumerate(repeat_factors):
+ repeat_indices.extend([dataset_index] * math.ceil(repeat_factor))
+ self.repeat_indices = repeat_indices
+
+ flags = []
+ if hasattr(self.dataset, 'flag'):
+ for flag, repeat_factor in zip(self.dataset.flag, repeat_factors):
+ flags.extend([flag] * int(math.ceil(repeat_factor)))
+ assert len(flags) == len(repeat_indices)
+ self.flag = np.asarray(flags, dtype=np.uint8)
+
+ def _get_repeat_factors(self, dataset, repeat_thr):
+ # 1. For each category c, compute the fraction # of images
+ # that contain it: f(c)
+ category_freq = defaultdict(int)
+ num_images = len(dataset)
+ for idx in range(num_images):
+ cat_ids = set(self.dataset.get_cat_ids(idx))
+ for cat_id in cat_ids:
+ category_freq[cat_id] += 1
+ for k, v in category_freq.items():
+ assert v > 0, f'caterogy {k} does not contain any images'
+ category_freq[k] = v / num_images
+
+ # 2. For each category c, compute the category-level repeat factor:
+ # r(c) = max(1, sqrt(t/f(c)))
+ category_repeat = {
+ cat_id: max(1.0, math.sqrt(repeat_thr / cat_freq))
+ for cat_id, cat_freq in category_freq.items()
+ }
+
+ # 3. For each image I and its labels L(I), compute the image-level
+ # repeat factor:
+ # r(I) = max_{c in L(I)} r(c)
+ repeat_factors = []
+ for idx in range(num_images):
+ cat_ids = set(self.dataset.get_cat_ids(idx))
+ repeat_factor = max(
+ {category_repeat[cat_id]
+ for cat_id in cat_ids})
+ repeat_factors.append(repeat_factor)
+
+ return repeat_factors
+
+ def __getitem__(self, idx):
+ ori_index = self.repeat_indices[idx]
+ return self.dataset[ori_index]
+
+ def __len__(self):
+ return len(self.repeat_indices)
+
+ def evaluate(self, *args, **kwargs):
+ raise NotImplementedError(
+ 'evaluate results on a class-balanced dataset is weird. '
+ 'Please inference and evaluate on the original dataset.')
+
+ def __repr__(self):
+ """Print the number of instance number."""
+ dataset_type = 'Test' if self.test_mode else 'Train'
+ result = (
+ f'\n{self.__class__.__name__} ({self.dataset.__class__.__name__}) '
+ f'{dataset_type} dataset with total number of samples {len(self)}.'
+ )
+ return result
+
+
+@DATASETS.register_module()
+class KFoldDataset:
+ """A wrapper of dataset for K-Fold cross-validation.
+
+ K-Fold cross-validation divides all the samples in groups of samples,
+ called folds, of almost equal sizes. And we use k-1 of folds to do training
+ and use the fold left to do validation.
+
+ Args:
+ dataset (:obj:`BaseDataset`): The dataset to be divided.
+ fold (int): The fold used to do validation. Defaults to 0.
+ num_splits (int): The number of all folds. Defaults to 5.
+ test_mode (bool): Use the training dataset or validation dataset.
+ Defaults to False.
+ seed (int, optional): The seed to shuffle the dataset before splitting.
+ If None, not shuffle the dataset. Defaults to None.
+ """
+
+ def __init__(self,
+ dataset,
+ fold=0,
+ num_splits=5,
+ test_mode=False,
+ seed=None):
+ self.dataset = dataset
+ self.CLASSES = dataset.CLASSES
+ self.test_mode = test_mode
+ self.num_splits = num_splits
+
+ length = len(dataset)
+ indices = list(range(length))
+ if isinstance(seed, int):
+ rng = np.random.default_rng(seed)
+ rng.shuffle(indices)
+
+ test_start = length * fold // num_splits
+ test_end = length * (fold + 1) // num_splits
+ if test_mode:
+ self.indices = indices[test_start:test_end]
+ else:
+ self.indices = indices[:test_start] + indices[test_end:]
+
+ def get_cat_ids(self, idx):
+ return self.dataset.get_cat_ids(self.indices[idx])
+
+ def get_gt_labels(self):
+ dataset_gt_labels = self.dataset.get_gt_labels()
+ gt_labels = np.array([dataset_gt_labels[idx] for idx in self.indices])
+ return gt_labels
+
+ def __getitem__(self, idx):
+ return self.dataset[self.indices[idx]]
+
+ def __len__(self):
+ return len(self.indices)
+
+ def evaluate(self, *args, **kwargs):
+ kwargs['indices'] = self.indices
+ return self.dataset.evaluate(*args, **kwargs)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/imagenet.py b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/imagenet.py
similarity index 91%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/imagenet.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/datasets/imagenet.py
index 86ac5c5c5dee0cc15d175ba9e4f9a8eb90572bcc..84341dc9e5ea00e6db3a370fcf2110db522803f6 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/imagenet.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/imagenet.py
@@ -1,75 +1,42 @@
-import os
+# Copyright (c) OpenMMLab. All rights reserved.
+from typing import Optional, Sequence, Union
-import numpy as np
-
-from .base_dataset import BaseDataset
from .builder import DATASETS
+from .custom import CustomDataset
-def has_file_allowed_extension(filename, extensions):
- """Checks if a file is an allowed extension.
-
- Args:
- filename (string): path to a file
-
- Returns:
- bool: True if the filename ends with a known image extension
- """
- filename_lower = filename.lower()
- return any(filename_lower.endswith(ext) for ext in extensions)
-
-
-def find_folders(root):
- """Find classes by folders under a root.
-
- Args:
- root (string): root directory of folders
-
- Returns:
- folder_to_idx (dict): the map from folder name to class idx
- """
- folders = [
- d for d in os.listdir(root) if os.path.isdir(os.path.join(root, d))
- ]
- folders.sort()
- folder_to_idx = {folders[i]: i for i in range(len(folders))}
- return folder_to_idx
-
+@DATASETS.register_module()
+class ImageNet(CustomDataset):
+ """`ImageNet `_ Dataset.
-def get_samples(root, folder_to_idx, extensions):
- """Make dataset by walking all images under a root.
+ The dataset supports two kinds of annotation format. More details can be
+ found in :class:`CustomDataset`.
Args:
- root (string): root directory of folders
- folder_to_idx (dict): the map from class name to class idx
- extensions (tuple): allowed extensions
-
- Returns:
- samples (list): a list of tuple where each element is (image, label)
- """
- samples = []
- root = os.path.expanduser(root)
- for folder_name in sorted(os.listdir(root)):
- _dir = os.path.join(root, folder_name)
- if not os.path.isdir(_dir):
- continue
-
- for _, _, fns in sorted(os.walk(_dir)):
- for fn in sorted(fns):
- if has_file_allowed_extension(fn, extensions):
- path = os.path.join(folder_name, fn)
- item = (path, folder_to_idx[folder_name])
- samples.append(item)
- return samples
+ data_prefix (str): The path of data directory.
+ pipeline (Sequence[dict]): A list of dict, where each element
+ represents a operation defined in :mod:`mmcls.datasets.pipelines`.
+ Defaults to an empty tuple.
+ classes (str | Sequence[str], optional): Specify names of classes.
+ - If is string, it should be a file path, and the every line of
+ the file is a name of a class.
+ - If is a sequence of string, every item is a name of class.
+ - If is None, use the default ImageNet-1k classes names.
-@DATASETS.register_module()
-class ImageNet(BaseDataset):
- """`ImageNet `_ Dataset.
-
- This implementation is modified from
- https://github.com/pytorch/vision/blob/master/torchvision/datasets/imagenet.py # noqa: E501
- """
+ Defaults to None.
+ ann_file (str, optional): The annotation file. If is string, read
+ samples paths from the ann_file. If is None, find samples in
+ ``data_prefix``. Defaults to None.
+ extensions (Sequence[str]): A sequence of allowed extensions. Defaults
+ to ('.jpg', '.jpeg', '.png', '.ppm', '.bmp', '.pgm', '.tif').
+ test_mode (bool): In train mode or test mode. It's only a mark and
+ won't be used in this class. Defaults to False.
+ file_client_args (dict, optional): Arguments to instantiate a
+ FileClient. See :class:`mmcv.fileio.FileClient` for details.
+ If None, automatically inference from the specified path.
+ Defaults to None.
+ """ # noqa: E501
IMG_EXTENSIONS = ('.jpg', '.jpeg', '.png', '.ppm', '.bmp', '.pgm', '.tif')
CLASSES = [
@@ -1075,31 +1042,18 @@ class ImageNet(BaseDataset):
'toilet tissue, toilet paper, bathroom tissue'
]
- def load_annotations(self):
- if self.ann_file is None:
- folder_to_idx = find_folders(self.data_prefix)
- samples = get_samples(
- self.data_prefix,
- folder_to_idx,
- extensions=self.IMG_EXTENSIONS)
- if len(samples) == 0:
- raise (RuntimeError('Found 0 files in subfolders of: '
- f'{self.data_prefix}. '
- 'Supported extensions are: '
- f'{",".join(self.IMG_EXTENSIONS)}'))
-
- self.folder_to_idx = folder_to_idx
- elif isinstance(self.ann_file, str):
- with open(self.ann_file) as f:
- samples = [x.strip().split(' ') for x in f.readlines()]
- else:
- raise TypeError('ann_file must be a str or None')
- self.samples = samples
-
- data_infos = []
- for filename, gt_label in self.samples:
- info = {'img_prefix': self.data_prefix}
- info['img_info'] = {'filename': filename}
- info['gt_label'] = np.array(gt_label, dtype=np.int64)
- data_infos.append(info)
- return data_infos
+ def __init__(self,
+ data_prefix: str,
+ pipeline: Sequence = (),
+ classes: Union[str, Sequence[str], None] = None,
+ ann_file: Optional[str] = None,
+ test_mode: bool = False,
+ file_client_args: Optional[dict] = None):
+ super().__init__(
+ data_prefix=data_prefix,
+ pipeline=pipeline,
+ classes=classes,
+ ann_file=ann_file,
+ extensions=self.IMG_EXTENSIONS,
+ test_mode=test_mode,
+ file_client_args=file_client_args)
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/imagenet21k.py b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/imagenet21k.py
new file mode 100644
index 0000000000000000000000000000000000000000..864e215c46ea8e35b446f629570291593eca7755
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/imagenet21k.py
@@ -0,0 +1,174 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import gc
+import pickle
+import warnings
+from typing import List, Optional, Sequence, Tuple, Union
+
+import numpy as np
+
+from .builder import DATASETS
+from .custom import CustomDataset
+
+
+@DATASETS.register_module()
+class ImageNet21k(CustomDataset):
+ """ImageNet21k Dataset.
+
+ Since the dataset ImageNet21k is extremely big, cantains 21k+ classes
+ and 1.4B files. This class has improved the following points on the
+ basis of the class ``ImageNet``, in order to save memory, we enable the
+ ``serialize_data`` optional by default. With this option, the annotation
+ won't be stored in the list ``data_infos``, but be serialized as an
+ array.
+
+ Args:
+ data_prefix (str): The path of data directory.
+ pipeline (Sequence[dict]): A list of dict, where each element
+ represents a operation defined in :mod:`mmcls.datasets.pipelines`.
+ Defaults to an empty tuple.
+ classes (str | Sequence[str], optional): Specify names of classes.
+
+ - If is string, it should be a file path, and the every line of
+ the file is a name of a class.
+ - If is a sequence of string, every item is a name of class.
+ - If is None, the object won't have category information.
+ (Not recommended)
+
+ Defaults to None.
+ ann_file (str, optional): The annotation file. If is string, read
+ samples paths from the ann_file. If is None, find samples in
+ ``data_prefix``. Defaults to None.
+ serialize_data (bool): Whether to hold memory using serialized objects,
+ when enabled, data loader workers can use shared RAM from master
+ process instead of making a copy. Defaults to True.
+ multi_label (bool): Not implement by now. Use multi label or not.
+ Defaults to False.
+ recursion_subdir(bool): Deprecated, and the dataset will recursively
+ get all images now.
+ test_mode (bool): In train mode or test mode. It's only a mark and
+ won't be used in this class. Defaults to False.
+ file_client_args (dict, optional): Arguments to instantiate a
+ FileClient. See :class:`mmcv.fileio.FileClient` for details.
+ If None, automatically inference from the specified path.
+ Defaults to None.
+ """
+
+ IMG_EXTENSIONS = ('.jpg', '.jpeg', '.png', '.ppm', '.bmp', '.pgm', '.tif')
+ CLASSES = None
+
+ def __init__(self,
+ data_prefix: str,
+ pipeline: Sequence = (),
+ classes: Union[str, Sequence[str], None] = None,
+ ann_file: Optional[str] = None,
+ serialize_data: bool = True,
+ multi_label: bool = False,
+ recursion_subdir: bool = True,
+ test_mode=False,
+ file_client_args: Optional[dict] = None):
+ assert recursion_subdir, 'The `recursion_subdir` option is ' \
+ 'deprecated. Now the dataset will recursively get all images.'
+ if multi_label:
+ raise NotImplementedError(
+ 'The `multi_label` option is not supported by now.')
+ self.multi_label = multi_label
+ self.serialize_data = serialize_data
+
+ if ann_file is None:
+ warnings.warn(
+ 'The ImageNet21k dataset is large, and scanning directory may '
+ 'consume long time. Considering to specify the `ann_file` to '
+ 'accelerate the initialization.', UserWarning)
+
+ if classes is None:
+ warnings.warn(
+ 'The CLASSES is not stored in the `ImageNet21k` class. '
+ 'Considering to specify the `classes` argument if you need '
+ 'do inference on the ImageNet-21k dataset', UserWarning)
+
+ super().__init__(
+ data_prefix=data_prefix,
+ pipeline=pipeline,
+ classes=classes,
+ ann_file=ann_file,
+ extensions=self.IMG_EXTENSIONS,
+ test_mode=test_mode,
+ file_client_args=file_client_args)
+
+ if self.serialize_data:
+ self.data_infos_bytes, self.data_address = self._serialize_data()
+ # Empty cache for preventing making multiple copies of
+ # `self.data_infos` when loading data multi-processes.
+ self.data_infos.clear()
+ gc.collect()
+
+ def get_cat_ids(self, idx: int) -> List[int]:
+ """Get category id by index.
+
+ Args:
+ idx (int): Index of data.
+
+ Returns:
+ cat_ids (List[int]): Image category of specified index.
+ """
+
+ return [int(self.get_data_info(idx)['gt_label'])]
+
+ def get_data_info(self, idx: int) -> dict:
+ """Get annotation by index.
+
+ Args:
+ idx (int): The index of data.
+
+ Returns:
+ dict: The idx-th annotation of the dataset.
+ """
+ if self.serialize_data:
+ start_addr = 0 if idx == 0 else self.data_address[idx - 1].item()
+ end_addr = self.data_address[idx].item()
+ bytes = memoryview(self.data_infos_bytes[start_addr:end_addr])
+ data_info = pickle.loads(bytes)
+ else:
+ data_info = self.data_infos[idx]
+
+ return data_info
+
+ def prepare_data(self, idx):
+ data_info = self.get_data_info(idx)
+ return self.pipeline(data_info)
+
+ def _serialize_data(self) -> Tuple[np.ndarray, np.ndarray]:
+ """Serialize ``self.data_infos`` to save memory when launching multiple
+ workers in data loading. This function will be called in ``full_init``.
+
+ Hold memory using serialized objects, and data loader workers can use
+ shared RAM from master process instead of making a copy.
+
+ Returns:
+ Tuple[np.ndarray, np.ndarray]: serialize result and corresponding
+ address.
+ """
+
+ def _serialize(data):
+ buffer = pickle.dumps(data, protocol=4)
+ return np.frombuffer(buffer, dtype=np.uint8)
+
+ serialized_data_infos_list = [_serialize(x) for x in self.data_infos]
+ address_list = np.asarray([len(x) for x in serialized_data_infos_list],
+ dtype=np.int64)
+ data_address: np.ndarray = np.cumsum(address_list)
+ serialized_data_infos = np.concatenate(serialized_data_infos_list)
+
+ return serialized_data_infos, data_address
+
+ def __len__(self) -> int:
+ """Get the length of filtered dataset and automatically call
+ ``full_init`` if the dataset has not been fully init.
+
+ Returns:
+ int: The length of filtered dataset.
+ """
+ if self.serialize_data:
+ return len(self.data_address)
+ else:
+ return len(self.data_infos)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/mnist.py b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/mnist.py
similarity index 98%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/mnist.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/datasets/mnist.py
index f00ef20e2b85ff76f05acb621de997722bfbf251..4065e0d54220df0839f1e0ced3739a21bb181038 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/mnist.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/mnist.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import codecs
import os
import os.path as osp
@@ -17,8 +18,8 @@ class MNIST(BaseDataset):
"""`MNIST `_ Dataset.
This implementation is modified from
- https://github.com/pytorch/vision/blob/master/torchvision/datasets/mnist.py # noqa: E501
- """
+ https://github.com/pytorch/vision/blob/master/torchvision/datasets/mnist.py
+ """ # noqa: E501
resource_prefix = 'http://yann.lecun.com/exdb/mnist/'
resources = {
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/multi_label.py b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/multi_label.py
similarity index 81%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/multi_label.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/datasets/multi_label.py
index 68076a66b2fb42772acc132d64edbf1752b42e19..02480f0b76b8a1cee2fdae444f1d14a16fb1b550 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/multi_label.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/multi_label.py
@@ -1,4 +1,5 @@
-import warnings
+# Copyright (c) OpenMMLab. All rights reserved.
+from typing import List
import numpy as np
@@ -9,25 +10,25 @@ from .base_dataset import BaseDataset
class MultiLabelDataset(BaseDataset):
"""Multi-label Dataset."""
- def get_cat_ids(self, idx):
+ def get_cat_ids(self, idx: int) -> List[int]:
"""Get category ids by index.
Args:
idx (int): Index of data.
Returns:
- np.ndarray: Image categories of specified index.
+ cat_ids (List[int]): Image categories of specified index.
"""
gt_labels = self.data_infos[idx]['gt_label']
- cat_ids = np.where(gt_labels == 1)[0]
+ cat_ids = np.where(gt_labels == 1)[0].tolist()
return cat_ids
def evaluate(self,
results,
metric='mAP',
metric_options=None,
- logger=None,
- **deprecated_kwargs):
+ indices=None,
+ logger=None):
"""Evaluate the dataset.
Args:
@@ -39,19 +40,13 @@ class MultiLabelDataset(BaseDataset):
Allowed keys are 'k' and 'thr'. Defaults to None
logger (logging.Logger | str, optional): Logger used for printing
related information during evaluation. Defaults to None.
- deprecated_kwargs (dict): Used for containing deprecated arguments.
Returns:
dict: evaluation results
"""
- if metric_options is None:
+ if metric_options is None or metric_options == {}:
metric_options = {'thr': 0.5}
- if deprecated_kwargs != {}:
- warnings.warn('Option arguments for metrics has been changed to '
- '`metric_options`.')
- metric_options = {**deprecated_kwargs}
-
if isinstance(metric, str):
metrics = [metric]
else:
@@ -60,6 +55,8 @@ class MultiLabelDataset(BaseDataset):
eval_results = {}
results = np.vstack(results)
gt_labels = self.get_gt_labels()
+ if indices is not None:
+ gt_labels = gt_labels[indices]
num_imgs = len(results)
assert len(gt_labels) == num_imgs, 'dataset testing results should '\
'be of the same length as gt_labels.'
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/pipelines/__init__.py b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/pipelines/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e010ed1d45721fe5d048a36879311ff76d3dc739
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/pipelines/__init__.py
@@ -0,0 +1,22 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from .auto_augment import (AutoAugment, AutoContrast, Brightness,
+ ColorTransform, Contrast, Cutout, Equalize, Invert,
+ Posterize, RandAugment, Rotate, Sharpness, Shear,
+ Solarize, SolarizeAdd, Translate)
+from .compose import Compose
+from .formatting import (Collect, ImageToTensor, ToNumpy, ToPIL, ToTensor,
+ Transpose, to_tensor)
+from .loading import LoadImageFromFile
+from .transforms import (CenterCrop, ColorJitter, Lighting, Normalize, Pad,
+ RandomCrop, RandomErasing, RandomFlip,
+ RandomGrayscale, RandomResizedCrop, Resize)
+
+__all__ = [
+ 'Compose', 'to_tensor', 'ToTensor', 'ImageToTensor', 'ToPIL', 'ToNumpy',
+ 'Transpose', 'Collect', 'LoadImageFromFile', 'Resize', 'CenterCrop',
+ 'RandomFlip', 'Normalize', 'RandomCrop', 'RandomResizedCrop',
+ 'RandomGrayscale', 'Shear', 'Translate', 'Rotate', 'Invert',
+ 'ColorTransform', 'Solarize', 'Posterize', 'AutoContrast', 'Equalize',
+ 'Contrast', 'Brightness', 'Sharpness', 'AutoAugment', 'SolarizeAdd',
+ 'Cutout', 'RandAugment', 'Lighting', 'ColorJitter', 'RandomErasing', 'Pad'
+]
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/pipelines/auto_augment.py b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/pipelines/auto_augment.py
similarity index 88%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/pipelines/auto_augment.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/datasets/pipelines/auto_augment.py
index 6087464fe8714c04de46f621cf8df52243e46778..e7fffd6d70c34f0c53c58ea70dfd4f267539d9ee 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/pipelines/auto_augment.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/pipelines/auto_augment.py
@@ -1,5 +1,8 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import copy
+import inspect
import random
+from math import ceil
from numbers import Number
from typing import Sequence
@@ -9,18 +12,43 @@ import numpy as np
from ..builder import PIPELINES
from .compose import Compose
+# Default hyperparameters for all Ops
+_HPARAMS_DEFAULT = dict(pad_val=128)
+
def random_negative(value, random_negative_prob):
"""Randomly negate value based on random_negative_prob."""
return -value if np.random.rand() < random_negative_prob else value
+def merge_hparams(policy: dict, hparams: dict):
+ """Merge hyperparameters into policy config.
+
+ Only merge partial hyperparameters required of the policy.
+
+ Args:
+ policy (dict): Original policy config dict.
+ hparams (dict): Hyperparameters need to be merged.
+
+ Returns:
+ dict: Policy config dict after adding ``hparams``.
+ """
+ op = PIPELINES.get(policy['type'])
+ assert op is not None, f'Invalid policy type "{policy["type"]}".'
+ for key, value in hparams.items():
+ if policy.get(key, None) is not None:
+ continue
+ if key in inspect.getfullargspec(op.__init__).args:
+ policy[key] = value
+ return policy
+
+
@PIPELINES.register_module()
class AutoAugment(object):
- """Auto augmentation. This data augmentation is proposed in `AutoAugment:
- Learning Augmentation Policies from Data.
+ """Auto augmentation.
- `_.
+ This data augmentation is proposed in `AutoAugment: Learning Augmentation
+ Policies from Data `_.
Args:
policies (list[list[dict]]): The policies of auto augmentation. Each
@@ -28,9 +56,12 @@ class AutoAugment(object):
composed by several augmentations (dict). When AutoAugment is
called, a random policy in ``policies`` will be selected to
augment images.
+ hparams (dict): Configs of hyperparameters. Hyperparameters will be
+ used in policies that require these arguments if these arguments
+ are not set in policy dicts. Defaults to use _HPARAMS_DEFAULT.
"""
- def __init__(self, policies):
+ def __init__(self, policies, hparams=_HPARAMS_DEFAULT):
assert isinstance(policies, list) and len(policies) > 0, \
'Policies must be a non-empty list.'
for policy in policies:
@@ -41,7 +72,13 @@ class AutoAugment(object):
'Each specific augmentation must be a dict with key' \
' "type".'
- self.policies = copy.deepcopy(policies)
+ self.hparams = hparams
+ policies = copy.deepcopy(policies)
+ self.policies = []
+ for sub in policies:
+ merged_sub = [merge_hparams(policy, hparams) for policy in sub]
+ self.policies.append(merged_sub)
+
self.sub_policy = [Compose(policy) for policy in self.policies]
def __call__(self, results):
@@ -56,9 +93,10 @@ class AutoAugment(object):
@PIPELINES.register_module()
class RandAugment(object):
- """Random augmentation. This data augmentation is proposed in `RandAugment:
- Practical automated data augmentation with a reduced search space.
+ r"""Random augmentation.
+ This data augmentation is proposed in `RandAugment: Practical automated
+ data augmentation with a reduced search space
`_.
Args:
@@ -78,19 +116,26 @@ class RandAugment(object):
total_level (int | float): Total level for the magnitude. Defaults to
30.
magnitude_std (Number | str): Deviation of magnitude noise applied.
- If positive number, magnitude is sampled from normal distribution
- (mean=magnitude, std=magnitude_std).
- If 0 or negative number, magnitude remains unchanged.
- If str "inf", magnitude is sampled from uniform distribution
- (range=[min, magnitude]).
+
+ - If positive number, magnitude is sampled from normal distribution
+ (mean=magnitude, std=magnitude_std).
+ - If 0 or negative number, magnitude remains unchanged.
+ - If str "inf", magnitude is sampled from uniform distribution
+ (range=[min, magnitude]).
+ hparams (dict): Configs of hyperparameters. Hyperparameters will be
+ used in policies that require these arguments if these arguments
+ are not set in policy dicts. Defaults to use _HPARAMS_DEFAULT.
Note:
`magnitude_std` will introduce some randomness to policy, modified by
- https://github.com/rwightman/pytorch-image-models
+ https://github.com/rwightman/pytorch-image-models.
+
When magnitude_std=0, we calculate the magnitude as follows:
.. math::
- magnitude = magnitude_level / total_level * (val2 - val1) + val1
+ \text{magnitude} = \frac{\text{magnitude_level}}
+ {\text{totallevel}} \times (\text{val2} - \text{val1})
+ + \text{val1}
"""
def __init__(self,
@@ -98,7 +143,8 @@ class RandAugment(object):
num_policies,
magnitude_level,
magnitude_std=0.,
- total_level=30):
+ total_level=30,
+ hparams=_HPARAMS_DEFAULT):
assert isinstance(num_policies, int), 'Number of policies must be ' \
f'of int type, got {type(num_policies)} instead.'
assert isinstance(magnitude_level, (int, float)), \
@@ -125,8 +171,10 @@ class RandAugment(object):
self.magnitude_level = magnitude_level
self.magnitude_std = magnitude_std
self.total_level = total_level
- self.policies = policies
- self._check_policies(self.policies)
+ self.hparams = hparams
+ policies = copy.deepcopy(policies)
+ self._check_policies(policies)
+ self.policies = [merge_hparams(policy, hparams) for policy in policies]
def _check_policies(self, policies):
for policy in policies:
@@ -190,8 +238,8 @@ class Shear(object):
Args:
magnitude (int | float): The magnitude used for shear.
- pad_val (int, tuple[int]): Pixel pad_val value for constant fill. If a
- tuple of length 3, it is used to pad_val R, G, B channels
+ pad_val (int, Sequence[int]): Pixel pad_val value for constant fill.
+ If a sequence of length 3, it is used to pad_val R, G, B channels
respectively. Defaults to 128.
prob (float): The probability for performing Shear therefore should be
in range [0, 1]. Defaults to 0.5.
@@ -214,7 +262,7 @@ class Shear(object):
f'be int or float, but got {type(magnitude)} instead.'
if isinstance(pad_val, int):
pad_val = tuple([pad_val] * 3)
- elif isinstance(pad_val, tuple):
+ elif isinstance(pad_val, Sequence):
assert len(pad_val) == 3, 'pad_val as a tuple must have 3 ' \
f'elements, got {len(pad_val)} instead.'
assert all(isinstance(i, int) for i in pad_val), 'pad_val as a '\
@@ -229,7 +277,7 @@ class Shear(object):
f'should be in range [0,1], got {random_negative_prob} instead.'
self.magnitude = magnitude
- self.pad_val = pad_val
+ self.pad_val = tuple(pad_val)
self.prob = prob
self.direction = direction
self.random_negative_prob = random_negative_prob
@@ -269,9 +317,9 @@ class Translate(object):
magnitude (int | float): The magnitude used for translate. Note that
the offset is calculated by magnitude * size in the corresponding
direction. With a magnitude of 1, the whole image will be moved out
- of the range.
- pad_val (int, tuple[int]): Pixel pad_val value for constant fill. If a
- tuple of length 3, it is used to pad_val R, G, B channels
+ of the range.
+ pad_val (int, Sequence[int]): Pixel pad_val value for constant fill.
+ If a sequence of length 3, it is used to pad_val R, G, B channels
respectively. Defaults to 128.
prob (float): The probability for performing translate therefore should
be in range [0, 1]. Defaults to 0.5.
@@ -294,7 +342,7 @@ class Translate(object):
f'be int or float, but got {type(magnitude)} instead.'
if isinstance(pad_val, int):
pad_val = tuple([pad_val] * 3)
- elif isinstance(pad_val, tuple):
+ elif isinstance(pad_val, Sequence):
assert len(pad_val) == 3, 'pad_val as a tuple must have 3 ' \
f'elements, got {len(pad_val)} instead.'
assert all(isinstance(i, int) for i in pad_val), 'pad_val as a '\
@@ -309,7 +357,7 @@ class Translate(object):
f'should be in range [0,1], got {random_negative_prob} instead.'
self.magnitude = magnitude
- self.pad_val = pad_val
+ self.pad_val = tuple(pad_val)
self.prob = prob
self.direction = direction
self.random_negative_prob = random_negative_prob
@@ -354,11 +402,11 @@ class Rotate(object):
angle (float): The angle used for rotate. Positive values stand for
clockwise rotation.
center (tuple[float], optional): Center point (w, h) of the rotation in
- the source image. If None, the center of the image will be used.
- defaults to None.
+ the source image. If None, the center of the image will be used.
+ Defaults to None.
scale (float): Isotropic scale factor. Defaults to 1.0.
- pad_val (int, tuple[int]): Pixel pad_val value for constant fill. If a
- tuple of length 3, it is used to pad_val R, G, B channels
+ pad_val (int, Sequence[int]): Pixel pad_val value for constant fill.
+ If a sequence of length 3, it is used to pad_val R, G, B channels
respectively. Defaults to 128.
prob (float): The probability for performing Rotate therefore should be
in range [0, 1]. Defaults to 0.5.
@@ -388,7 +436,7 @@ class Rotate(object):
f'got {type(scale)} instead.'
if isinstance(pad_val, int):
pad_val = tuple([pad_val] * 3)
- elif isinstance(pad_val, tuple):
+ elif isinstance(pad_val, Sequence):
assert len(pad_val) == 3, 'pad_val as a tuple must have 3 ' \
f'elements, got {len(pad_val)} instead.'
assert all(isinstance(i, int) for i in pad_val), 'pad_val as a '\
@@ -403,7 +451,7 @@ class Rotate(object):
self.angle = angle
self.center = center
self.scale = scale
- self.pad_val = pad_val
+ self.pad_val = tuple(pad_val)
self.prob = prob
self.random_negative_prob = random_negative_prob
self.interpolation = interpolation
@@ -621,7 +669,8 @@ class Posterize(object):
assert 0 <= prob <= 1.0, 'The prob should be in range [0,1], ' \
f'got {prob} instead.'
- self.bits = int(bits)
+ # To align timm version, we need to round up to integer here.
+ self.bits = ceil(bits)
self.prob = prob
def __call__(self, results):
@@ -692,7 +741,7 @@ class ColorTransform(object):
Args:
magnitude (int | float): The magnitude used for color transform. A
positive magnitude would enhance the color and a negative magnitude
- would make the image grayer. A magnitude=0 gives the origin img.
+ would make the image grayer. A magnitude=0 gives the origin img.
prob (float): The probability for performing ColorTransform therefore
should be in range [0, 1]. Defaults to 0.5.
random_negative_prob (float): The probability that turns the magnitude
@@ -827,8 +876,8 @@ class Cutout(object):
shape (int | float | tuple(int | float)): Expected cutout shape (h, w).
If given as a single value, the value will be used for
both h and w.
- pad_val (int, tuple[int]): Pixel pad_val value for constant fill. If
- it is a tuple, it must have the same length with the image
+ pad_val (int, Sequence[int]): Pixel pad_val value for constant fill.
+ If it is a sequence, it must have the same length with the image
channels. Defaults to 128.
prob (float): The probability for performing cutout therefore should
be in range [0, 1]. Defaults to 0.5.
@@ -843,11 +892,16 @@ class Cutout(object):
raise TypeError(
'shape must be of '
f'type int, float or tuple, got {type(shape)} instead')
+ if isinstance(pad_val, int):
+ pad_val = tuple([pad_val] * 3)
+ elif isinstance(pad_val, Sequence):
+ assert len(pad_val) == 3, 'pad_val as a tuple must have 3 ' \
+ f'elements, got {len(pad_val)} instead.'
assert 0 <= prob <= 1.0, 'The prob should be in range [0,1], ' \
f'got {prob} instead.'
self.shape = shape
- self.pad_val = pad_val
+ self.pad_val = tuple(pad_val)
self.prob = prob
def __call__(self, results):
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/pipelines/compose.py b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/pipelines/compose.py
similarity index 96%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/pipelines/compose.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/datasets/pipelines/compose.py
index 21960b2225aaaffb342edc90385f85a21d31390a..012d2b63b85895916aa56e6b2860b189b8fc6db9 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/pipelines/compose.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/pipelines/compose.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
from collections.abc import Sequence
from mmcv.utils import build_from_cfg
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/pipelines/formatting.py b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/pipelines/formatting.py
new file mode 100644
index 0000000000000000000000000000000000000000..eeb1650e96f63e0c1b407502a8fd96c91b72795c
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/pipelines/formatting.py
@@ -0,0 +1,195 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from collections.abc import Sequence
+
+import mmcv
+import numpy as np
+import torch
+from mmcv.parallel import DataContainer as DC
+from PIL import Image
+
+from ..builder import PIPELINES
+
+
+def to_tensor(data):
+ """Convert objects of various python types to :obj:`torch.Tensor`.
+
+ Supported types are: :class:`numpy.ndarray`, :class:`torch.Tensor`,
+ :class:`Sequence`, :class:`int` and :class:`float`.
+ """
+ if isinstance(data, torch.Tensor):
+ return data
+ elif isinstance(data, np.ndarray):
+ return torch.from_numpy(data)
+ elif isinstance(data, Sequence) and not mmcv.is_str(data):
+ return torch.tensor(data)
+ elif isinstance(data, int):
+ return torch.LongTensor([data])
+ elif isinstance(data, float):
+ return torch.FloatTensor([data])
+ else:
+ raise TypeError(
+ f'Type {type(data)} cannot be converted to tensor.'
+ 'Supported types are: `numpy.ndarray`, `torch.Tensor`, '
+ '`Sequence`, `int` and `float`')
+
+
+@PIPELINES.register_module()
+class ToTensor(object):
+
+ def __init__(self, keys):
+ self.keys = keys
+
+ def __call__(self, results):
+ for key in self.keys:
+ results[key] = to_tensor(results[key])
+ return results
+
+ def __repr__(self):
+ return self.__class__.__name__ + f'(keys={self.keys})'
+
+
+@PIPELINES.register_module()
+class ImageToTensor(object):
+
+ def __init__(self, keys):
+ self.keys = keys
+
+ def __call__(self, results):
+ for key in self.keys:
+ img = results[key]
+ if len(img.shape) < 3:
+ img = np.expand_dims(img, -1)
+ results[key] = to_tensor(img.transpose(2, 0, 1))
+ return results
+
+ def __repr__(self):
+ return self.__class__.__name__ + f'(keys={self.keys})'
+
+
+@PIPELINES.register_module()
+class Transpose(object):
+
+ def __init__(self, keys, order):
+ self.keys = keys
+ self.order = order
+
+ def __call__(self, results):
+ for key in self.keys:
+ results[key] = results[key].transpose(self.order)
+ return results
+
+ def __repr__(self):
+ return self.__class__.__name__ + \
+ f'(keys={self.keys}, order={self.order})'
+
+
+@PIPELINES.register_module()
+class ToPIL(object):
+
+ def __init__(self):
+ pass
+
+ def __call__(self, results):
+ results['img'] = Image.fromarray(results['img'])
+ return results
+
+
+@PIPELINES.register_module()
+class ToNumpy(object):
+
+ def __init__(self):
+ pass
+
+ def __call__(self, results):
+ results['img'] = np.array(results['img'], dtype=np.float32)
+ return results
+
+
+@PIPELINES.register_module()
+class Collect(object):
+ """Collect data from the loader relevant to the specific task.
+
+ This is usually the last stage of the data loader pipeline. Typically keys
+ is set to some subset of "img" and "gt_label".
+
+ Args:
+ keys (Sequence[str]): Keys of results to be collected in ``data``.
+ meta_keys (Sequence[str], optional): Meta keys to be converted to
+ ``mmcv.DataContainer`` and collected in ``data[img_metas]``.
+ Default: ('filename', 'ori_shape', 'img_shape', 'flip',
+ 'flip_direction', 'img_norm_cfg')
+
+ Returns:
+ dict: The result dict contains the following keys
+
+ - keys in ``self.keys``
+ - ``img_metas`` if available
+ """
+
+ def __init__(self,
+ keys,
+ meta_keys=('filename', 'ori_filename', 'ori_shape',
+ 'img_shape', 'flip', 'flip_direction',
+ 'img_norm_cfg')):
+ self.keys = keys
+ self.meta_keys = meta_keys
+
+ def __call__(self, results):
+ data = {}
+ img_meta = {}
+ for key in self.meta_keys:
+ if key in results:
+ img_meta[key] = results[key]
+ data['img_metas'] = DC(img_meta, cpu_only=True)
+ for key in self.keys:
+ data[key] = results[key]
+ return data
+
+ def __repr__(self):
+ return self.__class__.__name__ + \
+ f'(keys={self.keys}, meta_keys={self.meta_keys})'
+
+
+@PIPELINES.register_module()
+class WrapFieldsToLists(object):
+ """Wrap fields of the data dictionary into lists for evaluation.
+
+ This class can be used as a last step of a test or validation
+ pipeline for single image evaluation or inference.
+
+ Example:
+ >>> test_pipeline = [
+ >>> dict(type='LoadImageFromFile'),
+ >>> dict(type='Normalize',
+ mean=[123.675, 116.28, 103.53],
+ std=[58.395, 57.12, 57.375],
+ to_rgb=True),
+ >>> dict(type='ImageToTensor', keys=['img']),
+ >>> dict(type='Collect', keys=['img']),
+ >>> dict(type='WrapIntoLists')
+ >>> ]
+ """
+
+ def __call__(self, results):
+ # Wrap dict fields into lists
+ for key, val in results.items():
+ results[key] = [val]
+ return results
+
+ def __repr__(self):
+ return f'{self.__class__.__name__}()'
+
+
+@PIPELINES.register_module()
+class ToHalf(object):
+
+ def __init__(self, keys):
+ self.keys = keys
+
+ def __call__(self, results):
+ for k in self.keys:
+ if isinstance(results[k], torch.Tensor):
+ results[k] = results[k].to(torch.half)
+ else:
+ results[k] = results[k].astype(np.float16)
+ return results
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/pipelines/loading.py b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/pipelines/loading.py
similarity index 98%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/pipelines/loading.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/datasets/pipelines/loading.py
index ce185f0369596046bfc15608edd2170365f54291..b5d8e95d76ba8c612ae23720e3014cce9efaaf80 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/pipelines/loading.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/pipelines/loading.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import os.path as osp
import mmcv
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/pipelines/transforms.py b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/pipelines/transforms.py
similarity index 84%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/pipelines/transforms.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/datasets/pipelines/transforms.py
index fdc72efc5e76f30ab64dd8d4cbc1cd9aebdbd291..a56ce3c362b129a1b7118dc54c8032c89c586b64 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/datasets/pipelines/transforms.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/pipelines/transforms.py
@@ -1,3 +1,5 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import copy
import inspect
import math
import random
@@ -36,18 +38,19 @@ class RandomCrop(object):
pad_val (Number | Sequence[Number]): Pixel pad_val value for constant
fill. If a tuple of length 3, it is used to pad_val R, G, B
channels respectively. Default: 0.
- padding_mode (str): Type of padding. Should be: constant, edge,
- reflect or symmetric. Default: constant.
- -constant: Pads with a constant value, this value is specified
+ padding_mode (str): Type of padding. Defaults to "constant". Should
+ be one of the following:
+
+ - constant: Pads with a constant value, this value is specified \
with pad_val.
- -edge: pads with the last value at the edge of the image.
- -reflect: Pads with reflection of image without repeating the
- last value on the edge. For example, padding [1, 2, 3, 4]
- with 2 elements on both sides in reflect mode will result
+ - edge: pads with the last value at the edge of the image.
+ - reflect: Pads with reflection of image without repeating the \
+ last value on the edge. For example, padding [1, 2, 3, 4] \
+ with 2 elements on both sides in reflect mode will result \
in [3, 2, 1, 2, 3, 4, 3, 2].
- -symmetric: Pads with reflection of image repeating the last
- value on the edge. For example, padding [1, 2, 3, 4] with
- 2 elements on both sides in symmetric mode will result in
+ - symmetric: Pads with reflection of image repeating the last \
+ value on the edge. For example, padding [1, 2, 3, 4] with \
+ 2 elements on both sides in symmetric mode will result in \
[2, 1, 1, 2, 3, 4, 4, 3].
"""
@@ -151,7 +154,7 @@ class RandomResizedCrop(object):
to the original image. Defaults to (0.08, 1.0).
ratio (tuple): Range of the random aspect ratio of the cropped image
compared to the original image. Defaults to (3. / 4., 4. / 3.).
- max_attempts (int): Maxinum number of attempts before falling back to
+ max_attempts (int): Maximum number of attempts before falling back to
Central Crop. Defaults to 10.
efficientnet_style (bool): Whether to use efficientnet style Random
ResizedCrop. Defaults to False.
@@ -163,7 +166,7 @@ class RandomResizedCrop(object):
interpolation (str): Interpolation method, accepted values are
'nearest', 'bilinear', 'bicubic', 'area', 'lanczos'. Defaults to
'bilinear'.
- backend (str): The image resize backend type, accpeted values are
+ backend (str): The image resize backend type, accepted values are
`cv2` and `pillow`. Defaults to `cv2`.
"""
@@ -191,7 +194,7 @@ class RandomResizedCrop(object):
f'But received scale {scale} and rato {ratio}.')
assert min_covered >= 0, 'min_covered should be no less than 0.'
assert isinstance(max_attempts, int) and max_attempts >= 0, \
- 'max_attempts mush be of typle int and no less than 0.'
+ 'max_attempts mush be int and no less than 0.'
assert interpolation in ('nearest', 'bilinear', 'bicubic', 'area',
'lanczos')
if backend not in ['cv2', 'pillow']:
@@ -217,7 +220,7 @@ class RandomResizedCrop(object):
compared to the original image size.
ratio (tuple): Range of the random aspect ratio of the cropped
image compared to the original image area.
- max_attempts (int): Maxinum number of attempts before falling back
+ max_attempts (int): Maximum number of attempts before falling back
to central crop. Defaults to 10.
Returns:
@@ -279,7 +282,7 @@ class RandomResizedCrop(object):
compared to the original image size.
ratio (tuple): Range of the random aspect ratio of the cropped
image compared to the original image area.
- max_attempts (int): Maxinum number of attempts before falling back
+ max_attempts (int): Maximum number of attempts before falling back
to central crop. Defaults to 10.
min_covered (Number): Minimum ratio of the cropped area to the
original area. Only valid if efficientnet_style is true.
@@ -311,7 +314,7 @@ class RandomResizedCrop(object):
max_target_height = min(max_target_height, height)
min_target_height = min(max_target_height, min_target_height)
- # slightly differs from tf inplementation
+ # slightly differs from tf implementation
target_height = int(
round(random.uniform(min_target_height, max_target_height)))
target_width = int(round(target_height * aspect_ratio))
@@ -393,11 +396,12 @@ class RandomGrayscale(object):
grayscale. Default: 0.1.
Returns:
- ndarray: Grayscale version of the input image with probability
- gray_prob and unchanged with probability (1-gray_prob).
- - If input image is 1 channel: grayscale version is 1 channel.
- - If input image is 3 channel: grayscale version is 3 channel
- with r == g == b.
+ ndarray: Image after randomly grayscale transform.
+
+ Notes:
+ - If input image is 1 channel: grayscale version is 1 channel.
+ - If input image is 3 channel: grayscale version is 3 channel
+ with r == g == b.
"""
def __init__(self, gray_prob=0.1):
@@ -484,20 +488,24 @@ class RandomErasing(object):
if float, it will be converted to (aspect_ratio, 1/aspect_ratio)
Default: (3/10, 10/3)
mode (str): Fill method in erased area, can be:
- - 'const' (default): All pixels are assign with the same value.
- - 'rand': each pixel is assigned with a random value in [0, 255]
+
+ - const (default): All pixels are assign with the same value.
+ - rand: each pixel is assigned with a random value in [0, 255]
+
fill_color (sequence | Number): Base color filled in erased area.
- Default: (128, 128, 128)
- fill_std (sequence | Number, optional): If set and mode='rand', fill
- erased area with random color from normal distribution
+ Defaults to (128, 128, 128).
+ fill_std (sequence | Number, optional): If set and ``mode`` is 'rand',
+ fill erased area with random color from normal distribution
(mean=fill_color, std=fill_std); If not set, fill erased area with
- random color from uniform distribution (0~255)
- Default: None
+ random color from uniform distribution (0~255). Defaults to None.
Note:
- See https://arxiv.org/pdf/1708.04896.pdf
+ See `Random Erasing Data Augmentation
+ `_
+
This paper provided 4 modes: RE-R, RE-M, RE-0, RE-255, and use RE-M as
- default.
+ default. The config of these 4 modes are:
+
- RE-R: RandomErasing(mode='rand')
- RE-M: RandomErasing(mode='const', fill_color=(123.67, 116.3, 103.5))
- RE-0: RandomErasing(mode='const', fill_color=0)
@@ -605,6 +613,58 @@ class RandomErasing(object):
return repr_str
+@PIPELINES.register_module()
+class Pad(object):
+ """Pad images.
+
+ Args:
+ size (tuple[int] | None): Expected padding size (h, w). Conflicts with
+ pad_to_square. Defaults to None.
+ pad_to_square (bool): Pad any image to square shape. Defaults to False.
+ pad_val (Number | Sequence[Number]): Values to be filled in padding
+ areas when padding_mode is 'constant'. Default to 0.
+ padding_mode (str): Type of padding. Should be: constant, edge,
+ reflect or symmetric. Default to "constant".
+ """
+
+ def __init__(self,
+ size=None,
+ pad_to_square=False,
+ pad_val=0,
+ padding_mode='constant'):
+ assert (size is None) ^ (pad_to_square is False), \
+ 'Only one of [size, pad_to_square] should be given, ' \
+ f'but get {(size is not None) + (pad_to_square is not False)}'
+ self.size = size
+ self.pad_to_square = pad_to_square
+ self.pad_val = pad_val
+ self.padding_mode = padding_mode
+
+ def __call__(self, results):
+ for key in results.get('img_fields', ['img']):
+ img = results[key]
+ if self.pad_to_square:
+ target_size = tuple(
+ max(img.shape[0], img.shape[1]) for _ in range(2))
+ else:
+ target_size = self.size
+ img = mmcv.impad(
+ img,
+ shape=target_size,
+ pad_val=self.pad_val,
+ padding_mode=self.padding_mode)
+ results[key] = img
+ results['img_shape'] = img.shape
+ return results
+
+ def __repr__(self):
+ repr_str = self.__class__.__name__
+ repr_str += f'(size={self.size}, '
+ repr_str += f'(pad_val={self.pad_val}, '
+ repr_str += f'padding_mode={self.padding_mode})'
+ return repr_str
+
+
@PIPELINES.register_module()
class Resize(object):
"""Resize images.
@@ -613,35 +673,49 @@ class Resize(object):
size (int | tuple): Images scales for resizing (h, w).
When size is int, the default behavior is to resize an image
to (size, size). When size is tuple and the second value is -1,
- the short edge of an image is resized to its first value.
- For example, when size is 224, the image is resized to 224x224.
- When size is (224, -1), the short side is resized to 224 and the
- other side is computed based on the short side, maintaining the
- aspect ratio.
- interpolation (str): Interpolation method, accepted values are
- "nearest", "bilinear", "bicubic", "area", "lanczos".
+ the image will be resized according to adaptive_side. For example,
+ when size is 224, the image is resized to 224x224. When size is
+ (224, -1) and adaptive_size is "short", the short side is resized
+ to 224 and the other side is computed based on the short side,
+ maintaining the aspect ratio.
+ interpolation (str): Interpolation method. For "cv2" backend, accepted
+ values are "nearest", "bilinear", "bicubic", "area", "lanczos". For
+ "pillow" backend, accepted values are "nearest", "bilinear",
+ "bicubic", "box", "lanczos", "hamming".
More details can be found in `mmcv.image.geometric`.
- backend (str): The image resize backend type, accpeted values are
+ adaptive_side(str): Adaptive resize policy, accepted values are
+ "short", "long", "height", "width". Default to "short".
+ backend (str): The image resize backend type, accepted values are
`cv2` and `pillow`. Default: `cv2`.
"""
- def __init__(self, size, interpolation='bilinear', backend='cv2'):
+ def __init__(self,
+ size,
+ interpolation='bilinear',
+ adaptive_side='short',
+ backend='cv2'):
assert isinstance(size, int) or (isinstance(size, tuple)
and len(size) == 2)
- self.resize_w_short_side = False
+ assert adaptive_side in {'short', 'long', 'height', 'width'}
+
+ self.adaptive_side = adaptive_side
+ self.adaptive_resize = False
if isinstance(size, int):
assert size > 0
size = (size, size)
else:
assert size[0] > 0 and (size[1] > 0 or size[1] == -1)
if size[1] == -1:
- self.resize_w_short_side = True
- assert interpolation in ('nearest', 'bilinear', 'bicubic', 'area',
- 'lanczos')
+ self.adaptive_resize = True
if backend not in ['cv2', 'pillow']:
raise ValueError(f'backend: {backend} is not supported for resize.'
'Supported backends are "cv2", "pillow"')
-
+ if backend == 'cv2':
+ assert interpolation in ('nearest', 'bilinear', 'bicubic', 'area',
+ 'lanczos')
+ else:
+ assert interpolation in ('nearest', 'bilinear', 'bicubic', 'box',
+ 'lanczos', 'hamming')
self.size = size
self.interpolation = interpolation
self.backend = backend
@@ -650,19 +724,29 @@ class Resize(object):
for key in results.get('img_fields', ['img']):
img = results[key]
ignore_resize = False
- if self.resize_w_short_side:
+ if self.adaptive_resize:
h, w = img.shape[:2]
- short_side = self.size[0]
- if (w <= h and w == short_side) or (h <= w
- and h == short_side):
+ target_size = self.size[0]
+
+ condition_ignore_resize = {
+ 'short': min(h, w) == target_size,
+ 'long': max(h, w) == target_size,
+ 'height': h == target_size,
+ 'width': w == target_size
+ }
+
+ if condition_ignore_resize[self.adaptive_side]:
ignore_resize = True
+ elif any([
+ self.adaptive_side == 'short' and w < h,
+ self.adaptive_side == 'long' and w > h,
+ self.adaptive_side == 'width',
+ ]):
+ width = target_size
+ height = int(target_size * h / w)
else:
- if w < h:
- width = short_side
- height = int(short_side * h / w)
- else:
- height = short_side
- width = int(short_side * w / h)
+ height = target_size
+ width = int(target_size * w / h)
else:
height, width = self.size
if not ignore_resize:
@@ -700,21 +784,23 @@ class CenterCrop(object):
32.
interpolation (str): Interpolation method, accepted values are
'nearest', 'bilinear', 'bicubic', 'area', 'lanczos'. Only valid if
- efficientnet style is True. Defaults to 'bilinear'.
- backend (str): The image resize backend type, accpeted values are
+ ``efficientnet_style`` is True. Defaults to 'bilinear'.
+ backend (str): The image resize backend type, accepted values are
`cv2` and `pillow`. Only valid if efficientnet style is True.
Defaults to `cv2`.
Notes:
- If the image is smaller than the crop size, return the original image.
- If efficientnet_style is set to False, the pipeline would be a simple
- center crop using the crop_size.
- If efficientnet_style is set to True, the pipeline will be to first to
- perform the center crop with the crop_size_ as:
+ - If the image is smaller than the crop size, return the original
+ image.
+ - If efficientnet_style is set to False, the pipeline would be a simple
+ center crop using the crop_size.
+ - If efficientnet_style is set to True, the pipeline will be to first
+ to perform the center crop with the ``crop_size_`` as:
.. math::
- crop\_size\_ = crop\_size / (crop\_size + crop\_padding) * short\_edge
+ \text{crop_size_} = \frac{\text{crop_size}}{\text{crop_size} +
+ \text{crop_padding}} \times \text{short_edge}
And then the pipeline resizes the img to the input crop size.
"""
@@ -886,7 +972,7 @@ class Lighting(object):
eigvec (list[list]): the eigenvector of the convariance matrix of pixel
values, respectively.
alphastd (float): The standard deviation for distribution of alpha.
- Dafaults to 0.1
+ Defaults to 0.1
to_rgb (bool): Whether to convert img to rgb.
"""
@@ -1032,19 +1118,23 @@ class Albu(object):
return updated_dict
def __call__(self, results):
+
+ # backup gt_label in case Albu modify it.
+ _gt_label = copy.deepcopy(results.get('gt_label', None))
+
# dict to albumentations format
results = self.mapper(results, self.keymap_to_albu)
+ # process aug
results = self.aug(**results)
- if 'gt_labels' in results:
- if isinstance(results['gt_labels'], list):
- results['gt_labels'] = np.array(results['gt_labels'])
- results['gt_labels'] = results['gt_labels'].astype(np.int64)
-
# back to the original format
results = self.mapper(results, self.keymap_back)
+ if _gt_label is not None:
+ # recover backup gt_label
+ results.update({'gt_label': _gt_label})
+
# update final shape
if self.update_pad_shape:
results['pad_shape'] = results['img'].shape
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/samplers/__init__.py b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/samplers/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..70162885a1dd57ae0b9289dbb8e00bd046c51989
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/samplers/__init__.py
@@ -0,0 +1,5 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from .distributed_sampler import DistributedSampler
+from .repeat_aug import RepeatAugSampler
+
+__all__ = ('DistributedSampler', 'RepeatAugSampler')
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/samplers/distributed_sampler.py b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/samplers/distributed_sampler.py
new file mode 100644
index 0000000000000000000000000000000000000000..9e78c400693a3edb4d124eacf1beae648f5004ee
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/samplers/distributed_sampler.py
@@ -0,0 +1,61 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import torch
+from torch.utils.data import DistributedSampler as _DistributedSampler
+
+from mmcls.core.utils import sync_random_seed
+from mmcls.datasets import SAMPLERS
+from mmcls.utils import auto_select_device
+
+
+@SAMPLERS.register_module()
+class DistributedSampler(_DistributedSampler):
+
+ def __init__(self,
+ dataset,
+ num_replicas=None,
+ rank=None,
+ shuffle=True,
+ round_up=True,
+ seed=0):
+ super().__init__(dataset, num_replicas=num_replicas, rank=rank)
+ self.shuffle = shuffle
+ self.round_up = round_up
+ if self.round_up:
+ self.total_size = self.num_samples * self.num_replicas
+ else:
+ self.total_size = len(self.dataset)
+
+ # In distributed sampling, different ranks should sample
+ # non-overlapped data in the dataset. Therefore, this function
+ # is used to make sure that each rank shuffles the data indices
+ # in the same order based on the same seed. Then different ranks
+ # could use different indices to select non-overlapped data from the
+ # same data list.
+ self.seed = sync_random_seed(seed, device=auto_select_device())
+
+ def __iter__(self):
+ # deterministically shuffle based on epoch
+ if self.shuffle:
+ g = torch.Generator()
+ # When :attr:`shuffle=True`, this ensures all replicas
+ # use a different random ordering for each epoch.
+ # Otherwise, the next iteration of this sampler will
+ # yield the same ordering.
+ g.manual_seed(self.epoch + self.seed)
+ indices = torch.randperm(len(self.dataset), generator=g).tolist()
+ else:
+ indices = torch.arange(len(self.dataset)).tolist()
+
+ # add extra samples to make it evenly divisible
+ if self.round_up:
+ indices = (
+ indices *
+ int(self.total_size / len(indices) + 1))[:self.total_size]
+ assert len(indices) == self.total_size
+
+ # subsample
+ indices = indices[self.rank:self.total_size:self.num_replicas]
+ if self.round_up:
+ assert len(indices) == self.num_samples
+
+ return iter(indices)
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/samplers/repeat_aug.py b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/samplers/repeat_aug.py
new file mode 100644
index 0000000000000000000000000000000000000000..5de096bdef28bfe9b0b37fc8e37fe3d0e92023a8
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/samplers/repeat_aug.py
@@ -0,0 +1,106 @@
+import math
+
+import torch
+from mmcv.runner import get_dist_info
+from torch.utils.data import Sampler
+
+from mmcls.core.utils import sync_random_seed
+from mmcls.datasets import SAMPLERS
+
+
+@SAMPLERS.register_module()
+class RepeatAugSampler(Sampler):
+ """Sampler that restricts data loading to a subset of the dataset for
+ distributed, with repeated augmentation. It ensures that different each
+ augmented version of a sample will be visible to a different process (GPU).
+ Heavily based on torch.utils.data.DistributedSampler.
+
+ This sampler was taken from
+ https://github.com/facebookresearch/deit/blob/0c4b8f60/samplers.py
+ Used in
+ Copyright (c) 2015-present, Facebook, Inc.
+ """
+
+ def __init__(self,
+ dataset,
+ num_replicas=None,
+ rank=None,
+ shuffle=True,
+ num_repeats=3,
+ selected_round=256,
+ selected_ratio=0,
+ seed=0):
+ default_rank, default_world_size = get_dist_info()
+ rank = default_rank if rank is None else rank
+ num_replicas = (
+ default_world_size if num_replicas is None else num_replicas)
+
+ self.dataset = dataset
+ self.num_replicas = num_replicas
+ self.rank = rank
+ self.shuffle = shuffle
+ self.num_repeats = num_repeats
+ self.epoch = 0
+ self.num_samples = int(
+ math.ceil(len(self.dataset) * num_repeats / self.num_replicas))
+ self.total_size = self.num_samples * self.num_replicas
+ # Determine the number of samples to select per epoch for each rank.
+ # num_selected logic defaults to be the same as original RASampler
+ # impl, but this one can be tweaked
+ # via selected_ratio and selected_round args.
+ selected_ratio = selected_ratio or num_replicas # ratio to reduce
+ # selected samples by, num_replicas if 0
+ if selected_round:
+ self.num_selected_samples = int(
+ math.floor(
+ len(self.dataset) // selected_round * selected_round /
+ selected_ratio))
+ else:
+ self.num_selected_samples = int(
+ math.ceil(len(self.dataset) / selected_ratio))
+
+ # In distributed sampling, different ranks should sample
+ # non-overlapped data in the dataset. Therefore, this function
+ # is used to make sure that each rank shuffles the data indices
+ # in the same order based on the same seed. Then different ranks
+ # could use different indices to select non-overlapped data from the
+ # same data list.
+ self.seed = sync_random_seed(seed)
+
+ def __iter__(self):
+ # deterministically shuffle based on epoch
+ if self.shuffle:
+ if self.num_replicas > 1: # In distributed environment
+ # deterministically shuffle based on epoch
+ g = torch.Generator()
+ # When :attr:`shuffle=True`, this ensures all replicas
+ # use a different random ordering for each epoch.
+ # Otherwise, the next iteration of this sampler will
+ # yield the same ordering.
+ g.manual_seed(self.epoch + self.seed)
+ indices = torch.randperm(
+ len(self.dataset), generator=g).tolist()
+ else:
+ indices = torch.randperm(len(self.dataset)).tolist()
+ else:
+ indices = list(range(len(self.dataset)))
+
+ # produce repeats e.g. [0, 0, 0, 1, 1, 1, 2, 2, 2....]
+ indices = [x for x in indices for _ in range(self.num_repeats)]
+ # add extra samples to make it evenly divisible
+ padding_size = self.total_size - len(indices)
+ indices += indices[:padding_size]
+ assert len(indices) == self.total_size
+
+ # subsample per rank
+ indices = indices[self.rank:self.total_size:self.num_replicas]
+ assert len(indices) == self.num_samples
+
+ # return up to num selected samples
+ return iter(indices[:self.num_selected_samples])
+
+ def __len__(self):
+ return self.num_selected_samples
+
+ def set_epoch(self, epoch):
+ self.epoch = epoch
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/stanford_cars.py b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/stanford_cars.py
new file mode 100644
index 0000000000000000000000000000000000000000..df1f95126f65b58869ebdad4f09110bc822c37ea
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/stanford_cars.py
@@ -0,0 +1,210 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import os.path as osp
+from typing import Optional
+
+import numpy as np
+
+from .base_dataset import BaseDataset
+from .builder import DATASETS
+
+
+@DATASETS.register_module()
+class StanfordCars(BaseDataset):
+ """`Stanford Cars`_ Dataset.
+
+ After downloading and decompression, the dataset
+ directory structure is as follows.
+
+ Stanford Cars dataset directory::
+
+ Stanford Cars
+ ├── cars_train
+ │ ├── 00001.jpg
+ │ ├── 00002.jpg
+ │ └── ...
+ ├── cars_test
+ │ ├── 00001.jpg
+ │ ├── 00002.jpg
+ │ └── ...
+ └── devkit
+ ├── cars_meta.mat
+ ├── cars_train_annos.mat
+ ├── cars_test_annos.mat
+ ├── cars_test_annoswithlabels.mat
+ ├── eval_train.m
+ └── train_perfect_preds.txt
+
+ .. _Stanford Cars: https://ai.stanford.edu/~jkrause/cars/car_dataset.html
+
+ Args:
+ data_prefix (str): the prefix of data path
+ test_mode (bool): ``test_mode=True`` means in test phase. It determines
+ to use the training set or test set.
+ ann_file (str, optional): The annotation file. If is string, read
+ samples paths from the ann_file. If is None, read samples path
+ from cars_{train|test}_annos.mat file. Defaults to None.
+ """ # noqa: E501
+
+ CLASSES = [
+ 'AM General Hummer SUV 2000', 'Acura RL Sedan 2012',
+ 'Acura TL Sedan 2012', 'Acura TL Type-S 2008', 'Acura TSX Sedan 2012',
+ 'Acura Integra Type R 2001', 'Acura ZDX Hatchback 2012',
+ 'Aston Martin V8 Vantage Convertible 2012',
+ 'Aston Martin V8 Vantage Coupe 2012',
+ 'Aston Martin Virage Convertible 2012',
+ 'Aston Martin Virage Coupe 2012', 'Audi RS 4 Convertible 2008',
+ 'Audi A5 Coupe 2012', 'Audi TTS Coupe 2012', 'Audi R8 Coupe 2012',
+ 'Audi V8 Sedan 1994', 'Audi 100 Sedan 1994', 'Audi 100 Wagon 1994',
+ 'Audi TT Hatchback 2011', 'Audi S6 Sedan 2011',
+ 'Audi S5 Convertible 2012', 'Audi S5 Coupe 2012', 'Audi S4 Sedan 2012',
+ 'Audi S4 Sedan 2007', 'Audi TT RS Coupe 2012',
+ 'BMW ActiveHybrid 5 Sedan 2012', 'BMW 1 Series Convertible 2012',
+ 'BMW 1 Series Coupe 2012', 'BMW 3 Series Sedan 2012',
+ 'BMW 3 Series Wagon 2012', 'BMW 6 Series Convertible 2007',
+ 'BMW X5 SUV 2007', 'BMW X6 SUV 2012', 'BMW M3 Coupe 2012',
+ 'BMW M5 Sedan 2010', 'BMW M6 Convertible 2010', 'BMW X3 SUV 2012',
+ 'BMW Z4 Convertible 2012',
+ 'Bentley Continental Supersports Conv. Convertible 2012',
+ 'Bentley Arnage Sedan 2009', 'Bentley Mulsanne Sedan 2011',
+ 'Bentley Continental GT Coupe 2012',
+ 'Bentley Continental GT Coupe 2007',
+ 'Bentley Continental Flying Spur Sedan 2007',
+ 'Bugatti Veyron 16.4 Convertible 2009',
+ 'Bugatti Veyron 16.4 Coupe 2009', 'Buick Regal GS 2012',
+ 'Buick Rainier SUV 2007', 'Buick Verano Sedan 2012',
+ 'Buick Enclave SUV 2012', 'Cadillac CTS-V Sedan 2012',
+ 'Cadillac SRX SUV 2012', 'Cadillac Escalade EXT Crew Cab 2007',
+ 'Chevrolet Silverado 1500 Hybrid Crew Cab 2012',
+ 'Chevrolet Corvette Convertible 2012', 'Chevrolet Corvette ZR1 2012',
+ 'Chevrolet Corvette Ron Fellows Edition Z06 2007',
+ 'Chevrolet Traverse SUV 2012', 'Chevrolet Camaro Convertible 2012',
+ 'Chevrolet HHR SS 2010', 'Chevrolet Impala Sedan 2007',
+ 'Chevrolet Tahoe Hybrid SUV 2012', 'Chevrolet Sonic Sedan 2012',
+ 'Chevrolet Express Cargo Van 2007',
+ 'Chevrolet Avalanche Crew Cab 2012', 'Chevrolet Cobalt SS 2010',
+ 'Chevrolet Malibu Hybrid Sedan 2010', 'Chevrolet TrailBlazer SS 2009',
+ 'Chevrolet Silverado 2500HD Regular Cab 2012',
+ 'Chevrolet Silverado 1500 Classic Extended Cab 2007',
+ 'Chevrolet Express Van 2007', 'Chevrolet Monte Carlo Coupe 2007',
+ 'Chevrolet Malibu Sedan 2007',
+ 'Chevrolet Silverado 1500 Extended Cab 2012',
+ 'Chevrolet Silverado 1500 Regular Cab 2012', 'Chrysler Aspen SUV 2009',
+ 'Chrysler Sebring Convertible 2010',
+ 'Chrysler Town and Country Minivan 2012', 'Chrysler 300 SRT-8 2010',
+ 'Chrysler Crossfire Convertible 2008',
+ 'Chrysler PT Cruiser Convertible 2008', 'Daewoo Nubira Wagon 2002',
+ 'Dodge Caliber Wagon 2012', 'Dodge Caliber Wagon 2007',
+ 'Dodge Caravan Minivan 1997', 'Dodge Ram Pickup 3500 Crew Cab 2010',
+ 'Dodge Ram Pickup 3500 Quad Cab 2009', 'Dodge Sprinter Cargo Van 2009',
+ 'Dodge Journey SUV 2012', 'Dodge Dakota Crew Cab 2010',
+ 'Dodge Dakota Club Cab 2007', 'Dodge Magnum Wagon 2008',
+ 'Dodge Challenger SRT8 2011', 'Dodge Durango SUV 2012',
+ 'Dodge Durango SUV 2007', 'Dodge Charger Sedan 2012',
+ 'Dodge Charger SRT-8 2009', 'Eagle Talon Hatchback 1998',
+ 'FIAT 500 Abarth 2012', 'FIAT 500 Convertible 2012',
+ 'Ferrari FF Coupe 2012', 'Ferrari California Convertible 2012',
+ 'Ferrari 458 Italia Convertible 2012', 'Ferrari 458 Italia Coupe 2012',
+ 'Fisker Karma Sedan 2012', 'Ford F-450 Super Duty Crew Cab 2012',
+ 'Ford Mustang Convertible 2007', 'Ford Freestar Minivan 2007',
+ 'Ford Expedition EL SUV 2009', 'Ford Edge SUV 2012',
+ 'Ford Ranger SuperCab 2011', 'Ford GT Coupe 2006',
+ 'Ford F-150 Regular Cab 2012', 'Ford F-150 Regular Cab 2007',
+ 'Ford Focus Sedan 2007', 'Ford E-Series Wagon Van 2012',
+ 'Ford Fiesta Sedan 2012', 'GMC Terrain SUV 2012',
+ 'GMC Savana Van 2012', 'GMC Yukon Hybrid SUV 2012',
+ 'GMC Acadia SUV 2012', 'GMC Canyon Extended Cab 2012',
+ 'Geo Metro Convertible 1993', 'HUMMER H3T Crew Cab 2010',
+ 'HUMMER H2 SUT Crew Cab 2009', 'Honda Odyssey Minivan 2012',
+ 'Honda Odyssey Minivan 2007', 'Honda Accord Coupe 2012',
+ 'Honda Accord Sedan 2012', 'Hyundai Veloster Hatchback 2012',
+ 'Hyundai Santa Fe SUV 2012', 'Hyundai Tucson SUV 2012',
+ 'Hyundai Veracruz SUV 2012', 'Hyundai Sonata Hybrid Sedan 2012',
+ 'Hyundai Elantra Sedan 2007', 'Hyundai Accent Sedan 2012',
+ 'Hyundai Genesis Sedan 2012', 'Hyundai Sonata Sedan 2012',
+ 'Hyundai Elantra Touring Hatchback 2012', 'Hyundai Azera Sedan 2012',
+ 'Infiniti G Coupe IPL 2012', 'Infiniti QX56 SUV 2011',
+ 'Isuzu Ascender SUV 2008', 'Jaguar XK XKR 2012',
+ 'Jeep Patriot SUV 2012', 'Jeep Wrangler SUV 2012',
+ 'Jeep Liberty SUV 2012', 'Jeep Grand Cherokee SUV 2012',
+ 'Jeep Compass SUV 2012', 'Lamborghini Reventon Coupe 2008',
+ 'Lamborghini Aventador Coupe 2012',
+ 'Lamborghini Gallardo LP 570-4 Superleggera 2012',
+ 'Lamborghini Diablo Coupe 2001', 'Land Rover Range Rover SUV 2012',
+ 'Land Rover LR2 SUV 2012', 'Lincoln Town Car Sedan 2011',
+ 'MINI Cooper Roadster Convertible 2012',
+ 'Maybach Landaulet Convertible 2012', 'Mazda Tribute SUV 2011',
+ 'McLaren MP4-12C Coupe 2012',
+ 'Mercedes-Benz 300-Class Convertible 1993',
+ 'Mercedes-Benz C-Class Sedan 2012',
+ 'Mercedes-Benz SL-Class Coupe 2009',
+ 'Mercedes-Benz E-Class Sedan 2012', 'Mercedes-Benz S-Class Sedan 2012',
+ 'Mercedes-Benz Sprinter Van 2012', 'Mitsubishi Lancer Sedan 2012',
+ 'Nissan Leaf Hatchback 2012', 'Nissan NV Passenger Van 2012',
+ 'Nissan Juke Hatchback 2012', 'Nissan 240SX Coupe 1998',
+ 'Plymouth Neon Coupe 1999', 'Porsche Panamera Sedan 2012',
+ 'Ram C/V Cargo Van Minivan 2012',
+ 'Rolls-Royce Phantom Drophead Coupe Convertible 2012',
+ 'Rolls-Royce Ghost Sedan 2012', 'Rolls-Royce Phantom Sedan 2012',
+ 'Scion xD Hatchback 2012', 'Spyker C8 Convertible 2009',
+ 'Spyker C8 Coupe 2009', 'Suzuki Aerio Sedan 2007',
+ 'Suzuki Kizashi Sedan 2012', 'Suzuki SX4 Hatchback 2012',
+ 'Suzuki SX4 Sedan 2012', 'Tesla Model S Sedan 2012',
+ 'Toyota Sequoia SUV 2012', 'Toyota Camry Sedan 2012',
+ 'Toyota Corolla Sedan 2012', 'Toyota 4Runner SUV 2012',
+ 'Volkswagen Golf Hatchback 2012', 'Volkswagen Golf Hatchback 1991',
+ 'Volkswagen Beetle Hatchback 2012', 'Volvo C30 Hatchback 2012',
+ 'Volvo 240 Sedan 1993', 'Volvo XC90 SUV 2007',
+ 'smart fortwo Convertible 2012'
+ ]
+
+ def __init__(self,
+ data_prefix: str,
+ test_mode: bool,
+ ann_file: Optional[str] = None,
+ **kwargs):
+ if test_mode:
+ if ann_file is not None:
+ self.test_ann_file = ann_file
+ else:
+ self.test_ann_file = osp.join(
+ data_prefix, 'devkit/cars_test_annos_withlabels.mat')
+ data_prefix = osp.join(data_prefix, 'cars_test')
+ else:
+ if ann_file is not None:
+ self.train_ann_file = ann_file
+ else:
+ self.train_ann_file = osp.join(data_prefix,
+ 'devkit/cars_train_annos.mat')
+ data_prefix = osp.join(data_prefix, 'cars_train')
+ super(StanfordCars, self).__init__(
+ ann_file=ann_file,
+ data_prefix=data_prefix,
+ test_mode=test_mode,
+ **kwargs)
+
+ def load_annotations(self):
+ try:
+ import scipy.io as sio
+ except ImportError:
+ raise ImportError(
+ 'please run `pip install scipy` to install package `scipy`.')
+
+ data_infos = []
+ if self.test_mode:
+ data = sio.loadmat(self.test_ann_file)
+ else:
+ data = sio.loadmat(self.train_ann_file)
+ for img in data['annotations'][0]:
+ info = {'img_prefix': self.data_prefix}
+ # The organization of each record is as follows,
+ # 0: bbox_x1 of each image
+ # 1: bbox_y1 of each image
+ # 2: bbox_x2 of each image
+ # 3: bbox_y2 of each image
+ # 4: class_id, start from 0, so
+ # here we need to '- 1' to let them start from 0
+ # 5: file name of each image
+ info['img_info'] = {'filename': img[5][0]}
+ info['gt_label'] = np.array(img[4][0][0] - 1, dtype=np.int64)
+ data_infos.append(info)
+ return data_infos
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/utils.py b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..75070bc06427528320e1a53faefec885d44082e8
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/utils.py
@@ -0,0 +1,153 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import gzip
+import hashlib
+import os
+import os.path
+import shutil
+import tarfile
+import urllib.error
+import urllib.request
+import zipfile
+
+__all__ = ['rm_suffix', 'check_integrity', 'download_and_extract_archive']
+
+
+def rm_suffix(s, suffix=None):
+ if suffix is None:
+ return s[:s.rfind('.')]
+ else:
+ return s[:s.rfind(suffix)]
+
+
+def calculate_md5(fpath, chunk_size=1024 * 1024):
+ md5 = hashlib.md5()
+ with open(fpath, 'rb') as f:
+ for chunk in iter(lambda: f.read(chunk_size), b''):
+ md5.update(chunk)
+ return md5.hexdigest()
+
+
+def check_md5(fpath, md5, **kwargs):
+ return md5 == calculate_md5(fpath, **kwargs)
+
+
+def check_integrity(fpath, md5=None):
+ if not os.path.isfile(fpath):
+ return False
+ if md5 is None:
+ return True
+ return check_md5(fpath, md5)
+
+
+def download_url_to_file(url, fpath):
+ with urllib.request.urlopen(url) as resp, open(fpath, 'wb') as of:
+ shutil.copyfileobj(resp, of)
+
+
+def download_url(url, root, filename=None, md5=None):
+ """Download a file from a url and place it in root.
+
+ Args:
+ url (str): URL to download file from.
+ root (str): Directory to place downloaded file in.
+ filename (str | None): Name to save the file under.
+ If filename is None, use the basename of the URL.
+ md5 (str | None): MD5 checksum of the download.
+ If md5 is None, download without md5 check.
+ """
+ root = os.path.expanduser(root)
+ if not filename:
+ filename = os.path.basename(url)
+ fpath = os.path.join(root, filename)
+
+ os.makedirs(root, exist_ok=True)
+
+ if check_integrity(fpath, md5):
+ print(f'Using downloaded and verified file: {fpath}')
+ else:
+ try:
+ print(f'Downloading {url} to {fpath}')
+ download_url_to_file(url, fpath)
+ except (urllib.error.URLError, IOError) as e:
+ if url[:5] == 'https':
+ url = url.replace('https:', 'http:')
+ print('Failed download. Trying https -> http instead.'
+ f' Downloading {url} to {fpath}')
+ download_url_to_file(url, fpath)
+ else:
+ raise e
+ # check integrity of downloaded file
+ if not check_integrity(fpath, md5):
+ raise RuntimeError('File not found or corrupted.')
+
+
+def _is_tarxz(filename):
+ return filename.endswith('.tar.xz')
+
+
+def _is_tar(filename):
+ return filename.endswith('.tar')
+
+
+def _is_targz(filename):
+ return filename.endswith('.tar.gz')
+
+
+def _is_tgz(filename):
+ return filename.endswith('.tgz')
+
+
+def _is_gzip(filename):
+ return filename.endswith('.gz') and not filename.endswith('.tar.gz')
+
+
+def _is_zip(filename):
+ return filename.endswith('.zip')
+
+
+def extract_archive(from_path, to_path=None, remove_finished=False):
+ if to_path is None:
+ to_path = os.path.dirname(from_path)
+
+ if _is_tar(from_path):
+ with tarfile.open(from_path, 'r') as tar:
+ tar.extractall(path=to_path)
+ elif _is_targz(from_path) or _is_tgz(from_path):
+ with tarfile.open(from_path, 'r:gz') as tar:
+ tar.extractall(path=to_path)
+ elif _is_tarxz(from_path):
+ with tarfile.open(from_path, 'r:xz') as tar:
+ tar.extractall(path=to_path)
+ elif _is_gzip(from_path):
+ to_path = os.path.join(
+ to_path,
+ os.path.splitext(os.path.basename(from_path))[0])
+ with open(to_path, 'wb') as out_f, gzip.GzipFile(from_path) as zip_f:
+ out_f.write(zip_f.read())
+ elif _is_zip(from_path):
+ with zipfile.ZipFile(from_path, 'r') as z:
+ z.extractall(to_path)
+ else:
+ raise ValueError(f'Extraction of {from_path} not supported')
+
+ if remove_finished:
+ os.remove(from_path)
+
+
+def download_and_extract_archive(url,
+ download_root,
+ extract_root=None,
+ filename=None,
+ md5=None,
+ remove_finished=False):
+ download_root = os.path.expanduser(download_root)
+ if extract_root is None:
+ extract_root = download_root
+ if not filename:
+ filename = os.path.basename(url)
+
+ download_url(url, download_root, filename, md5)
+
+ archive = os.path.join(download_root, filename)
+ print(f'Extracting {archive} to {extract_root}')
+ extract_archive(archive, extract_root, remove_finished)
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/voc.py b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/voc.py
new file mode 100644
index 0000000000000000000000000000000000000000..e9c8bceb18aaf7347fc88e45328917c314ac336c
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/datasets/voc.py
@@ -0,0 +1,94 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import os.path as osp
+import xml.etree.ElementTree as ET
+
+import mmcv
+import numpy as np
+
+from .builder import DATASETS
+from .multi_label import MultiLabelDataset
+
+
+@DATASETS.register_module()
+class VOC(MultiLabelDataset):
+ """`Pascal VOC `_ Dataset.
+
+ Args:
+ data_prefix (str): the prefix of data path
+ pipeline (list): a list of dict, where each element represents
+ a operation defined in `mmcls.datasets.pipelines`
+ ann_file (str | None): the annotation file. When ann_file is str,
+ the subclass is expected to read from the ann_file. When ann_file
+ is None, the subclass is expected to read according to data_prefix
+ difficult_as_postive (Optional[bool]): Whether to map the difficult
+ labels as positive. If it set to True, map difficult examples to
+ positive ones(1), If it set to False, map difficult examples to
+ negative ones(0). Defaults to None, the difficult labels will be
+ set to '-1'.
+ """
+
+ CLASSES = ('aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car',
+ 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse',
+ 'motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train',
+ 'tvmonitor')
+
+ def __init__(self, difficult_as_postive=None, **kwargs):
+ self.difficult_as_postive = difficult_as_postive
+ super(VOC, self).__init__(**kwargs)
+ if 'VOC2007' in self.data_prefix:
+ self.year = 2007
+ else:
+ raise ValueError('Cannot infer dataset year from img_prefix.')
+
+ def load_annotations(self):
+ """Load annotations.
+
+ Returns:
+ list[dict]: Annotation info from XML file.
+ """
+ data_infos = []
+ img_ids = mmcv.list_from_file(self.ann_file)
+ for img_id in img_ids:
+ filename = f'JPEGImages/{img_id}.jpg'
+ xml_path = osp.join(self.data_prefix, 'Annotations',
+ f'{img_id}.xml')
+ tree = ET.parse(xml_path)
+ root = tree.getroot()
+ labels = []
+ labels_difficult = []
+ for obj in root.findall('object'):
+ label_name = obj.find('name').text
+ # in case customized dataset has wrong labels
+ # or CLASSES has been override.
+ if label_name not in self.CLASSES:
+ continue
+ label = self.class_to_idx[label_name]
+ difficult = int(obj.find('difficult').text)
+ if difficult:
+ labels_difficult.append(label)
+ else:
+ labels.append(label)
+
+ gt_label = np.zeros(len(self.CLASSES))
+ # set difficult example first, then set postivate examples.
+ # The order cannot be swapped for the case where multiple objects
+ # of the same kind exist and some are difficult.
+ if self.difficult_as_postive is None:
+ # map difficult examples to -1,
+ # it may be used in evaluation to ignore difficult targets.
+ gt_label[labels_difficult] = -1
+ elif self.difficult_as_postive:
+ # map difficult examples to positive ones(1).
+ gt_label[labels_difficult] = 1
+ else:
+ # map difficult examples to negative ones(0).
+ gt_label[labels_difficult] = 0
+ gt_label[labels] = 1
+
+ info = dict(
+ img_prefix=self.data_prefix,
+ img_info=dict(filename=filename),
+ gt_label=gt_label.astype(np.int8))
+ data_infos.append(info)
+
+ return data_infos
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/__init__.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..b501833eae63f3cd7b2678d392441f724ca8f6bf
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/__init__.py
@@ -0,0 +1,14 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from .backbones import * # noqa: F401,F403
+from .builder import (BACKBONES, CLASSIFIERS, HEADS, LOSSES, NECKS,
+ build_backbone, build_classifier, build_head, build_loss,
+ build_neck)
+from .classifiers import * # noqa: F401,F403
+from .heads import * # noqa: F401,F403
+from .losses import * # noqa: F401,F403
+from .necks import * # noqa: F401,F403
+
+__all__ = [
+ 'BACKBONES', 'HEADS', 'NECKS', 'LOSSES', 'CLASSIFIERS', 'build_backbone',
+ 'build_head', 'build_neck', 'build_loss', 'build_classifier'
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/__init__.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..a919a42cba0d109279dab61a659eafe22022d0be
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/__init__.py
@@ -0,0 +1,51 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from .alexnet import AlexNet
+from .conformer import Conformer
+from .convmixer import ConvMixer
+from .convnext import ConvNeXt
+from .cspnet import CSPDarkNet, CSPNet, CSPResNet, CSPResNeXt
+from .deit import DistilledVisionTransformer
+from .densenet import DenseNet
+from .efficientformer import EfficientFormer
+from .efficientnet import EfficientNet
+from .hornet import HorNet
+from .hrnet import HRNet
+from .lenet import LeNet5
+from .mlp_mixer import MlpMixer
+from .mobilenet_v2 import MobileNetV2
+from .mobilenet_v3 import MobileNetV3
+from .mvit import MViT
+from .poolformer import PoolFormer
+from .regnet import RegNet
+from .repmlp import RepMLPNet
+from .repvgg import RepVGG
+from .res2net import Res2Net
+from .resnest import ResNeSt
+from .resnet import ResNet, ResNetV1c, ResNetV1d
+from .resnet_cifar import ResNet_CIFAR
+from .resnext import ResNeXt
+from .seresnet import SEResNet
+from .seresnext import SEResNeXt
+from .shufflenet_v1 import ShuffleNetV1
+from .shufflenet_v2 import ShuffleNetV2
+from .swin_transformer import SwinTransformer
+from .swin_transformer_v2 import SwinTransformerV2
+from .t2t_vit import T2T_ViT
+from .timm_backbone import TIMMBackbone
+from .tnt import TNT
+from .twins import PCPVT, SVT
+from .van import VAN
+from .vgg import VGG
+from .vision_transformer import VisionTransformer
+
+__all__ = [
+ 'LeNet5', 'AlexNet', 'VGG', 'RegNet', 'ResNet', 'ResNeXt', 'ResNetV1d',
+ 'ResNeSt', 'ResNet_CIFAR', 'SEResNet', 'SEResNeXt', 'ShuffleNetV1',
+ 'ShuffleNetV2', 'MobileNetV2', 'MobileNetV3', 'VisionTransformer',
+ 'SwinTransformer', 'SwinTransformerV2', 'TNT', 'TIMMBackbone', 'T2T_ViT',
+ 'Res2Net', 'RepVGG', 'Conformer', 'MlpMixer', 'DistilledVisionTransformer',
+ 'PCPVT', 'SVT', 'EfficientNet', 'ConvNeXt', 'HRNet', 'ResNetV1c',
+ 'ConvMixer', 'CSPDarkNet', 'CSPResNet', 'CSPResNeXt', 'CSPNet',
+ 'RepMLPNet', 'PoolFormer', 'DenseNet', 'VAN', 'MViT', 'EfficientFormer',
+ 'HorNet'
+]
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/alexnet.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/alexnet.py
similarity index 96%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/alexnet.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/alexnet.py
index ee1d2afe05ab601cf094d59b42efcd8184361d3b..1b74dc70aad2311802025f34781cd0d8b65f1c4c 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/alexnet.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/alexnet.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import torch.nn as nn
from ..builder import BACKBONES
@@ -52,4 +53,4 @@ class AlexNet(BaseBackbone):
x = x.view(x.size(0), 256 * 6 * 6)
x = self.classifier(x)
- return x
+ return (x, )
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/base_backbone.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/base_backbone.py
similarity index 94%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/base_backbone.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/base_backbone.py
index 12852d322fadd4fa3fd3e4282590c2e7c7fd25ad..c1050fab1c35c7886571c44638ce0ea25b88653d 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/base_backbone.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/base_backbone.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
from abc import ABCMeta, abstractmethod
from mmcv.runner import BaseModule
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/conformer.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/conformer.py
new file mode 100644
index 0000000000000000000000000000000000000000..e70c62dbe1c5218016591f6105fee2a06cfbf799
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/conformer.py
@@ -0,0 +1,626 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from typing import Sequence
+
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+from mmcv.cnn import build_activation_layer, build_norm_layer
+from mmcv.cnn.bricks.drop import DropPath
+from mmcv.cnn.bricks.transformer import AdaptivePadding
+from mmcv.cnn.utils.weight_init import trunc_normal_
+
+from mmcls.utils import get_root_logger
+from ..builder import BACKBONES
+from .base_backbone import BaseBackbone, BaseModule
+from .vision_transformer import TransformerEncoderLayer
+
+
+class ConvBlock(BaseModule):
+ """Basic convluation block used in Conformer.
+
+ This block includes three convluation modules, and supports three new
+ functions:
+ 1. Returns the output of both the final layers and the second convluation
+ module.
+ 2. Fuses the input of the second convluation module with an extra input
+ feature map.
+ 3. Supports to add an extra convluation module to the identity connection.
+
+ Args:
+ in_channels (int): The number of input channels.
+ out_channels (int): The number of output channels.
+ stride (int): The stride of the second convluation module.
+ Defaults to 1.
+ groups (int): The groups of the second convluation module.
+ Defaults to 1.
+ drop_path_rate (float): The rate of the DropPath layer. Defaults to 0.
+ with_residual_conv (bool): Whether to add an extra convluation module
+ to the identity connection. Defaults to False.
+ norm_cfg (dict): The config of normalization layers.
+ Defaults to ``dict(type='BN', eps=1e-6)``.
+ act_cfg (dict): The config of activative functions.
+ Defaults to ``dict(type='ReLU', inplace=True))``.
+ init_cfg (dict, optional): The extra config to initialize the module.
+ Defaults to None.
+ """
+
+ def __init__(self,
+ in_channels,
+ out_channels,
+ stride=1,
+ groups=1,
+ drop_path_rate=0.,
+ with_residual_conv=False,
+ norm_cfg=dict(type='BN', eps=1e-6),
+ act_cfg=dict(type='ReLU', inplace=True),
+ init_cfg=None):
+ super(ConvBlock, self).__init__(init_cfg=init_cfg)
+
+ expansion = 4
+ mid_channels = out_channels // expansion
+
+ self.conv1 = nn.Conv2d(
+ in_channels,
+ mid_channels,
+ kernel_size=1,
+ stride=1,
+ padding=0,
+ bias=False)
+ self.bn1 = build_norm_layer(norm_cfg, mid_channels)[1]
+ self.act1 = build_activation_layer(act_cfg)
+
+ self.conv2 = nn.Conv2d(
+ mid_channels,
+ mid_channels,
+ kernel_size=3,
+ stride=stride,
+ groups=groups,
+ padding=1,
+ bias=False)
+ self.bn2 = build_norm_layer(norm_cfg, mid_channels)[1]
+ self.act2 = build_activation_layer(act_cfg)
+
+ self.conv3 = nn.Conv2d(
+ mid_channels,
+ out_channels,
+ kernel_size=1,
+ stride=1,
+ padding=0,
+ bias=False)
+ self.bn3 = build_norm_layer(norm_cfg, out_channels)[1]
+ self.act3 = build_activation_layer(act_cfg)
+
+ if with_residual_conv:
+ self.residual_conv = nn.Conv2d(
+ in_channels,
+ out_channels,
+ kernel_size=1,
+ stride=stride,
+ padding=0,
+ bias=False)
+ self.residual_bn = build_norm_layer(norm_cfg, out_channels)[1]
+
+ self.with_residual_conv = with_residual_conv
+ self.drop_path = DropPath(
+ drop_path_rate) if drop_path_rate > 0. else nn.Identity()
+
+ def zero_init_last_bn(self):
+ nn.init.zeros_(self.bn3.weight)
+
+ def forward(self, x, fusion_features=None, out_conv2=True):
+ identity = x
+
+ x = self.conv1(x)
+ x = self.bn1(x)
+ x = self.act1(x)
+
+ x = self.conv2(x) if fusion_features is None else self.conv2(
+ x + fusion_features)
+ x = self.bn2(x)
+ x2 = self.act2(x)
+
+ x = self.conv3(x2)
+ x = self.bn3(x)
+
+ if self.drop_path is not None:
+ x = self.drop_path(x)
+
+ if self.with_residual_conv:
+ identity = self.residual_conv(identity)
+ identity = self.residual_bn(identity)
+
+ x += identity
+ x = self.act3(x)
+
+ if out_conv2:
+ return x, x2
+ else:
+ return x
+
+
+class FCUDown(BaseModule):
+ """CNN feature maps -> Transformer patch embeddings."""
+
+ def __init__(self,
+ in_channels,
+ out_channels,
+ down_stride,
+ with_cls_token=True,
+ norm_cfg=dict(type='LN', eps=1e-6),
+ act_cfg=dict(type='GELU'),
+ init_cfg=None):
+ super(FCUDown, self).__init__(init_cfg=init_cfg)
+ self.down_stride = down_stride
+ self.with_cls_token = with_cls_token
+
+ self.conv_project = nn.Conv2d(
+ in_channels, out_channels, kernel_size=1, stride=1, padding=0)
+ self.sample_pooling = nn.AvgPool2d(
+ kernel_size=down_stride, stride=down_stride)
+
+ self.ln = build_norm_layer(norm_cfg, out_channels)[1]
+ self.act = build_activation_layer(act_cfg)
+
+ def forward(self, x, x_t):
+ x = self.conv_project(x) # [N, C, H, W]
+
+ x = self.sample_pooling(x).flatten(2).transpose(1, 2)
+ x = self.ln(x)
+ x = self.act(x)
+
+ if self.with_cls_token:
+ x = torch.cat([x_t[:, 0][:, None, :], x], dim=1)
+
+ return x
+
+
+class FCUUp(BaseModule):
+ """Transformer patch embeddings -> CNN feature maps."""
+
+ def __init__(self,
+ in_channels,
+ out_channels,
+ up_stride,
+ with_cls_token=True,
+ norm_cfg=dict(type='BN', eps=1e-6),
+ act_cfg=dict(type='ReLU', inplace=True),
+ init_cfg=None):
+ super(FCUUp, self).__init__(init_cfg=init_cfg)
+
+ self.up_stride = up_stride
+ self.with_cls_token = with_cls_token
+
+ self.conv_project = nn.Conv2d(
+ in_channels, out_channels, kernel_size=1, stride=1, padding=0)
+ self.bn = build_norm_layer(norm_cfg, out_channels)[1]
+ self.act = build_activation_layer(act_cfg)
+
+ def forward(self, x, H, W):
+ B, _, C = x.shape
+ # [N, 197, 384] -> [N, 196, 384] -> [N, 384, 196] -> [N, 384, 14, 14]
+ if self.with_cls_token:
+ x_r = x[:, 1:].transpose(1, 2).reshape(B, C, H, W)
+ else:
+ x_r = x.transpose(1, 2).reshape(B, C, H, W)
+
+ x_r = self.act(self.bn(self.conv_project(x_r)))
+
+ return F.interpolate(
+ x_r, size=(H * self.up_stride, W * self.up_stride))
+
+
+class ConvTransBlock(BaseModule):
+ """Basic module for Conformer.
+
+ This module is a fusion of CNN block transformer encoder block.
+
+ Args:
+ in_channels (int): The number of input channels in conv blocks.
+ out_channels (int): The number of output channels in conv blocks.
+ embed_dims (int): The embedding dimension in transformer blocks.
+ conv_stride (int): The stride of conv2d layers. Defaults to 1.
+ groups (int): The groups of conv blocks. Defaults to 1.
+ with_residual_conv (bool): Whether to add a conv-bn layer to the
+ identity connect in the conv block. Defaults to False.
+ down_stride (int): The stride of the downsample pooling layer.
+ Defaults to 4.
+ num_heads (int): The number of heads in transformer attention layers.
+ Defaults to 12.
+ mlp_ratio (float): The expansion ratio in transformer FFN module.
+ Defaults to 4.
+ qkv_bias (bool): Enable bias for qkv if True. Defaults to False.
+ with_cls_token (bool): Whether use class token or not.
+ Defaults to True.
+ drop_rate (float): The dropout rate of the output projection and
+ FFN in the transformer block. Defaults to 0.
+ attn_drop_rate (float): The dropout rate after the attention
+ calculation in the transformer block. Defaults to 0.
+ drop_path_rate (bloat): The drop path rate in both the conv block
+ and the transformer block. Defaults to 0.
+ last_fusion (bool): Whether this block is the last stage. If so,
+ downsample the fusion feature map.
+ init_cfg (dict, optional): The extra config to initialize the module.
+ Defaults to None.
+ """
+
+ def __init__(self,
+ in_channels,
+ out_channels,
+ embed_dims,
+ conv_stride=1,
+ groups=1,
+ with_residual_conv=False,
+ down_stride=4,
+ num_heads=12,
+ mlp_ratio=4.,
+ qkv_bias=False,
+ with_cls_token=True,
+ drop_rate=0.,
+ attn_drop_rate=0.,
+ drop_path_rate=0.,
+ last_fusion=False,
+ init_cfg=None):
+ super(ConvTransBlock, self).__init__(init_cfg=init_cfg)
+ expansion = 4
+ self.cnn_block = ConvBlock(
+ in_channels=in_channels,
+ out_channels=out_channels,
+ with_residual_conv=with_residual_conv,
+ stride=conv_stride,
+ groups=groups)
+
+ if last_fusion:
+ self.fusion_block = ConvBlock(
+ in_channels=out_channels,
+ out_channels=out_channels,
+ stride=2,
+ with_residual_conv=True,
+ groups=groups,
+ drop_path_rate=drop_path_rate)
+ else:
+ self.fusion_block = ConvBlock(
+ in_channels=out_channels,
+ out_channels=out_channels,
+ groups=groups,
+ drop_path_rate=drop_path_rate)
+
+ self.squeeze_block = FCUDown(
+ in_channels=out_channels // expansion,
+ out_channels=embed_dims,
+ down_stride=down_stride,
+ with_cls_token=with_cls_token)
+
+ self.expand_block = FCUUp(
+ in_channels=embed_dims,
+ out_channels=out_channels // expansion,
+ up_stride=down_stride,
+ with_cls_token=with_cls_token)
+
+ self.trans_block = TransformerEncoderLayer(
+ embed_dims=embed_dims,
+ num_heads=num_heads,
+ feedforward_channels=int(embed_dims * mlp_ratio),
+ drop_rate=drop_rate,
+ drop_path_rate=drop_path_rate,
+ attn_drop_rate=attn_drop_rate,
+ qkv_bias=qkv_bias,
+ norm_cfg=dict(type='LN', eps=1e-6))
+
+ self.down_stride = down_stride
+ self.embed_dim = embed_dims
+ self.last_fusion = last_fusion
+
+ def forward(self, cnn_input, trans_input):
+ x, x_conv2 = self.cnn_block(cnn_input, out_conv2=True)
+
+ _, _, H, W = x_conv2.shape
+
+ # Convert the feature map of conv2 to transformer embedding
+ # and concat with class token.
+ conv2_embedding = self.squeeze_block(x_conv2, trans_input)
+
+ trans_output = self.trans_block(conv2_embedding + trans_input)
+
+ # Convert the transformer output embedding to feature map
+ trans_features = self.expand_block(trans_output, H // self.down_stride,
+ W // self.down_stride)
+ x = self.fusion_block(
+ x, fusion_features=trans_features, out_conv2=False)
+
+ return x, trans_output
+
+
+@BACKBONES.register_module()
+class Conformer(BaseBackbone):
+ """Conformer backbone.
+
+ A PyTorch implementation of : `Conformer: Local Features Coupling Global
+ Representations for Visual Recognition `_
+
+ Args:
+ arch (str | dict): Conformer architecture. Defaults to 'tiny'.
+ patch_size (int): The patch size. Defaults to 16.
+ base_channels (int): The base number of channels in CNN network.
+ Defaults to 64.
+ mlp_ratio (float): The expansion ratio of FFN network in transformer
+ block. Defaults to 4.
+ with_cls_token (bool): Whether use class token or not.
+ Defaults to True.
+ drop_path_rate (float): stochastic depth rate. Defaults to 0.
+ out_indices (Sequence | int): Output from which stages.
+ Defaults to -1, means the last stage.
+ init_cfg (dict, optional): Initialization config dict.
+ Defaults to None.
+ """
+ arch_zoo = {
+ **dict.fromkeys(['t', 'tiny'],
+ {'embed_dims': 384,
+ 'channel_ratio': 1,
+ 'num_heads': 6,
+ 'depths': 12
+ }),
+ **dict.fromkeys(['s', 'small'],
+ {'embed_dims': 384,
+ 'channel_ratio': 4,
+ 'num_heads': 6,
+ 'depths': 12
+ }),
+ **dict.fromkeys(['b', 'base'],
+ {'embed_dims': 576,
+ 'channel_ratio': 6,
+ 'num_heads': 9,
+ 'depths': 12
+ }),
+ } # yapf: disable
+
+ _version = 1
+
+ def __init__(self,
+ arch='tiny',
+ patch_size=16,
+ base_channels=64,
+ mlp_ratio=4.,
+ qkv_bias=True,
+ with_cls_token=True,
+ drop_path_rate=0.,
+ norm_eval=True,
+ frozen_stages=0,
+ out_indices=-1,
+ init_cfg=None):
+
+ super().__init__(init_cfg=init_cfg)
+
+ if isinstance(arch, str):
+ arch = arch.lower()
+ assert arch in set(self.arch_zoo), \
+ f'Arch {arch} is not in default archs {set(self.arch_zoo)}'
+ self.arch_settings = self.arch_zoo[arch]
+ else:
+ essential_keys = {
+ 'embed_dims', 'depths', 'num_heads', 'channel_ratio'
+ }
+ assert isinstance(arch, dict) and set(arch) == essential_keys, \
+ f'Custom arch needs a dict with keys {essential_keys}'
+ self.arch_settings = arch
+
+ self.num_features = self.embed_dims = self.arch_settings['embed_dims']
+ self.depths = self.arch_settings['depths']
+ self.num_heads = self.arch_settings['num_heads']
+ self.channel_ratio = self.arch_settings['channel_ratio']
+
+ if isinstance(out_indices, int):
+ out_indices = [out_indices]
+ assert isinstance(out_indices, Sequence), \
+ f'"out_indices" must by a sequence or int, ' \
+ f'get {type(out_indices)} instead.'
+ for i, index in enumerate(out_indices):
+ if index < 0:
+ out_indices[i] = self.depths + index + 1
+ assert out_indices[i] >= 0, f'Invalid out_indices {index}'
+ self.out_indices = out_indices
+
+ self.norm_eval = norm_eval
+ self.frozen_stages = frozen_stages
+
+ self.with_cls_token = with_cls_token
+ if self.with_cls_token:
+ self.cls_token = nn.Parameter(torch.zeros(1, 1, self.embed_dims))
+
+ # stochastic depth decay rule
+ self.trans_dpr = [
+ x.item() for x in torch.linspace(0, drop_path_rate, self.depths)
+ ]
+
+ # Stem stage: get the feature maps by conv block
+ self.conv1 = nn.Conv2d(
+ 3, 64, kernel_size=7, stride=2, padding=3,
+ bias=False) # 1 / 2 [112, 112]
+ self.bn1 = nn.BatchNorm2d(64)
+ self.act1 = nn.ReLU(inplace=True)
+ self.maxpool = nn.MaxPool2d(
+ kernel_size=3, stride=2, padding=1) # 1 / 4 [56, 56]
+
+ assert patch_size % 16 == 0, 'The patch size of Conformer must ' \
+ 'be divisible by 16.'
+ trans_down_stride = patch_size // 4
+
+ # To solve the issue #680
+ # Auto pad the feature map to be divisible by trans_down_stride
+ self.auto_pad = AdaptivePadding(trans_down_stride, trans_down_stride)
+
+ # 1 stage
+ stage1_channels = int(base_channels * self.channel_ratio)
+ self.conv_1 = ConvBlock(
+ in_channels=64,
+ out_channels=stage1_channels,
+ with_residual_conv=True,
+ stride=1)
+ self.trans_patch_conv = nn.Conv2d(
+ 64,
+ self.embed_dims,
+ kernel_size=trans_down_stride,
+ stride=trans_down_stride,
+ padding=0)
+
+ self.trans_1 = TransformerEncoderLayer(
+ embed_dims=self.embed_dims,
+ num_heads=self.num_heads,
+ feedforward_channels=int(self.embed_dims * mlp_ratio),
+ drop_path_rate=self.trans_dpr[0],
+ qkv_bias=qkv_bias,
+ norm_cfg=dict(type='LN', eps=1e-6))
+
+ # 2~4 stage
+ init_stage = 2
+ fin_stage = self.depths // 3 + 1
+ for i in range(init_stage, fin_stage):
+ self.add_module(
+ f'conv_trans_{i}',
+ ConvTransBlock(
+ in_channels=stage1_channels,
+ out_channels=stage1_channels,
+ embed_dims=self.embed_dims,
+ conv_stride=1,
+ with_residual_conv=False,
+ down_stride=trans_down_stride,
+ num_heads=self.num_heads,
+ mlp_ratio=mlp_ratio,
+ qkv_bias=qkv_bias,
+ drop_path_rate=self.trans_dpr[i - 1],
+ with_cls_token=self.with_cls_token))
+
+ stage2_channels = int(base_channels * self.channel_ratio * 2)
+ # 5~8 stage
+ init_stage = fin_stage # 5
+ fin_stage = fin_stage + self.depths // 3 # 9
+ for i in range(init_stage, fin_stage):
+ if i == init_stage:
+ conv_stride = 2
+ in_channels = stage1_channels
+ else:
+ conv_stride = 1
+ in_channels = stage2_channels
+
+ with_residual_conv = True if i == init_stage else False
+ self.add_module(
+ f'conv_trans_{i}',
+ ConvTransBlock(
+ in_channels=in_channels,
+ out_channels=stage2_channels,
+ embed_dims=self.embed_dims,
+ conv_stride=conv_stride,
+ with_residual_conv=with_residual_conv,
+ down_stride=trans_down_stride // 2,
+ num_heads=self.num_heads,
+ mlp_ratio=mlp_ratio,
+ qkv_bias=qkv_bias,
+ drop_path_rate=self.trans_dpr[i - 1],
+ with_cls_token=self.with_cls_token))
+
+ stage3_channels = int(base_channels * self.channel_ratio * 2 * 2)
+ # 9~12 stage
+ init_stage = fin_stage # 9
+ fin_stage = fin_stage + self.depths // 3 # 13
+ for i in range(init_stage, fin_stage):
+ if i == init_stage:
+ conv_stride = 2
+ in_channels = stage2_channels
+ with_residual_conv = True
+ else:
+ conv_stride = 1
+ in_channels = stage3_channels
+ with_residual_conv = False
+
+ last_fusion = (i == self.depths)
+
+ self.add_module(
+ f'conv_trans_{i}',
+ ConvTransBlock(
+ in_channels=in_channels,
+ out_channels=stage3_channels,
+ embed_dims=self.embed_dims,
+ conv_stride=conv_stride,
+ with_residual_conv=with_residual_conv,
+ down_stride=trans_down_stride // 4,
+ num_heads=self.num_heads,
+ mlp_ratio=mlp_ratio,
+ qkv_bias=qkv_bias,
+ drop_path_rate=self.trans_dpr[i - 1],
+ with_cls_token=self.with_cls_token,
+ last_fusion=last_fusion))
+ self.fin_stage = fin_stage
+
+ self.pooling = nn.AdaptiveAvgPool2d(1)
+ self.trans_norm = nn.LayerNorm(self.embed_dims)
+
+ if self.with_cls_token:
+ trunc_normal_(self.cls_token, std=.02)
+
+ def _init_weights(self, m):
+ if isinstance(m, nn.Linear):
+ trunc_normal_(m.weight, std=.02)
+ if isinstance(m, nn.Linear) and m.bias is not None:
+ nn.init.constant_(m.bias, 0)
+ elif isinstance(m, nn.LayerNorm):
+ nn.init.constant_(m.bias, 0)
+ nn.init.constant_(m.weight, 1.0)
+ elif isinstance(m, nn.Conv2d):
+ nn.init.kaiming_normal_(
+ m.weight, mode='fan_out', nonlinearity='relu')
+ elif isinstance(m, nn.BatchNorm2d):
+ nn.init.constant_(m.weight, 1.)
+ nn.init.constant_(m.bias, 0.)
+
+ if hasattr(m, 'zero_init_last_bn'):
+ m.zero_init_last_bn()
+
+ def init_weights(self):
+ super(Conformer, self).init_weights()
+ logger = get_root_logger()
+
+ if (isinstance(self.init_cfg, dict)
+ and self.init_cfg['type'] == 'Pretrained'):
+ # Suppress default init if use pretrained model.
+ return
+ else:
+ logger.info(f'No pre-trained weights for '
+ f'{self.__class__.__name__}, '
+ f'training start from scratch')
+ self.apply(self._init_weights)
+
+ def forward(self, x):
+ output = []
+ B = x.shape[0]
+ if self.with_cls_token:
+ cls_tokens = self.cls_token.expand(B, -1, -1)
+
+ # stem
+ x_base = self.maxpool(self.act1(self.bn1(self.conv1(x))))
+ x_base = self.auto_pad(x_base)
+
+ # 1 stage [N, 64, 56, 56] -> [N, 128, 56, 56]
+ x = self.conv_1(x_base, out_conv2=False)
+ x_t = self.trans_patch_conv(x_base).flatten(2).transpose(1, 2)
+ if self.with_cls_token:
+ x_t = torch.cat([cls_tokens, x_t], dim=1)
+ x_t = self.trans_1(x_t)
+
+ # 2 ~ final
+ for i in range(2, self.fin_stage):
+ stage = getattr(self, f'conv_trans_{i}')
+ x, x_t = stage(x, x_t)
+ if i in self.out_indices:
+ if self.with_cls_token:
+ output.append([
+ self.pooling(x).flatten(1),
+ self.trans_norm(x_t)[:, 0]
+ ])
+ else:
+ # if no class token, use the mean patch token
+ # as the transformer feature.
+ output.append([
+ self.pooling(x).flatten(1),
+ self.trans_norm(x_t).mean(dim=1)
+ ])
+
+ return tuple(output)
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/convmixer.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/convmixer.py
new file mode 100644
index 0000000000000000000000000000000000000000..cb33fbfca8fc2aed9d11c032ff5eb2ae7dfda782
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/convmixer.py
@@ -0,0 +1,176 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from typing import Sequence
+
+import torch
+import torch.nn as nn
+from mmcv.cnn.bricks import (Conv2dAdaptivePadding, build_activation_layer,
+ build_norm_layer)
+from mmcv.utils import digit_version
+
+from ..builder import BACKBONES
+from .base_backbone import BaseBackbone
+
+
+class Residual(nn.Module):
+
+ def __init__(self, fn):
+ super().__init__()
+ self.fn = fn
+
+ def forward(self, x):
+ return self.fn(x) + x
+
+
+@BACKBONES.register_module()
+class ConvMixer(BaseBackbone):
+ """ConvMixer. .
+
+ A PyTorch implementation of : `Patches Are All You Need?
+ `_
+
+ Modified from the `official repo
+ `_
+ and `timm
+ `_.
+
+ Args:
+ arch (str | dict): The model's architecture. If string, it should be
+ one of architecture in ``ConvMixer.arch_settings``. And if dict, it
+ should include the following two keys:
+
+ - embed_dims (int): The dimensions of patch embedding.
+ - depth (int): Number of repetitions of ConvMixer Layer.
+ - patch_size (int): The patch size.
+ - kernel_size (int): The kernel size of depthwise conv layers.
+
+ Defaults to '768/32'.
+ in_channels (int): Number of input image channels. Defaults to 3.
+ patch_size (int): The size of one patch in the patch embed layer.
+ Defaults to 7.
+ norm_cfg (dict): The config dict for norm layers.
+ Defaults to ``dict(type='BN')``.
+ act_cfg (dict): The config dict for activation after each convolution.
+ Defaults to ``dict(type='GELU')``.
+ out_indices (Sequence | int): Output from which stages.
+ Defaults to -1, means the last stage.
+ frozen_stages (int): Stages to be frozen (all param fixed).
+ Defaults to 0, which means not freezing any parameters.
+ init_cfg (dict, optional): Initialization config dict.
+ """
+ arch_settings = {
+ '768/32': {
+ 'embed_dims': 768,
+ 'depth': 32,
+ 'patch_size': 7,
+ 'kernel_size': 7
+ },
+ '1024/20': {
+ 'embed_dims': 1024,
+ 'depth': 20,
+ 'patch_size': 14,
+ 'kernel_size': 9
+ },
+ '1536/20': {
+ 'embed_dims': 1536,
+ 'depth': 20,
+ 'patch_size': 7,
+ 'kernel_size': 9
+ },
+ }
+
+ def __init__(self,
+ arch='768/32',
+ in_channels=3,
+ norm_cfg=dict(type='BN'),
+ act_cfg=dict(type='GELU'),
+ out_indices=-1,
+ frozen_stages=0,
+ init_cfg=None):
+ super().__init__(init_cfg=init_cfg)
+
+ if isinstance(arch, str):
+ assert arch in self.arch_settings, \
+ f'Unavailable arch, please choose from ' \
+ f'({set(self.arch_settings)}) or pass a dict.'
+ arch = self.arch_settings[arch]
+ elif isinstance(arch, dict):
+ essential_keys = {
+ 'embed_dims', 'depth', 'patch_size', 'kernel_size'
+ }
+ assert isinstance(arch, dict) and essential_keys <= set(arch), \
+ f'Custom arch needs a dict with keys {essential_keys}'
+
+ self.embed_dims = arch['embed_dims']
+ self.depth = arch['depth']
+ self.patch_size = arch['patch_size']
+ self.kernel_size = arch['kernel_size']
+ self.act = build_activation_layer(act_cfg)
+
+ # check out indices and frozen stages
+ if isinstance(out_indices, int):
+ out_indices = [out_indices]
+ assert isinstance(out_indices, Sequence), \
+ f'"out_indices" must by a sequence or int, ' \
+ f'get {type(out_indices)} instead.'
+ for i, index in enumerate(out_indices):
+ if index < 0:
+ out_indices[i] = self.depth + index
+ assert out_indices[i] >= 0, f'Invalid out_indices {index}'
+ self.out_indices = out_indices
+ self.frozen_stages = frozen_stages
+
+ # Set stem layers
+ self.stem = nn.Sequential(
+ nn.Conv2d(
+ in_channels,
+ self.embed_dims,
+ kernel_size=self.patch_size,
+ stride=self.patch_size), self.act,
+ build_norm_layer(norm_cfg, self.embed_dims)[1])
+
+ # Set conv2d according to torch version
+ convfunc = nn.Conv2d
+ if digit_version(torch.__version__) < digit_version('1.9.0'):
+ convfunc = Conv2dAdaptivePadding
+
+ # Repetitions of ConvMixer Layer
+ self.stages = nn.Sequential(*[
+ nn.Sequential(
+ Residual(
+ nn.Sequential(
+ convfunc(
+ self.embed_dims,
+ self.embed_dims,
+ self.kernel_size,
+ groups=self.embed_dims,
+ padding='same'), self.act,
+ build_norm_layer(norm_cfg, self.embed_dims)[1])),
+ nn.Conv2d(self.embed_dims, self.embed_dims, kernel_size=1),
+ self.act,
+ build_norm_layer(norm_cfg, self.embed_dims)[1])
+ for _ in range(self.depth)
+ ])
+
+ self._freeze_stages()
+
+ def forward(self, x):
+ x = self.stem(x)
+ outs = []
+ for i, stage in enumerate(self.stages):
+ x = stage(x)
+ if i in self.out_indices:
+ outs.append(x)
+
+ # x = self.pooling(x).flatten(1)
+ return tuple(outs)
+
+ def train(self, mode=True):
+ super(ConvMixer, self).train(mode)
+ self._freeze_stages()
+
+ def _freeze_stages(self):
+ for i in range(self.frozen_stages):
+ stage = self.stages[i]
+ stage.eval()
+ for param in stage.parameters():
+ param.requires_grad = False
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/convnext.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/convnext.py
new file mode 100644
index 0000000000000000000000000000000000000000..00393638aaebefe5d3b73bb7dd542fbf1d70364f
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/convnext.py
@@ -0,0 +1,333 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from functools import partial
+from itertools import chain
+from typing import Sequence
+
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+from mmcv.cnn.bricks import (NORM_LAYERS, DropPath, build_activation_layer,
+ build_norm_layer)
+from mmcv.runner import BaseModule
+from mmcv.runner.base_module import ModuleList, Sequential
+
+from ..builder import BACKBONES
+from .base_backbone import BaseBackbone
+
+
+@NORM_LAYERS.register_module('LN2d')
+class LayerNorm2d(nn.LayerNorm):
+ """LayerNorm on channels for 2d images.
+
+ Args:
+ num_channels (int): The number of channels of the input tensor.
+ eps (float): a value added to the denominator for numerical stability.
+ Defaults to 1e-5.
+ elementwise_affine (bool): a boolean value that when set to ``True``,
+ this module has learnable per-element affine parameters initialized
+ to ones (for weights) and zeros (for biases). Defaults to True.
+ """
+
+ def __init__(self, num_channels: int, **kwargs) -> None:
+ super().__init__(num_channels, **kwargs)
+ self.num_channels = self.normalized_shape[0]
+
+ def forward(self, x):
+ assert x.dim() == 4, 'LayerNorm2d only supports inputs with shape ' \
+ f'(N, C, H, W), but got tensor with shape {x.shape}'
+ return F.layer_norm(
+ x.permute(0, 2, 3, 1).contiguous(), self.normalized_shape,
+ self.weight, self.bias, self.eps).permute(0, 3, 1, 2).contiguous()
+
+
+class ConvNeXtBlock(BaseModule):
+ """ConvNeXt Block.
+
+ Args:
+ in_channels (int): The number of input channels.
+ norm_cfg (dict): The config dict for norm layers.
+ Defaults to ``dict(type='LN2d', eps=1e-6)``.
+ act_cfg (dict): The config dict for activation between pointwise
+ convolution. Defaults to ``dict(type='GELU')``.
+ mlp_ratio (float): The expansion ratio in both pointwise convolution.
+ Defaults to 4.
+ linear_pw_conv (bool): Whether to use linear layer to do pointwise
+ convolution. More details can be found in the note.
+ Defaults to True.
+ drop_path_rate (float): Stochastic depth rate. Defaults to 0.
+ layer_scale_init_value (float): Init value for Layer Scale.
+ Defaults to 1e-6.
+
+ Note:
+ There are two equivalent implementations:
+
+ 1. DwConv -> LayerNorm -> 1x1 Conv -> GELU -> 1x1 Conv;
+ all outputs are in (N, C, H, W).
+ 2. DwConv -> LayerNorm -> Permute to (N, H, W, C) -> Linear -> GELU
+ -> Linear; Permute back
+
+ As default, we use the second to align with the official repository.
+ And it may be slightly faster.
+ """
+
+ def __init__(self,
+ in_channels,
+ norm_cfg=dict(type='LN2d', eps=1e-6),
+ act_cfg=dict(type='GELU'),
+ mlp_ratio=4.,
+ linear_pw_conv=True,
+ drop_path_rate=0.,
+ layer_scale_init_value=1e-6):
+ super().__init__()
+ self.depthwise_conv = nn.Conv2d(
+ in_channels,
+ in_channels,
+ kernel_size=7,
+ padding=3,
+ groups=in_channels)
+
+ self.linear_pw_conv = linear_pw_conv
+ self.norm = build_norm_layer(norm_cfg, in_channels)[1]
+
+ mid_channels = int(mlp_ratio * in_channels)
+ if self.linear_pw_conv:
+ # Use linear layer to do pointwise conv.
+ pw_conv = nn.Linear
+ else:
+ pw_conv = partial(nn.Conv2d, kernel_size=1)
+
+ self.pointwise_conv1 = pw_conv(in_channels, mid_channels)
+ self.act = build_activation_layer(act_cfg)
+ self.pointwise_conv2 = pw_conv(mid_channels, in_channels)
+
+ self.gamma = nn.Parameter(
+ layer_scale_init_value * torch.ones((in_channels)),
+ requires_grad=True) if layer_scale_init_value > 0 else None
+
+ self.drop_path = DropPath(
+ drop_path_rate) if drop_path_rate > 0. else nn.Identity()
+
+ def forward(self, x):
+ shortcut = x
+ x = self.depthwise_conv(x)
+ x = self.norm(x)
+
+ if self.linear_pw_conv:
+ x = x.permute(0, 2, 3, 1) # (N, C, H, W) -> (N, H, W, C)
+
+ x = self.pointwise_conv1(x)
+ x = self.act(x)
+ x = self.pointwise_conv2(x)
+
+ if self.linear_pw_conv:
+ x = x.permute(0, 3, 1, 2) # permute back
+
+ if self.gamma is not None:
+ x = x.mul(self.gamma.view(1, -1, 1, 1))
+
+ x = shortcut + self.drop_path(x)
+ return x
+
+
+@BACKBONES.register_module()
+class ConvNeXt(BaseBackbone):
+ """ConvNeXt.
+
+ A PyTorch implementation of : `A ConvNet for the 2020s
+ `_
+
+ Modified from the `official repo
+ `_
+ and `timm
+ `_.
+
+ Args:
+ arch (str | dict): The model's architecture. If string, it should be
+ one of architecture in ``ConvNeXt.arch_settings``. And if dict, it
+ should include the following two keys:
+
+ - depths (list[int]): Number of blocks at each stage.
+ - channels (list[int]): The number of channels at each stage.
+
+ Defaults to 'tiny'.
+ in_channels (int): Number of input image channels. Defaults to 3.
+ stem_patch_size (int): The size of one patch in the stem layer.
+ Defaults to 4.
+ norm_cfg (dict): The config dict for norm layers.
+ Defaults to ``dict(type='LN2d', eps=1e-6)``.
+ act_cfg (dict): The config dict for activation between pointwise
+ convolution. Defaults to ``dict(type='GELU')``.
+ linear_pw_conv (bool): Whether to use linear layer to do pointwise
+ convolution. Defaults to True.
+ drop_path_rate (float): Stochastic depth rate. Defaults to 0.
+ layer_scale_init_value (float): Init value for Layer Scale.
+ Defaults to 1e-6.
+ out_indices (Sequence | int): Output from which stages.
+ Defaults to -1, means the last stage.
+ frozen_stages (int): Stages to be frozen (all param fixed).
+ Defaults to 0, which means not freezing any parameters.
+ gap_before_final_norm (bool): Whether to globally average the feature
+ map before the final norm layer. In the official repo, it's only
+ used in classification task. Defaults to True.
+ init_cfg (dict, optional): Initialization config dict
+ """ # noqa: E501
+ arch_settings = {
+ 'tiny': {
+ 'depths': [3, 3, 9, 3],
+ 'channels': [96, 192, 384, 768]
+ },
+ 'small': {
+ 'depths': [3, 3, 27, 3],
+ 'channels': [96, 192, 384, 768]
+ },
+ 'base': {
+ 'depths': [3, 3, 27, 3],
+ 'channels': [128, 256, 512, 1024]
+ },
+ 'large': {
+ 'depths': [3, 3, 27, 3],
+ 'channels': [192, 384, 768, 1536]
+ },
+ 'xlarge': {
+ 'depths': [3, 3, 27, 3],
+ 'channels': [256, 512, 1024, 2048]
+ },
+ }
+
+ def __init__(self,
+ arch='tiny',
+ in_channels=3,
+ stem_patch_size=4,
+ norm_cfg=dict(type='LN2d', eps=1e-6),
+ act_cfg=dict(type='GELU'),
+ linear_pw_conv=True,
+ drop_path_rate=0.,
+ layer_scale_init_value=1e-6,
+ out_indices=-1,
+ frozen_stages=0,
+ gap_before_final_norm=True,
+ init_cfg=None):
+ super().__init__(init_cfg=init_cfg)
+
+ if isinstance(arch, str):
+ assert arch in self.arch_settings, \
+ f'Unavailable arch, please choose from ' \
+ f'({set(self.arch_settings)}) or pass a dict.'
+ arch = self.arch_settings[arch]
+ elif isinstance(arch, dict):
+ assert 'depths' in arch and 'channels' in arch, \
+ f'The arch dict must have "depths" and "channels", ' \
+ f'but got {list(arch.keys())}.'
+
+ self.depths = arch['depths']
+ self.channels = arch['channels']
+ assert (isinstance(self.depths, Sequence)
+ and isinstance(self.channels, Sequence)
+ and len(self.depths) == len(self.channels)), \
+ f'The "depths" ({self.depths}) and "channels" ({self.channels}) ' \
+ 'should be both sequence with the same length.'
+
+ self.num_stages = len(self.depths)
+
+ if isinstance(out_indices, int):
+ out_indices = [out_indices]
+ assert isinstance(out_indices, Sequence), \
+ f'"out_indices" must by a sequence or int, ' \
+ f'get {type(out_indices)} instead.'
+ for i, index in enumerate(out_indices):
+ if index < 0:
+ out_indices[i] = 4 + index
+ assert out_indices[i] >= 0, f'Invalid out_indices {index}'
+ self.out_indices = out_indices
+
+ self.frozen_stages = frozen_stages
+ self.gap_before_final_norm = gap_before_final_norm
+
+ # stochastic depth decay rule
+ dpr = [
+ x.item()
+ for x in torch.linspace(0, drop_path_rate, sum(self.depths))
+ ]
+ block_idx = 0
+
+ # 4 downsample layers between stages, including the stem layer.
+ self.downsample_layers = ModuleList()
+ stem = nn.Sequential(
+ nn.Conv2d(
+ in_channels,
+ self.channels[0],
+ kernel_size=stem_patch_size,
+ stride=stem_patch_size),
+ build_norm_layer(norm_cfg, self.channels[0])[1],
+ )
+ self.downsample_layers.append(stem)
+
+ # 4 feature resolution stages, each consisting of multiple residual
+ # blocks
+ self.stages = nn.ModuleList()
+
+ for i in range(self.num_stages):
+ depth = self.depths[i]
+ channels = self.channels[i]
+
+ if i >= 1:
+ downsample_layer = nn.Sequential(
+ LayerNorm2d(self.channels[i - 1]),
+ nn.Conv2d(
+ self.channels[i - 1],
+ channels,
+ kernel_size=2,
+ stride=2),
+ )
+ self.downsample_layers.append(downsample_layer)
+
+ stage = Sequential(*[
+ ConvNeXtBlock(
+ in_channels=channels,
+ drop_path_rate=dpr[block_idx + j],
+ norm_cfg=norm_cfg,
+ act_cfg=act_cfg,
+ linear_pw_conv=linear_pw_conv,
+ layer_scale_init_value=layer_scale_init_value)
+ for j in range(depth)
+ ])
+ block_idx += depth
+
+ self.stages.append(stage)
+
+ if i in self.out_indices:
+ norm_layer = build_norm_layer(norm_cfg, channels)[1]
+ self.add_module(f'norm{i}', norm_layer)
+
+ self._freeze_stages()
+
+ def forward(self, x):
+ outs = []
+ for i, stage in enumerate(self.stages):
+ x = self.downsample_layers[i](x)
+ x = stage(x)
+ if i in self.out_indices:
+ norm_layer = getattr(self, f'norm{i}')
+ if self.gap_before_final_norm:
+ gap = x.mean([-2, -1], keepdim=True)
+ outs.append(norm_layer(gap).flatten(1))
+ else:
+ # The output of LayerNorm2d may be discontiguous, which
+ # may cause some problem in the downstream tasks
+ outs.append(norm_layer(x).contiguous())
+
+ return tuple(outs)
+
+ def _freeze_stages(self):
+ for i in range(self.frozen_stages):
+ downsample_layer = self.downsample_layers[i]
+ stage = self.stages[i]
+ downsample_layer.eval()
+ stage.eval()
+ for param in chain(downsample_layer.parameters(),
+ stage.parameters()):
+ param.requires_grad = False
+
+ def train(self, mode=True):
+ super(ConvNeXt, self).train(mode)
+ self._freeze_stages()
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/cspnet.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/cspnet.py
new file mode 100644
index 0000000000000000000000000000000000000000..70aff4c881a9d67563471a500dc4c7d925d610ef
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/cspnet.py
@@ -0,0 +1,679 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import math
+from typing import Sequence
+
+import torch
+import torch.nn as nn
+from mmcv.cnn import ConvModule, DepthwiseSeparableConvModule
+from mmcv.cnn.bricks import DropPath
+from mmcv.runner import BaseModule, Sequential
+from torch.nn.modules.batchnorm import _BatchNorm
+
+from ..builder import BACKBONES
+from ..utils import to_ntuple
+from .resnet import Bottleneck as ResNetBottleneck
+from .resnext import Bottleneck as ResNeXtBottleneck
+
+eps = 1.0e-5
+
+
+class DarknetBottleneck(BaseModule):
+ """The basic bottleneck block used in Darknet. Each DarknetBottleneck
+ consists of two ConvModules and the input is added to the final output.
+ Each ConvModule is composed of Conv, BN, and LeakyReLU. The first convLayer
+ has filter size of 1x1 and the second one has the filter size of 3x3.
+
+ Args:
+ in_channels (int): The input channels of this Module.
+ out_channels (int): The output channels of this Module.
+ expansion (int): The ratio of ``out_channels/mid_channels`` where
+ ``mid_channels`` is the input/output channels of conv2.
+ Defaults to 4.
+ add_identity (bool): Whether to add identity to the out.
+ Defaults to True.
+ use_depthwise (bool): Whether to use depthwise separable convolution.
+ Defaults to False.
+ conv_cfg (dict): Config dict for convolution layer. Defaults to None,
+ which means using conv2d.
+ drop_path_rate (float): The ratio of the drop path layer. Default: 0.
+ norm_cfg (dict): Config dict for normalization layer.
+ Defaults to ``dict(type='BN', eps=1e-5)``.
+ act_cfg (dict): Config dict for activation layer.
+ Defaults to ``dict(type='Swish')``.
+ """
+
+ def __init__(self,
+ in_channels,
+ out_channels,
+ expansion=2,
+ add_identity=True,
+ use_depthwise=False,
+ conv_cfg=None,
+ drop_path_rate=0,
+ norm_cfg=dict(type='BN', eps=1e-5),
+ act_cfg=dict(type='LeakyReLU', inplace=True),
+ init_cfg=None):
+ super().__init__(init_cfg)
+ hidden_channels = int(out_channels / expansion)
+ conv = DepthwiseSeparableConvModule if use_depthwise else ConvModule
+ self.conv1 = ConvModule(
+ in_channels,
+ hidden_channels,
+ 1,
+ conv_cfg=conv_cfg,
+ norm_cfg=norm_cfg,
+ act_cfg=act_cfg)
+ self.conv2 = conv(
+ hidden_channels,
+ out_channels,
+ 3,
+ stride=1,
+ padding=1,
+ conv_cfg=conv_cfg,
+ norm_cfg=norm_cfg,
+ act_cfg=act_cfg)
+ self.add_identity = \
+ add_identity and in_channels == out_channels
+
+ self.drop_path = DropPath(drop_prob=drop_path_rate
+ ) if drop_path_rate > eps else nn.Identity()
+
+ def forward(self, x):
+ identity = x
+ out = self.conv1(x)
+ out = self.conv2(out)
+ out = self.drop_path(out)
+
+ if self.add_identity:
+ return out + identity
+ else:
+ return out
+
+
+class CSPStage(BaseModule):
+ """Cross Stage Partial Stage.
+
+ .. code:: text
+
+ Downsample Convolution (optional)
+ |
+ |
+ Expand Convolution
+ |
+ |
+ Split to xa, xb
+ | \
+ | \
+ | blocks(xb)
+ | /
+ | / transition
+ | /
+ Concat xa, blocks(xb)
+ |
+ Transition Convolution
+
+ Args:
+ block_fn (nn.module): The basic block function in the Stage.
+ in_channels (int): The input channels of the CSP layer.
+ out_channels (int): The output channels of the CSP layer.
+ has_downsampler (bool): Whether to add a downsampler in the stage.
+ Default: False.
+ down_growth (bool): Whether to expand the channels in the
+ downsampler layer of the stage. Default: False.
+ expand_ratio (float): The expand ratio to adjust the number of
+ channels of the expand conv layer. Default: 0.5
+ bottle_ratio (float): Ratio to adjust the number of channels of the
+ hidden layer. Default: 0.5
+ block_dpr (float): The ratio of the drop path layer in the
+ blocks of the stage. Default: 0.
+ num_blocks (int): Number of blocks. Default: 1
+ conv_cfg (dict, optional): Config dict for convolution layer.
+ Default: None, which means using conv2d.
+ norm_cfg (dict): Config dict for normalization layer.
+ Default: dict(type='BN')
+ act_cfg (dict): Config dict for activation layer.
+ Default: dict(type='LeakyReLU', inplace=True)
+ """
+
+ def __init__(self,
+ block_fn,
+ in_channels,
+ out_channels,
+ has_downsampler=True,
+ down_growth=False,
+ expand_ratio=0.5,
+ bottle_ratio=2,
+ num_blocks=1,
+ block_dpr=0,
+ block_args={},
+ conv_cfg=None,
+ norm_cfg=dict(type='BN', eps=1e-5),
+ act_cfg=dict(type='LeakyReLU', inplace=True),
+ init_cfg=None):
+ super().__init__(init_cfg)
+ # grow downsample channels to output channels
+ down_channels = out_channels if down_growth else in_channels
+ block_dpr = to_ntuple(num_blocks)(block_dpr)
+
+ if has_downsampler:
+ self.downsample_conv = ConvModule(
+ in_channels=in_channels,
+ out_channels=down_channels,
+ kernel_size=3,
+ stride=2,
+ padding=1,
+ groups=32 if block_fn is ResNeXtBottleneck else 1,
+ norm_cfg=norm_cfg,
+ act_cfg=act_cfg)
+ else:
+ self.downsample_conv = nn.Identity()
+
+ exp_channels = int(down_channels * expand_ratio)
+ self.expand_conv = ConvModule(
+ in_channels=down_channels,
+ out_channels=exp_channels,
+ kernel_size=1,
+ norm_cfg=norm_cfg,
+ act_cfg=act_cfg if block_fn is DarknetBottleneck else None)
+
+ assert exp_channels % 2 == 0, \
+ 'The channel number before blocks must be divisible by 2.'
+ block_channels = exp_channels // 2
+ blocks = []
+ for i in range(num_blocks):
+ block_cfg = dict(
+ in_channels=block_channels,
+ out_channels=block_channels,
+ expansion=bottle_ratio,
+ drop_path_rate=block_dpr[i],
+ conv_cfg=conv_cfg,
+ norm_cfg=norm_cfg,
+ act_cfg=act_cfg,
+ **block_args)
+ blocks.append(block_fn(**block_cfg))
+ self.blocks = Sequential(*blocks)
+ self.atfer_blocks_conv = ConvModule(
+ block_channels,
+ block_channels,
+ 1,
+ norm_cfg=norm_cfg,
+ act_cfg=act_cfg)
+
+ self.final_conv = ConvModule(
+ 2 * block_channels,
+ out_channels,
+ 1,
+ norm_cfg=norm_cfg,
+ act_cfg=act_cfg)
+
+ def forward(self, x):
+ x = self.downsample_conv(x)
+ x = self.expand_conv(x)
+
+ split = x.shape[1] // 2
+ xa, xb = x[:, :split], x[:, split:]
+
+ xb = self.blocks(xb)
+ xb = self.atfer_blocks_conv(xb).contiguous()
+
+ x_final = torch.cat((xa, xb), dim=1)
+ return self.final_conv(x_final)
+
+
+class CSPNet(BaseModule):
+ """The abstract CSP Network class.
+
+ A Pytorch implementation of `CSPNet: A New Backbone that can Enhance
+ Learning Capability of CNN `_
+
+ This class is an abstract class because the Cross Stage Partial Network
+ (CSPNet) is a kind of universal network structure, and you
+ network block to implement networks like CSPResNet, CSPResNeXt and
+ CSPDarkNet.
+
+ Args:
+ arch (dict): The architecture of the CSPNet.
+ It should have the following keys:
+
+ - block_fn (Callable): A function or class to return a block
+ module, and it should accept at least ``in_channels``,
+ ``out_channels``, ``expansion``, ``drop_path_rate``, ``norm_cfg``
+ and ``act_cfg``.
+ - in_channels (Tuple[int]): The number of input channels of each
+ stage.
+ - out_channels (Tuple[int]): The number of output channels of each
+ stage.
+ - num_blocks (Tuple[int]): The number of blocks in each stage.
+ - expansion_ratio (float | Tuple[float]): The expansion ratio in
+ the expand convolution of each stage. Defaults to 0.5.
+ - bottle_ratio (float | Tuple[float]): The expansion ratio of
+ blocks in each stage. Defaults to 2.
+ - has_downsampler (bool | Tuple[bool]): Whether to add a
+ downsample convolution in each stage. Defaults to True
+ - down_growth (bool | Tuple[bool]): Whether to expand the channels
+ in the downsampler layer of each stage. Defaults to False.
+ - block_args (dict | Tuple[dict], optional): The extra arguments to
+ the blocks in each stage. Defaults to None.
+
+ stem_fn (Callable): A function or class to return a stem module.
+ And it should accept ``in_channels``.
+ in_channels (int): Number of input image channels. Defaults to 3.
+ out_indices (int | Sequence[int]): Output from which stages.
+ Defaults to -1, which means the last stage.
+ frozen_stages (int): Stages to be frozen (stop grad and set eval mode).
+ -1 means not freezing any parameters. Defaults to -1.
+ conv_cfg (dict, optional): The config dict for conv layers in blocks.
+ Defaults to None, which means use Conv2d.
+ norm_cfg (dict): The config dict for norm layers.
+ Defaults to ``dict(type='BN', eps=1e-5)``.
+ act_cfg (dict): The config dict for activation functions.
+ Defaults to ``dict(type='LeakyReLU', inplace=True)``.
+ norm_eval (bool): Whether to set norm layers to eval mode, namely,
+ freeze running stats (mean and var). Note: Effect on Batch Norm
+ and its variants only. Defaults to False.
+ init_cfg (dict, optional): The initialization settings.
+ Defaults to ``dict(type='Kaiming', layer='Conv2d'))``.
+
+ Example:
+ >>> from functools import partial
+ >>> import torch
+ >>> import torch.nn as nn
+ >>> from mmcls.models import CSPNet
+ >>> from mmcls.models.backbones.resnet import Bottleneck
+ >>>
+ >>> # A simple example to build CSPNet.
+ >>> arch = dict(
+ ... block_fn=Bottleneck,
+ ... in_channels=[32, 64],
+ ... out_channels=[64, 128],
+ ... num_blocks=[3, 4]
+ ... )
+ >>> stem_fn = partial(nn.Conv2d, out_channels=32, kernel_size=3)
+ >>> model = CSPNet(arch=arch, stem_fn=stem_fn, out_indices=(0, 1))
+ >>> inputs = torch.rand(1, 3, 224, 224)
+ >>> outs = model(inputs)
+ >>> for out in outs:
+ ... print(out.shape)
+ ...
+ (1, 64, 111, 111)
+ (1, 128, 56, 56)
+ """
+
+ def __init__(self,
+ arch,
+ stem_fn,
+ in_channels=3,
+ out_indices=-1,
+ frozen_stages=-1,
+ drop_path_rate=0.,
+ conv_cfg=None,
+ norm_cfg=dict(type='BN', eps=1e-5),
+ act_cfg=dict(type='LeakyReLU', inplace=True),
+ norm_eval=False,
+ init_cfg=dict(type='Kaiming', layer='Conv2d')):
+ super().__init__(init_cfg=init_cfg)
+ self.arch = self.expand_arch(arch)
+ self.num_stages = len(self.arch['in_channels'])
+ self.conv_cfg = conv_cfg
+ self.norm_cfg = norm_cfg
+ self.act_cfg = act_cfg
+ self.norm_eval = norm_eval
+ if frozen_stages not in range(-1, self.num_stages):
+ raise ValueError('frozen_stages must be in range(-1, '
+ f'{self.num_stages}). But received '
+ f'{frozen_stages}')
+ self.frozen_stages = frozen_stages
+
+ self.stem = stem_fn(in_channels)
+
+ stages = []
+ depths = self.arch['num_blocks']
+ dpr = torch.linspace(0, drop_path_rate, sum(depths)).split(depths)
+
+ for i in range(self.num_stages):
+ stage_cfg = {k: v[i] for k, v in self.arch.items()}
+ csp_stage = CSPStage(
+ **stage_cfg,
+ block_dpr=dpr[i].tolist(),
+ conv_cfg=conv_cfg,
+ norm_cfg=norm_cfg,
+ act_cfg=act_cfg,
+ init_cfg=init_cfg)
+ stages.append(csp_stage)
+ self.stages = Sequential(*stages)
+
+ if isinstance(out_indices, int):
+ out_indices = [out_indices]
+ assert isinstance(out_indices, Sequence), \
+ f'"out_indices" must by a sequence or int, ' \
+ f'get {type(out_indices)} instead.'
+ out_indices = list(out_indices)
+ for i, index in enumerate(out_indices):
+ if index < 0:
+ out_indices[i] = len(self.stages) + index
+ assert 0 <= out_indices[i] <= len(self.stages), \
+ f'Invalid out_indices {index}.'
+ self.out_indices = out_indices
+
+ @staticmethod
+ def expand_arch(arch):
+ num_stages = len(arch['in_channels'])
+
+ def to_tuple(x, name=''):
+ if isinstance(x, (list, tuple)):
+ assert len(x) == num_stages, \
+ f'The length of {name} ({len(x)}) does not ' \
+ f'equals to the number of stages ({num_stages})'
+ return tuple(x)
+ else:
+ return (x, ) * num_stages
+
+ full_arch = {k: to_tuple(v, k) for k, v in arch.items()}
+ if 'block_args' not in full_arch:
+ full_arch['block_args'] = to_tuple({})
+ return full_arch
+
+ def _freeze_stages(self):
+ if self.frozen_stages >= 0:
+ self.stem.eval()
+ for param in self.stem.parameters():
+ param.requires_grad = False
+
+ for i in range(self.frozen_stages + 1):
+ m = self.stages[i]
+ m.eval()
+ for param in m.parameters():
+ param.requires_grad = False
+
+ def train(self, mode=True):
+ super(CSPNet, self).train(mode)
+ self._freeze_stages()
+ if mode and self.norm_eval:
+ for m in self.modules():
+ if isinstance(m, _BatchNorm):
+ m.eval()
+
+ def forward(self, x):
+ outs = []
+
+ x = self.stem(x)
+ for i, stage in enumerate(self.stages):
+ x = stage(x)
+ if i in self.out_indices:
+ outs.append(x)
+ return tuple(outs)
+
+
+@BACKBONES.register_module()
+class CSPDarkNet(CSPNet):
+ """CSP-Darknet backbone used in YOLOv4.
+
+ Args:
+ depth (int): Depth of CSP-Darknet. Default: 53.
+ in_channels (int): Number of input image channels. Default: 3.
+ out_indices (Sequence[int]): Output from which stages.
+ Default: (3, ).
+ frozen_stages (int): Stages to be frozen (stop grad and set eval
+ mode). -1 means not freezing any parameters. Default: -1.
+ conv_cfg (dict): Config dict for convolution layer. Default: None.
+ norm_cfg (dict): Dictionary to construct and config norm layer.
+ Default: dict(type='BN', requires_grad=True).
+ act_cfg (dict): Config dict for activation layer.
+ Default: dict(type='LeakyReLU', negative_slope=0.1).
+ norm_eval (bool): Whether to set norm layers to eval mode, namely,
+ freeze running stats (mean and var). Note: Effect on Batch Norm
+ and its variants only.
+ init_cfg (dict or list[dict], optional): Initialization config dict.
+ Default: None.
+
+ Example:
+ >>> from mmcls.models import CSPDarkNet
+ >>> import torch
+ >>> model = CSPDarkNet(depth=53, out_indices=(0, 1, 2, 3, 4))
+ >>> model.eval()
+ >>> inputs = torch.rand(1, 3, 416, 416)
+ >>> level_outputs = model(inputs)
+ >>> for level_out in level_outputs:
+ ... print(tuple(level_out.shape))
+ ...
+ (1, 64, 208, 208)
+ (1, 128, 104, 104)
+ (1, 256, 52, 52)
+ (1, 512, 26, 26)
+ (1, 1024, 13, 13)
+ """
+ arch_settings = {
+ 53:
+ dict(
+ block_fn=DarknetBottleneck,
+ in_channels=(32, 64, 128, 256, 512),
+ out_channels=(64, 128, 256, 512, 1024),
+ num_blocks=(1, 2, 8, 8, 4),
+ expand_ratio=(2, 1, 1, 1, 1),
+ bottle_ratio=(2, 1, 1, 1, 1),
+ has_downsampler=True,
+ down_growth=True,
+ ),
+ }
+
+ def __init__(self,
+ depth,
+ in_channels=3,
+ out_indices=(4, ),
+ frozen_stages=-1,
+ conv_cfg=None,
+ norm_cfg=dict(type='BN', eps=1e-5),
+ act_cfg=dict(type='LeakyReLU', inplace=True),
+ norm_eval=False,
+ init_cfg=dict(
+ type='Kaiming',
+ layer='Conv2d',
+ a=math.sqrt(5),
+ distribution='uniform',
+ mode='fan_in',
+ nonlinearity='leaky_relu')):
+
+ assert depth in self.arch_settings, 'depth must be one of ' \
+ f'{list(self.arch_settings.keys())}, but get {depth}.'
+
+ super().__init__(
+ arch=self.arch_settings[depth],
+ stem_fn=self._make_stem_layer,
+ in_channels=in_channels,
+ out_indices=out_indices,
+ frozen_stages=frozen_stages,
+ conv_cfg=conv_cfg,
+ norm_cfg=norm_cfg,
+ act_cfg=act_cfg,
+ norm_eval=norm_eval,
+ init_cfg=init_cfg)
+
+ def _make_stem_layer(self, in_channels):
+ """using a stride=1 conv as the stem in CSPDarknet."""
+ # `stem_channels` equals to the `in_channels` in the first stage.
+ stem_channels = self.arch['in_channels'][0]
+ stem = ConvModule(
+ in_channels=in_channels,
+ out_channels=stem_channels,
+ kernel_size=3,
+ padding=1,
+ norm_cfg=self.norm_cfg,
+ act_cfg=self.act_cfg)
+ return stem
+
+
+@BACKBONES.register_module()
+class CSPResNet(CSPNet):
+ """CSP-ResNet backbone.
+
+ Args:
+ depth (int): Depth of CSP-ResNet. Default: 50.
+ out_indices (Sequence[int]): Output from which stages.
+ Default: (4, ).
+ frozen_stages (int): Stages to be frozen (stop grad and set eval
+ mode). -1 means not freezing any parameters. Default: -1.
+ conv_cfg (dict): Config dict for convolution layer. Default: None.
+ norm_cfg (dict): Dictionary to construct and config norm layer.
+ Default: dict(type='BN', requires_grad=True).
+ act_cfg (dict): Config dict for activation layer.
+ Default: dict(type='LeakyReLU', negative_slope=0.1).
+ norm_eval (bool): Whether to set norm layers to eval mode, namely,
+ freeze running stats (mean and var). Note: Effect on Batch Norm
+ and its variants only.
+ init_cfg (dict or list[dict], optional): Initialization config dict.
+ Default: None.
+ Example:
+ >>> from mmcls.models import CSPResNet
+ >>> import torch
+ >>> model = CSPResNet(depth=50, out_indices=(0, 1, 2, 3))
+ >>> model.eval()
+ >>> inputs = torch.rand(1, 3, 416, 416)
+ >>> level_outputs = model(inputs)
+ >>> for level_out in level_outputs:
+ ... print(tuple(level_out.shape))
+ ...
+ (1, 128, 104, 104)
+ (1, 256, 52, 52)
+ (1, 512, 26, 26)
+ (1, 1024, 13, 13)
+ """
+ arch_settings = {
+ 50:
+ dict(
+ block_fn=ResNetBottleneck,
+ in_channels=(64, 128, 256, 512),
+ out_channels=(128, 256, 512, 1024),
+ num_blocks=(3, 3, 5, 2),
+ expand_ratio=4,
+ bottle_ratio=2,
+ has_downsampler=(False, True, True, True),
+ down_growth=False),
+ }
+
+ def __init__(self,
+ depth,
+ in_channels=3,
+ out_indices=(3, ),
+ frozen_stages=-1,
+ deep_stem=False,
+ conv_cfg=None,
+ norm_cfg=dict(type='BN', eps=1e-5),
+ act_cfg=dict(type='LeakyReLU', inplace=True),
+ norm_eval=False,
+ init_cfg=dict(type='Kaiming', layer='Conv2d')):
+ assert depth in self.arch_settings, 'depth must be one of ' \
+ f'{list(self.arch_settings.keys())}, but get {depth}.'
+ self.deep_stem = deep_stem
+
+ super().__init__(
+ arch=self.arch_settings[depth],
+ stem_fn=self._make_stem_layer,
+ in_channels=in_channels,
+ out_indices=out_indices,
+ frozen_stages=frozen_stages,
+ conv_cfg=conv_cfg,
+ norm_cfg=norm_cfg,
+ act_cfg=act_cfg,
+ norm_eval=norm_eval,
+ init_cfg=init_cfg)
+
+ def _make_stem_layer(self, in_channels):
+ # `stem_channels` equals to the `in_channels` in the first stage.
+ stem_channels = self.arch['in_channels'][0]
+ if self.deep_stem:
+ stem = nn.Sequential(
+ ConvModule(
+ in_channels,
+ stem_channels // 2,
+ kernel_size=3,
+ stride=2,
+ padding=1,
+ conv_cfg=self.conv_cfg,
+ norm_cfg=self.norm_cfg,
+ act_cfg=self.act_cfg),
+ ConvModule(
+ stem_channels // 2,
+ stem_channels // 2,
+ kernel_size=3,
+ stride=1,
+ padding=1,
+ conv_cfg=self.conv_cfg,
+ norm_cfg=self.norm_cfg,
+ act_cfg=self.act_cfg),
+ ConvModule(
+ stem_channels // 2,
+ stem_channels,
+ kernel_size=3,
+ stride=1,
+ padding=1,
+ conv_cfg=self.conv_cfg,
+ norm_cfg=self.norm_cfg,
+ act_cfg=self.act_cfg))
+ else:
+ stem = nn.Sequential(
+ ConvModule(
+ in_channels,
+ stem_channels,
+ kernel_size=7,
+ stride=2,
+ padding=3,
+ conv_cfg=self.conv_cfg,
+ norm_cfg=self.norm_cfg,
+ act_cfg=self.act_cfg),
+ nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
+ return stem
+
+
+@BACKBONES.register_module()
+class CSPResNeXt(CSPResNet):
+ """CSP-ResNeXt backbone.
+
+ Args:
+ depth (int): Depth of CSP-ResNeXt. Default: 50.
+ out_indices (Sequence[int]): Output from which stages.
+ Default: (4, ).
+ frozen_stages (int): Stages to be frozen (stop grad and set eval
+ mode). -1 means not freezing any parameters. Default: -1.
+ conv_cfg (dict): Config dict for convolution layer. Default: None.
+ norm_cfg (dict): Dictionary to construct and config norm layer.
+ Default: dict(type='BN', requires_grad=True).
+ act_cfg (dict): Config dict for activation layer.
+ Default: dict(type='LeakyReLU', negative_slope=0.1).
+ norm_eval (bool): Whether to set norm layers to eval mode, namely,
+ freeze running stats (mean and var). Note: Effect on Batch Norm
+ and its variants only.
+ init_cfg (dict or list[dict], optional): Initialization config dict.
+ Default: None.
+ Example:
+ >>> from mmcls.models import CSPResNeXt
+ >>> import torch
+ >>> model = CSPResNeXt(depth=50, out_indices=(0, 1, 2, 3))
+ >>> model.eval()
+ >>> inputs = torch.rand(1, 3, 224, 224)
+ >>> level_outputs = model(inputs)
+ >>> for level_out in level_outputs:
+ ... print(tuple(level_out.shape))
+ ...
+ (1, 256, 56, 56)
+ (1, 512, 28, 28)
+ (1, 1024, 14, 14)
+ (1, 2048, 7, 7)
+ """
+ arch_settings = {
+ 50:
+ dict(
+ block_fn=ResNeXtBottleneck,
+ in_channels=(64, 256, 512, 1024),
+ out_channels=(256, 512, 1024, 2048),
+ num_blocks=(3, 3, 5, 2),
+ expand_ratio=(4, 2, 2, 2),
+ bottle_ratio=4,
+ has_downsampler=(False, True, True, True),
+ down_growth=False,
+ # the base_channels is changed from 64 to 32 in CSPNet
+ block_args=dict(base_channels=32),
+ ),
+ }
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/deit.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/deit.py
new file mode 100644
index 0000000000000000000000000000000000000000..56e74e07f830033068bab23fde420eb6496d7607
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/deit.py
@@ -0,0 +1,117 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import torch
+import torch.nn as nn
+from mmcv.cnn.utils.weight_init import trunc_normal_
+
+from ..builder import BACKBONES
+from .vision_transformer import VisionTransformer
+
+
+@BACKBONES.register_module()
+class DistilledVisionTransformer(VisionTransformer):
+ """Distilled Vision Transformer.
+
+ A PyTorch implement of : `Training data-efficient image transformers &
+ distillation through attention `_
+
+ Args:
+ arch (str | dict): Vision Transformer architecture. If use string,
+ choose from 'small', 'base', 'large', 'deit-tiny', 'deit-small'
+ and 'deit-base'. If use dict, it should have below keys:
+
+ - **embed_dims** (int): The dimensions of embedding.
+ - **num_layers** (int): The number of transformer encoder layers.
+ - **num_heads** (int): The number of heads in attention modules.
+ - **feedforward_channels** (int): The hidden dimensions in
+ feedforward modules.
+
+ Defaults to 'deit-base'.
+ img_size (int | tuple): The expected input image shape. Because we
+ support dynamic input shape, just set the argument to the most
+ common input image shape. Defaults to 224.
+ patch_size (int | tuple): The patch size in patch embedding.
+ Defaults to 16.
+ in_channels (int): The num of input channels. Defaults to 3.
+ out_indices (Sequence | int): Output from which stages.
+ Defaults to -1, means the last stage.
+ drop_rate (float): Probability of an element to be zeroed.
+ Defaults to 0.
+ drop_path_rate (float): stochastic depth rate. Defaults to 0.
+ qkv_bias (bool): Whether to add bias for qkv in attention modules.
+ Defaults to True.
+ norm_cfg (dict): Config dict for normalization layer.
+ Defaults to ``dict(type='LN')``.
+ final_norm (bool): Whether to add a additional layer to normalize
+ final feature map. Defaults to True.
+ with_cls_token (bool): Whether concatenating class token into image
+ tokens as transformer input. Defaults to True.
+ output_cls_token (bool): Whether output the cls_token. If set True,
+ ``with_cls_token`` must be True. Defaults to True.
+ interpolate_mode (str): Select the interpolate mode for position
+ embeding vector resize. Defaults to "bicubic".
+ patch_cfg (dict): Configs of patch embeding. Defaults to an empty dict.
+ layer_cfgs (Sequence | dict): Configs of each transformer layer in
+ encoder. Defaults to an empty dict.
+ init_cfg (dict, optional): Initialization config dict.
+ Defaults to None.
+ """
+ num_extra_tokens = 2 # cls_token, dist_token
+
+ def __init__(self, arch='deit-base', *args, **kwargs):
+ super(DistilledVisionTransformer, self).__init__(
+ arch=arch, *args, **kwargs)
+ self.dist_token = nn.Parameter(torch.zeros(1, 1, self.embed_dims))
+
+ def forward(self, x):
+ B = x.shape[0]
+ x, patch_resolution = self.patch_embed(x)
+
+ # stole cls_tokens impl from Phil Wang, thanks
+ cls_tokens = self.cls_token.expand(B, -1, -1)
+ dist_token = self.dist_token.expand(B, -1, -1)
+ x = torch.cat((cls_tokens, dist_token, x), dim=1)
+ x = x + self.resize_pos_embed(
+ self.pos_embed,
+ self.patch_resolution,
+ patch_resolution,
+ mode=self.interpolate_mode,
+ num_extra_tokens=self.num_extra_tokens)
+ x = self.drop_after_pos(x)
+
+ if not self.with_cls_token:
+ # Remove class token for transformer encoder input
+ x = x[:, 2:]
+
+ outs = []
+ for i, layer in enumerate(self.layers):
+ x = layer(x)
+
+ if i == len(self.layers) - 1 and self.final_norm:
+ x = self.norm1(x)
+
+ if i in self.out_indices:
+ B, _, C = x.shape
+ if self.with_cls_token:
+ patch_token = x[:, 2:].reshape(B, *patch_resolution, C)
+ patch_token = patch_token.permute(0, 3, 1, 2)
+ cls_token = x[:, 0]
+ dist_token = x[:, 1]
+ else:
+ patch_token = x.reshape(B, *patch_resolution, C)
+ patch_token = patch_token.permute(0, 3, 1, 2)
+ cls_token = None
+ dist_token = None
+ if self.output_cls_token:
+ out = [patch_token, cls_token, dist_token]
+ else:
+ out = patch_token
+ outs.append(out)
+
+ return tuple(outs)
+
+ def init_weights(self):
+ super(DistilledVisionTransformer, self).init_weights()
+
+ if not (isinstance(self.init_cfg, dict)
+ and self.init_cfg['type'] == 'Pretrained'):
+ trunc_normal_(self.dist_token, std=0.02)
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/densenet.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/densenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..9947fbf5d8671b60c5a5ee587065a5d71588fd52
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/densenet.py
@@ -0,0 +1,332 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import math
+from itertools import chain
+from typing import Sequence
+
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+import torch.utils.checkpoint as cp
+from mmcv.cnn.bricks import build_activation_layer, build_norm_layer
+from torch.jit.annotations import List
+
+from ..builder import BACKBONES
+from .base_backbone import BaseBackbone
+
+
+class DenseLayer(BaseBackbone):
+ """DenseBlock layers."""
+
+ def __init__(self,
+ in_channels,
+ growth_rate,
+ bn_size,
+ norm_cfg=dict(type='BN'),
+ act_cfg=dict(type='ReLU'),
+ drop_rate=0.,
+ memory_efficient=False):
+ super(DenseLayer, self).__init__()
+
+ self.norm1 = build_norm_layer(norm_cfg, in_channels)[1]
+ self.conv1 = nn.Conv2d(
+ in_channels,
+ bn_size * growth_rate,
+ kernel_size=1,
+ stride=1,
+ bias=False)
+ self.act = build_activation_layer(act_cfg)
+ self.norm2 = build_norm_layer(norm_cfg, bn_size * growth_rate)[1]
+ self.conv2 = nn.Conv2d(
+ bn_size * growth_rate,
+ growth_rate,
+ kernel_size=3,
+ stride=1,
+ padding=1,
+ bias=False)
+ self.drop_rate = float(drop_rate)
+ self.memory_efficient = memory_efficient
+
+ def bottleneck_fn(self, xs):
+ # type: (List[torch.Tensor]) -> torch.Tensor
+ concated_features = torch.cat(xs, 1)
+ bottleneck_output = self.conv1(
+ self.act(self.norm1(concated_features))) # noqa: T484
+ return bottleneck_output
+
+ # todo: rewrite when torchscript supports any
+ def any_requires_grad(self, x):
+ # type: (List[torch.Tensor]) -> bool
+ for tensor in x:
+ if tensor.requires_grad:
+ return True
+ return False
+
+ # This decorator indicates to the compiler that a function or method
+ # should be ignored and replaced with the raising of an exception.
+ # Here this function is incompatible with torchscript.
+ @torch.jit.unused # noqa: T484
+ def call_checkpoint_bottleneck(self, x):
+ # type: (List[torch.Tensor]) -> torch.Tensor
+ def closure(*xs):
+ return self.bottleneck_fn(xs)
+
+ # Here use torch.utils.checkpoint to rerun a forward-pass during
+ # backward in bottleneck to save memories.
+ return cp.checkpoint(closure, *x)
+
+ def forward(self, x): # noqa: F811
+ # type: (List[torch.Tensor]) -> torch.Tensor
+ # assert input features is a list of Tensor
+ assert isinstance(x, list)
+
+ if self.memory_efficient and self.any_requires_grad(x):
+ if torch.jit.is_scripting():
+ raise Exception('Memory Efficient not supported in JIT')
+ bottleneck_output = self.call_checkpoint_bottleneck(x)
+ else:
+ bottleneck_output = self.bottleneck_fn(x)
+
+ new_features = self.conv2(self.act(self.norm2(bottleneck_output)))
+ if self.drop_rate > 0:
+ new_features = F.dropout(
+ new_features, p=self.drop_rate, training=self.training)
+ return new_features
+
+
+class DenseBlock(nn.Module):
+ """DenseNet Blocks."""
+
+ def __init__(self,
+ num_layers,
+ in_channels,
+ bn_size,
+ growth_rate,
+ norm_cfg=dict(type='BN'),
+ act_cfg=dict(type='ReLU'),
+ drop_rate=0.,
+ memory_efficient=False):
+ super(DenseBlock, self).__init__()
+ self.block = nn.ModuleList([
+ DenseLayer(
+ in_channels + i * growth_rate,
+ growth_rate=growth_rate,
+ bn_size=bn_size,
+ norm_cfg=norm_cfg,
+ act_cfg=act_cfg,
+ drop_rate=drop_rate,
+ memory_efficient=memory_efficient) for i in range(num_layers)
+ ])
+
+ def forward(self, init_features):
+ features = [init_features]
+ for layer in self.block:
+ new_features = layer(features)
+ features.append(new_features)
+ return torch.cat(features, 1)
+
+
+class DenseTransition(nn.Sequential):
+ """DenseNet Transition Layers."""
+
+ def __init__(self,
+ in_channels,
+ out_channels,
+ norm_cfg=dict(type='BN'),
+ act_cfg=dict(type='ReLU')):
+ super(DenseTransition, self).__init__()
+ self.add_module('norm', build_norm_layer(norm_cfg, in_channels)[1])
+ self.add_module('act', build_activation_layer(act_cfg))
+ self.add_module(
+ 'conv',
+ nn.Conv2d(
+ in_channels, out_channels, kernel_size=1, stride=1,
+ bias=False))
+ self.add_module('pool', nn.AvgPool2d(kernel_size=2, stride=2))
+
+
+@BACKBONES.register_module()
+class DenseNet(BaseBackbone):
+ """DenseNet.
+
+ A PyTorch implementation of : `Densely Connected Convolutional Networks
+ `_
+
+ Modified from the `official repo
+ `_
+ and `pytorch
+ `_.
+
+ Args:
+ arch (str | dict): The model's architecture. If string, it should be
+ one of architecture in ``DenseNet.arch_settings``. And if dict, it
+ should include the following two keys:
+
+ - growth_rate (int): Each layer of DenseBlock produce `k` feature
+ maps. Here refers `k` as the growth rate of the network.
+ - depths (list[int]): Number of repeated layers in each DenseBlock.
+ - init_channels (int): The output channels of stem layers.
+
+ Defaults to '121'.
+ in_channels (int): Number of input image channels. Defaults to 3.
+ bn_size (int): Refers to channel expansion parameter of 1x1
+ convolution layer. Defaults to 4.
+ drop_rate (float): Drop rate of Dropout Layer. Defaults to 0.
+ compression_factor (float): The reduction rate of transition layers.
+ Defaults to 0.5.
+ memory_efficient (bool): If True, uses checkpointing. Much more memory
+ efficient, but slower. Defaults to False.
+ See `"paper" `_.
+ norm_cfg (dict): The config dict for norm layers.
+ Defaults to ``dict(type='BN')``.
+ act_cfg (dict): The config dict for activation after each convolution.
+ Defaults to ``dict(type='ReLU')``.
+ out_indices (Sequence | int): Output from which stages.
+ Defaults to -1, means the last stage.
+ frozen_stages (int): Stages to be frozen (all param fixed).
+ Defaults to 0, which means not freezing any parameters.
+ init_cfg (dict, optional): Initialization config dict.
+ """
+ arch_settings = {
+ '121': {
+ 'growth_rate': 32,
+ 'depths': [6, 12, 24, 16],
+ 'init_channels': 64,
+ },
+ '169': {
+ 'growth_rate': 32,
+ 'depths': [6, 12, 32, 32],
+ 'init_channels': 64,
+ },
+ '201': {
+ 'growth_rate': 32,
+ 'depths': [6, 12, 48, 32],
+ 'init_channels': 64,
+ },
+ '161': {
+ 'growth_rate': 48,
+ 'depths': [6, 12, 36, 24],
+ 'init_channels': 96,
+ },
+ }
+
+ def __init__(self,
+ arch='121',
+ in_channels=3,
+ bn_size=4,
+ drop_rate=0,
+ compression_factor=0.5,
+ memory_efficient=False,
+ norm_cfg=dict(type='BN'),
+ act_cfg=dict(type='ReLU'),
+ out_indices=-1,
+ frozen_stages=0,
+ init_cfg=None):
+ super().__init__(init_cfg=init_cfg)
+
+ if isinstance(arch, str):
+ assert arch in self.arch_settings, \
+ f'Unavailable arch, please choose from ' \
+ f'({set(self.arch_settings)}) or pass a dict.'
+ arch = self.arch_settings[arch]
+ elif isinstance(arch, dict):
+ essential_keys = {'growth_rate', 'depths', 'init_channels'}
+ assert isinstance(arch, dict) and essential_keys <= set(arch), \
+ f'Custom arch needs a dict with keys {essential_keys}'
+
+ self.growth_rate = arch['growth_rate']
+ self.depths = arch['depths']
+ self.init_channels = arch['init_channels']
+ self.act = build_activation_layer(act_cfg)
+
+ self.num_stages = len(self.depths)
+
+ # check out indices and frozen stages
+ if isinstance(out_indices, int):
+ out_indices = [out_indices]
+ assert isinstance(out_indices, Sequence), \
+ f'"out_indices" must by a sequence or int, ' \
+ f'get {type(out_indices)} instead.'
+ for i, index in enumerate(out_indices):
+ if index < 0:
+ out_indices[i] = self.num_stages + index
+ assert out_indices[i] >= 0, f'Invalid out_indices {index}'
+ self.out_indices = out_indices
+ self.frozen_stages = frozen_stages
+
+ # Set stem layers
+ self.stem = nn.Sequential(
+ nn.Conv2d(
+ in_channels,
+ self.init_channels,
+ kernel_size=7,
+ stride=2,
+ padding=3,
+ bias=False),
+ build_norm_layer(norm_cfg, self.init_channels)[1], self.act,
+ nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
+
+ # Repetitions of DenseNet Blocks
+ self.stages = nn.ModuleList()
+ self.transitions = nn.ModuleList()
+
+ channels = self.init_channels
+ for i in range(self.num_stages):
+ depth = self.depths[i]
+
+ stage = DenseBlock(
+ num_layers=depth,
+ in_channels=channels,
+ bn_size=bn_size,
+ growth_rate=self.growth_rate,
+ norm_cfg=norm_cfg,
+ act_cfg=act_cfg,
+ drop_rate=drop_rate,
+ memory_efficient=memory_efficient)
+ self.stages.append(stage)
+ channels += depth * self.growth_rate
+
+ if i != self.num_stages - 1:
+ transition = DenseTransition(
+ in_channels=channels,
+ out_channels=math.floor(channels * compression_factor),
+ norm_cfg=norm_cfg,
+ act_cfg=act_cfg,
+ )
+ channels = math.floor(channels * compression_factor)
+ else:
+ # Final layers after dense block is just bn with act.
+ # Unlike the paper, the original repo also put this in
+ # transition layer, whereas torchvision take this out.
+ # We reckon this as transition layer here.
+ transition = nn.Sequential(
+ build_norm_layer(norm_cfg, channels)[1],
+ self.act,
+ )
+ self.transitions.append(transition)
+
+ self._freeze_stages()
+
+ def forward(self, x):
+ x = self.stem(x)
+ outs = []
+ for i in range(self.num_stages):
+ x = self.stages[i](x)
+ x = self.transitions[i](x)
+ if i in self.out_indices:
+ outs.append(x)
+
+ return tuple(outs)
+
+ def _freeze_stages(self):
+ for i in range(self.frozen_stages):
+ downsample_layer = self.transitions[i]
+ stage = self.stages[i]
+ downsample_layer.eval()
+ stage.eval()
+ for param in chain(downsample_layer.parameters(),
+ stage.parameters()):
+ param.requires_grad = False
+
+ def train(self, mode=True):
+ super(DenseNet, self).train(mode)
+ self._freeze_stages()
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/efficientformer.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/efficientformer.py
new file mode 100644
index 0000000000000000000000000000000000000000..173444ff22b386151b5e6d39ebc9ddf0cc17eeea
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/efficientformer.py
@@ -0,0 +1,606 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import itertools
+from typing import Optional, Sequence
+
+import torch
+import torch.nn as nn
+from mmcv.cnn.bricks import (ConvModule, DropPath, build_activation_layer,
+ build_norm_layer)
+from mmcv.runner import BaseModule, ModuleList, Sequential
+
+from ..builder import BACKBONES
+from ..utils import LayerScale
+from .base_backbone import BaseBackbone
+from .poolformer import Pooling
+
+
+class AttentionWithBias(BaseModule):
+ """Multi-head Attention Module with attention_bias.
+
+ Args:
+ embed_dims (int): The embedding dimension.
+ num_heads (int): Parallel attention heads. Defaults to 8.
+ key_dim (int): The dimension of q, k. Defaults to 32.
+ attn_ratio (float): The dimension of v equals to
+ ``key_dim * attn_ratio``. Defaults to 4.
+ resolution (int): The height and width of attention_bias.
+ Defaults to 7.
+ init_cfg (dict, optional): The Config for initialization.
+ Defaults to None.
+ """
+
+ def __init__(self,
+ embed_dims,
+ num_heads=8,
+ key_dim=32,
+ attn_ratio=4.,
+ resolution=7,
+ init_cfg=None):
+ super().__init__(init_cfg=init_cfg)
+ self.num_heads = num_heads
+ self.scale = key_dim**-0.5
+ self.attn_ratio = attn_ratio
+ self.key_dim = key_dim
+ self.nh_kd = key_dim * num_heads
+ self.d = int(attn_ratio * key_dim)
+ self.dh = int(attn_ratio * key_dim) * num_heads
+ h = self.dh + self.nh_kd * 2
+ self.qkv = nn.Linear(embed_dims, h)
+ self.proj = nn.Linear(self.dh, embed_dims)
+
+ points = list(itertools.product(range(resolution), range(resolution)))
+ N = len(points)
+ attention_offsets = {}
+ idxs = []
+ for p1 in points:
+ for p2 in points:
+ offset = (abs(p1[0] - p2[0]), abs(p1[1] - p2[1]))
+ if offset not in attention_offsets:
+ attention_offsets[offset] = len(attention_offsets)
+ idxs.append(attention_offsets[offset])
+ self.attention_biases = nn.Parameter(
+ torch.zeros(num_heads, len(attention_offsets)))
+ self.register_buffer('attention_bias_idxs',
+ torch.LongTensor(idxs).view(N, N))
+
+ @torch.no_grad()
+ def train(self, mode=True):
+ """change the mode of model."""
+ super().train(mode)
+ if mode and hasattr(self, 'ab'):
+ del self.ab
+ else:
+ self.ab = self.attention_biases[:, self.attention_bias_idxs]
+
+ def forward(self, x):
+ """forward function.
+
+ Args:
+ x (tensor): input features with shape of (B, N, C)
+ """
+ B, N, _ = x.shape
+ qkv = self.qkv(x)
+ qkv = qkv.reshape(B, N, self.num_heads, -1).permute(0, 2, 1, 3)
+ q, k, v = qkv.split([self.key_dim, self.key_dim, self.d], dim=-1)
+
+ attn = ((q @ k.transpose(-2, -1)) * self.scale +
+ (self.attention_biases[:, self.attention_bias_idxs]
+ if self.training else self.ab))
+ attn = attn.softmax(dim=-1)
+ x = (attn @ v).transpose(1, 2).reshape(B, N, self.dh)
+ x = self.proj(x)
+ return x
+
+
+class Flat(nn.Module):
+ """Flat the input from (B, C, H, W) to (B, H*W, C)."""
+
+ def __init__(self, ):
+ super().__init__()
+
+ def forward(self, x: torch.Tensor):
+ x = x.flatten(2).transpose(1, 2)
+ return x
+
+
+class LinearMlp(BaseModule):
+ """Mlp implemented with linear.
+
+ The shape of input and output tensor are (B, N, C).
+
+ Args:
+ in_features (int): Dimension of input features.
+ hidden_features (int): Dimension of hidden features.
+ out_features (int): Dimension of output features.
+ norm_cfg (dict): Config dict for normalization layer.
+ Defaults to ``dict(type='BN')``.
+ act_cfg (dict): The config dict for activation between pointwise
+ convolution. Defaults to ``dict(type='GELU')``.
+ drop (float): Dropout rate. Defaults to 0.0.
+ init_cfg (obj:`mmcv.ConfigDict`): The Config for initialization.
+ Default: None.
+ """
+
+ def __init__(self,
+ in_features: int,
+ hidden_features: Optional[int] = None,
+ out_features: Optional[int] = None,
+ act_cfg=dict(type='GELU'),
+ drop=0.,
+ init_cfg=None):
+ super().__init__(init_cfg=init_cfg)
+ out_features = out_features or in_features
+ hidden_features = hidden_features or in_features
+
+ self.fc1 = nn.Linear(in_features, hidden_features)
+ self.act = build_activation_layer(act_cfg)
+ self.drop1 = nn.Dropout(drop)
+ self.fc2 = nn.Linear(hidden_features, out_features)
+ self.drop2 = nn.Dropout(drop)
+
+ def forward(self, x):
+ """
+ Args:
+ x (torch.Tensor): input tensor with shape (B, N, C).
+
+ Returns:
+ torch.Tensor: output tensor with shape (B, N, C).
+ """
+ x = self.drop1(self.act(self.fc1(x)))
+ x = self.drop2(self.fc2(x))
+ return x
+
+
+class ConvMlp(BaseModule):
+ """Mlp implemented with 1*1 convolutions.
+
+ Args:
+ in_features (int): Dimension of input features.
+ hidden_features (int): Dimension of hidden features.
+ out_features (int): Dimension of output features.
+ norm_cfg (dict): Config dict for normalization layer.
+ Defaults to ``dict(type='BN')``.
+ act_cfg (dict): The config dict for activation between pointwise
+ convolution. Defaults to ``dict(type='GELU')``.
+ drop (float): Dropout rate. Defaults to 0.0.
+ init_cfg (obj:`mmcv.ConfigDict`): The Config for initialization.
+ Default: None.
+ """
+
+ def __init__(self,
+ in_features,
+ hidden_features=None,
+ out_features=None,
+ norm_cfg=dict(type='BN'),
+ act_cfg=dict(type='GELU'),
+ drop=0.,
+ init_cfg=None):
+ super().__init__(init_cfg=init_cfg)
+ out_features = out_features or in_features
+ hidden_features = hidden_features or in_features
+ self.fc1 = nn.Conv2d(in_features, hidden_features, 1)
+ self.act = build_activation_layer(act_cfg)
+ self.fc2 = nn.Conv2d(hidden_features, out_features, 1)
+ self.norm1 = build_norm_layer(norm_cfg, hidden_features)[1]
+ self.norm2 = build_norm_layer(norm_cfg, out_features)[1]
+
+ self.drop = nn.Dropout(drop)
+
+ def forward(self, x):
+ """
+ Args:
+ x (torch.Tensor): input tensor with shape (B, C, H, W).
+
+ Returns:
+ torch.Tensor: output tensor with shape (B, C, H, W).
+ """
+
+ x = self.act(self.norm1(self.fc1(x)))
+ x = self.drop(x)
+ x = self.norm2(self.fc2(x))
+ x = self.drop(x)
+ return x
+
+
+class Meta3D(BaseModule):
+ """Meta Former block using 3 dimensions inputs, ``torch.Tensor`` with shape
+ (B, N, C)."""
+
+ def __init__(self,
+ dim,
+ mlp_ratio=4.,
+ norm_cfg=dict(type='LN'),
+ act_cfg=dict(type='GELU'),
+ drop=0.,
+ drop_path=0.,
+ use_layer_scale=True,
+ init_cfg=None):
+ super().__init__(init_cfg=init_cfg)
+ self.norm1 = build_norm_layer(norm_cfg, dim)[1]
+ self.token_mixer = AttentionWithBias(dim)
+ self.norm2 = build_norm_layer(norm_cfg, dim)[1]
+ mlp_hidden_dim = int(dim * mlp_ratio)
+ self.mlp = LinearMlp(
+ in_features=dim,
+ hidden_features=mlp_hidden_dim,
+ act_cfg=act_cfg,
+ drop=drop)
+
+ self.drop_path = DropPath(drop_path) if drop_path > 0. \
+ else nn.Identity()
+ if use_layer_scale:
+ self.ls1 = LayerScale(dim)
+ self.ls2 = LayerScale(dim)
+ else:
+ self.ls1, self.ls2 = nn.Identity(), nn.Identity()
+
+ def forward(self, x):
+ x = x + self.drop_path(self.ls1(self.token_mixer(self.norm1(x))))
+ x = x + self.drop_path(self.ls2(self.mlp(self.norm2(x))))
+ return x
+
+
+class Meta4D(BaseModule):
+ """Meta Former block using 4 dimensions inputs, ``torch.Tensor`` with shape
+ (B, C, H, W)."""
+
+ def __init__(self,
+ dim,
+ pool_size=3,
+ mlp_ratio=4.,
+ act_cfg=dict(type='GELU'),
+ drop=0.,
+ drop_path=0.,
+ use_layer_scale=True,
+ init_cfg=None):
+ super().__init__(init_cfg=init_cfg)
+
+ self.token_mixer = Pooling(pool_size=pool_size)
+ mlp_hidden_dim = int(dim * mlp_ratio)
+ self.mlp = ConvMlp(
+ in_features=dim,
+ hidden_features=mlp_hidden_dim,
+ act_cfg=act_cfg,
+ drop=drop)
+
+ self.drop_path = DropPath(drop_path) if drop_path > 0. \
+ else nn.Identity()
+ if use_layer_scale:
+ self.ls1 = LayerScale(dim, data_format='channels_first')
+ self.ls2 = LayerScale(dim, data_format='channels_first')
+ else:
+ self.ls1, self.ls2 = nn.Identity(), nn.Identity()
+
+ def forward(self, x):
+ x = x + self.drop_path(self.ls1(self.token_mixer(x)))
+ x = x + self.drop_path(self.ls2(self.mlp(x)))
+ return x
+
+
+def basic_blocks(in_channels,
+ out_channels,
+ index,
+ layers,
+ pool_size=3,
+ mlp_ratio=4.,
+ act_cfg=dict(type='GELU'),
+ drop_rate=.0,
+ drop_path_rate=0.,
+ use_layer_scale=True,
+ vit_num=1,
+ has_downsamper=False):
+ """generate EfficientFormer blocks for a stage."""
+ blocks = []
+ if has_downsamper:
+ blocks.append(
+ ConvModule(
+ in_channels=in_channels,
+ out_channels=out_channels,
+ kernel_size=3,
+ stride=2,
+ padding=1,
+ bias=True,
+ norm_cfg=dict(type='BN'),
+ act_cfg=None))
+ if index == 3 and vit_num == layers[index]:
+ blocks.append(Flat())
+ for block_idx in range(layers[index]):
+ block_dpr = drop_path_rate * (block_idx + sum(layers[:index])) / (
+ sum(layers) - 1)
+ if index == 3 and layers[index] - block_idx <= vit_num:
+ blocks.append(
+ Meta3D(
+ out_channels,
+ mlp_ratio=mlp_ratio,
+ act_cfg=act_cfg,
+ drop=drop_rate,
+ drop_path=block_dpr,
+ use_layer_scale=use_layer_scale,
+ ))
+ else:
+ blocks.append(
+ Meta4D(
+ out_channels,
+ pool_size=pool_size,
+ act_cfg=act_cfg,
+ drop=drop_rate,
+ drop_path=block_dpr,
+ use_layer_scale=use_layer_scale))
+ if index == 3 and layers[index] - block_idx - 1 == vit_num:
+ blocks.append(Flat())
+ blocks = nn.Sequential(*blocks)
+ return blocks
+
+
+@BACKBONES.register_module()
+class EfficientFormer(BaseBackbone):
+ """EfficientFormer.
+
+ A PyTorch implementation of EfficientFormer introduced by:
+ `EfficientFormer: Vision Transformers at MobileNet Speed `_
+
+ Modified from the `official repo
+ `.
+
+ Args:
+ arch (str | dict): The model's architecture. If string, it should be
+ one of architecture in ``EfficientFormer.arch_settings``. And if dict,
+ it should include the following 4 keys:
+
+ - layers (list[int]): Number of blocks at each stage.
+ - embed_dims (list[int]): The number of channels at each stage.
+ - downsamples (list[int]): Has downsample or not in the four stages.
+ - vit_num (int): The num of vit blocks in the last stage.
+
+ Defaults to 'l1'.
+
+ in_channels (int): The num of input channels. Defaults to 3.
+ pool_size (int): The pooling size of ``Meta4D`` blocks. Defaults to 3.
+ mlp_ratios (int): The dimension ratio of multi-head attention mechanism
+ in ``Meta4D`` blocks. Defaults to 3.
+ reshape_last_feat (bool): Whether to reshape the feature map from
+ (B, N, C) to (B, C, H, W) in the last stage, when the ``vit-num``
+ in ``arch`` is not 0. Defaults to False. Usually set to True
+ in downstream tasks.
+ out_indices (Sequence[int]): Output from which stages.
+ Defaults to -1.
+ frozen_stages (int): Stages to be frozen (stop grad and set eval mode).
+ -1 means not freezing any parameters. Defaults to -1.
+ act_cfg (dict): The config dict for activation between pointwise
+ convolution. Defaults to ``dict(type='GELU')``.
+ drop_rate (float): Dropout rate. Defaults to 0.
+ drop_path_rate (float): Stochastic depth rate. Defaults to 0.
+ use_layer_scale (bool): Whether to use use_layer_scale in MetaFormer
+ block. Defaults to True.
+ init_cfg (dict, optional): Initialization config dict.
+ Defaults to None.
+
+ Example:
+ >>> from mmcls.models import EfficientFormer
+ >>> import torch
+ >>> inputs = torch.rand((1, 3, 224, 224))
+ >>> # build EfficientFormer backbone for classification task
+ >>> model = EfficientFormer(arch="l1")
+ >>> model.eval()
+ >>> level_outputs = model(inputs)
+ >>> for level_out in level_outputs:
+ ... print(tuple(level_out.shape))
+ (1, 448, 49)
+ >>> # build EfficientFormer backbone for downstream task
+ >>> model = EfficientFormer(
+ >>> arch="l3",
+ >>> out_indices=(0, 1, 2, 3),
+ >>> reshape_last_feat=True)
+ >>> model.eval()
+ >>> level_outputs = model(inputs)
+ >>> for level_out in level_outputs:
+ ... print(tuple(level_out.shape))
+ (1, 64, 56, 56)
+ (1, 128, 28, 28)
+ (1, 320, 14, 14)
+ (1, 512, 7, 7)
+ """ # noqa: E501
+
+ # --layers: [x,x,x,x], numbers of layers for the four stages
+ # --embed_dims: [x,x,x,x], embedding dims for the four stages
+ # --downsamples: [x,x,x,x], has downsample or not in the four stages
+ # --vit_num:(int), the num of vit blocks in the last stage
+ arch_settings = {
+ 'l1': {
+ 'layers': [3, 2, 6, 4],
+ 'embed_dims': [48, 96, 224, 448],
+ 'downsamples': [False, True, True, True],
+ 'vit_num': 1,
+ },
+ 'l3': {
+ 'layers': [4, 4, 12, 6],
+ 'embed_dims': [64, 128, 320, 512],
+ 'downsamples': [False, True, True, True],
+ 'vit_num': 4,
+ },
+ 'l7': {
+ 'layers': [6, 6, 18, 8],
+ 'embed_dims': [96, 192, 384, 768],
+ 'downsamples': [False, True, True, True],
+ 'vit_num': 8,
+ },
+ }
+
+ def __init__(self,
+ arch='l1',
+ in_channels=3,
+ pool_size=3,
+ mlp_ratios=4,
+ reshape_last_feat=False,
+ out_indices=-1,
+ frozen_stages=-1,
+ act_cfg=dict(type='GELU'),
+ drop_rate=0.,
+ drop_path_rate=0.,
+ use_layer_scale=True,
+ init_cfg=None):
+
+ super().__init__(init_cfg=init_cfg)
+ self.num_extra_tokens = 0 # no cls_token, no dist_token
+
+ if isinstance(arch, str):
+ assert arch in self.arch_settings, \
+ f'Unavailable arch, please choose from ' \
+ f'({set(self.arch_settings)}) or pass a dict.'
+ arch = self.arch_settings[arch]
+ elif isinstance(arch, dict):
+ default_keys = set(self.arch_settings['l1'].keys())
+ assert set(arch.keys()) == default_keys, \
+ f'The arch dict must have {default_keys}, ' \
+ f'but got {list(arch.keys())}.'
+
+ self.layers = arch['layers']
+ self.embed_dims = arch['embed_dims']
+ self.downsamples = arch['downsamples']
+ assert isinstance(self.layers, list) and isinstance(
+ self.embed_dims, list) and isinstance(self.downsamples, list)
+ assert len(self.layers) == len(self.embed_dims) == len(
+ self.downsamples)
+
+ self.vit_num = arch['vit_num']
+ self.reshape_last_feat = reshape_last_feat
+
+ assert self.vit_num >= 0, "'vit_num' must be an integer " \
+ 'greater than or equal to 0.'
+ assert self.vit_num <= self.layers[-1], (
+ "'vit_num' must be an integer smaller than layer number")
+
+ self._make_stem(in_channels, self.embed_dims[0])
+
+ # set the main block in network
+ network = []
+ for i in range(len(self.layers)):
+ if i != 0:
+ in_channels = self.embed_dims[i - 1]
+ else:
+ in_channels = self.embed_dims[i]
+ out_channels = self.embed_dims[i]
+ stage = basic_blocks(
+ in_channels,
+ out_channels,
+ i,
+ self.layers,
+ pool_size=pool_size,
+ mlp_ratio=mlp_ratios,
+ act_cfg=act_cfg,
+ drop_rate=drop_rate,
+ drop_path_rate=drop_path_rate,
+ vit_num=self.vit_num,
+ use_layer_scale=use_layer_scale,
+ has_downsamper=self.downsamples[i])
+ network.append(stage)
+
+ self.network = ModuleList(network)
+
+ if isinstance(out_indices, int):
+ out_indices = [out_indices]
+ assert isinstance(out_indices, Sequence), \
+ f'"out_indices" must by a sequence or int, ' \
+ f'get {type(out_indices)} instead.'
+ for i, index in enumerate(out_indices):
+ if index < 0:
+ out_indices[i] = 4 + index
+ assert out_indices[i] >= 0, f'Invalid out_indices {index}'
+
+ self.out_indices = out_indices
+ for i_layer in self.out_indices:
+ if not self.reshape_last_feat and \
+ i_layer == 3 and self.vit_num > 0:
+ layer = build_norm_layer(
+ dict(type='LN'), self.embed_dims[i_layer])[1]
+ else:
+ # use GN with 1 group as channel-first LN2D
+ layer = build_norm_layer(
+ dict(type='GN', num_groups=1), self.embed_dims[i_layer])[1]
+
+ layer_name = f'norm{i_layer}'
+ self.add_module(layer_name, layer)
+
+ self.frozen_stages = frozen_stages
+ self._freeze_stages()
+
+ def _make_stem(self, in_channels: int, stem_channels: int):
+ """make 2-ConvBNReLu stem layer."""
+ self.patch_embed = Sequential(
+ ConvModule(
+ in_channels,
+ stem_channels // 2,
+ kernel_size=3,
+ stride=2,
+ padding=1,
+ bias=True,
+ conv_cfg=None,
+ norm_cfg=dict(type='BN'),
+ inplace=True),
+ ConvModule(
+ stem_channels // 2,
+ stem_channels,
+ kernel_size=3,
+ stride=2,
+ padding=1,
+ bias=True,
+ conv_cfg=None,
+ norm_cfg=dict(type='BN'),
+ inplace=True))
+
+ def forward_tokens(self, x):
+ outs = []
+ for idx, block in enumerate(self.network):
+ if idx == len(self.network) - 1:
+ N, _, H, W = x.shape
+ if self.downsamples[idx]:
+ H, W = H // 2, W // 2
+ x = block(x)
+ if idx in self.out_indices:
+ norm_layer = getattr(self, f'norm{idx}')
+
+ if idx == len(self.network) - 1 and x.dim() == 3:
+ # when ``vit-num`` > 0 and in the last stage,
+ # if `self.reshape_last_feat`` is True, reshape the
+ # features to `BCHW` format before the final normalization.
+ # if `self.reshape_last_feat`` is False, do
+ # normalization directly and permute the features to `BCN`.
+ if self.reshape_last_feat:
+ x = x.permute((0, 2, 1)).reshape(N, -1, H, W)
+ x_out = norm_layer(x)
+ else:
+ x_out = norm_layer(x).permute((0, 2, 1))
+ else:
+ x_out = norm_layer(x)
+
+ outs.append(x_out.contiguous())
+ return tuple(outs)
+
+ def forward(self, x):
+ # input embedding
+ x = self.patch_embed(x)
+ # through stages
+ x = self.forward_tokens(x)
+ return x
+
+ def _freeze_stages(self):
+ if self.frozen_stages >= 0:
+ self.patch_embed.eval()
+ for param in self.patch_embed.parameters():
+ param.requires_grad = False
+
+ for i in range(self.frozen_stages):
+ # Include both block and downsample layer.
+ module = self.network[i]
+ module.eval()
+ for param in module.parameters():
+ param.requires_grad = False
+ if i in self.out_indices:
+ norm_layer = getattr(self, f'norm{i}')
+ norm_layer.eval()
+ for param in norm_layer.parameters():
+ param.requires_grad = False
+
+ def train(self, mode=True):
+ super(EfficientFormer, self).train(mode)
+ self._freeze_stages()
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/efficientnet.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/efficientnet.py
new file mode 100644
index 0000000000000000000000000000000000000000..ede2c184e14e674b82627da90c4d562c4a52fa6e
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/efficientnet.py
@@ -0,0 +1,407 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import copy
+import math
+from functools import partial
+
+import torch
+import torch.nn as nn
+import torch.utils.checkpoint as cp
+from mmcv.cnn.bricks import ConvModule, DropPath
+from mmcv.runner import BaseModule, Sequential
+
+from mmcls.models.backbones.base_backbone import BaseBackbone
+from mmcls.models.utils import InvertedResidual, SELayer, make_divisible
+from ..builder import BACKBONES
+
+
+class EdgeResidual(BaseModule):
+ """Edge Residual Block.
+
+ Args:
+ in_channels (int): The input channels of this module.
+ out_channels (int): The output channels of this module.
+ mid_channels (int): The input channels of the second convolution.
+ kernel_size (int): The kernel size of the first convolution.
+ Defaults to 3.
+ stride (int): The stride of the first convolution. Defaults to 1.
+ se_cfg (dict, optional): Config dict for se layer. Defaults to None,
+ which means no se layer.
+ with_residual (bool): Use residual connection. Defaults to True.
+ conv_cfg (dict, optional): Config dict for convolution layer.
+ Defaults to None, which means using conv2d.
+ norm_cfg (dict): Config dict for normalization layer.
+ Defaults to ``dict(type='BN')``.
+ act_cfg (dict): Config dict for activation layer.
+ Defaults to ``dict(type='ReLU')``.
+ drop_path_rate (float): stochastic depth rate. Defaults to 0.
+ with_cp (bool): Use checkpoint or not. Using checkpoint will save some
+ memory while slowing down the training speed. Defaults to False.
+ init_cfg (dict | list[dict], optional): Initialization config dict.
+ """
+
+ def __init__(self,
+ in_channels,
+ out_channels,
+ mid_channels,
+ kernel_size=3,
+ stride=1,
+ se_cfg=None,
+ with_residual=True,
+ conv_cfg=None,
+ norm_cfg=dict(type='BN'),
+ act_cfg=dict(type='ReLU'),
+ drop_path_rate=0.,
+ with_cp=False,
+ init_cfg=None):
+ super(EdgeResidual, self).__init__(init_cfg=init_cfg)
+ assert stride in [1, 2]
+ self.with_cp = with_cp
+ self.drop_path = DropPath(
+ drop_path_rate) if drop_path_rate > 0 else nn.Identity()
+ self.with_se = se_cfg is not None
+ self.with_residual = (
+ stride == 1 and in_channels == out_channels and with_residual)
+
+ if self.with_se:
+ assert isinstance(se_cfg, dict)
+
+ self.conv1 = ConvModule(
+ in_channels=in_channels,
+ out_channels=mid_channels,
+ kernel_size=kernel_size,
+ stride=1,
+ padding=kernel_size // 2,
+ conv_cfg=conv_cfg,
+ norm_cfg=norm_cfg,
+ act_cfg=act_cfg)
+
+ if self.with_se:
+ self.se = SELayer(**se_cfg)
+
+ self.conv2 = ConvModule(
+ in_channels=mid_channels,
+ out_channels=out_channels,
+ kernel_size=1,
+ stride=stride,
+ padding=0,
+ conv_cfg=conv_cfg,
+ norm_cfg=norm_cfg,
+ act_cfg=None)
+
+ def forward(self, x):
+
+ def _inner_forward(x):
+ out = x
+ out = self.conv1(out)
+
+ if self.with_se:
+ out = self.se(out)
+
+ out = self.conv2(out)
+
+ if self.with_residual:
+ return x + self.drop_path(out)
+ else:
+ return out
+
+ if self.with_cp and x.requires_grad:
+ out = cp.checkpoint(_inner_forward, x)
+ else:
+ out = _inner_forward(x)
+
+ return out
+
+
+def model_scaling(layer_setting, arch_setting):
+ """Scaling operation to the layer's parameters according to the
+ arch_setting."""
+ # scale width
+ new_layer_setting = copy.deepcopy(layer_setting)
+ for layer_cfg in new_layer_setting:
+ for block_cfg in layer_cfg:
+ block_cfg[1] = make_divisible(block_cfg[1] * arch_setting[0], 8)
+
+ # scale depth
+ split_layer_setting = [new_layer_setting[0]]
+ for layer_cfg in new_layer_setting[1:-1]:
+ tmp_index = [0]
+ for i in range(len(layer_cfg) - 1):
+ if layer_cfg[i + 1][1] != layer_cfg[i][1]:
+ tmp_index.append(i + 1)
+ tmp_index.append(len(layer_cfg))
+ for i in range(len(tmp_index) - 1):
+ split_layer_setting.append(layer_cfg[tmp_index[i]:tmp_index[i +
+ 1]])
+ split_layer_setting.append(new_layer_setting[-1])
+
+ num_of_layers = [len(layer_cfg) for layer_cfg in split_layer_setting[1:-1]]
+ new_layers = [
+ int(math.ceil(arch_setting[1] * num)) for num in num_of_layers
+ ]
+
+ merge_layer_setting = [split_layer_setting[0]]
+ for i, layer_cfg in enumerate(split_layer_setting[1:-1]):
+ if new_layers[i] <= num_of_layers[i]:
+ tmp_layer_cfg = layer_cfg[:new_layers[i]]
+ else:
+ tmp_layer_cfg = copy.deepcopy(layer_cfg) + [layer_cfg[-1]] * (
+ new_layers[i] - num_of_layers[i])
+ if tmp_layer_cfg[0][3] == 1 and i != 0:
+ merge_layer_setting[-1] += tmp_layer_cfg.copy()
+ else:
+ merge_layer_setting.append(tmp_layer_cfg.copy())
+ merge_layer_setting.append(split_layer_setting[-1])
+
+ return merge_layer_setting
+
+
+@BACKBONES.register_module()
+class EfficientNet(BaseBackbone):
+ """EfficientNet backbone.
+
+ Args:
+ arch (str): Architecture of efficientnet. Defaults to b0.
+ out_indices (Sequence[int]): Output from which stages.
+ Defaults to (6, ).
+ frozen_stages (int): Stages to be frozen (all param fixed).
+ Defaults to 0, which means not freezing any parameters.
+ conv_cfg (dict): Config dict for convolution layer.
+ Defaults to None, which means using conv2d.
+ norm_cfg (dict): Config dict for normalization layer.
+ Defaults to dict(type='BN').
+ act_cfg (dict): Config dict for activation layer.
+ Defaults to dict(type='Swish').
+ norm_eval (bool): Whether to set norm layers to eval mode, namely,
+ freeze running stats (mean and var). Note: Effect on Batch Norm
+ and its variants only. Defaults to False.
+ with_cp (bool): Use checkpoint or not. Using checkpoint will save some
+ memory while slowing down the training speed. Defaults to False.
+ """
+
+ # Parameters to build layers.
+ # 'b' represents the architecture of normal EfficientNet family includes
+ # 'b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8'.
+ # 'e' represents the architecture of EfficientNet-EdgeTPU including 'es',
+ # 'em', 'el'.
+ # 6 parameters are needed to construct a layer, From left to right:
+ # - kernel_size: The kernel size of the block
+ # - out_channel: The number of out_channels of the block
+ # - se_ratio: The sequeeze ratio of SELayer.
+ # - stride: The stride of the block
+ # - expand_ratio: The expand_ratio of the mid_channels
+ # - block_type: -1: Not a block, 0: InvertedResidual, 1: EdgeResidual
+ layer_settings = {
+ 'b': [[[3, 32, 0, 2, 0, -1]],
+ [[3, 16, 4, 1, 1, 0]],
+ [[3, 24, 4, 2, 6, 0],
+ [3, 24, 4, 1, 6, 0]],
+ [[5, 40, 4, 2, 6, 0],
+ [5, 40, 4, 1, 6, 0]],
+ [[3, 80, 4, 2, 6, 0],
+ [3, 80, 4, 1, 6, 0],
+ [3, 80, 4, 1, 6, 0],
+ [5, 112, 4, 1, 6, 0],
+ [5, 112, 4, 1, 6, 0],
+ [5, 112, 4, 1, 6, 0]],
+ [[5, 192, 4, 2, 6, 0],
+ [5, 192, 4, 1, 6, 0],
+ [5, 192, 4, 1, 6, 0],
+ [5, 192, 4, 1, 6, 0],
+ [3, 320, 4, 1, 6, 0]],
+ [[1, 1280, 0, 1, 0, -1]]
+ ],
+ 'e': [[[3, 32, 0, 2, 0, -1]],
+ [[3, 24, 0, 1, 3, 1]],
+ [[3, 32, 0, 2, 8, 1],
+ [3, 32, 0, 1, 8, 1]],
+ [[3, 48, 0, 2, 8, 1],
+ [3, 48, 0, 1, 8, 1],
+ [3, 48, 0, 1, 8, 1],
+ [3, 48, 0, 1, 8, 1]],
+ [[5, 96, 0, 2, 8, 0],
+ [5, 96, 0, 1, 8, 0],
+ [5, 96, 0, 1, 8, 0],
+ [5, 96, 0, 1, 8, 0],
+ [5, 96, 0, 1, 8, 0],
+ [5, 144, 0, 1, 8, 0],
+ [5, 144, 0, 1, 8, 0],
+ [5, 144, 0, 1, 8, 0],
+ [5, 144, 0, 1, 8, 0]],
+ [[5, 192, 0, 2, 8, 0],
+ [5, 192, 0, 1, 8, 0]],
+ [[1, 1280, 0, 1, 0, -1]]
+ ]
+ } # yapf: disable
+
+ # Parameters to build different kinds of architecture.
+ # From left to right: scaling factor for width, scaling factor for depth,
+ # resolution.
+ arch_settings = {
+ 'b0': (1.0, 1.0, 224),
+ 'b1': (1.0, 1.1, 240),
+ 'b2': (1.1, 1.2, 260),
+ 'b3': (1.2, 1.4, 300),
+ 'b4': (1.4, 1.8, 380),
+ 'b5': (1.6, 2.2, 456),
+ 'b6': (1.8, 2.6, 528),
+ 'b7': (2.0, 3.1, 600),
+ 'b8': (2.2, 3.6, 672),
+ 'es': (1.0, 1.0, 224),
+ 'em': (1.0, 1.1, 240),
+ 'el': (1.2, 1.4, 300)
+ }
+
+ def __init__(self,
+ arch='b0',
+ drop_path_rate=0.,
+ out_indices=(6, ),
+ frozen_stages=0,
+ conv_cfg=dict(type='Conv2dAdaptivePadding'),
+ norm_cfg=dict(type='BN', eps=1e-3),
+ act_cfg=dict(type='Swish'),
+ norm_eval=False,
+ with_cp=False,
+ init_cfg=[
+ dict(type='Kaiming', layer='Conv2d'),
+ dict(
+ type='Constant',
+ layer=['_BatchNorm', 'GroupNorm'],
+ val=1)
+ ]):
+ super(EfficientNet, self).__init__(init_cfg)
+ assert arch in self.arch_settings, \
+ f'"{arch}" is not one of the arch_settings ' \
+ f'({", ".join(self.arch_settings.keys())})'
+ self.arch_setting = self.arch_settings[arch]
+ self.layer_setting = self.layer_settings[arch[:1]]
+ for index in out_indices:
+ if index not in range(0, len(self.layer_setting)):
+ raise ValueError('the item in out_indices must in '
+ f'range(0, {len(self.layer_setting)}). '
+ f'But received {index}')
+
+ if frozen_stages not in range(len(self.layer_setting) + 1):
+ raise ValueError('frozen_stages must be in range(0, '
+ f'{len(self.layer_setting) + 1}). '
+ f'But received {frozen_stages}')
+ self.drop_path_rate = drop_path_rate
+ self.out_indices = out_indices
+ self.frozen_stages = frozen_stages
+ self.conv_cfg = conv_cfg
+ self.norm_cfg = norm_cfg
+ self.act_cfg = act_cfg
+ self.norm_eval = norm_eval
+ self.with_cp = with_cp
+
+ self.layer_setting = model_scaling(self.layer_setting,
+ self.arch_setting)
+ block_cfg_0 = self.layer_setting[0][0]
+ block_cfg_last = self.layer_setting[-1][0]
+ self.in_channels = make_divisible(block_cfg_0[1], 8)
+ self.out_channels = block_cfg_last[1]
+ self.layers = nn.ModuleList()
+ self.layers.append(
+ ConvModule(
+ in_channels=3,
+ out_channels=self.in_channels,
+ kernel_size=block_cfg_0[0],
+ stride=block_cfg_0[3],
+ padding=block_cfg_0[0] // 2,
+ conv_cfg=self.conv_cfg,
+ norm_cfg=self.norm_cfg,
+ act_cfg=self.act_cfg))
+ self.make_layer()
+ self.layers.append(
+ ConvModule(
+ in_channels=self.in_channels,
+ out_channels=self.out_channels,
+ kernel_size=block_cfg_last[0],
+ stride=block_cfg_last[3],
+ padding=block_cfg_last[0] // 2,
+ conv_cfg=self.conv_cfg,
+ norm_cfg=self.norm_cfg,
+ act_cfg=self.act_cfg))
+
+ def make_layer(self):
+ # Without the first and the final conv block.
+ layer_setting = self.layer_setting[1:-1]
+
+ total_num_blocks = sum([len(x) for x in layer_setting])
+ block_idx = 0
+ dpr = [
+ x.item()
+ for x in torch.linspace(0, self.drop_path_rate, total_num_blocks)
+ ] # stochastic depth decay rule
+
+ for layer_cfg in layer_setting:
+ layer = []
+ for i, block_cfg in enumerate(layer_cfg):
+ (kernel_size, out_channels, se_ratio, stride, expand_ratio,
+ block_type) = block_cfg
+
+ mid_channels = int(self.in_channels * expand_ratio)
+ out_channels = make_divisible(out_channels, 8)
+ if se_ratio <= 0:
+ se_cfg = None
+ else:
+ se_cfg = dict(
+ channels=mid_channels,
+ ratio=expand_ratio * se_ratio,
+ divisor=1,
+ act_cfg=(self.act_cfg, dict(type='Sigmoid')))
+ if block_type == 1: # edge tpu
+ if i > 0 and expand_ratio == 3:
+ with_residual = False
+ expand_ratio = 4
+ else:
+ with_residual = True
+ mid_channels = int(self.in_channels * expand_ratio)
+ if se_cfg is not None:
+ se_cfg = dict(
+ channels=mid_channels,
+ ratio=se_ratio * expand_ratio,
+ divisor=1,
+ act_cfg=(self.act_cfg, dict(type='Sigmoid')))
+ block = partial(EdgeResidual, with_residual=with_residual)
+ else:
+ block = InvertedResidual
+ layer.append(
+ block(
+ in_channels=self.in_channels,
+ out_channels=out_channels,
+ mid_channels=mid_channels,
+ kernel_size=kernel_size,
+ stride=stride,
+ se_cfg=se_cfg,
+ conv_cfg=self.conv_cfg,
+ norm_cfg=self.norm_cfg,
+ act_cfg=self.act_cfg,
+ drop_path_rate=dpr[block_idx],
+ with_cp=self.with_cp))
+ self.in_channels = out_channels
+ block_idx += 1
+ self.layers.append(Sequential(*layer))
+
+ def forward(self, x):
+ outs = []
+ for i, layer in enumerate(self.layers):
+ x = layer(x)
+ if i in self.out_indices:
+ outs.append(x)
+
+ return tuple(outs)
+
+ def _freeze_stages(self):
+ for i in range(self.frozen_stages):
+ m = self.layers[i]
+ m.eval()
+ for param in m.parameters():
+ param.requires_grad = False
+
+ def train(self, mode=True):
+ super(EfficientNet, self).train(mode)
+ self._freeze_stages()
+ if mode and self.norm_eval:
+ for m in self.modules():
+ if isinstance(m, nn.BatchNorm2d):
+ m.eval()
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/hornet.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/hornet.py
new file mode 100644
index 0000000000000000000000000000000000000000..1822b7c0f13a12f2fdcd5a3da40d88c53a8af23e
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/hornet.py
@@ -0,0 +1,499 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+# Adapted from official impl at https://github.com/raoyongming/HorNet.
+try:
+ import torch.fft
+ fft = True
+except ImportError:
+ fft = None
+
+import copy
+from functools import partial
+from typing import Sequence
+
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+import torch.utils.checkpoint as checkpoint
+from mmcv.cnn.bricks import DropPath
+
+from mmcls.models.builder import BACKBONES
+from ..utils import LayerScale
+from .base_backbone import BaseBackbone
+
+
+def get_dwconv(dim, kernel_size, bias=True):
+ """build a pepth-wise convolution."""
+ return nn.Conv2d(
+ dim,
+ dim,
+ kernel_size=kernel_size,
+ padding=(kernel_size - 1) // 2,
+ bias=bias,
+ groups=dim)
+
+
+class HorNetLayerNorm(nn.Module):
+ """An implementation of LayerNorm of HorNet.
+
+ The differences between HorNetLayerNorm & torch LayerNorm:
+ 1. Supports two data formats channels_last or channels_first.
+
+ Args:
+ normalized_shape (int or list or torch.Size): input shape from an
+ expected input of size.
+ eps (float): a value added to the denominator for numerical stability.
+ Defaults to 1e-5.
+ data_format (str): The ordering of the dimensions in the inputs.
+ channels_last corresponds to inputs with shape (batch_size, height,
+ width, channels) while channels_first corresponds to inputs with
+ shape (batch_size, channels, height, width).
+ Defaults to 'channels_last'.
+ """
+
+ def __init__(self,
+ normalized_shape,
+ eps=1e-6,
+ data_format='channels_last'):
+ super().__init__()
+ self.weight = nn.Parameter(torch.ones(normalized_shape))
+ self.bias = nn.Parameter(torch.zeros(normalized_shape))
+ self.eps = eps
+ self.data_format = data_format
+ if self.data_format not in ['channels_last', 'channels_first']:
+ raise ValueError(
+ 'data_format must be channels_last or channels_first')
+ self.normalized_shape = (normalized_shape, )
+
+ def forward(self, x):
+ if self.data_format == 'channels_last':
+ return F.layer_norm(x, self.normalized_shape, self.weight,
+ self.bias, self.eps)
+ elif self.data_format == 'channels_first':
+ u = x.mean(1, keepdim=True)
+ s = (x - u).pow(2).mean(1, keepdim=True)
+ x = (x - u) / torch.sqrt(s + self.eps)
+ x = self.weight[:, None, None] * x + self.bias[:, None, None]
+ return x
+
+
+class GlobalLocalFilter(nn.Module):
+ """A GlobalLocalFilter of HorNet.
+
+ Args:
+ dim (int): Number of input channels.
+ h (int): Height of complex_weight.
+ Defaults to 14.
+ w (int): Width of complex_weight.
+ Defaults to 8.
+ """
+
+ def __init__(self, dim, h=14, w=8):
+ super().__init__()
+ self.dw = nn.Conv2d(
+ dim // 2,
+ dim // 2,
+ kernel_size=3,
+ padding=1,
+ bias=False,
+ groups=dim // 2)
+ self.complex_weight = nn.Parameter(
+ torch.randn(dim // 2, h, w, 2, dtype=torch.float32) * 0.02)
+ self.pre_norm = HorNetLayerNorm(
+ dim, eps=1e-6, data_format='channels_first')
+ self.post_norm = HorNetLayerNorm(
+ dim, eps=1e-6, data_format='channels_first')
+
+ def forward(self, x):
+ x = self.pre_norm(x)
+ x1, x2 = torch.chunk(x, 2, dim=1)
+ x1 = self.dw(x1)
+
+ x2 = x2.to(torch.float32)
+ B, C, a, b = x2.shape
+ x2 = torch.fft.rfft2(x2, dim=(2, 3), norm='ortho')
+
+ weight = self.complex_weight
+ if not weight.shape[1:3] == x2.shape[2:4]:
+ weight = F.interpolate(
+ weight.permute(3, 0, 1, 2),
+ size=x2.shape[2:4],
+ mode='bilinear',
+ align_corners=True).permute(1, 2, 3, 0)
+
+ weight = torch.view_as_complex(weight.contiguous())
+
+ x2 = x2 * weight
+ x2 = torch.fft.irfft2(x2, s=(a, b), dim=(2, 3), norm='ortho')
+
+ x = torch.cat([x1.unsqueeze(2), x2.unsqueeze(2)],
+ dim=2).reshape(B, 2 * C, a, b)
+ x = self.post_norm(x)
+ return x
+
+
+class gnConv(nn.Module):
+ """A gnConv of HorNet.
+
+ Args:
+ dim (int): Number of input channels.
+ order (int): Order of gnConv.
+ Defaults to 5.
+ dw_cfg (dict): The Config for dw conv.
+ Defaults to ``dict(type='DW', kernel_size=7)``.
+ scale (float): Scaling parameter of gflayer outputs.
+ Defaults to 1.0.
+ """
+
+ def __init__(self,
+ dim,
+ order=5,
+ dw_cfg=dict(type='DW', kernel_size=7),
+ scale=1.0):
+ super().__init__()
+ self.order = order
+ self.dims = [dim // 2**i for i in range(order)]
+ self.dims.reverse()
+ self.proj_in = nn.Conv2d(dim, 2 * dim, 1)
+
+ cfg = copy.deepcopy(dw_cfg)
+ dw_type = cfg.pop('type')
+ assert dw_type in ['DW', 'GF'],\
+ 'dw_type should be `DW` or `GF`'
+ if dw_type == 'DW':
+ self.dwconv = get_dwconv(sum(self.dims), **cfg)
+ elif dw_type == 'GF':
+ self.dwconv = GlobalLocalFilter(sum(self.dims), **cfg)
+
+ self.proj_out = nn.Conv2d(dim, dim, 1)
+
+ self.projs = nn.ModuleList([
+ nn.Conv2d(self.dims[i], self.dims[i + 1], 1)
+ for i in range(order - 1)
+ ])
+
+ self.scale = scale
+
+ def forward(self, x):
+ x = self.proj_in(x)
+ y, x = torch.split(x, (self.dims[0], sum(self.dims)), dim=1)
+
+ x = self.dwconv(x) * self.scale
+
+ dw_list = torch.split(x, self.dims, dim=1)
+ x = y * dw_list[0]
+
+ for i in range(self.order - 1):
+ x = self.projs[i](x) * dw_list[i + 1]
+
+ x = self.proj_out(x)
+
+ return x
+
+
+class HorNetBlock(nn.Module):
+ """A block of HorNet.
+
+ Args:
+ dim (int): Number of input channels.
+ order (int): Order of gnConv.
+ Defaults to 5.
+ dw_cfg (dict): The Config for dw conv.
+ Defaults to ``dict(type='DW', kernel_size=7)``.
+ scale (float): Scaling parameter of gflayer outputs.
+ Defaults to 1.0.
+ drop_path_rate (float): Stochastic depth rate. Defaults to 0.
+ use_layer_scale (bool): Whether to use use_layer_scale in HorNet
+ block. Defaults to True.
+ """
+
+ def __init__(self,
+ dim,
+ order=5,
+ dw_cfg=dict(type='DW', kernel_size=7),
+ scale=1.0,
+ drop_path_rate=0.,
+ use_layer_scale=True):
+ super().__init__()
+ self.out_channels = dim
+
+ self.norm1 = HorNetLayerNorm(
+ dim, eps=1e-6, data_format='channels_first')
+ self.gnconv = gnConv(dim, order, dw_cfg, scale)
+ self.norm2 = HorNetLayerNorm(dim, eps=1e-6)
+ self.pwconv1 = nn.Linear(dim, 4 * dim)
+ self.act = nn.GELU()
+ self.pwconv2 = nn.Linear(4 * dim, dim)
+
+ if use_layer_scale:
+ self.gamma1 = LayerScale(dim, data_format='channels_first')
+ self.gamma2 = LayerScale(dim)
+ else:
+ self.gamma1, self.gamma2 = nn.Identity(), nn.Identity()
+
+ self.drop_path = DropPath(
+ drop_path_rate) if drop_path_rate > 0. else nn.Identity()
+
+ def forward(self, x):
+ x = x + self.drop_path(self.gamma1(self.gnconv(self.norm1(x))))
+
+ input = x
+ x = x.permute(0, 2, 3, 1) # (N, C, H, W) -> (N, H, W, C)
+ x = self.norm2(x)
+ x = self.pwconv1(x)
+ x = self.act(x)
+ x = self.pwconv2(x)
+ x = self.gamma2(x)
+ x = x.permute(0, 3, 1, 2) # (N, H, W, C) -> (N, C, H, W)
+
+ x = input + self.drop_path(x)
+ return x
+
+
+@BACKBONES.register_module()
+class HorNet(BaseBackbone):
+ """HorNet
+ A PyTorch impl of : `HorNet: Efficient High-Order Spatial Interactions
+ with Recursive Gated Convolutions`
+
+ Inspiration from
+ https://github.com/raoyongming/HorNet
+
+ Args:
+ arch (str | dict): HorNet architecture.
+ If use string, choose from 'tiny', 'small', 'base' and 'large'.
+ If use dict, it should have below keys:
+ - **base_dim** (int): The base dimensions of embedding.
+ - **depths** (List[int]): The number of blocks in each stage.
+ - **orders** (List[int]): The number of order of gnConv in each
+ stage.
+ - **dw_cfg** (List[dict]): The Config for dw conv.
+
+ Defaults to 'tiny'.
+ in_channels (int): Number of input image channels. Defaults to 3.
+ drop_path_rate (float): Stochastic depth rate. Defaults to 0.
+ scale (float): Scaling parameter of gflayer outputs. Defaults to 1/3.
+ use_layer_scale (bool): Whether to use use_layer_scale in HorNet
+ block. Defaults to True.
+ out_indices (Sequence[int]): Output from which stages.
+ Default: ``(3, )``.
+ frozen_stages (int): Stages to be frozen (stop grad and set eval mode).
+ -1 means not freezing any parameters. Defaults to -1.
+ with_cp (bool): Use checkpoint or not. Using checkpoint will save some
+ memory while slowing down the training speed. Defaults to False.
+ gap_before_final_norm (bool): Whether to globally average the feature
+ map before the final norm layer. In the official repo, it's only
+ used in classification task. Defaults to True.
+ init_cfg (dict, optional): The Config for initialization.
+ Defaults to None.
+ """
+ arch_zoo = {
+ **dict.fromkeys(['t', 'tiny'],
+ {'base_dim': 64,
+ 'depths': [2, 3, 18, 2],
+ 'orders': [2, 3, 4, 5],
+ 'dw_cfg': [dict(type='DW', kernel_size=7)] * 4}),
+ **dict.fromkeys(['t-gf', 'tiny-gf'],
+ {'base_dim': 64,
+ 'depths': [2, 3, 18, 2],
+ 'orders': [2, 3, 4, 5],
+ 'dw_cfg': [
+ dict(type='DW', kernel_size=7),
+ dict(type='DW', kernel_size=7),
+ dict(type='GF', h=14, w=8),
+ dict(type='GF', h=7, w=4)]}),
+ **dict.fromkeys(['s', 'small'],
+ {'base_dim': 96,
+ 'depths': [2, 3, 18, 2],
+ 'orders': [2, 3, 4, 5],
+ 'dw_cfg': [dict(type='DW', kernel_size=7)] * 4}),
+ **dict.fromkeys(['s-gf', 'small-gf'],
+ {'base_dim': 96,
+ 'depths': [2, 3, 18, 2],
+ 'orders': [2, 3, 4, 5],
+ 'dw_cfg': [
+ dict(type='DW', kernel_size=7),
+ dict(type='DW', kernel_size=7),
+ dict(type='GF', h=14, w=8),
+ dict(type='GF', h=7, w=4)]}),
+ **dict.fromkeys(['b', 'base'],
+ {'base_dim': 128,
+ 'depths': [2, 3, 18, 2],
+ 'orders': [2, 3, 4, 5],
+ 'dw_cfg': [dict(type='DW', kernel_size=7)] * 4}),
+ **dict.fromkeys(['b-gf', 'base-gf'],
+ {'base_dim': 128,
+ 'depths': [2, 3, 18, 2],
+ 'orders': [2, 3, 4, 5],
+ 'dw_cfg': [
+ dict(type='DW', kernel_size=7),
+ dict(type='DW', kernel_size=7),
+ dict(type='GF', h=14, w=8),
+ dict(type='GF', h=7, w=4)]}),
+ **dict.fromkeys(['b-gf384', 'base-gf384'],
+ {'base_dim': 128,
+ 'depths': [2, 3, 18, 2],
+ 'orders': [2, 3, 4, 5],
+ 'dw_cfg': [
+ dict(type='DW', kernel_size=7),
+ dict(type='DW', kernel_size=7),
+ dict(type='GF', h=24, w=12),
+ dict(type='GF', h=13, w=7)]}),
+ **dict.fromkeys(['l', 'large'],
+ {'base_dim': 192,
+ 'depths': [2, 3, 18, 2],
+ 'orders': [2, 3, 4, 5],
+ 'dw_cfg': [dict(type='DW', kernel_size=7)] * 4}),
+ **dict.fromkeys(['l-gf', 'large-gf'],
+ {'base_dim': 192,
+ 'depths': [2, 3, 18, 2],
+ 'orders': [2, 3, 4, 5],
+ 'dw_cfg': [
+ dict(type='DW', kernel_size=7),
+ dict(type='DW', kernel_size=7),
+ dict(type='GF', h=14, w=8),
+ dict(type='GF', h=7, w=4)]}),
+ **dict.fromkeys(['l-gf384', 'large-gf384'],
+ {'base_dim': 192,
+ 'depths': [2, 3, 18, 2],
+ 'orders': [2, 3, 4, 5],
+ 'dw_cfg': [
+ dict(type='DW', kernel_size=7),
+ dict(type='DW', kernel_size=7),
+ dict(type='GF', h=24, w=12),
+ dict(type='GF', h=13, w=7)]}),
+ } # yapf: disable
+
+ def __init__(self,
+ arch='tiny',
+ in_channels=3,
+ drop_path_rate=0.,
+ scale=1 / 3,
+ use_layer_scale=True,
+ out_indices=(3, ),
+ frozen_stages=-1,
+ with_cp=False,
+ gap_before_final_norm=True,
+ init_cfg=None):
+ super().__init__(init_cfg=init_cfg)
+ if fft is None:
+ raise RuntimeError(
+ 'Failed to import torch.fft. Please install "torch>=1.7".')
+
+ if isinstance(arch, str):
+ arch = arch.lower()
+ assert arch in set(self.arch_zoo), \
+ f'Arch {arch} is not in default archs {set(self.arch_zoo)}'
+ self.arch_settings = self.arch_zoo[arch]
+ else:
+ essential_keys = {'base_dim', 'depths', 'orders', 'dw_cfg'}
+ assert isinstance(arch, dict) and set(arch) == essential_keys, \
+ f'Custom arch needs a dict with keys {essential_keys}'
+ self.arch_settings = arch
+
+ self.scale = scale
+ self.out_indices = out_indices
+ self.frozen_stages = frozen_stages
+ self.with_cp = with_cp
+ self.gap_before_final_norm = gap_before_final_norm
+
+ base_dim = self.arch_settings['base_dim']
+ dims = list(map(lambda x: 2**x * base_dim, range(4)))
+
+ self.downsample_layers = nn.ModuleList()
+ stem = nn.Sequential(
+ nn.Conv2d(in_channels, dims[0], kernel_size=4, stride=4),
+ HorNetLayerNorm(dims[0], eps=1e-6, data_format='channels_first'))
+ self.downsample_layers.append(stem)
+ for i in range(3):
+ downsample_layer = nn.Sequential(
+ HorNetLayerNorm(
+ dims[i], eps=1e-6, data_format='channels_first'),
+ nn.Conv2d(dims[i], dims[i + 1], kernel_size=2, stride=2),
+ )
+ self.downsample_layers.append(downsample_layer)
+
+ total_depth = sum(self.arch_settings['depths'])
+ dpr = [
+ x.item() for x in torch.linspace(0, drop_path_rate, total_depth)
+ ] # stochastic depth decay rule
+
+ cur_block_idx = 0
+ self.stages = nn.ModuleList()
+ for i in range(4):
+ stage = nn.Sequential(*[
+ HorNetBlock(
+ dim=dims[i],
+ order=self.arch_settings['orders'][i],
+ dw_cfg=self.arch_settings['dw_cfg'][i],
+ scale=self.scale,
+ drop_path_rate=dpr[cur_block_idx + j],
+ use_layer_scale=use_layer_scale)
+ for j in range(self.arch_settings['depths'][i])
+ ])
+ self.stages.append(stage)
+ cur_block_idx += self.arch_settings['depths'][i]
+
+ if isinstance(out_indices, int):
+ out_indices = [out_indices]
+ assert isinstance(out_indices, Sequence), \
+ f'"out_indices" must by a sequence or int, ' \
+ f'get {type(out_indices)} instead.'
+ out_indices = list(out_indices)
+ for i, index in enumerate(out_indices):
+ if index < 0:
+ out_indices[i] = len(self.stages) + index
+ assert 0 <= out_indices[i] <= len(self.stages), \
+ f'Invalid out_indices {index}.'
+ self.out_indices = out_indices
+
+ norm_layer = partial(
+ HorNetLayerNorm, eps=1e-6, data_format='channels_first')
+ for i_layer in out_indices:
+ layer = norm_layer(dims[i_layer])
+ layer_name = f'norm{i_layer}'
+ self.add_module(layer_name, layer)
+
+ def train(self, mode=True):
+ super(HorNet, self).train(mode)
+ self._freeze_stages()
+
+ def _freeze_stages(self):
+ for i in range(0, self.frozen_stages + 1):
+ # freeze patch embed
+ m = self.downsample_layers[i]
+ m.eval()
+ for param in m.parameters():
+ param.requires_grad = False
+
+ # freeze blocks
+ m = self.stages[i]
+ m.eval()
+ for param in m.parameters():
+ param.requires_grad = False
+
+ if i in self.out_indices:
+ # freeze norm
+ m = getattr(self, f'norm{i + 1}')
+ m.eval()
+ for param in m.parameters():
+ param.requires_grad = False
+
+ def forward(self, x):
+ outs = []
+ for i in range(4):
+ x = self.downsample_layers[i](x)
+ if self.with_cp:
+ x = checkpoint.checkpoint_sequential(self.stages[i],
+ len(self.stages[i]), x)
+ else:
+ x = self.stages[i](x)
+ if i in self.out_indices:
+ norm_layer = getattr(self, f'norm{i}')
+ if self.gap_before_final_norm:
+ gap = x.mean([-2, -1], keepdim=True)
+ outs.append(norm_layer(gap).flatten(1))
+ else:
+ # The output of LayerNorm2d may be discontiguous, which
+ # may cause some problem in the downstream tasks
+ outs.append(norm_layer(x).contiguous())
+ return tuple(outs)
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/hrnet.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/hrnet.py
new file mode 100644
index 0000000000000000000000000000000000000000..57baf0cae74c724470e5c968cff515cda73ac2ef
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/hrnet.py
@@ -0,0 +1,563 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import torch.nn as nn
+from mmcv.cnn import build_conv_layer, build_norm_layer
+from mmcv.runner import BaseModule, ModuleList, Sequential
+from torch.nn.modules.batchnorm import _BatchNorm
+
+from ..builder import BACKBONES
+from .resnet import BasicBlock, Bottleneck, ResLayer, get_expansion
+
+
+class HRModule(BaseModule):
+ """High-Resolution Module for HRNet.
+
+ In this module, every branch has 4 BasicBlocks/Bottlenecks. Fusion/Exchange
+ is in this module.
+
+ Args:
+ num_branches (int): The number of branches.
+ block (``BaseModule``): Convolution block module.
+ num_blocks (tuple): The number of blocks in each branch.
+ The length must be equal to ``num_branches``.
+ num_channels (tuple): The number of base channels in each branch.
+ The length must be equal to ``num_branches``.
+ multiscale_output (bool): Whether to output multi-level features
+ produced by multiple branches. If False, only the first level
+ feature will be output. Defaults to True.
+ with_cp (bool): Use checkpoint or not. Using checkpoint will save some
+ memory while slowing down the training speed. Defaults to False.
+ conv_cfg (dict, optional): Dictionary to construct and config conv
+ layer. Defaults to None.
+ norm_cfg (dict): Dictionary to construct and config norm layer.
+ Defaults to ``dict(type='BN')``.
+ block_init_cfg (dict, optional): The initialization configs of every
+ blocks. Defaults to None.
+ init_cfg (dict or list[dict], optional): Initialization config dict.
+ Defaults to None.
+ """
+
+ def __init__(self,
+ num_branches,
+ block,
+ num_blocks,
+ in_channels,
+ num_channels,
+ multiscale_output=True,
+ with_cp=False,
+ conv_cfg=None,
+ norm_cfg=dict(type='BN'),
+ block_init_cfg=None,
+ init_cfg=None):
+ super(HRModule, self).__init__(init_cfg)
+ self.block_init_cfg = block_init_cfg
+ self._check_branches(num_branches, num_blocks, in_channels,
+ num_channels)
+
+ self.in_channels = in_channels
+ self.num_branches = num_branches
+
+ self.multiscale_output = multiscale_output
+ self.norm_cfg = norm_cfg
+ self.conv_cfg = conv_cfg
+ self.with_cp = with_cp
+ self.branches = self._make_branches(num_branches, block, num_blocks,
+ num_channels)
+ self.fuse_layers = self._make_fuse_layers()
+ self.relu = nn.ReLU(inplace=False)
+
+ def _check_branches(self, num_branches, num_blocks, in_channels,
+ num_channels):
+ if num_branches != len(num_blocks):
+ error_msg = f'NUM_BRANCHES({num_branches}) ' \
+ f'!= NUM_BLOCKS({len(num_blocks)})'
+ raise ValueError(error_msg)
+
+ if num_branches != len(num_channels):
+ error_msg = f'NUM_BRANCHES({num_branches}) ' \
+ f'!= NUM_CHANNELS({len(num_channels)})'
+ raise ValueError(error_msg)
+
+ if num_branches != len(in_channels):
+ error_msg = f'NUM_BRANCHES({num_branches}) ' \
+ f'!= NUM_INCHANNELS({len(in_channels)})'
+ raise ValueError(error_msg)
+
+ def _make_branches(self, num_branches, block, num_blocks, num_channels):
+ branches = []
+
+ for i in range(num_branches):
+ out_channels = num_channels[i] * get_expansion(block)
+ branches.append(
+ ResLayer(
+ block=block,
+ num_blocks=num_blocks[i],
+ in_channels=self.in_channels[i],
+ out_channels=out_channels,
+ conv_cfg=self.conv_cfg,
+ norm_cfg=self.norm_cfg,
+ with_cp=self.with_cp,
+ init_cfg=self.block_init_cfg,
+ ))
+
+ return ModuleList(branches)
+
+ def _make_fuse_layers(self):
+ if self.num_branches == 1:
+ return None
+
+ num_branches = self.num_branches
+ in_channels = self.in_channels
+ fuse_layers = []
+ num_out_branches = num_branches if self.multiscale_output else 1
+ for i in range(num_out_branches):
+ fuse_layer = []
+ for j in range(num_branches):
+ if j > i:
+ # Upsample the feature maps of smaller scales.
+ fuse_layer.append(
+ nn.Sequential(
+ build_conv_layer(
+ self.conv_cfg,
+ in_channels[j],
+ in_channels[i],
+ kernel_size=1,
+ stride=1,
+ padding=0,
+ bias=False),
+ build_norm_layer(self.norm_cfg, in_channels[i])[1],
+ nn.Upsample(
+ scale_factor=2**(j - i), mode='nearest')))
+ elif j == i:
+ # Keep the feature map with the same scale.
+ fuse_layer.append(None)
+ else:
+ # Downsample the feature maps of larger scales.
+ conv_downsamples = []
+ for k in range(i - j):
+ # Use stacked convolution layers to downsample.
+ if k == i - j - 1:
+ conv_downsamples.append(
+ nn.Sequential(
+ build_conv_layer(
+ self.conv_cfg,
+ in_channels[j],
+ in_channels[i],
+ kernel_size=3,
+ stride=2,
+ padding=1,
+ bias=False),
+ build_norm_layer(self.norm_cfg,
+ in_channels[i])[1]))
+ else:
+ conv_downsamples.append(
+ nn.Sequential(
+ build_conv_layer(
+ self.conv_cfg,
+ in_channels[j],
+ in_channels[j],
+ kernel_size=3,
+ stride=2,
+ padding=1,
+ bias=False),
+ build_norm_layer(self.norm_cfg,
+ in_channels[j])[1],
+ nn.ReLU(inplace=False)))
+ fuse_layer.append(nn.Sequential(*conv_downsamples))
+ fuse_layers.append(nn.ModuleList(fuse_layer))
+
+ return nn.ModuleList(fuse_layers)
+
+ def forward(self, x):
+ """Forward function."""
+ if self.num_branches == 1:
+ return [self.branches[0](x[0])]
+
+ for i in range(self.num_branches):
+ x[i] = self.branches[i](x[i])
+
+ x_fuse = []
+ for i in range(len(self.fuse_layers)):
+ y = 0
+ for j in range(self.num_branches):
+ if i == j:
+ y += x[j]
+ else:
+ y += self.fuse_layers[i][j](x[j])
+ x_fuse.append(self.relu(y))
+ return x_fuse
+
+
+@BACKBONES.register_module()
+class HRNet(BaseModule):
+ """HRNet backbone.
+
+ `High-Resolution Representations for Labeling Pixels and Regions
+ `_.
+
+ Args:
+ arch (str): The preset HRNet architecture, includes 'w18', 'w30',
+ 'w32', 'w40', 'w44', 'w48', 'w64'. It will only be used if
+ extra is ``None``. Defaults to 'w32'.
+ extra (dict, optional): Detailed configuration for each stage of HRNet.
+ There must be 4 stages, the configuration for each stage must have
+ 5 keys:
+
+ - num_modules (int): The number of HRModule in this stage.
+ - num_branches (int): The number of branches in the HRModule.
+ - block (str): The type of convolution block. Please choose between
+ 'BOTTLENECK' and 'BASIC'.
+ - num_blocks (tuple): The number of blocks in each branch.
+ The length must be equal to num_branches.
+ - num_channels (tuple): The number of base channels in each branch.
+ The length must be equal to num_branches.
+
+ Defaults to None.
+ in_channels (int): Number of input image channels. Defaults to 3.
+ conv_cfg (dict, optional): Dictionary to construct and config conv
+ layer. Defaults to None.
+ norm_cfg (dict): Dictionary to construct and config norm layer.
+ Defaults to ``dict(type='BN')``.
+ norm_eval (bool): Whether to set norm layers to eval mode, namely,
+ freeze running stats (mean and var). Note: Effect on Batch Norm
+ and its variants only. Defaults to False.
+ with_cp (bool): Use checkpoint or not. Using checkpoint will save some
+ memory while slowing down the training speed. Defaults to False.
+ zero_init_residual (bool): Whether to use zero init for last norm layer
+ in resblocks to let them behave as identity. Defaults to False.
+ multiscale_output (bool): Whether to output multi-level features
+ produced by multiple branches. If False, only the first level
+ feature will be output. Defaults to True.
+ init_cfg (dict or list[dict], optional): Initialization config dict.
+ Defaults to None.
+
+ Example:
+ >>> import torch
+ >>> from mmcls.models import HRNet
+ >>> extra = dict(
+ >>> stage1=dict(
+ >>> num_modules=1,
+ >>> num_branches=1,
+ >>> block='BOTTLENECK',
+ >>> num_blocks=(4, ),
+ >>> num_channels=(64, )),
+ >>> stage2=dict(
+ >>> num_modules=1,
+ >>> num_branches=2,
+ >>> block='BASIC',
+ >>> num_blocks=(4, 4),
+ >>> num_channels=(32, 64)),
+ >>> stage3=dict(
+ >>> num_modules=4,
+ >>> num_branches=3,
+ >>> block='BASIC',
+ >>> num_blocks=(4, 4, 4),
+ >>> num_channels=(32, 64, 128)),
+ >>> stage4=dict(
+ >>> num_modules=3,
+ >>> num_branches=4,
+ >>> block='BASIC',
+ >>> num_blocks=(4, 4, 4, 4),
+ >>> num_channels=(32, 64, 128, 256)))
+ >>> self = HRNet(extra, in_channels=1)
+ >>> self.eval()
+ >>> inputs = torch.rand(1, 1, 32, 32)
+ >>> level_outputs = self.forward(inputs)
+ >>> for level_out in level_outputs:
+ ... print(tuple(level_out.shape))
+ (1, 32, 8, 8)
+ (1, 64, 4, 4)
+ (1, 128, 2, 2)
+ (1, 256, 1, 1)
+ """
+
+ blocks_dict = {'BASIC': BasicBlock, 'BOTTLENECK': Bottleneck}
+ arch_zoo = {
+ # num_modules, num_branches, block, num_blocks, num_channels
+ 'w18': [[1, 1, 'BOTTLENECK', (4, ), (64, )],
+ [1, 2, 'BASIC', (4, 4), (18, 36)],
+ [4, 3, 'BASIC', (4, 4, 4), (18, 36, 72)],
+ [3, 4, 'BASIC', (4, 4, 4, 4), (18, 36, 72, 144)]],
+ 'w30': [[1, 1, 'BOTTLENECK', (4, ), (64, )],
+ [1, 2, 'BASIC', (4, 4), (30, 60)],
+ [4, 3, 'BASIC', (4, 4, 4), (30, 60, 120)],
+ [3, 4, 'BASIC', (4, 4, 4, 4), (30, 60, 120, 240)]],
+ 'w32': [[1, 1, 'BOTTLENECK', (4, ), (64, )],
+ [1, 2, 'BASIC', (4, 4), (32, 64)],
+ [4, 3, 'BASIC', (4, 4, 4), (32, 64, 128)],
+ [3, 4, 'BASIC', (4, 4, 4, 4), (32, 64, 128, 256)]],
+ 'w40': [[1, 1, 'BOTTLENECK', (4, ), (64, )],
+ [1, 2, 'BASIC', (4, 4), (40, 80)],
+ [4, 3, 'BASIC', (4, 4, 4), (40, 80, 160)],
+ [3, 4, 'BASIC', (4, 4, 4, 4), (40, 80, 160, 320)]],
+ 'w44': [[1, 1, 'BOTTLENECK', (4, ), (64, )],
+ [1, 2, 'BASIC', (4, 4), (44, 88)],
+ [4, 3, 'BASIC', (4, 4, 4), (44, 88, 176)],
+ [3, 4, 'BASIC', (4, 4, 4, 4), (44, 88, 176, 352)]],
+ 'w48': [[1, 1, 'BOTTLENECK', (4, ), (64, )],
+ [1, 2, 'BASIC', (4, 4), (48, 96)],
+ [4, 3, 'BASIC', (4, 4, 4), (48, 96, 192)],
+ [3, 4, 'BASIC', (4, 4, 4, 4), (48, 96, 192, 384)]],
+ 'w64': [[1, 1, 'BOTTLENECK', (4, ), (64, )],
+ [1, 2, 'BASIC', (4, 4), (64, 128)],
+ [4, 3, 'BASIC', (4, 4, 4), (64, 128, 256)],
+ [3, 4, 'BASIC', (4, 4, 4, 4), (64, 128, 256, 512)]],
+ } # yapf:disable
+
+ def __init__(self,
+ arch='w32',
+ extra=None,
+ in_channels=3,
+ conv_cfg=None,
+ norm_cfg=dict(type='BN'),
+ norm_eval=False,
+ with_cp=False,
+ zero_init_residual=False,
+ multiscale_output=True,
+ init_cfg=[
+ dict(type='Kaiming', layer='Conv2d'),
+ dict(
+ type='Constant',
+ val=1,
+ layer=['_BatchNorm', 'GroupNorm'])
+ ]):
+ super(HRNet, self).__init__(init_cfg)
+
+ extra = self.parse_arch(arch, extra)
+
+ # Assert configurations of 4 stages are in extra
+ for i in range(1, 5):
+ assert f'stage{i}' in extra, f'Missing stage{i} config in "extra".'
+ # Assert whether the length of `num_blocks` and `num_channels` are
+ # equal to `num_branches`
+ cfg = extra[f'stage{i}']
+ assert len(cfg['num_blocks']) == cfg['num_branches'] and \
+ len(cfg['num_channels']) == cfg['num_branches']
+
+ self.extra = extra
+ self.conv_cfg = conv_cfg
+ self.norm_cfg = norm_cfg
+ self.norm_eval = norm_eval
+ self.with_cp = with_cp
+ self.zero_init_residual = zero_init_residual
+
+ # -------------------- stem net --------------------
+ self.conv1 = build_conv_layer(
+ self.conv_cfg,
+ in_channels,
+ out_channels=64,
+ kernel_size=3,
+ stride=2,
+ padding=1,
+ bias=False)
+
+ self.norm1_name, norm1 = build_norm_layer(self.norm_cfg, 64, postfix=1)
+ self.add_module(self.norm1_name, norm1)
+
+ self.conv2 = build_conv_layer(
+ self.conv_cfg,
+ in_channels=64,
+ out_channels=64,
+ kernel_size=3,
+ stride=2,
+ padding=1,
+ bias=False)
+
+ self.norm2_name, norm2 = build_norm_layer(self.norm_cfg, 64, postfix=2)
+ self.add_module(self.norm2_name, norm2)
+ self.relu = nn.ReLU(inplace=True)
+
+ # -------------------- stage 1 --------------------
+ self.stage1_cfg = self.extra['stage1']
+ base_channels = self.stage1_cfg['num_channels']
+ block_type = self.stage1_cfg['block']
+ num_blocks = self.stage1_cfg['num_blocks']
+
+ block = self.blocks_dict[block_type]
+ num_channels = [
+ channel * get_expansion(block) for channel in base_channels
+ ]
+ # To align with the original code, use layer1 instead of stage1 here.
+ self.layer1 = ResLayer(
+ block,
+ in_channels=64,
+ out_channels=num_channels[0],
+ num_blocks=num_blocks[0])
+ pre_num_channels = num_channels
+
+ # -------------------- stage 2~4 --------------------
+ for i in range(2, 5):
+ stage_cfg = self.extra[f'stage{i}']
+ base_channels = stage_cfg['num_channels']
+ block = self.blocks_dict[stage_cfg['block']]
+ multiscale_output_ = multiscale_output if i == 4 else True
+
+ num_channels = [
+ channel * get_expansion(block) for channel in base_channels
+ ]
+ # The transition layer from layer1 to stage2
+ transition = self._make_transition_layer(pre_num_channels,
+ num_channels)
+ self.add_module(f'transition{i-1}', transition)
+ stage = self._make_stage(
+ stage_cfg, num_channels, multiscale_output=multiscale_output_)
+ self.add_module(f'stage{i}', stage)
+
+ pre_num_channels = num_channels
+
+ @property
+ def norm1(self):
+ """nn.Module: the normalization layer named "norm1" """
+ return getattr(self, self.norm1_name)
+
+ @property
+ def norm2(self):
+ """nn.Module: the normalization layer named "norm2" """
+ return getattr(self, self.norm2_name)
+
+ def _make_transition_layer(self, num_channels_pre_layer,
+ num_channels_cur_layer):
+ num_branches_cur = len(num_channels_cur_layer)
+ num_branches_pre = len(num_channels_pre_layer)
+
+ transition_layers = []
+ for i in range(num_branches_cur):
+ if i < num_branches_pre:
+ # For existing scale branches,
+ # add conv block when the channels are not the same.
+ if num_channels_cur_layer[i] != num_channels_pre_layer[i]:
+ transition_layers.append(
+ nn.Sequential(
+ build_conv_layer(
+ self.conv_cfg,
+ num_channels_pre_layer[i],
+ num_channels_cur_layer[i],
+ kernel_size=3,
+ stride=1,
+ padding=1,
+ bias=False),
+ build_norm_layer(self.norm_cfg,
+ num_channels_cur_layer[i])[1],
+ nn.ReLU(inplace=True)))
+ else:
+ transition_layers.append(nn.Identity())
+ else:
+ # For new scale branches, add stacked downsample conv blocks.
+ # For example, num_branches_pre = 2, for the 4th branch, add
+ # stacked two downsample conv blocks.
+ conv_downsamples = []
+ for j in range(i + 1 - num_branches_pre):
+ in_channels = num_channels_pre_layer[-1]
+ out_channels = num_channels_cur_layer[i] \
+ if j == i - num_branches_pre else in_channels
+ conv_downsamples.append(
+ nn.Sequential(
+ build_conv_layer(
+ self.conv_cfg,
+ in_channels,
+ out_channels,
+ kernel_size=3,
+ stride=2,
+ padding=1,
+ bias=False),
+ build_norm_layer(self.norm_cfg, out_channels)[1],
+ nn.ReLU(inplace=True)))
+ transition_layers.append(nn.Sequential(*conv_downsamples))
+
+ return nn.ModuleList(transition_layers)
+
+ def _make_stage(self, layer_config, in_channels, multiscale_output=True):
+ num_modules = layer_config['num_modules']
+ num_branches = layer_config['num_branches']
+ num_blocks = layer_config['num_blocks']
+ num_channels = layer_config['num_channels']
+ block = self.blocks_dict[layer_config['block']]
+
+ hr_modules = []
+ block_init_cfg = None
+ if self.zero_init_residual:
+ if block is BasicBlock:
+ block_init_cfg = dict(
+ type='Constant', val=0, override=dict(name='norm2'))
+ elif block is Bottleneck:
+ block_init_cfg = dict(
+ type='Constant', val=0, override=dict(name='norm3'))
+
+ for i in range(num_modules):
+ # multi_scale_output is only used for the last module
+ if not multiscale_output and i == num_modules - 1:
+ reset_multiscale_output = False
+ else:
+ reset_multiscale_output = True
+
+ hr_modules.append(
+ HRModule(
+ num_branches,
+ block,
+ num_blocks,
+ in_channels,
+ num_channels,
+ reset_multiscale_output,
+ with_cp=self.with_cp,
+ norm_cfg=self.norm_cfg,
+ conv_cfg=self.conv_cfg,
+ block_init_cfg=block_init_cfg))
+
+ return Sequential(*hr_modules)
+
+ def forward(self, x):
+ """Forward function."""
+ x = self.conv1(x)
+ x = self.norm1(x)
+ x = self.relu(x)
+ x = self.conv2(x)
+ x = self.norm2(x)
+ x = self.relu(x)
+ x = self.layer1(x)
+
+ x_list = [x]
+
+ for i in range(2, 5):
+ # Apply transition
+ transition = getattr(self, f'transition{i-1}')
+ inputs = []
+ for j, layer in enumerate(transition):
+ if j < len(x_list):
+ inputs.append(layer(x_list[j]))
+ else:
+ inputs.append(layer(x_list[-1]))
+ # Forward HRModule
+ stage = getattr(self, f'stage{i}')
+ x_list = stage(inputs)
+
+ return tuple(x_list)
+
+ def train(self, mode=True):
+ """Convert the model into training mode will keeping the normalization
+ layer freezed."""
+ super(HRNet, self).train(mode)
+ if mode and self.norm_eval:
+ for m in self.modules():
+ # trick: eval have effect on BatchNorm only
+ if isinstance(m, _BatchNorm):
+ m.eval()
+
+ def parse_arch(self, arch, extra=None):
+ if extra is not None:
+ return extra
+
+ assert arch in self.arch_zoo, \
+ ('Invalid arch, please choose arch from '
+ f'{list(self.arch_zoo.keys())}, or specify `extra` '
+ 'argument directly.')
+
+ extra = dict()
+ for i, stage_setting in enumerate(self.arch_zoo[arch], start=1):
+ extra[f'stage{i}'] = dict(
+ num_modules=stage_setting[0],
+ num_branches=stage_setting[1],
+ block=stage_setting[2],
+ num_blocks=stage_setting[3],
+ num_channels=stage_setting[4],
+ )
+
+ return extra
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/lenet.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/lenet.py
similarity index 94%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/lenet.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/lenet.py
index 81b9c59e15e653a0a65b63e3b27353f4a40fc631..11686619eb3b3a0ba3490a3038095316af3b6802 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/lenet.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/lenet.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import torch.nn as nn
from ..builder import BACKBONES
@@ -38,4 +39,4 @@ class LeNet5(BaseBackbone):
if self.num_classes > 0:
x = self.classifier(x.squeeze())
- return x
+ return (x, )
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/mlp_mixer.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/mlp_mixer.py
new file mode 100644
index 0000000000000000000000000000000000000000..13171a4b389594ce296d4202ffd7d5500865957b
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/mlp_mixer.py
@@ -0,0 +1,263 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from typing import Sequence
+
+import torch.nn as nn
+from mmcv.cnn import build_norm_layer
+from mmcv.cnn.bricks.transformer import FFN, PatchEmbed
+from mmcv.runner.base_module import BaseModule, ModuleList
+
+from ..builder import BACKBONES
+from ..utils import to_2tuple
+from .base_backbone import BaseBackbone
+
+
+class MixerBlock(BaseModule):
+ """Mlp-Mixer basic block.
+
+ Basic module of `MLP-Mixer: An all-MLP Architecture for Vision
+ `_
+
+ Args:
+ num_tokens (int): The number of patched tokens
+ embed_dims (int): The feature dimension
+ tokens_mlp_dims (int): The hidden dimension for tokens FFNs
+ channels_mlp_dims (int): The hidden dimension for channels FFNs
+ drop_rate (float): Probability of an element to be zeroed
+ after the feed forward layer. Defaults to 0.
+ drop_path_rate (float): Stochastic depth rate. Defaults to 0.
+ num_fcs (int): The number of fully-connected layers for FFNs.
+ Defaults to 2.
+ act_cfg (dict): The activation config for FFNs.
+ Defaluts to ``dict(type='GELU')``.
+ norm_cfg (dict): Config dict for normalization layer.
+ Defaults to ``dict(type='LN')``.
+ init_cfg (dict, optional): Initialization config dict.
+ Defaults to None.
+ """
+
+ def __init__(self,
+ num_tokens,
+ embed_dims,
+ tokens_mlp_dims,
+ channels_mlp_dims,
+ drop_rate=0.,
+ drop_path_rate=0.,
+ num_fcs=2,
+ act_cfg=dict(type='GELU'),
+ norm_cfg=dict(type='LN'),
+ init_cfg=None):
+ super(MixerBlock, self).__init__(init_cfg=init_cfg)
+
+ self.norm1_name, norm1 = build_norm_layer(
+ norm_cfg, embed_dims, postfix=1)
+ self.add_module(self.norm1_name, norm1)
+ self.token_mix = FFN(
+ embed_dims=num_tokens,
+ feedforward_channels=tokens_mlp_dims,
+ num_fcs=num_fcs,
+ ffn_drop=drop_rate,
+ dropout_layer=dict(type='DropPath', drop_prob=drop_path_rate),
+ act_cfg=act_cfg,
+ add_identity=False)
+
+ self.norm2_name, norm2 = build_norm_layer(
+ norm_cfg, embed_dims, postfix=2)
+ self.add_module(self.norm2_name, norm2)
+ self.channel_mix = FFN(
+ embed_dims=embed_dims,
+ feedforward_channels=channels_mlp_dims,
+ num_fcs=num_fcs,
+ ffn_drop=drop_rate,
+ dropout_layer=dict(type='DropPath', drop_prob=drop_path_rate),
+ act_cfg=act_cfg)
+
+ @property
+ def norm1(self):
+ return getattr(self, self.norm1_name)
+
+ @property
+ def norm2(self):
+ return getattr(self, self.norm2_name)
+
+ def init_weights(self):
+ super(MixerBlock, self).init_weights()
+ for m in self.token_mix.modules():
+ if isinstance(m, nn.Linear):
+ nn.init.xavier_uniform_(m.weight)
+ nn.init.normal_(m.bias, std=1e-6)
+ for m in self.channel_mix.modules():
+ if isinstance(m, nn.Linear):
+ nn.init.xavier_uniform_(m.weight)
+ nn.init.normal_(m.bias, std=1e-6)
+
+ def forward(self, x):
+ out = self.norm1(x).transpose(1, 2)
+ x = x + self.token_mix(out).transpose(1, 2)
+ x = self.channel_mix(self.norm2(x), identity=x)
+ return x
+
+
+@BACKBONES.register_module()
+class MlpMixer(BaseBackbone):
+ """Mlp-Mixer backbone.
+
+ Pytorch implementation of `MLP-Mixer: An all-MLP Architecture for Vision
+ `_
+
+ Args:
+ arch (str | dict): MLP Mixer architecture. If use string, choose from
+ 'small', 'base' and 'large'. If use dict, it should have below
+ keys:
+
+ - **embed_dims** (int): The dimensions of embedding.
+ - **num_layers** (int): The number of MLP blocks.
+ - **tokens_mlp_dims** (int): The hidden dimensions for tokens FFNs.
+ - **channels_mlp_dims** (int): The The hidden dimensions for
+ channels FFNs.
+
+ Defaults to 'base'.
+ img_size (int | tuple): The input image shape. Defaults to 224.
+ patch_size (int | tuple): The patch size in patch embedding.
+ Defaults to 16.
+ out_indices (Sequence | int): Output from which layer.
+ Defaults to -1, means the last layer.
+ drop_rate (float): Probability of an element to be zeroed.
+ Defaults to 0.
+ drop_path_rate (float): stochastic depth rate. Defaults to 0.
+ norm_cfg (dict): Config dict for normalization layer.
+ Defaults to ``dict(type='LN')``.
+ act_cfg (dict): The activation config for FFNs. Default GELU.
+ patch_cfg (dict): Configs of patch embeding. Defaults to an empty dict.
+ layer_cfgs (Sequence | dict): Configs of each mixer block layer.
+ Defaults to an empty dict.
+ init_cfg (dict, optional): Initialization config dict.
+ Defaults to None.
+ """
+
+ arch_zoo = {
+ **dict.fromkeys(
+ ['s', 'small'], {
+ 'embed_dims': 512,
+ 'num_layers': 8,
+ 'tokens_mlp_dims': 256,
+ 'channels_mlp_dims': 2048,
+ }),
+ **dict.fromkeys(
+ ['b', 'base'], {
+ 'embed_dims': 768,
+ 'num_layers': 12,
+ 'tokens_mlp_dims': 384,
+ 'channels_mlp_dims': 3072,
+ }),
+ **dict.fromkeys(
+ ['l', 'large'], {
+ 'embed_dims': 1024,
+ 'num_layers': 24,
+ 'tokens_mlp_dims': 512,
+ 'channels_mlp_dims': 4096,
+ }),
+ }
+
+ def __init__(self,
+ arch='base',
+ img_size=224,
+ patch_size=16,
+ out_indices=-1,
+ drop_rate=0.,
+ drop_path_rate=0.,
+ norm_cfg=dict(type='LN'),
+ act_cfg=dict(type='GELU'),
+ patch_cfg=dict(),
+ layer_cfgs=dict(),
+ init_cfg=None):
+ super(MlpMixer, self).__init__(init_cfg)
+
+ if isinstance(arch, str):
+ arch = arch.lower()
+ assert arch in set(self.arch_zoo), \
+ f'Arch {arch} is not in default archs {set(self.arch_zoo)}'
+ self.arch_settings = self.arch_zoo[arch]
+ else:
+ essential_keys = {
+ 'embed_dims', 'num_layers', 'tokens_mlp_dims',
+ 'channels_mlp_dims'
+ }
+ assert isinstance(arch, dict) and set(arch) == essential_keys, \
+ f'Custom arch needs a dict with keys {essential_keys}'
+ self.arch_settings = arch
+
+ self.embed_dims = self.arch_settings['embed_dims']
+ self.num_layers = self.arch_settings['num_layers']
+ self.tokens_mlp_dims = self.arch_settings['tokens_mlp_dims']
+ self.channels_mlp_dims = self.arch_settings['channels_mlp_dims']
+
+ self.img_size = to_2tuple(img_size)
+
+ _patch_cfg = dict(
+ input_size=img_size,
+ embed_dims=self.embed_dims,
+ conv_type='Conv2d',
+ kernel_size=patch_size,
+ stride=patch_size,
+ )
+ _patch_cfg.update(patch_cfg)
+ self.patch_embed = PatchEmbed(**_patch_cfg)
+ self.patch_resolution = self.patch_embed.init_out_size
+ num_patches = self.patch_resolution[0] * self.patch_resolution[1]
+
+ if isinstance(out_indices, int):
+ out_indices = [out_indices]
+ assert isinstance(out_indices, Sequence), \
+ f'"out_indices" must be a sequence or int, ' \
+ f'get {type(out_indices)} instead.'
+ for i, index in enumerate(out_indices):
+ if index < 0:
+ out_indices[i] = self.num_layers + index
+ assert out_indices[i] >= 0, f'Invalid out_indices {index}'
+ else:
+ assert index >= self.num_layers, f'Invalid out_indices {index}'
+ self.out_indices = out_indices
+
+ self.layers = ModuleList()
+ if isinstance(layer_cfgs, dict):
+ layer_cfgs = [layer_cfgs] * self.num_layers
+ for i in range(self.num_layers):
+ _layer_cfg = dict(
+ num_tokens=num_patches,
+ embed_dims=self.embed_dims,
+ tokens_mlp_dims=self.tokens_mlp_dims,
+ channels_mlp_dims=self.channels_mlp_dims,
+ drop_rate=drop_rate,
+ drop_path_rate=drop_path_rate,
+ act_cfg=act_cfg,
+ norm_cfg=norm_cfg,
+ )
+ _layer_cfg.update(layer_cfgs[i])
+ self.layers.append(MixerBlock(**_layer_cfg))
+
+ self.norm1_name, norm1 = build_norm_layer(
+ norm_cfg, self.embed_dims, postfix=1)
+ self.add_module(self.norm1_name, norm1)
+
+ @property
+ def norm1(self):
+ return getattr(self, self.norm1_name)
+
+ def forward(self, x):
+ assert x.shape[2:] == self.img_size, \
+ "The MLP-Mixer doesn't support dynamic input shape. " \
+ f'Please input images with shape {self.img_size}'
+ x, _ = self.patch_embed(x)
+
+ outs = []
+ for i, layer in enumerate(self.layers):
+ x = layer(x)
+
+ if i == len(self.layers) - 1:
+ x = self.norm1(x)
+
+ if i in self.out_indices:
+ out = x.transpose(1, 2)
+ outs.append(out)
+
+ return tuple(outs)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/mobilenet_v2.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/mobilenet_v2.py
similarity index 91%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/mobilenet_v2.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/mobilenet_v2.py
index 37f27cd82fbc03804f0c6e5c8ceb3295c803b337..8f171eda790c3c6c81da27c85d2ad2e7570a0fbb 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/mobilenet_v2.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/mobilenet_v2.py
@@ -1,9 +1,8 @@
-import logging
-
+# Copyright (c) OpenMMLab. All rights reserved.
import torch.nn as nn
import torch.utils.checkpoint as cp
-from mmcv.cnn import ConvModule, constant_init, kaiming_init
-from mmcv.runner import load_checkpoint
+from mmcv.cnn import ConvModule
+from mmcv.runner import BaseModule
from torch.nn.modules.batchnorm import _BatchNorm
from mmcls.models.utils import make_divisible
@@ -11,7 +10,7 @@ from ..builder import BACKBONES
from .base_backbone import BaseBackbone
-class InvertedResidual(nn.Module):
+class InvertedResidual(BaseModule):
"""InvertedResidual block for MobileNetV2.
Args:
@@ -41,8 +40,9 @@ class InvertedResidual(nn.Module):
conv_cfg=None,
norm_cfg=dict(type='BN'),
act_cfg=dict(type='ReLU6'),
- with_cp=False):
- super(InvertedResidual, self).__init__()
+ with_cp=False,
+ init_cfg=None):
+ super(InvertedResidual, self).__init__(init_cfg)
self.stride = stride
assert stride in [1, 2], f'stride must in [1, 2]. ' \
f'But received {stride}.'
@@ -233,19 +233,6 @@ class MobileNetV2(BaseBackbone):
return nn.Sequential(*layers)
- def init_weights(self, pretrained=None):
- if isinstance(pretrained, str):
- logger = logging.getLogger()
- load_checkpoint(self, pretrained, strict=False, logger=logger)
- elif pretrained is None:
- for m in self.modules():
- if isinstance(m, nn.Conv2d):
- kaiming_init(m)
- elif isinstance(m, (_BatchNorm, nn.GroupNorm)):
- constant_init(m, 1)
- else:
- raise TypeError('pretrained must be a str or None')
-
def forward(self, x):
x = self.conv1(x)
@@ -256,10 +243,7 @@ class MobileNetV2(BaseBackbone):
if i in self.out_indices:
outs.append(x)
- if len(outs) == 1:
- return outs[0]
- else:
- return tuple(outs)
+ return tuple(outs)
def _freeze_stages(self):
if self.frozen_stages >= 0:
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/mobilenet_v3.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/mobilenet_v3.py
new file mode 100644
index 0000000000000000000000000000000000000000..b612b88781ea6c0043cfd7bfd9e2c8b0cfaca903
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/mobilenet_v3.py
@@ -0,0 +1,195 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from mmcv.cnn import ConvModule
+from torch.nn.modules.batchnorm import _BatchNorm
+
+from ..builder import BACKBONES
+from ..utils import InvertedResidual
+from .base_backbone import BaseBackbone
+
+
+@BACKBONES.register_module()
+class MobileNetV3(BaseBackbone):
+ """MobileNetV3 backbone.
+
+ Args:
+ arch (str): Architecture of mobilnetv3, from {small, large}.
+ Default: small.
+ conv_cfg (dict, optional): Config dict for convolution layer.
+ Default: None, which means using conv2d.
+ norm_cfg (dict): Config dict for normalization layer.
+ Default: dict(type='BN').
+ out_indices (None or Sequence[int]): Output from which stages.
+ Default: None, which means output tensors from final stage.
+ frozen_stages (int): Stages to be frozen (all param fixed).
+ Default: -1, which means not freezing any parameters.
+ norm_eval (bool): Whether to set norm layers to eval mode, namely,
+ freeze running stats (mean and var). Note: Effect on Batch Norm
+ and its variants only. Default: False.
+ with_cp (bool): Use checkpoint or not. Using checkpoint will save
+ some memory while slowing down the training speed.
+ Default: False.
+ """
+ # Parameters to build each block:
+ # [kernel size, mid channels, out channels, with_se, act type, stride]
+ arch_settings = {
+ 'small': [[3, 16, 16, True, 'ReLU', 2],
+ [3, 72, 24, False, 'ReLU', 2],
+ [3, 88, 24, False, 'ReLU', 1],
+ [5, 96, 40, True, 'HSwish', 2],
+ [5, 240, 40, True, 'HSwish', 1],
+ [5, 240, 40, True, 'HSwish', 1],
+ [5, 120, 48, True, 'HSwish', 1],
+ [5, 144, 48, True, 'HSwish', 1],
+ [5, 288, 96, True, 'HSwish', 2],
+ [5, 576, 96, True, 'HSwish', 1],
+ [5, 576, 96, True, 'HSwish', 1]],
+ 'large': [[3, 16, 16, False, 'ReLU', 1],
+ [3, 64, 24, False, 'ReLU', 2],
+ [3, 72, 24, False, 'ReLU', 1],
+ [5, 72, 40, True, 'ReLU', 2],
+ [5, 120, 40, True, 'ReLU', 1],
+ [5, 120, 40, True, 'ReLU', 1],
+ [3, 240, 80, False, 'HSwish', 2],
+ [3, 200, 80, False, 'HSwish', 1],
+ [3, 184, 80, False, 'HSwish', 1],
+ [3, 184, 80, False, 'HSwish', 1],
+ [3, 480, 112, True, 'HSwish', 1],
+ [3, 672, 112, True, 'HSwish', 1],
+ [5, 672, 160, True, 'HSwish', 2],
+ [5, 960, 160, True, 'HSwish', 1],
+ [5, 960, 160, True, 'HSwish', 1]]
+ } # yapf: disable
+
+ def __init__(self,
+ arch='small',
+ conv_cfg=None,
+ norm_cfg=dict(type='BN', eps=0.001, momentum=0.01),
+ out_indices=None,
+ frozen_stages=-1,
+ norm_eval=False,
+ with_cp=False,
+ init_cfg=[
+ dict(
+ type='Kaiming',
+ layer=['Conv2d'],
+ nonlinearity='leaky_relu'),
+ dict(type='Normal', layer=['Linear'], std=0.01),
+ dict(type='Constant', layer=['BatchNorm2d'], val=1)
+ ]):
+ super(MobileNetV3, self).__init__(init_cfg)
+ assert arch in self.arch_settings
+ if out_indices is None:
+ out_indices = (12, ) if arch == 'small' else (16, )
+ for order, index in enumerate(out_indices):
+ if index not in range(0, len(self.arch_settings[arch]) + 2):
+ raise ValueError(
+ 'the item in out_indices must in '
+ f'range(0, {len(self.arch_settings[arch]) + 2}). '
+ f'But received {index}')
+
+ if frozen_stages not in range(-1, len(self.arch_settings[arch]) + 2):
+ raise ValueError('frozen_stages must be in range(-1, '
+ f'{len(self.arch_settings[arch]) + 2}). '
+ f'But received {frozen_stages}')
+ self.arch = arch
+ self.conv_cfg = conv_cfg
+ self.norm_cfg = norm_cfg
+ self.out_indices = out_indices
+ self.frozen_stages = frozen_stages
+ self.norm_eval = norm_eval
+ self.with_cp = with_cp
+
+ self.layers = self._make_layer()
+ self.feat_dim = self.arch_settings[arch][-1][1]
+
+ def _make_layer(self):
+ layers = []
+ layer_setting = self.arch_settings[self.arch]
+ in_channels = 16
+
+ layer = ConvModule(
+ in_channels=3,
+ out_channels=in_channels,
+ kernel_size=3,
+ stride=2,
+ padding=1,
+ conv_cfg=self.conv_cfg,
+ norm_cfg=self.norm_cfg,
+ act_cfg=dict(type='HSwish'))
+ self.add_module('layer0', layer)
+ layers.append('layer0')
+
+ for i, params in enumerate(layer_setting):
+ (kernel_size, mid_channels, out_channels, with_se, act,
+ stride) = params
+ if with_se:
+ se_cfg = dict(
+ channels=mid_channels,
+ ratio=4,
+ act_cfg=(dict(type='ReLU'),
+ dict(
+ type='HSigmoid',
+ bias=3,
+ divisor=6,
+ min_value=0,
+ max_value=1)))
+ else:
+ se_cfg = None
+
+ layer = InvertedResidual(
+ in_channels=in_channels,
+ out_channels=out_channels,
+ mid_channels=mid_channels,
+ kernel_size=kernel_size,
+ stride=stride,
+ se_cfg=se_cfg,
+ conv_cfg=self.conv_cfg,
+ norm_cfg=self.norm_cfg,
+ act_cfg=dict(type=act),
+ with_cp=self.with_cp)
+ in_channels = out_channels
+ layer_name = 'layer{}'.format(i + 1)
+ self.add_module(layer_name, layer)
+ layers.append(layer_name)
+
+ # Build the last layer before pooling
+ # TODO: No dilation
+ layer = ConvModule(
+ in_channels=in_channels,
+ out_channels=576 if self.arch == 'small' else 960,
+ kernel_size=1,
+ stride=1,
+ padding=0,
+ conv_cfg=self.conv_cfg,
+ norm_cfg=self.norm_cfg,
+ act_cfg=dict(type='HSwish'))
+ layer_name = 'layer{}'.format(len(layer_setting) + 1)
+ self.add_module(layer_name, layer)
+ layers.append(layer_name)
+
+ return layers
+
+ def forward(self, x):
+ outs = []
+ for i, layer_name in enumerate(self.layers):
+ layer = getattr(self, layer_name)
+ x = layer(x)
+ if i in self.out_indices:
+ outs.append(x)
+
+ return tuple(outs)
+
+ def _freeze_stages(self):
+ for i in range(0, self.frozen_stages + 1):
+ layer = getattr(self, f'layer{i}')
+ layer.eval()
+ for param in layer.parameters():
+ param.requires_grad = False
+
+ def train(self, mode=True):
+ super(MobileNetV3, self).train(mode)
+ self._freeze_stages()
+ if mode and self.norm_eval:
+ for m in self.modules():
+ if isinstance(m, _BatchNorm):
+ m.eval()
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/mvit.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/mvit.py
new file mode 100644
index 0000000000000000000000000000000000000000..b9e67df95be8e8d639102df29dcced20836dd7b4
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/mvit.py
@@ -0,0 +1,700 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from typing import Optional, Sequence
+
+import numpy as np
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+from mmcv.cnn import build_norm_layer
+from mmcv.cnn.bricks import DropPath
+from mmcv.cnn.bricks.transformer import PatchEmbed, build_activation_layer
+from mmcv.cnn.utils.weight_init import trunc_normal_
+from mmcv.runner import BaseModule, ModuleList
+from mmcv.utils import to_2tuple
+
+from ..builder import BACKBONES
+from ..utils import resize_pos_embed
+from .base_backbone import BaseBackbone
+
+
+def resize_decomposed_rel_pos(rel_pos, q_size, k_size):
+ """Get relative positional embeddings according to the relative positions
+ of query and key sizes.
+
+ Args:
+ q_size (int): size of query q.
+ k_size (int): size of key k.
+ rel_pos (Tensor): relative position embeddings (L, C).
+
+ Returns:
+ Extracted positional embeddings according to relative positions.
+ """
+ max_rel_dist = int(2 * max(q_size, k_size) - 1)
+ # Interpolate rel pos if needed.
+ if rel_pos.shape[0] != max_rel_dist:
+ # Interpolate rel pos.
+ resized = F.interpolate(
+ # (L, C) -> (1, C, L)
+ rel_pos.transpose(0, 1).unsqueeze(0),
+ size=max_rel_dist,
+ mode='linear',
+ )
+ # (1, C, L) -> (L, C)
+ resized = resized.squeeze(0).transpose(0, 1)
+ else:
+ resized = rel_pos
+
+ # Scale the coords with short length if shapes for q and k are different.
+ q_h_ratio = max(k_size / q_size, 1.0)
+ k_h_ratio = max(q_size / k_size, 1.0)
+ q_coords = torch.arange(q_size)[:, None] * q_h_ratio
+ k_coords = torch.arange(k_size)[None, :] * k_h_ratio
+ relative_coords = (q_coords - k_coords) + (k_size - 1) * k_h_ratio
+
+ return resized[relative_coords.long()]
+
+
+def add_decomposed_rel_pos(attn,
+ q,
+ q_shape,
+ k_shape,
+ rel_pos_h,
+ rel_pos_w,
+ has_cls_token=False):
+ """Spatial Relative Positional Embeddings."""
+ sp_idx = 1 if has_cls_token else 0
+ B, num_heads, _, C = q.shape
+ q_h, q_w = q_shape
+ k_h, k_w = k_shape
+
+ Rh = resize_decomposed_rel_pos(rel_pos_h, q_h, k_h)
+ Rw = resize_decomposed_rel_pos(rel_pos_w, q_w, k_w)
+
+ r_q = q[:, :, sp_idx:].reshape(B, num_heads, q_h, q_w, C)
+ rel_h = torch.einsum('byhwc,hkc->byhwk', r_q, Rh)
+ rel_w = torch.einsum('byhwc,wkc->byhwk', r_q, Rw)
+ rel_pos_embed = rel_h[:, :, :, :, :, None] + rel_w[:, :, :, :, None, :]
+
+ attn_map = attn[:, :, sp_idx:, sp_idx:].view(B, -1, q_h, q_w, k_h, k_w)
+ attn_map += rel_pos_embed
+ attn[:, :, sp_idx:, sp_idx:] = attn_map.view(B, -1, q_h * q_w, k_h * k_w)
+
+ return attn
+
+
+class MLP(BaseModule):
+ """Two-layer multilayer perceptron.
+
+ Comparing with :class:`mmcv.cnn.bricks.transformer.FFN`, this class allows
+ different input and output channel numbers.
+
+ Args:
+ in_channels (int): The number of input channels.
+ hidden_channels (int, optional): The number of hidden layer channels.
+ If None, same as the ``in_channels``. Defaults to None.
+ out_channels (int, optional): The number of output channels. If None,
+ same as the ``in_channels``. Defaults to None.
+ act_cfg (dict): The config of activation function.
+ Defaults to ``dict(type='GELU')``.
+ init_cfg (dict, optional): The config of weight initialization.
+ Defaults to None.
+ """
+
+ def __init__(self,
+ in_channels,
+ hidden_channels=None,
+ out_channels=None,
+ act_cfg=dict(type='GELU'),
+ init_cfg=None):
+ super().__init__(init_cfg=init_cfg)
+ out_channels = out_channels or in_channels
+ hidden_channels = hidden_channels or in_channels
+ self.fc1 = nn.Linear(in_channels, hidden_channels)
+ self.act = build_activation_layer(act_cfg)
+ self.fc2 = nn.Linear(hidden_channels, out_channels)
+
+ def forward(self, x):
+ x = self.fc1(x)
+ x = self.act(x)
+ x = self.fc2(x)
+ return x
+
+
+def attention_pool(x: torch.Tensor,
+ pool: nn.Module,
+ in_size: tuple,
+ norm: Optional[nn.Module] = None):
+ """Pooling the feature tokens.
+
+ Args:
+ x (torch.Tensor): The input tensor, should be with shape
+ ``(B, num_heads, L, C)`` or ``(B, L, C)``.
+ pool (nn.Module): The pooling module.
+ in_size (Tuple[int]): The shape of the input feature map.
+ norm (nn.Module, optional): The normalization module.
+ Defaults to None.
+ """
+ ndim = x.ndim
+ if ndim == 4:
+ B, num_heads, L, C = x.shape
+ elif ndim == 3:
+ num_heads = 1
+ B, L, C = x.shape
+ else:
+ raise RuntimeError(f'Unsupported input dimension {x.shape}')
+
+ H, W = in_size
+ assert L == H * W
+
+ # (B, num_heads, H*W, C) -> (B*num_heads, C, H, W)
+ x = x.reshape(B * num_heads, H, W, C).permute(0, 3, 1, 2).contiguous()
+ x = pool(x)
+ out_size = x.shape[-2:]
+
+ # (B*num_heads, C, H', W') -> (B, num_heads, H'*W', C)
+ x = x.reshape(B, num_heads, C, -1).transpose(2, 3)
+
+ if norm is not None:
+ x = norm(x)
+
+ if ndim == 3:
+ x = x.squeeze(1)
+
+ return x, out_size
+
+
+class MultiScaleAttention(BaseModule):
+ """Multiscale Multi-head Attention block.
+
+ Args:
+ in_dims (int): Number of input channels.
+ out_dims (int): Number of output channels.
+ num_heads (int): Number of attention heads.
+ qkv_bias (bool): If True, add a learnable bias to query, key and
+ value. Defaults to True.
+ norm_cfg (dict): The config of normalization layers.
+ Defaults to ``dict(type='LN')``.
+ pool_kernel (tuple): kernel size for qkv pooling layers.
+ Defaults to (3, 3).
+ stride_q (int): stride size for q pooling layer. Defaults to 1.
+ stride_kv (int): stride size for kv pooling layer. Defaults to 1.
+ rel_pos_spatial (bool): Whether to enable the spatial relative
+ position embedding. Defaults to True.
+ residual_pooling (bool): Whether to enable the residual connection
+ after attention pooling. Defaults to True.
+ input_size (Tuple[int], optional): The input resolution, necessary
+ if enable the ``rel_pos_spatial``. Defaults to None.
+ rel_pos_zero_init (bool): If True, zero initialize relative
+ positional parameters. Defaults to False.
+ init_cfg (dict, optional): The config of weight initialization.
+ Defaults to None.
+ """
+
+ def __init__(self,
+ in_dims,
+ out_dims,
+ num_heads,
+ qkv_bias=True,
+ norm_cfg=dict(type='LN'),
+ pool_kernel=(3, 3),
+ stride_q=1,
+ stride_kv=1,
+ rel_pos_spatial=False,
+ residual_pooling=True,
+ input_size=None,
+ rel_pos_zero_init=False,
+ init_cfg=None):
+ super().__init__(init_cfg=init_cfg)
+ self.num_heads = num_heads
+ self.in_dims = in_dims
+ self.out_dims = out_dims
+
+ head_dim = out_dims // num_heads
+ self.scale = head_dim**-0.5
+
+ self.qkv = nn.Linear(in_dims, out_dims * 3, bias=qkv_bias)
+ self.proj = nn.Linear(out_dims, out_dims)
+
+ # qkv pooling
+ pool_padding = [k // 2 for k in pool_kernel]
+ pool_dims = out_dims // num_heads
+
+ def build_pooling(stride):
+ pool = nn.Conv2d(
+ pool_dims,
+ pool_dims,
+ pool_kernel,
+ stride=stride,
+ padding=pool_padding,
+ groups=pool_dims,
+ bias=False,
+ )
+ norm = build_norm_layer(norm_cfg, pool_dims)[1]
+ return pool, norm
+
+ self.pool_q, self.norm_q = build_pooling(stride_q)
+ self.pool_k, self.norm_k = build_pooling(stride_kv)
+ self.pool_v, self.norm_v = build_pooling(stride_kv)
+
+ self.residual_pooling = residual_pooling
+
+ self.rel_pos_spatial = rel_pos_spatial
+ self.rel_pos_zero_init = rel_pos_zero_init
+ if self.rel_pos_spatial:
+ # initialize relative positional embeddings
+ assert input_size[0] == input_size[1]
+
+ size = input_size[0]
+ rel_dim = 2 * max(size // stride_q, size // stride_kv) - 1
+ self.rel_pos_h = nn.Parameter(torch.zeros(rel_dim, head_dim))
+ self.rel_pos_w = nn.Parameter(torch.zeros(rel_dim, head_dim))
+
+ def init_weights(self):
+ """Weight initialization."""
+ super().init_weights()
+
+ if (isinstance(self.init_cfg, dict)
+ and self.init_cfg['type'] == 'Pretrained'):
+ # Suppress rel_pos_zero_init if use pretrained model.
+ return
+
+ if not self.rel_pos_zero_init:
+ trunc_normal_(self.rel_pos_h, std=0.02)
+ trunc_normal_(self.rel_pos_w, std=0.02)
+
+ def forward(self, x, in_size):
+ """Forward the MultiScaleAttention."""
+ B, N, _ = x.shape # (B, H*W, C)
+
+ # qkv: (B, H*W, 3, num_heads, C)
+ qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, -1)
+ # q, k, v: (B, num_heads, H*W, C)
+ q, k, v = qkv.permute(2, 0, 3, 1, 4).unbind(0)
+
+ q, q_shape = attention_pool(q, self.pool_q, in_size, norm=self.norm_q)
+ k, k_shape = attention_pool(k, self.pool_k, in_size, norm=self.norm_k)
+ v, v_shape = attention_pool(v, self.pool_v, in_size, norm=self.norm_v)
+
+ attn = (q * self.scale) @ k.transpose(-2, -1)
+ if self.rel_pos_spatial:
+ attn = add_decomposed_rel_pos(attn, q, q_shape, k_shape,
+ self.rel_pos_h, self.rel_pos_w)
+
+ attn = attn.softmax(dim=-1)
+ x = attn @ v
+
+ if self.residual_pooling:
+ x = x + q
+
+ # (B, num_heads, H'*W', C'//num_heads) -> (B, H'*W', C')
+ x = x.transpose(1, 2).reshape(B, -1, self.out_dims)
+ x = self.proj(x)
+
+ return x, q_shape
+
+
+class MultiScaleBlock(BaseModule):
+ """Multiscale Transformer blocks.
+
+ Args:
+ in_dims (int): Number of input channels.
+ out_dims (int): Number of output channels.
+ num_heads (int): Number of attention heads.
+ mlp_ratio (float): Ratio of hidden dimensions in MLP layers.
+ Defaults to 4.0.
+ qkv_bias (bool): If True, add a learnable bias to query, key and
+ value. Defaults to True.
+ drop_path (float): Stochastic depth rate. Defaults to 0.
+ norm_cfg (dict): The config of normalization layers.
+ Defaults to ``dict(type='LN')``.
+ act_cfg (dict): The config of activation function.
+ Defaults to ``dict(type='GELU')``.
+ qkv_pool_kernel (tuple): kernel size for qkv pooling layers.
+ Defaults to (3, 3).
+ stride_q (int): stride size for q pooling layer. Defaults to 1.
+ stride_kv (int): stride size for kv pooling layer. Defaults to 1.
+ rel_pos_spatial (bool): Whether to enable the spatial relative
+ position embedding. Defaults to True.
+ residual_pooling (bool): Whether to enable the residual connection
+ after attention pooling. Defaults to True.
+ dim_mul_in_attention (bool): Whether to multiply the ``embed_dims`` in
+ attention layers. If False, multiply it in MLP layers.
+ Defaults to True.
+ input_size (Tuple[int], optional): The input resolution, necessary
+ if enable the ``rel_pos_spatial``. Defaults to None.
+ rel_pos_zero_init (bool): If True, zero initialize relative
+ positional parameters. Defaults to False.
+ init_cfg (dict, optional): The config of weight initialization.
+ Defaults to None.
+ """
+
+ def __init__(
+ self,
+ in_dims,
+ out_dims,
+ num_heads,
+ mlp_ratio=4.0,
+ qkv_bias=True,
+ drop_path=0.0,
+ norm_cfg=dict(type='LN'),
+ act_cfg=dict(type='GELU'),
+ qkv_pool_kernel=(3, 3),
+ stride_q=1,
+ stride_kv=1,
+ rel_pos_spatial=True,
+ residual_pooling=True,
+ dim_mul_in_attention=True,
+ input_size=None,
+ rel_pos_zero_init=False,
+ init_cfg=None,
+ ):
+ super().__init__(init_cfg=init_cfg)
+ self.in_dims = in_dims
+ self.out_dims = out_dims
+ self.norm1 = build_norm_layer(norm_cfg, in_dims)[1]
+ self.dim_mul_in_attention = dim_mul_in_attention
+
+ attn_dims = out_dims if dim_mul_in_attention else in_dims
+ self.attn = MultiScaleAttention(
+ in_dims,
+ attn_dims,
+ num_heads=num_heads,
+ qkv_bias=qkv_bias,
+ norm_cfg=norm_cfg,
+ pool_kernel=qkv_pool_kernel,
+ stride_q=stride_q,
+ stride_kv=stride_kv,
+ rel_pos_spatial=rel_pos_spatial,
+ residual_pooling=residual_pooling,
+ input_size=input_size,
+ rel_pos_zero_init=rel_pos_zero_init)
+ self.drop_path = DropPath(
+ drop_path) if drop_path > 0.0 else nn.Identity()
+
+ self.norm2 = build_norm_layer(norm_cfg, attn_dims)[1]
+
+ self.mlp = MLP(
+ in_channels=attn_dims,
+ hidden_channels=int(attn_dims * mlp_ratio),
+ out_channels=out_dims,
+ act_cfg=act_cfg)
+
+ if in_dims != out_dims:
+ self.proj = nn.Linear(in_dims, out_dims)
+ else:
+ self.proj = None
+
+ if stride_q > 1:
+ kernel_skip = stride_q + 1
+ padding_skip = int(kernel_skip // 2)
+ self.pool_skip = nn.MaxPool2d(
+ kernel_skip, stride_q, padding_skip, ceil_mode=False)
+
+ if input_size is not None:
+ input_size = to_2tuple(input_size)
+ out_size = [size // stride_q for size in input_size]
+ self.init_out_size = out_size
+ else:
+ self.init_out_size = None
+ else:
+ self.pool_skip = None
+ self.init_out_size = input_size
+
+ def forward(self, x, in_size):
+ x_norm = self.norm1(x)
+ x_attn, out_size = self.attn(x_norm, in_size)
+
+ if self.dim_mul_in_attention and self.proj is not None:
+ skip = self.proj(x_norm)
+ else:
+ skip = x
+
+ if self.pool_skip is not None:
+ skip, _ = attention_pool(skip, self.pool_skip, in_size)
+
+ x = skip + self.drop_path(x_attn)
+ x_norm = self.norm2(x)
+ x_mlp = self.mlp(x_norm)
+
+ if not self.dim_mul_in_attention and self.proj is not None:
+ skip = self.proj(x_norm)
+ else:
+ skip = x
+
+ x = skip + self.drop_path(x_mlp)
+
+ return x, out_size
+
+
+@BACKBONES.register_module()
+class MViT(BaseBackbone):
+ """Multi-scale ViT v2.
+
+ A PyTorch implement of : `MViTv2: Improved Multiscale Vision Transformers
+ for Classification and Detection `_
+
+ Inspiration from `the official implementation
+ `_ and `the detectron2
+ implementation `_
+
+ Args:
+ arch (str | dict): MViT architecture. If use string, choose
+ from 'tiny', 'small', 'base' and 'large'. If use dict, it should
+ have below keys:
+
+ - **embed_dims** (int): The dimensions of embedding.
+ - **num_layers** (int): The number of layers.
+ - **num_heads** (int): The number of heads in attention
+ modules of the initial layer.
+ - **downscale_indices** (List[int]): The layer indices to downscale
+ the feature map.
+
+ Defaults to 'base'.
+ img_size (int): The expected input image shape. Defaults to 224.
+ in_channels (int): The num of input channels. Defaults to 3.
+ out_scales (int | Sequence[int]): The output scale indices.
+ They should not exceed the length of ``downscale_indices``.
+ Defaults to -1, which means the last scale.
+ drop_path_rate (float): Stochastic depth rate. Defaults to 0.1.
+ use_abs_pos_embed (bool): If True, add absolute position embedding to
+ the patch embedding. Defaults to False.
+ interpolate_mode (str): Select the interpolate mode for absolute
+ position embedding vector resize. Defaults to "bicubic".
+ pool_kernel (tuple): kernel size for qkv pooling layers.
+ Defaults to (3, 3).
+ dim_mul (int): The magnification for ``embed_dims`` in the downscale
+ layers. Defaults to 2.
+ head_mul (int): The magnification for ``num_heads`` in the downscale
+ layers. Defaults to 2.
+ adaptive_kv_stride (int): The stride size for kv pooling in the initial
+ layer. Defaults to 4.
+ rel_pos_spatial (bool): Whether to enable the spatial relative position
+ embedding. Defaults to True.
+ residual_pooling (bool): Whether to enable the residual connection
+ after attention pooling. Defaults to True.
+ dim_mul_in_attention (bool): Whether to multiply the ``embed_dims`` in
+ attention layers. If False, multiply it in MLP layers.
+ Defaults to True.
+ rel_pos_zero_init (bool): If True, zero initialize relative
+ positional parameters. Defaults to False.
+ mlp_ratio (float): Ratio of hidden dimensions in MLP layers.
+ Defaults to 4.0.
+ qkv_bias (bool): enable bias for qkv if True. Defaults to True.
+ norm_cfg (dict): Config dict for normalization layer for all output
+ features. Defaults to ``dict(type='LN', eps=1e-6)``.
+ patch_cfg (dict): Config dict for the patch embedding layer.
+ Defaults to ``dict(kernel_size=7, stride=4, padding=3)``.
+ init_cfg (dict, optional): The Config for initialization.
+ Defaults to None.
+
+ Examples:
+ >>> import torch
+ >>> from mmcls.models import build_backbone
+ >>>
+ >>> cfg = dict(type='MViT', arch='tiny', out_scales=[0, 1, 2, 3])
+ >>> model = build_backbone(cfg)
+ >>> inputs = torch.rand(1, 3, 224, 224)
+ >>> outputs = model(inputs)
+ >>> for i, output in enumerate(outputs):
+ >>> print(f'scale{i}: {output.shape}')
+ scale0: torch.Size([1, 96, 56, 56])
+ scale1: torch.Size([1, 192, 28, 28])
+ scale2: torch.Size([1, 384, 14, 14])
+ scale3: torch.Size([1, 768, 7, 7])
+ """
+ arch_zoo = {
+ 'tiny': {
+ 'embed_dims': 96,
+ 'num_layers': 10,
+ 'num_heads': 1,
+ 'downscale_indices': [1, 3, 8]
+ },
+ 'small': {
+ 'embed_dims': 96,
+ 'num_layers': 16,
+ 'num_heads': 1,
+ 'downscale_indices': [1, 3, 14]
+ },
+ 'base': {
+ 'embed_dims': 96,
+ 'num_layers': 24,
+ 'num_heads': 1,
+ 'downscale_indices': [2, 5, 21]
+ },
+ 'large': {
+ 'embed_dims': 144,
+ 'num_layers': 48,
+ 'num_heads': 2,
+ 'downscale_indices': [2, 8, 44]
+ },
+ }
+ num_extra_tokens = 0
+
+ def __init__(self,
+ arch='base',
+ img_size=224,
+ in_channels=3,
+ out_scales=-1,
+ drop_path_rate=0.,
+ use_abs_pos_embed=False,
+ interpolate_mode='bicubic',
+ pool_kernel=(3, 3),
+ dim_mul=2,
+ head_mul=2,
+ adaptive_kv_stride=4,
+ rel_pos_spatial=True,
+ residual_pooling=True,
+ dim_mul_in_attention=True,
+ rel_pos_zero_init=False,
+ mlp_ratio=4.,
+ qkv_bias=True,
+ norm_cfg=dict(type='LN', eps=1e-6),
+ patch_cfg=dict(kernel_size=7, stride=4, padding=3),
+ init_cfg=None):
+ super().__init__(init_cfg)
+
+ if isinstance(arch, str):
+ arch = arch.lower()
+ assert arch in set(self.arch_zoo), \
+ f'Arch {arch} is not in default archs {set(self.arch_zoo)}'
+ self.arch_settings = self.arch_zoo[arch]
+ else:
+ essential_keys = {
+ 'embed_dims', 'num_layers', 'num_heads', 'downscale_indices'
+ }
+ assert isinstance(arch, dict) and essential_keys <= set(arch), \
+ f'Custom arch needs a dict with keys {essential_keys}'
+ self.arch_settings = arch
+
+ self.embed_dims = self.arch_settings['embed_dims']
+ self.num_layers = self.arch_settings['num_layers']
+ self.num_heads = self.arch_settings['num_heads']
+ self.downscale_indices = self.arch_settings['downscale_indices']
+ self.num_scales = len(self.downscale_indices) + 1
+ self.stage_indices = {
+ index - 1: i
+ for i, index in enumerate(self.downscale_indices)
+ }
+ self.stage_indices[self.num_layers - 1] = self.num_scales - 1
+ self.use_abs_pos_embed = use_abs_pos_embed
+ self.interpolate_mode = interpolate_mode
+
+ if isinstance(out_scales, int):
+ out_scales = [out_scales]
+ assert isinstance(out_scales, Sequence), \
+ f'"out_scales" must by a sequence or int, ' \
+ f'get {type(out_scales)} instead.'
+ for i, index in enumerate(out_scales):
+ if index < 0:
+ out_scales[i] = self.num_scales + index
+ assert 0 <= out_scales[i] <= self.num_scales, \
+ f'Invalid out_scales {index}'
+ self.out_scales = sorted(list(out_scales))
+
+ # Set patch embedding
+ _patch_cfg = dict(
+ in_channels=in_channels,
+ input_size=img_size,
+ embed_dims=self.embed_dims,
+ conv_type='Conv2d',
+ )
+ _patch_cfg.update(patch_cfg)
+ self.patch_embed = PatchEmbed(**_patch_cfg)
+ self.patch_resolution = self.patch_embed.init_out_size
+
+ # Set absolute position embedding
+ if self.use_abs_pos_embed:
+ num_patches = self.patch_resolution[0] * self.patch_resolution[1]
+ self.pos_embed = nn.Parameter(
+ torch.zeros(1, num_patches, self.embed_dims))
+
+ # stochastic depth decay rule
+ dpr = np.linspace(0, drop_path_rate, self.num_layers)
+
+ self.blocks = ModuleList()
+ out_dims_list = [self.embed_dims]
+ num_heads = self.num_heads
+ stride_kv = adaptive_kv_stride
+ input_size = self.patch_resolution
+ for i in range(self.num_layers):
+ if i in self.downscale_indices:
+ num_heads *= head_mul
+ stride_q = 2
+ stride_kv = max(stride_kv // 2, 1)
+ else:
+ stride_q = 1
+
+ # Set output embed_dims
+ if dim_mul_in_attention and i in self.downscale_indices:
+ # multiply embed_dims in downscale layers.
+ out_dims = out_dims_list[-1] * dim_mul
+ elif not dim_mul_in_attention and i + 1 in self.downscale_indices:
+ # multiply embed_dims before downscale layers.
+ out_dims = out_dims_list[-1] * dim_mul
+ else:
+ out_dims = out_dims_list[-1]
+
+ attention_block = MultiScaleBlock(
+ in_dims=out_dims_list[-1],
+ out_dims=out_dims,
+ num_heads=num_heads,
+ mlp_ratio=mlp_ratio,
+ qkv_bias=qkv_bias,
+ drop_path=dpr[i],
+ norm_cfg=norm_cfg,
+ qkv_pool_kernel=pool_kernel,
+ stride_q=stride_q,
+ stride_kv=stride_kv,
+ rel_pos_spatial=rel_pos_spatial,
+ residual_pooling=residual_pooling,
+ dim_mul_in_attention=dim_mul_in_attention,
+ input_size=input_size,
+ rel_pos_zero_init=rel_pos_zero_init)
+ self.blocks.append(attention_block)
+
+ input_size = attention_block.init_out_size
+ out_dims_list.append(out_dims)
+
+ if i in self.stage_indices:
+ stage_index = self.stage_indices[i]
+ if stage_index in self.out_scales:
+ norm_layer = build_norm_layer(norm_cfg, out_dims)[1]
+ self.add_module(f'norm{stage_index}', norm_layer)
+
+ def init_weights(self):
+ super().init_weights()
+
+ if (isinstance(self.init_cfg, dict)
+ and self.init_cfg['type'] == 'Pretrained'):
+ # Suppress default init if use pretrained model.
+ return
+
+ if self.use_abs_pos_embed:
+ trunc_normal_(self.pos_embed, std=0.02)
+
+ def forward(self, x):
+ """Forward the MViT."""
+ B = x.shape[0]
+ x, patch_resolution = self.patch_embed(x)
+
+ if self.use_abs_pos_embed:
+ x = x + resize_pos_embed(
+ self.pos_embed,
+ self.patch_resolution,
+ patch_resolution,
+ mode=self.interpolate_mode,
+ num_extra_tokens=self.num_extra_tokens)
+
+ outs = []
+ for i, block in enumerate(self.blocks):
+ x, patch_resolution = block(x, patch_resolution)
+
+ if i in self.stage_indices:
+ stage_index = self.stage_indices[i]
+ if stage_index in self.out_scales:
+ B, _, C = x.shape
+ x = getattr(self, f'norm{stage_index}')(x)
+ out = x.transpose(1, 2).reshape(B, C, *patch_resolution)
+ outs.append(out.contiguous())
+
+ return tuple(outs)
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/poolformer.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/poolformer.py
new file mode 100644
index 0000000000000000000000000000000000000000..e3fc4e1cc0ccee9743268b4ac91f102f21a90b6d
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/poolformer.py
@@ -0,0 +1,416 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from typing import Sequence
+
+import torch
+import torch.nn as nn
+from mmcv.cnn.bricks import DropPath, build_activation_layer, build_norm_layer
+from mmcv.runner import BaseModule
+
+from ..builder import BACKBONES
+from .base_backbone import BaseBackbone
+
+
+class PatchEmbed(nn.Module):
+ """Patch Embedding module implemented by a layer of convolution.
+
+ Input: tensor in shape [B, C, H, W]
+ Output: tensor in shape [B, C, H/stride, W/stride]
+ Args:
+ patch_size (int): Patch size of the patch embedding. Defaults to 16.
+ stride (int): Stride of the patch embedding. Defaults to 16.
+ padding (int): Padding of the patch embedding. Defaults to 0.
+ in_chans (int): Input channels. Defaults to 3.
+ embed_dim (int): Output dimension of the patch embedding.
+ Defaults to 768.
+ norm_layer (module): Normalization module. Defaults to None (not use).
+ """
+
+ def __init__(self,
+ patch_size=16,
+ stride=16,
+ padding=0,
+ in_chans=3,
+ embed_dim=768,
+ norm_layer=None):
+ super().__init__()
+ self.proj = nn.Conv2d(
+ in_chans,
+ embed_dim,
+ kernel_size=patch_size,
+ stride=stride,
+ padding=padding)
+ self.norm = norm_layer(embed_dim) if norm_layer else nn.Identity()
+
+ def forward(self, x):
+ x = self.proj(x)
+ x = self.norm(x)
+ return x
+
+
+class Pooling(nn.Module):
+ """Pooling module.
+
+ Args:
+ pool_size (int): Pooling size. Defaults to 3.
+ """
+
+ def __init__(self, pool_size=3):
+ super().__init__()
+ self.pool = nn.AvgPool2d(
+ pool_size,
+ stride=1,
+ padding=pool_size // 2,
+ count_include_pad=False)
+
+ def forward(self, x):
+ return self.pool(x) - x
+
+
+class Mlp(nn.Module):
+ """Mlp implemented by with 1*1 convolutions.
+
+ Input: Tensor with shape [B, C, H, W].
+ Output: Tensor with shape [B, C, H, W].
+ Args:
+ in_features (int): Dimension of input features.
+ hidden_features (int): Dimension of hidden features.
+ out_features (int): Dimension of output features.
+ act_cfg (dict): The config dict for activation between pointwise
+ convolution. Defaults to ``dict(type='GELU')``.
+ drop (float): Dropout rate. Defaults to 0.0.
+ """
+
+ def __init__(self,
+ in_features,
+ hidden_features=None,
+ out_features=None,
+ act_cfg=dict(type='GELU'),
+ drop=0.):
+ super().__init__()
+ out_features = out_features or in_features
+ hidden_features = hidden_features or in_features
+ self.fc1 = nn.Conv2d(in_features, hidden_features, 1)
+ self.act = build_activation_layer(act_cfg)
+ self.fc2 = nn.Conv2d(hidden_features, out_features, 1)
+ self.drop = nn.Dropout(drop)
+
+ def forward(self, x):
+ x = self.fc1(x)
+ x = self.act(x)
+ x = self.drop(x)
+ x = self.fc2(x)
+ x = self.drop(x)
+ return x
+
+
+class PoolFormerBlock(BaseModule):
+ """PoolFormer Block.
+
+ Args:
+ dim (int): Embedding dim.
+ pool_size (int): Pooling size. Defaults to 3.
+ mlp_ratio (float): Mlp expansion ratio. Defaults to 4.
+ norm_cfg (dict): The config dict for norm layers.
+ Defaults to ``dict(type='GN', num_groups=1)``.
+ act_cfg (dict): The config dict for activation between pointwise
+ convolution. Defaults to ``dict(type='GELU')``.
+ drop (float): Dropout rate. Defaults to 0.
+ drop_path (float): Stochastic depth rate. Defaults to 0.
+ layer_scale_init_value (float): Init value for Layer Scale.
+ Defaults to 1e-5.
+ """
+
+ def __init__(self,
+ dim,
+ pool_size=3,
+ mlp_ratio=4.,
+ norm_cfg=dict(type='GN', num_groups=1),
+ act_cfg=dict(type='GELU'),
+ drop=0.,
+ drop_path=0.,
+ layer_scale_init_value=1e-5):
+
+ super().__init__()
+
+ self.norm1 = build_norm_layer(norm_cfg, dim)[1]
+ self.token_mixer = Pooling(pool_size=pool_size)
+ self.norm2 = build_norm_layer(norm_cfg, dim)[1]
+ mlp_hidden_dim = int(dim * mlp_ratio)
+ self.mlp = Mlp(
+ in_features=dim,
+ hidden_features=mlp_hidden_dim,
+ act_cfg=act_cfg,
+ drop=drop)
+
+ # The following two techniques are useful to train deep PoolFormers.
+ self.drop_path = DropPath(drop_path) if drop_path > 0. \
+ else nn.Identity()
+ self.layer_scale_1 = nn.Parameter(
+ layer_scale_init_value * torch.ones((dim)), requires_grad=True)
+ self.layer_scale_2 = nn.Parameter(
+ layer_scale_init_value * torch.ones((dim)), requires_grad=True)
+
+ def forward(self, x):
+ x = x + self.drop_path(
+ self.layer_scale_1.unsqueeze(-1).unsqueeze(-1) *
+ self.token_mixer(self.norm1(x)))
+ x = x + self.drop_path(
+ self.layer_scale_2.unsqueeze(-1).unsqueeze(-1) *
+ self.mlp(self.norm2(x)))
+ return x
+
+
+def basic_blocks(dim,
+ index,
+ layers,
+ pool_size=3,
+ mlp_ratio=4.,
+ norm_cfg=dict(type='GN', num_groups=1),
+ act_cfg=dict(type='GELU'),
+ drop_rate=.0,
+ drop_path_rate=0.,
+ layer_scale_init_value=1e-5):
+ """
+ generate PoolFormer blocks for a stage
+ return: PoolFormer blocks
+ """
+ blocks = []
+ for block_idx in range(layers[index]):
+ block_dpr = drop_path_rate * (block_idx + sum(layers[:index])) / (
+ sum(layers) - 1)
+ blocks.append(
+ PoolFormerBlock(
+ dim,
+ pool_size=pool_size,
+ mlp_ratio=mlp_ratio,
+ norm_cfg=norm_cfg,
+ act_cfg=act_cfg,
+ drop=drop_rate,
+ drop_path=block_dpr,
+ layer_scale_init_value=layer_scale_init_value,
+ ))
+ blocks = nn.Sequential(*blocks)
+
+ return blocks
+
+
+@BACKBONES.register_module()
+class PoolFormer(BaseBackbone):
+ """PoolFormer.
+
+ A PyTorch implementation of PoolFormer introduced by:
+ `MetaFormer is Actually What You Need for Vision `_
+
+ Modified from the `official repo
+ `.
+
+ Args:
+ arch (str | dict): The model's architecture. If string, it should be
+ one of architecture in ``PoolFormer.arch_settings``. And if dict, it
+ should include the following two keys:
+
+ - layers (list[int]): Number of blocks at each stage.
+ - embed_dims (list[int]): The number of channels at each stage.
+ - mlp_ratios (list[int]): Expansion ratio of MLPs.
+ - layer_scale_init_value (float): Init value for Layer Scale.
+
+ Defaults to 'S12'.
+
+ norm_cfg (dict): The config dict for norm layers.
+ Defaults to ``dict(type='LN2d', eps=1e-6)``.
+ act_cfg (dict): The config dict for activation between pointwise
+ convolution. Defaults to ``dict(type='GELU')``.
+ in_patch_size (int): The patch size of input image patch embedding.
+ Defaults to 7.
+ in_stride (int): The stride of input image patch embedding.
+ Defaults to 4.
+ in_pad (int): The padding of input image patch embedding.
+ Defaults to 2.
+ down_patch_size (int): The patch size of downsampling patch embedding.
+ Defaults to 3.
+ down_stride (int): The stride of downsampling patch embedding.
+ Defaults to 2.
+ down_pad (int): The padding of downsampling patch embedding.
+ Defaults to 1.
+ drop_rate (float): Dropout rate. Defaults to 0.
+ drop_path_rate (float): Stochastic depth rate. Defaults to 0.
+ out_indices (Sequence | int): Output from which network position.
+ Index 0-6 respectively corresponds to
+ [stage1, downsampling, stage2, downsampling, stage3, downsampling, stage4]
+ Defaults to -1, means the last stage.
+ frozen_stages (int): Stages to be frozen (all param fixed).
+ Defaults to 0, which means not freezing any parameters.
+ init_cfg (dict, optional): Initialization config dict
+ """ # noqa: E501
+
+ # --layers: [x,x,x,x], numbers of layers for the four stages
+ # --embed_dims, --mlp_ratios:
+ # embedding dims and mlp ratios for the four stages
+ # --downsamples: flags to apply downsampling or not in four blocks
+ arch_settings = {
+ 's12': {
+ 'layers': [2, 2, 6, 2],
+ 'embed_dims': [64, 128, 320, 512],
+ 'mlp_ratios': [4, 4, 4, 4],
+ 'layer_scale_init_value': 1e-5,
+ },
+ 's24': {
+ 'layers': [4, 4, 12, 4],
+ 'embed_dims': [64, 128, 320, 512],
+ 'mlp_ratios': [4, 4, 4, 4],
+ 'layer_scale_init_value': 1e-5,
+ },
+ 's36': {
+ 'layers': [6, 6, 18, 6],
+ 'embed_dims': [64, 128, 320, 512],
+ 'mlp_ratios': [4, 4, 4, 4],
+ 'layer_scale_init_value': 1e-6,
+ },
+ 'm36': {
+ 'layers': [6, 6, 18, 6],
+ 'embed_dims': [96, 192, 384, 768],
+ 'mlp_ratios': [4, 4, 4, 4],
+ 'layer_scale_init_value': 1e-6,
+ },
+ 'm48': {
+ 'layers': [8, 8, 24, 8],
+ 'embed_dims': [96, 192, 384, 768],
+ 'mlp_ratios': [4, 4, 4, 4],
+ 'layer_scale_init_value': 1e-6,
+ },
+ }
+
+ def __init__(self,
+ arch='s12',
+ pool_size=3,
+ norm_cfg=dict(type='GN', num_groups=1),
+ act_cfg=dict(type='GELU'),
+ in_patch_size=7,
+ in_stride=4,
+ in_pad=2,
+ down_patch_size=3,
+ down_stride=2,
+ down_pad=1,
+ drop_rate=0.,
+ drop_path_rate=0.,
+ out_indices=-1,
+ frozen_stages=0,
+ init_cfg=None):
+
+ super().__init__(init_cfg=init_cfg)
+
+ if isinstance(arch, str):
+ assert arch in self.arch_settings, \
+ f'Unavailable arch, please choose from ' \
+ f'({set(self.arch_settings)}) or pass a dict.'
+ arch = self.arch_settings[arch]
+ elif isinstance(arch, dict):
+ assert 'layers' in arch and 'embed_dims' in arch, \
+ f'The arch dict must have "layers" and "embed_dims", ' \
+ f'but got {list(arch.keys())}.'
+
+ layers = arch['layers']
+ embed_dims = arch['embed_dims']
+ mlp_ratios = arch['mlp_ratios'] \
+ if 'mlp_ratios' in arch else [4, 4, 4, 4]
+ layer_scale_init_value = arch['layer_scale_init_value'] \
+ if 'layer_scale_init_value' in arch else 1e-5
+
+ self.patch_embed = PatchEmbed(
+ patch_size=in_patch_size,
+ stride=in_stride,
+ padding=in_pad,
+ in_chans=3,
+ embed_dim=embed_dims[0])
+
+ # set the main block in network
+ network = []
+ for i in range(len(layers)):
+ stage = basic_blocks(
+ embed_dims[i],
+ i,
+ layers,
+ pool_size=pool_size,
+ mlp_ratio=mlp_ratios[i],
+ norm_cfg=norm_cfg,
+ act_cfg=act_cfg,
+ drop_rate=drop_rate,
+ drop_path_rate=drop_path_rate,
+ layer_scale_init_value=layer_scale_init_value)
+ network.append(stage)
+ if i >= len(layers) - 1:
+ break
+ if embed_dims[i] != embed_dims[i + 1]:
+ # downsampling between two stages
+ network.append(
+ PatchEmbed(
+ patch_size=down_patch_size,
+ stride=down_stride,
+ padding=down_pad,
+ in_chans=embed_dims[i],
+ embed_dim=embed_dims[i + 1]))
+
+ self.network = nn.ModuleList(network)
+
+ if isinstance(out_indices, int):
+ out_indices = [out_indices]
+ assert isinstance(out_indices, Sequence), \
+ f'"out_indices" must by a sequence or int, ' \
+ f'get {type(out_indices)} instead.'
+ for i, index in enumerate(out_indices):
+ if index < 0:
+ out_indices[i] = 7 + index
+ assert out_indices[i] >= 0, f'Invalid out_indices {index}'
+ self.out_indices = out_indices
+ if self.out_indices:
+ for i_layer in self.out_indices:
+ layer = build_norm_layer(norm_cfg,
+ embed_dims[(i_layer + 1) // 2])[1]
+ layer_name = f'norm{i_layer}'
+ self.add_module(layer_name, layer)
+
+ self.frozen_stages = frozen_stages
+ self._freeze_stages()
+
+ def forward_embeddings(self, x):
+ x = self.patch_embed(x)
+ return x
+
+ def forward_tokens(self, x):
+ outs = []
+ for idx, block in enumerate(self.network):
+ x = block(x)
+ if idx in self.out_indices:
+ norm_layer = getattr(self, f'norm{idx}')
+ x_out = norm_layer(x)
+ outs.append(x_out)
+ return tuple(outs)
+
+ def forward(self, x):
+ # input embedding
+ x = self.forward_embeddings(x)
+ # through backbone
+ x = self.forward_tokens(x)
+ return x
+
+ def _freeze_stages(self):
+ if self.frozen_stages >= 0:
+ self.patch_embed.eval()
+ for param in self.patch_embed.parameters():
+ param.requires_grad = False
+
+ for i in range(self.frozen_stages):
+ # Include both block and downsample layer.
+ module = self.network[i]
+ module.eval()
+ for param in module.parameters():
+ param.requires_grad = False
+ if i in self.out_indices:
+ norm_layer = getattr(self, f'norm{i}')
+ norm_layer.eval()
+ for param in norm_layer.parameters():
+ param.requires_grad = False
+
+ def train(self, mode=True):
+ super(PoolFormer, self).train(mode)
+ self._freeze_stages()
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/regnet.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/regnet.py
similarity index 85%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/regnet.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/regnet.py
index a3556ad52865e2ad728b2a033e90a3e748c508ef..036b699c4d5a649bb6b41c89490040151d81e67e 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/regnet.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/regnet.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import numpy as np
import torch.nn as nn
from mmcv.cnn import build_conv_layer, build_norm_layer
@@ -20,7 +21,7 @@ class RegNet(ResNet):
- wm (float): quantization parameter to quantize the width
- depth (int): depth of the backbone
- group_w (int): width of group
- - bot_mul (float): bottleneck ratio, i.e. expansion of bottlneck.
+ - bot_mul (float): bottleneck ratio, i.e. expansion of bottleneck.
strides (Sequence[int]): Strides of the first block of each stage.
base_channels (int): Base channels after stem layer.
in_channels (int): Number of input image channels. Default: 3.
@@ -42,26 +43,33 @@ class RegNet(ResNet):
in resblocks to let them behave as identity. Default: True.
Example:
- >>> from mmdet.models import RegNet
+ >>> from mmcls.models import RegNet
>>> import torch
- >>> self = RegNet(
- arch=dict(
- w0=88,
- wa=26.31,
- wm=2.25,
- group_w=48,
- depth=25,
- bot_mul=1.0))
- >>> self.eval()
>>> inputs = torch.rand(1, 3, 32, 32)
- >>> level_outputs = self.forward(inputs)
+ >>> # use str type 'arch'
+ >>> # Note that default out_indices is (3,)
+ >>> regnet_cfg = dict(arch='regnetx_4.0gf')
+ >>> model = RegNet(**regnet_cfg)
+ >>> model.eval()
+ >>> level_outputs = model(inputs)
>>> for level_out in level_outputs:
... print(tuple(level_out.shape))
- (1, 96, 8, 8)
- (1, 192, 4, 4)
- (1, 432, 2, 2)
- (1, 1008, 1, 1)
+ (1, 1360, 1, 1)
+ >>> # use dict type 'arch'
+ >>> arch_cfg =dict(w0=88, wa=26.31, wm=2.25,
+ >>> group_w=48, depth=25, bot_mul=1.0)
+ >>> regnet_cfg = dict(arch=arch_cfg, out_indices=(0, 1, 2, 3))
+ >>> model = RegNet(**regnet_cfg)
+ >>> model.eval()
+ >>> level_outputs = model(inputs)
+ >>> for level_out in level_outputs:
+ ... print(tuple(level_out.shape))
+ (1, 96, 8, 8)
+ (1, 192, 4, 4)
+ (1, 432, 2, 2)
+ (1, 1008, 1, 1)
"""
+
arch_settings = {
'regnetx_400mf':
dict(w0=24, wa=24.48, wm=2.54, group_w=16, depth=22, bot_mul=1.0),
@@ -81,31 +89,33 @@ class RegNet(ResNet):
dict(w0=168, wa=73.36, wm=2.37, group_w=112, depth=19, bot_mul=1.0),
}
- def __init__(self,
- arch,
- in_channels=3,
- stem_channels=32,
- base_channels=32,
- strides=(2, 2, 2, 2),
- dilations=(1, 1, 1, 1),
- out_indices=(3, ),
- style='pytorch',
- deep_stem=False,
- avg_down=False,
- frozen_stages=-1,
- conv_cfg=None,
- norm_cfg=dict(type='BN', requires_grad=True),
- norm_eval=False,
- with_cp=False,
- zero_init_residual=True,
- init_cfg=None):
+ def __init__(
+ self,
+ arch,
+ in_channels=3,
+ stem_channels=32,
+ base_channels=32,
+ strides=(2, 2, 2, 2),
+ dilations=(1, 1, 1, 1),
+ out_indices=(3, ),
+ style='pytorch',
+ deep_stem=False,
+ avg_down=False,
+ frozen_stages=-1,
+ conv_cfg=None,
+ norm_cfg=dict(type='BN', requires_grad=True),
+ norm_eval=False,
+ with_cp=False,
+ zero_init_residual=True,
+ init_cfg=None,
+ ):
super(ResNet, self).__init__(init_cfg)
# Generate RegNet parameters first
if isinstance(arch, str):
- assert arch in self.arch_settings, \
- f'"arch": "{arch}" is not one of the' \
- ' arch_settings'
+ assert arch in self.arch_settings, (
+ f'"arch": "{arch}" is not one of the'
+ ' arch_settings')
arch = self.arch_settings[arch]
elif not isinstance(arch, dict):
raise TypeError('Expect "arch" to be either a string '
@@ -179,7 +189,8 @@ class RegNet(ResNet):
norm_cfg=self.norm_cfg,
base_channels=self.stage_widths[i],
groups=stage_groups,
- width_per_group=group_width)
+ width_per_group=group_width,
+ )
_in_channels = self.stage_widths[i]
layer_name = f'layer{i + 1}'
self.add_module(layer_name, res_layer)
@@ -197,7 +208,8 @@ class RegNet(ResNet):
kernel_size=3,
stride=2,
padding=1,
- bias=False)
+ bias=False,
+ )
self.norm1_name, norm1 = build_norm_layer(
self.norm_cfg, base_channels, postfix=1)
self.add_module(self.norm1_name, norm1)
@@ -219,8 +231,9 @@ class RegNet(ResNet):
divisor (int): The divisor of channels. Defaults to 8.
Returns:
- list, int: return a list of widths of each stage and the number of
- stages
+ tuple: tuple containing:
+ - list: Widths of each stage.
+ - int: The number of stages.
"""
assert width_slope >= 0
assert initial_width > 0
@@ -307,7 +320,4 @@ class RegNet(ResNet):
if i in self.out_indices:
outs.append(x)
- if len(outs) == 1:
- return outs[0]
- else:
- return tuple(outs)
+ return tuple(outs)
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/repmlp.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/repmlp.py
new file mode 100644
index 0000000000000000000000000000000000000000..9e6e2ed988325c5b89c52356f361b5cb7039a1f1
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/repmlp.py
@@ -0,0 +1,578 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+# Adapted from official impl at https://github.com/DingXiaoH/RepMLP.
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+from mmcv.cnn import (ConvModule, build_activation_layer, build_conv_layer,
+ build_norm_layer)
+from mmcv.cnn.bricks.transformer import PatchEmbed as _PatchEmbed
+from mmcv.runner import BaseModule, ModuleList, Sequential
+
+from mmcls.models.builder import BACKBONES
+from mmcls.models.utils import SELayer, to_2tuple
+
+
+def fuse_bn(conv_or_fc, bn):
+ """fuse conv and bn."""
+ std = (bn.running_var + bn.eps).sqrt()
+ tmp_weight = bn.weight / std
+ tmp_weight = tmp_weight.reshape(-1, 1, 1, 1)
+
+ if len(tmp_weight) == conv_or_fc.weight.size(0):
+ return (conv_or_fc.weight * tmp_weight,
+ bn.bias - bn.running_mean * bn.weight / std)
+ else:
+ # in RepMLPBlock, dim0 of fc3 weights and fc3_bn weights
+ # are different.
+ repeat_times = conv_or_fc.weight.size(0) // len(tmp_weight)
+ repeated = tmp_weight.repeat_interleave(repeat_times, 0)
+ fused_weight = conv_or_fc.weight * repeated
+ bias = bn.bias - bn.running_mean * bn.weight / std
+ fused_bias = (bias).repeat_interleave(repeat_times, 0)
+ return (fused_weight, fused_bias)
+
+
+class PatchEmbed(_PatchEmbed):
+ """Image to Patch Embedding.
+
+ Compared with default Patch Embedding(in ViT), Patch Embedding of RepMLP
+ have ReLu and do not convert output tensor into shape (N, L, C).
+
+ Args:
+ in_channels (int): The num of input channels. Default: 3
+ embed_dims (int): The dimensions of embedding. Default: 768
+ conv_type (str): The type of convolution
+ to generate patch embedding. Default: "Conv2d".
+ kernel_size (int): The kernel_size of embedding conv. Default: 16.
+ stride (int): The slide stride of embedding conv.
+ Default: 16.
+ padding (int | tuple | string): The padding length of
+ embedding conv. When it is a string, it means the mode
+ of adaptive padding, support "same" and "corner" now.
+ Default: "corner".
+ dilation (int): The dilation rate of embedding conv. Default: 1.
+ bias (bool): Bias of embed conv. Default: True.
+ norm_cfg (dict, optional): Config dict for normalization layer.
+ Default: None.
+ input_size (int | tuple | None): The size of input, which will be
+ used to calculate the out size. Only works when `dynamic_size`
+ is False. Default: None.
+ init_cfg (`mmcv.ConfigDict`, optional): The Config for initialization.
+ Default: None.
+ """
+
+ def __init__(self, *args, **kwargs):
+ super(PatchEmbed, self).__init__(*args, **kwargs)
+ self.relu = nn.ReLU()
+
+ def forward(self, x):
+ """
+ Args:
+ x (Tensor): Has shape (B, C, H, W). In most case, C is 3.
+ Returns:
+ tuple: Contains merged results and its spatial shape.
+ - x (Tensor): The output tensor.
+ - out_size (tuple[int]): Spatial shape of x, arrange as
+ (out_h, out_w).
+ """
+
+ if self.adaptive_padding:
+ x = self.adaptive_padding(x)
+
+ x = self.projection(x)
+ if self.norm is not None:
+ x = self.norm(x)
+ x = self.relu(x)
+ out_size = (x.shape[2], x.shape[3])
+ return x, out_size
+
+
+class GlobalPerceptron(SELayer):
+ """GlobalPerceptron implemented by using ``mmcls.modes.SELayer``.
+
+ Args:
+ input_channels (int): The number of input (and output) channels
+ in the GlobalPerceptron.
+ ratio (int): Squeeze ratio in GlobalPerceptron, the intermediate
+ channel will be ``make_divisible(channels // ratio, divisor)``.
+ """
+
+ def __init__(self, input_channels: int, ratio: int, **kwargs) -> None:
+ super(GlobalPerceptron, self).__init__(
+ channels=input_channels,
+ ratio=ratio,
+ return_weight=True,
+ act_cfg=(dict(type='ReLU'), dict(type='Sigmoid')),
+ **kwargs)
+
+
+class RepMLPBlock(BaseModule):
+ """Basic RepMLPNet, consists of PartitionPerceptron and GlobalPerceptron.
+
+ Args:
+ channels (int): The number of input and the output channels of the
+ block.
+ path_h (int): The height of patches.
+ path_w (int): The weidth of patches.
+ reparam_conv_kernels (Squeue(int) | None): The conv kernels in the
+ GlobalPerceptron. Default: None.
+ globalperceptron_ratio (int): The reducation ratio in the
+ GlobalPerceptron. Default: 4.
+ num_sharesets (int): The number of sharesets in the
+ PartitionPerceptron. Default 1.
+ conv_cfg (dict, optional): Config dict for convolution layer.
+ Default: None, which means using conv2d.
+ norm_cfg (dict): dictionary to construct and config norm layer.
+ Default: dict(type='BN', requires_grad=True).
+ deploy (bool): Whether to switch the model structure to
+ deployment mode. Default: False.
+ init_cfg (dict or list[dict], optional): Initialization config dict.
+ Default: None
+ """
+
+ def __init__(self,
+ channels,
+ path_h,
+ path_w,
+ reparam_conv_kernels=None,
+ globalperceptron_ratio=4,
+ num_sharesets=1,
+ conv_cfg=None,
+ norm_cfg=dict(type='BN', requires_grad=True),
+ deploy=False,
+ init_cfg=None):
+ super().__init__(init_cfg=init_cfg)
+
+ self.deploy = deploy
+ self.channels = channels
+ self.num_sharesets = num_sharesets
+ self.path_h, self.path_w = path_h, path_w
+ # the input channel of fc3
+ self._path_vec_channles = path_h * path_w * num_sharesets
+
+ self.conv_cfg = conv_cfg
+ self.norm_cfg = norm_cfg
+
+ self.gp = GlobalPerceptron(
+ input_channels=channels, ratio=globalperceptron_ratio)
+
+ # using a conv layer to implement a fc layer
+ self.fc3 = build_conv_layer(
+ conv_cfg,
+ in_channels=self._path_vec_channles,
+ out_channels=self._path_vec_channles,
+ kernel_size=1,
+ stride=1,
+ padding=0,
+ bias=deploy,
+ groups=num_sharesets)
+ if deploy:
+ self.fc3_bn = nn.Identity()
+ else:
+ norm_layer = build_norm_layer(norm_cfg, num_sharesets)[1]
+ self.add_module('fc3_bn', norm_layer)
+
+ self.reparam_conv_kernels = reparam_conv_kernels
+ if not deploy and reparam_conv_kernels is not None:
+ for k in reparam_conv_kernels:
+ conv_branch = ConvModule(
+ in_channels=num_sharesets,
+ out_channels=num_sharesets,
+ kernel_size=k,
+ stride=1,
+ padding=k // 2,
+ norm_cfg=dict(type='BN', requires_grad=True),
+ groups=num_sharesets,
+ act_cfg=None)
+ self.__setattr__('repconv{}'.format(k), conv_branch)
+
+ def partition(self, x, h_parts, w_parts):
+ # convert (N, C, H, W) to (N, h_parts, w_parts, C, path_h, path_w)
+ x = x.reshape(-1, self.channels, h_parts, self.path_h, w_parts,
+ self.path_w)
+ x = x.permute(0, 2, 4, 1, 3, 5)
+ return x
+
+ def partition_affine(self, x, h_parts, w_parts):
+ """perform Partition Perceptron."""
+ fc_inputs = x.reshape(-1, self._path_vec_channles, 1, 1)
+ out = self.fc3(fc_inputs)
+ out = out.reshape(-1, self.num_sharesets, self.path_h, self.path_w)
+ out = self.fc3_bn(out)
+ out = out.reshape(-1, h_parts, w_parts, self.num_sharesets,
+ self.path_h, self.path_w)
+ return out
+
+ def forward(self, inputs):
+ # Global Perceptron
+ global_vec = self.gp(inputs)
+
+ origin_shape = inputs.size()
+ h_parts = origin_shape[2] // self.path_h
+ w_parts = origin_shape[3] // self.path_w
+
+ partitions = self.partition(inputs, h_parts, w_parts)
+
+ # Channel Perceptron
+ fc3_out = self.partition_affine(partitions, h_parts, w_parts)
+
+ # perform Local Perceptron
+ if self.reparam_conv_kernels is not None and not self.deploy:
+ conv_inputs = partitions.reshape(-1, self.num_sharesets,
+ self.path_h, self.path_w)
+ conv_out = 0
+ for k in self.reparam_conv_kernels:
+ conv_branch = self.__getattr__('repconv{}'.format(k))
+ conv_out += conv_branch(conv_inputs)
+ conv_out = conv_out.reshape(-1, h_parts, w_parts,
+ self.num_sharesets, self.path_h,
+ self.path_w)
+ fc3_out += conv_out
+
+ # N, h_parts, w_parts, num_sharesets, out_h, out_w
+ fc3_out = fc3_out.permute(0, 3, 1, 4, 2, 5)
+ out = fc3_out.reshape(*origin_shape)
+ out = out * global_vec
+ return out
+
+ def get_equivalent_fc3(self):
+ """get the equivalent fc3 weight and bias."""
+ fc_weight, fc_bias = fuse_bn(self.fc3, self.fc3_bn)
+ if self.reparam_conv_kernels is not None:
+ largest_k = max(self.reparam_conv_kernels)
+ largest_branch = self.__getattr__('repconv{}'.format(largest_k))
+ total_kernel, total_bias = fuse_bn(largest_branch.conv,
+ largest_branch.bn)
+ for k in self.reparam_conv_kernels:
+ if k != largest_k:
+ k_branch = self.__getattr__('repconv{}'.format(k))
+ kernel, bias = fuse_bn(k_branch.conv, k_branch.bn)
+ total_kernel += F.pad(kernel, [(largest_k - k) // 2] * 4)
+ total_bias += bias
+ rep_weight, rep_bias = self._convert_conv_to_fc(
+ total_kernel, total_bias)
+ final_fc3_weight = rep_weight.reshape_as(fc_weight) + fc_weight
+ final_fc3_bias = rep_bias + fc_bias
+ else:
+ final_fc3_weight = fc_weight
+ final_fc3_bias = fc_bias
+ return final_fc3_weight, final_fc3_bias
+
+ def local_inject(self):
+ """inject the Local Perceptron into Partition Perceptron."""
+ self.deploy = True
+ # Locality Injection
+ fc3_weight, fc3_bias = self.get_equivalent_fc3()
+ # Remove Local Perceptron
+ if self.reparam_conv_kernels is not None:
+ for k in self.reparam_conv_kernels:
+ self.__delattr__('repconv{}'.format(k))
+ self.__delattr__('fc3')
+ self.__delattr__('fc3_bn')
+ self.fc3 = build_conv_layer(
+ self.conv_cfg,
+ self._path_vec_channles,
+ self._path_vec_channles,
+ 1,
+ 1,
+ 0,
+ bias=True,
+ groups=self.num_sharesets)
+ self.fc3_bn = nn.Identity()
+ self.fc3.weight.data = fc3_weight
+ self.fc3.bias.data = fc3_bias
+
+ def _convert_conv_to_fc(self, conv_kernel, conv_bias):
+ """convert conv_k1 to fc, which is still a conv_k2, and the k2 > k1."""
+ in_channels = torch.eye(self.path_h * self.path_w).repeat(
+ 1, self.num_sharesets).reshape(self.path_h * self.path_w,
+ self.num_sharesets, self.path_h,
+ self.path_w).to(conv_kernel.device)
+ fc_k = F.conv2d(
+ in_channels,
+ conv_kernel,
+ padding=(conv_kernel.size(2) // 2, conv_kernel.size(3) // 2),
+ groups=self.num_sharesets)
+ fc_k = fc_k.reshape(self.path_w * self.path_w, self.num_sharesets *
+ self.path_h * self.path_w).t()
+ fc_bias = conv_bias.repeat_interleave(self.path_h * self.path_w)
+ return fc_k, fc_bias
+
+
+class RepMLPNetUnit(BaseModule):
+ """A basic unit in RepMLPNet : [REPMLPBlock + BN + ConvFFN + BN].
+
+ Args:
+ channels (int): The number of input and the output channels of the
+ unit.
+ path_h (int): The height of patches.
+ path_w (int): The weidth of patches.
+ reparam_conv_kernels (Squeue(int) | None): The conv kernels in the
+ GlobalPerceptron. Default: None.
+ globalperceptron_ratio (int): The reducation ratio in the
+ GlobalPerceptron. Default: 4.
+ num_sharesets (int): The number of sharesets in the
+ PartitionPerceptron. Default 1.
+ conv_cfg (dict, optional): Config dict for convolution layer.
+ Default: None, which means using conv2d.
+ norm_cfg (dict): dictionary to construct and config norm layer.
+ Default: dict(type='BN', requires_grad=True).
+ act_cfg (dict): Config dict for activation layer.
+ Default: dict(type='ReLU').
+ deploy (bool): Whether to switch the model structure to
+ deployment mode. Default: False.
+ init_cfg (dict or list[dict], optional): Initialization config dict.
+ Default: None
+ """
+
+ def __init__(self,
+ channels,
+ path_h,
+ path_w,
+ reparam_conv_kernels,
+ globalperceptron_ratio,
+ norm_cfg=dict(type='BN', requires_grad=True),
+ ffn_expand=4,
+ num_sharesets=1,
+ deploy=False,
+ init_cfg=None):
+ super().__init__(init_cfg=init_cfg)
+ self.repmlp_block = RepMLPBlock(
+ channels=channels,
+ path_h=path_h,
+ path_w=path_w,
+ reparam_conv_kernels=reparam_conv_kernels,
+ globalperceptron_ratio=globalperceptron_ratio,
+ num_sharesets=num_sharesets,
+ deploy=deploy)
+ self.ffn_block = ConvFFN(channels, channels * ffn_expand)
+ norm1 = build_norm_layer(norm_cfg, channels)[1]
+ self.add_module('norm1', norm1)
+ norm2 = build_norm_layer(norm_cfg, channels)[1]
+ self.add_module('norm2', norm2)
+
+ def forward(self, x):
+ y = x + self.repmlp_block(self.norm1(x))
+ out = y + self.ffn_block(self.norm2(y))
+ return out
+
+
+class ConvFFN(nn.Module):
+ """ConvFFN implemented by using point-wise convs."""
+
+ def __init__(self,
+ in_channels,
+ hidden_channels=None,
+ out_channels=None,
+ norm_cfg=dict(type='BN', requires_grad=True),
+ act_cfg=dict(type='GELU')):
+ super().__init__()
+ out_features = out_channels or in_channels
+ hidden_features = hidden_channels or in_channels
+ self.ffn_fc1 = ConvModule(
+ in_channels=in_channels,
+ out_channels=hidden_features,
+ kernel_size=1,
+ stride=1,
+ padding=0,
+ norm_cfg=norm_cfg,
+ act_cfg=None)
+ self.ffn_fc2 = ConvModule(
+ in_channels=hidden_features,
+ out_channels=out_features,
+ kernel_size=1,
+ stride=1,
+ padding=0,
+ norm_cfg=norm_cfg,
+ act_cfg=None)
+ self.act = build_activation_layer(act_cfg)
+
+ def forward(self, x):
+ x = self.ffn_fc1(x)
+ x = self.act(x)
+ x = self.ffn_fc2(x)
+ return x
+
+
+@BACKBONES.register_module()
+class RepMLPNet(BaseModule):
+ """RepMLPNet backbone.
+
+ A PyTorch impl of : `RepMLP: Re-parameterizing Convolutions into
+ Fully-connected Layers for Image Recognition
+ `_
+
+ Args:
+ arch (str | dict): RepMLP architecture. If use string, choose
+ from 'base' and 'b'. If use dict, it should have below keys:
+
+ - channels (List[int]): Number of blocks in each stage.
+ - depths (List[int]): The number of blocks in each branch.
+ - sharesets_nums (List[int]): RepVGG Block that declares
+ the need to apply group convolution.
+
+ img_size (int | tuple): The size of input image. Defaults: 224.
+ in_channels (int): Number of input image channels. Default: 3.
+ patch_size (int | tuple): The patch size in patch embedding.
+ Defaults to 4.
+ out_indices (Sequence[int]): Output from which stages.
+ Default: ``(3, )``.
+ reparam_conv_kernels (Squeue(int) | None): The conv kernels in the
+ GlobalPerceptron. Default: None.
+ globalperceptron_ratio (int): The reducation ratio in the
+ GlobalPerceptron. Default: 4.
+ num_sharesets (int): The number of sharesets in the
+ PartitionPerceptron. Default 1.
+ conv_cfg (dict | None): The config dict for conv layers. Default: None.
+ norm_cfg (dict): The config dict for norm layers.
+ Default: dict(type='BN', requires_grad=True).
+ patch_cfg (dict): Extra config dict for patch embedding.
+ Defaults to an empty dict.
+ final_norm (bool): Whether to add a additional layer to normalize
+ final feature map. Defaults to True.
+ act_cfg (dict): Config dict for activation layer.
+ Default: dict(type='ReLU').
+ deploy (bool): Whether to switch the model structure to deployment
+ mode. Default: False.
+ init_cfg (dict or list[dict], optional): Initialization config dict.
+ """
+ arch_zoo = {
+ **dict.fromkeys(['b', 'base'],
+ {'channels': [96, 192, 384, 768],
+ 'depths': [2, 2, 12, 2],
+ 'sharesets_nums': [1, 4, 32, 128]}),
+ } # yapf: disable
+
+ num_extra_tokens = 0 # there is no cls-token in RepMLP
+
+ def __init__(self,
+ arch,
+ img_size=224,
+ in_channels=3,
+ patch_size=4,
+ out_indices=(3, ),
+ reparam_conv_kernels=(3, ),
+ globalperceptron_ratio=4,
+ conv_cfg=None,
+ norm_cfg=dict(type='BN', requires_grad=True),
+ patch_cfg=dict(),
+ final_norm=True,
+ deploy=False,
+ init_cfg=None):
+ super(RepMLPNet, self).__init__(init_cfg=init_cfg)
+ if isinstance(arch, str):
+ arch = arch.lower()
+ assert arch in set(self.arch_zoo), \
+ f'Arch {arch} is not in default archs {set(self.arch_zoo)}'
+ self.arch_settings = self.arch_zoo[arch]
+ else:
+ essential_keys = {'channels', 'depths', 'sharesets_nums'}
+ assert isinstance(arch, dict) and set(arch) == essential_keys, \
+ f'Custom arch needs a dict with keys {essential_keys}.'
+ self.arch_settings = arch
+
+ self.img_size = to_2tuple(img_size)
+ self.patch_size = to_2tuple(patch_size)
+ self.conv_cfg = conv_cfg
+ self.norm_cfg = norm_cfg
+
+ self.num_stage = len(self.arch_settings['channels'])
+ for value in self.arch_settings.values():
+ assert isinstance(value, list) and len(value) == self.num_stage, (
+ 'Length of setting item in arch dict must be type of list and'
+ ' have the same length.')
+
+ self.channels = self.arch_settings['channels']
+ self.depths = self.arch_settings['depths']
+ self.sharesets_nums = self.arch_settings['sharesets_nums']
+
+ _patch_cfg = dict(
+ in_channels=in_channels,
+ input_size=self.img_size,
+ embed_dims=self.channels[0],
+ conv_type='Conv2d',
+ kernel_size=self.patch_size,
+ stride=self.patch_size,
+ norm_cfg=self.norm_cfg,
+ bias=False)
+ _patch_cfg.update(patch_cfg)
+ self.patch_embed = PatchEmbed(**_patch_cfg)
+ self.patch_resolution = self.patch_embed.init_out_size
+
+ self.patch_hs = [
+ self.patch_resolution[0] // 2**i for i in range(self.num_stage)
+ ]
+ self.patch_ws = [
+ self.patch_resolution[1] // 2**i for i in range(self.num_stage)
+ ]
+
+ self.stages = ModuleList()
+ self.downsample_layers = ModuleList()
+ for stage_idx in range(self.num_stage):
+ # make stage layers
+ _stage_cfg = dict(
+ channels=self.channels[stage_idx],
+ path_h=self.patch_hs[stage_idx],
+ path_w=self.patch_ws[stage_idx],
+ reparam_conv_kernels=reparam_conv_kernels,
+ globalperceptron_ratio=globalperceptron_ratio,
+ norm_cfg=self.norm_cfg,
+ ffn_expand=4,
+ num_sharesets=self.sharesets_nums[stage_idx],
+ deploy=deploy)
+ stage_blocks = [
+ RepMLPNetUnit(**_stage_cfg)
+ for _ in range(self.depths[stage_idx])
+ ]
+ self.stages.append(Sequential(*stage_blocks))
+
+ # make downsample layers
+ if stage_idx < self.num_stage - 1:
+ self.downsample_layers.append(
+ ConvModule(
+ in_channels=self.channels[stage_idx],
+ out_channels=self.channels[stage_idx + 1],
+ kernel_size=2,
+ stride=2,
+ padding=0,
+ conv_cfg=self.conv_cfg,
+ norm_cfg=self.norm_cfg,
+ inplace=True))
+
+ self.out_indice = out_indices
+
+ if final_norm:
+ norm_layer = build_norm_layer(norm_cfg, self.channels[-1])[1]
+ else:
+ norm_layer = nn.Identity()
+ self.add_module('final_norm', norm_layer)
+
+ def forward(self, x):
+ assert x.shape[2:] == self.img_size, \
+ "The Rep-MLP doesn't support dynamic input shape. " \
+ f'Please input images with shape {self.img_size}'
+
+ outs = []
+
+ x, _ = self.patch_embed(x)
+ for i, stage in enumerate(self.stages):
+ x = stage(x)
+
+ # downsample after each stage except last stage
+ if i < len(self.stages) - 1:
+ downsample = self.downsample_layers[i]
+ x = downsample(x)
+
+ if i in self.out_indice:
+ if self.final_norm and i == len(self.stages) - 1:
+ out = self.final_norm(x)
+ else:
+ out = x
+ outs.append(out)
+
+ return tuple(outs)
+
+ def switch_to_deploy(self):
+ for m in self.modules():
+ if hasattr(m, 'local_inject'):
+ m.local_inject()
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/repvgg.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/repvgg.py
new file mode 100644
index 0000000000000000000000000000000000000000..bbdbda2f48e88647fbc184076e2ccb58adaae0a7
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/repvgg.py
@@ -0,0 +1,619 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import torch
+import torch.nn.functional as F
+import torch.utils.checkpoint as cp
+from mmcv.cnn import (ConvModule, build_activation_layer, build_conv_layer,
+ build_norm_layer)
+from mmcv.runner import BaseModule, Sequential
+from mmcv.utils.parrots_wrapper import _BatchNorm
+from torch import nn
+
+from ..builder import BACKBONES
+from ..utils.se_layer import SELayer
+from .base_backbone import BaseBackbone
+
+
+class RepVGGBlock(BaseModule):
+ """RepVGG block for RepVGG backbone.
+
+ Args:
+ in_channels (int): The input channels of the block.
+ out_channels (int): The output channels of the block.
+ stride (int): Stride of the 3x3 and 1x1 convolution layer. Default: 1.
+ padding (int): Padding of the 3x3 convolution layer.
+ dilation (int): Dilation of the 3x3 convolution layer.
+ groups (int): Groups of the 3x3 and 1x1 convolution layer. Default: 1.
+ padding_mode (str): Padding mode of the 3x3 convolution layer.
+ Default: 'zeros'.
+ se_cfg (None or dict): The configuration of the se module.
+ Default: None.
+ with_cp (bool): Use checkpoint or not. Using checkpoint will save some
+ memory while slowing down the training speed. Default: False.
+ conv_cfg (dict, optional): Config dict for convolution layer.
+ Default: None, which means using conv2d.
+ norm_cfg (dict): dictionary to construct and config norm layer.
+ Default: dict(type='BN', requires_grad=True).
+ act_cfg (dict): Config dict for activation layer.
+ Default: dict(type='ReLU').
+ deploy (bool): Whether to switch the model structure to
+ deployment mode. Default: False.
+ init_cfg (dict or list[dict], optional): Initialization config dict.
+ Default: None
+ """
+
+ def __init__(self,
+ in_channels,
+ out_channels,
+ stride=1,
+ padding=1,
+ dilation=1,
+ groups=1,
+ padding_mode='zeros',
+ se_cfg=None,
+ with_cp=False,
+ conv_cfg=None,
+ norm_cfg=dict(type='BN'),
+ act_cfg=dict(type='ReLU'),
+ deploy=False,
+ init_cfg=None):
+ super(RepVGGBlock, self).__init__(init_cfg)
+
+ assert se_cfg is None or isinstance(se_cfg, dict)
+
+ self.in_channels = in_channels
+ self.out_channels = out_channels
+ self.stride = stride
+ self.padding = padding
+ self.dilation = dilation
+ self.groups = groups
+ self.se_cfg = se_cfg
+ self.with_cp = with_cp
+ self.conv_cfg = conv_cfg
+ self.norm_cfg = norm_cfg
+ self.act_cfg = act_cfg
+ self.deploy = deploy
+
+ if deploy:
+ self.branch_reparam = build_conv_layer(
+ conv_cfg,
+ in_channels=in_channels,
+ out_channels=out_channels,
+ kernel_size=3,
+ stride=stride,
+ padding=padding,
+ dilation=dilation,
+ groups=groups,
+ bias=True,
+ padding_mode=padding_mode)
+ else:
+ # judge if input shape and output shape are the same.
+ # If true, add a normalized identity shortcut.
+ if out_channels == in_channels and stride == 1 and \
+ padding == dilation:
+ self.branch_norm = build_norm_layer(norm_cfg, in_channels)[1]
+ else:
+ self.branch_norm = None
+
+ self.branch_3x3 = self.create_conv_bn(
+ kernel_size=3,
+ dilation=dilation,
+ padding=padding,
+ )
+ self.branch_1x1 = self.create_conv_bn(kernel_size=1)
+
+ if se_cfg is not None:
+ self.se_layer = SELayer(channels=out_channels, **se_cfg)
+ else:
+ self.se_layer = None
+
+ self.act = build_activation_layer(act_cfg)
+
+ def create_conv_bn(self, kernel_size, dilation=1, padding=0):
+ conv_bn = Sequential()
+ conv_bn.add_module(
+ 'conv',
+ build_conv_layer(
+ self.conv_cfg,
+ in_channels=self.in_channels,
+ out_channels=self.out_channels,
+ kernel_size=kernel_size,
+ stride=self.stride,
+ dilation=dilation,
+ padding=padding,
+ groups=self.groups,
+ bias=False))
+ conv_bn.add_module(
+ 'norm',
+ build_norm_layer(self.norm_cfg, num_features=self.out_channels)[1])
+
+ return conv_bn
+
+ def forward(self, x):
+
+ def _inner_forward(inputs):
+ if self.deploy:
+ return self.branch_reparam(inputs)
+
+ if self.branch_norm is None:
+ branch_norm_out = 0
+ else:
+ branch_norm_out = self.branch_norm(inputs)
+
+ inner_out = self.branch_3x3(inputs) + self.branch_1x1(
+ inputs) + branch_norm_out
+
+ if self.se_cfg is not None:
+ inner_out = self.se_layer(inner_out)
+
+ return inner_out
+
+ if self.with_cp and x.requires_grad:
+ out = cp.checkpoint(_inner_forward, x)
+ else:
+ out = _inner_forward(x)
+
+ out = self.act(out)
+
+ return out
+
+ def switch_to_deploy(self):
+ """Switch the model structure from training mode to deployment mode."""
+ if self.deploy:
+ return
+ assert self.norm_cfg['type'] == 'BN', \
+ "Switch is not allowed when norm_cfg['type'] != 'BN'."
+
+ reparam_weight, reparam_bias = self.reparameterize()
+ self.branch_reparam = build_conv_layer(
+ self.conv_cfg,
+ self.in_channels,
+ self.out_channels,
+ kernel_size=3,
+ stride=self.stride,
+ padding=self.padding,
+ dilation=self.dilation,
+ groups=self.groups,
+ bias=True)
+ self.branch_reparam.weight.data = reparam_weight
+ self.branch_reparam.bias.data = reparam_bias
+
+ for param in self.parameters():
+ param.detach_()
+ delattr(self, 'branch_3x3')
+ delattr(self, 'branch_1x1')
+ delattr(self, 'branch_norm')
+
+ self.deploy = True
+
+ def reparameterize(self):
+ """Fuse all the parameters of all branches.
+
+ Returns:
+ tuple[torch.Tensor, torch.Tensor]: Parameters after fusion of all
+ branches. the first element is the weights and the second is
+ the bias.
+ """
+ weight_3x3, bias_3x3 = self._fuse_conv_bn(self.branch_3x3)
+ weight_1x1, bias_1x1 = self._fuse_conv_bn(self.branch_1x1)
+ # pad a conv1x1 weight to a conv3x3 weight
+ weight_1x1 = F.pad(weight_1x1, [1, 1, 1, 1], value=0)
+
+ weight_norm, bias_norm = 0, 0
+ if self.branch_norm:
+ tmp_conv_bn = self._norm_to_conv3x3(self.branch_norm)
+ weight_norm, bias_norm = self._fuse_conv_bn(tmp_conv_bn)
+
+ return (weight_3x3 + weight_1x1 + weight_norm,
+ bias_3x3 + bias_1x1 + bias_norm)
+
+ def _fuse_conv_bn(self, branch):
+ """Fuse the parameters in a branch with a conv and bn.
+
+ Args:
+ branch (mmcv.runner.Sequential): A branch with conv and bn.
+
+ Returns:
+ tuple[torch.Tensor, torch.Tensor]: The parameters obtained after
+ fusing the parameters of conv and bn in one branch.
+ The first element is the weight and the second is the bias.
+ """
+ if branch is None:
+ return 0, 0
+ conv_weight = branch.conv.weight
+ running_mean = branch.norm.running_mean
+ running_var = branch.norm.running_var
+ gamma = branch.norm.weight
+ beta = branch.norm.bias
+ eps = branch.norm.eps
+
+ std = (running_var + eps).sqrt()
+ fused_weight = (gamma / std).reshape(-1, 1, 1, 1) * conv_weight
+ fused_bias = -running_mean * gamma / std + beta
+
+ return fused_weight, fused_bias
+
+ def _norm_to_conv3x3(self, branch_norm):
+ """Convert a norm layer to a conv3x3-bn sequence.
+
+ Args:
+ branch (nn.BatchNorm2d): A branch only with bn in the block.
+
+ Returns:
+ tmp_conv3x3 (mmcv.runner.Sequential): a sequential with conv3x3 and
+ bn.
+ """
+ input_dim = self.in_channels // self.groups
+ conv_weight = torch.zeros((self.in_channels, input_dim, 3, 3),
+ dtype=branch_norm.weight.dtype)
+
+ for i in range(self.in_channels):
+ conv_weight[i, i % input_dim, 1, 1] = 1
+ conv_weight = conv_weight.to(branch_norm.weight.device)
+
+ tmp_conv3x3 = self.create_conv_bn(kernel_size=3)
+ tmp_conv3x3.conv.weight.data = conv_weight
+ tmp_conv3x3.norm = branch_norm
+ return tmp_conv3x3
+
+
+class MTSPPF(nn.Module):
+ """MTSPPF block for YOLOX-PAI RepVGG backbone.
+
+ Args:
+ in_channels (int): The input channels of the block.
+ out_channels (int): The output channels of the block.
+ norm_cfg (dict): dictionary to construct and config norm layer.
+ Default: dict(type='BN').
+ act_cfg (dict): Config dict for activation layer.
+ Default: dict(type='ReLU').
+ kernel_size (int): Kernel size of pooling. Default: 5.
+ """
+
+ def __init__(self,
+ in_channels,
+ out_channels,
+ norm_cfg=dict(type='BN'),
+ act_cfg=dict(type='ReLU'),
+ kernel_size=5):
+ super().__init__()
+ hidden_features = in_channels // 2 # hidden channels
+ self.conv1 = ConvModule(
+ in_channels,
+ hidden_features,
+ 1,
+ stride=1,
+ norm_cfg=norm_cfg,
+ act_cfg=act_cfg)
+ self.conv2 = ConvModule(
+ hidden_features * 4,
+ out_channels,
+ 1,
+ stride=1,
+ norm_cfg=norm_cfg,
+ act_cfg=act_cfg)
+ self.maxpool = nn.MaxPool2d(
+ kernel_size=kernel_size, stride=1, padding=kernel_size // 2)
+
+ def forward(self, x):
+ x = self.conv1(x)
+ y1 = self.maxpool(x)
+ y2 = self.maxpool(y1)
+ return self.conv2(torch.cat([x, y1, y2, self.maxpool(y2)], 1))
+
+
+@BACKBONES.register_module()
+class RepVGG(BaseBackbone):
+ """RepVGG backbone.
+
+ A PyTorch impl of : `RepVGG: Making VGG-style ConvNets Great Again
+ `_
+
+ Args:
+ arch (str | dict): RepVGG architecture. If use string,
+ choose from 'A0', 'A1`', 'A2', 'B0', 'B1', 'B1g2', 'B1g4', 'B2'
+ , 'B2g2', 'B2g4', 'B3', 'B3g2', 'B3g4' or 'D2se'. If use dict,
+ it should have below keys:
+
+ - num_blocks (Sequence[int]): Number of blocks in each stage.
+ - width_factor (Sequence[float]): Width deflator in each stage.
+ - group_layer_map (dict | None): RepVGG Block that declares
+ the need to apply group convolution.
+ - se_cfg (dict | None): Se Layer config.
+ - stem_channels (int, optional): The stem channels, the final
+ stem channels will be
+ ``min(stem_channels, base_channels*width_factor[0])``.
+ If not set here, 64 is used by default in the code.
+
+ in_channels (int): Number of input image channels. Default: 3.
+ base_channels (int): Base channels of RepVGG backbone, work with
+ width_factor together. Defaults to 64.
+ out_indices (Sequence[int]): Output from which stages. Default: (3, ).
+ strides (Sequence[int]): Strides of the first block of each stage.
+ Default: (2, 2, 2, 2).
+ dilations (Sequence[int]): Dilation of each stage.
+ Default: (1, 1, 1, 1).
+ frozen_stages (int): Stages to be frozen (all param fixed). -1 means
+ not freezing any parameters. Default: -1.
+ conv_cfg (dict | None): The config dict for conv layers. Default: None.
+ norm_cfg (dict): The config dict for norm layers.
+ Default: dict(type='BN').
+ act_cfg (dict): Config dict for activation layer.
+ Default: dict(type='ReLU').
+ with_cp (bool): Use checkpoint or not. Using checkpoint will save some
+ memory while slowing down the training speed. Default: False.
+ deploy (bool): Whether to switch the model structure to deployment
+ mode. Default: False.
+ norm_eval (bool): Whether to set norm layers to eval mode, namely,
+ freeze running stats (mean and var). Note: Effect on Batch Norm
+ and its variants only. Default: False.
+ add_ppf (bool): Whether to use the MTSPPF block. Default: False.
+ init_cfg (dict or list[dict], optional): Initialization config dict.
+ """
+
+ groupwise_layers = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26]
+ g2_layer_map = {layer: 2 for layer in groupwise_layers}
+ g4_layer_map = {layer: 4 for layer in groupwise_layers}
+
+ arch_settings = {
+ 'A0':
+ dict(
+ num_blocks=[2, 4, 14, 1],
+ width_factor=[0.75, 0.75, 0.75, 2.5],
+ group_layer_map=None,
+ se_cfg=None),
+ 'A1':
+ dict(
+ num_blocks=[2, 4, 14, 1],
+ width_factor=[1, 1, 1, 2.5],
+ group_layer_map=None,
+ se_cfg=None),
+ 'A2':
+ dict(
+ num_blocks=[2, 4, 14, 1],
+ width_factor=[1.5, 1.5, 1.5, 2.75],
+ group_layer_map=None,
+ se_cfg=None),
+ 'B0':
+ dict(
+ num_blocks=[4, 6, 16, 1],
+ width_factor=[1, 1, 1, 2.5],
+ group_layer_map=None,
+ se_cfg=None,
+ stem_channels=64),
+ 'B1':
+ dict(
+ num_blocks=[4, 6, 16, 1],
+ width_factor=[2, 2, 2, 4],
+ group_layer_map=None,
+ se_cfg=None),
+ 'B1g2':
+ dict(
+ num_blocks=[4, 6, 16, 1],
+ width_factor=[2, 2, 2, 4],
+ group_layer_map=g2_layer_map,
+ se_cfg=None),
+ 'B1g4':
+ dict(
+ num_blocks=[4, 6, 16, 1],
+ width_factor=[2, 2, 2, 4],
+ group_layer_map=g4_layer_map,
+ se_cfg=None),
+ 'B2':
+ dict(
+ num_blocks=[4, 6, 16, 1],
+ width_factor=[2.5, 2.5, 2.5, 5],
+ group_layer_map=None,
+ se_cfg=None),
+ 'B2g2':
+ dict(
+ num_blocks=[4, 6, 16, 1],
+ width_factor=[2.5, 2.5, 2.5, 5],
+ group_layer_map=g2_layer_map,
+ se_cfg=None),
+ 'B2g4':
+ dict(
+ num_blocks=[4, 6, 16, 1],
+ width_factor=[2.5, 2.5, 2.5, 5],
+ group_layer_map=g4_layer_map,
+ se_cfg=None),
+ 'B3':
+ dict(
+ num_blocks=[4, 6, 16, 1],
+ width_factor=[3, 3, 3, 5],
+ group_layer_map=None,
+ se_cfg=None),
+ 'B3g2':
+ dict(
+ num_blocks=[4, 6, 16, 1],
+ width_factor=[3, 3, 3, 5],
+ group_layer_map=g2_layer_map,
+ se_cfg=None),
+ 'B3g4':
+ dict(
+ num_blocks=[4, 6, 16, 1],
+ width_factor=[3, 3, 3, 5],
+ group_layer_map=g4_layer_map,
+ se_cfg=None),
+ 'D2se':
+ dict(
+ num_blocks=[8, 14, 24, 1],
+ width_factor=[2.5, 2.5, 2.5, 5],
+ group_layer_map=None,
+ se_cfg=dict(ratio=16, divisor=1)),
+ 'yolox-pai-small':
+ dict(
+ num_blocks=[3, 5, 7, 3],
+ width_factor=[1, 1, 1, 1],
+ group_layer_map=None,
+ se_cfg=None,
+ stem_channels=32),
+ }
+
+ def __init__(self,
+ arch,
+ in_channels=3,
+ base_channels=64,
+ out_indices=(3, ),
+ strides=(2, 2, 2, 2),
+ dilations=(1, 1, 1, 1),
+ frozen_stages=-1,
+ conv_cfg=None,
+ norm_cfg=dict(type='BN'),
+ act_cfg=dict(type='ReLU'),
+ with_cp=False,
+ deploy=False,
+ norm_eval=False,
+ add_ppf=False,
+ init_cfg=[
+ dict(type='Kaiming', layer=['Conv2d']),
+ dict(
+ type='Constant',
+ val=1,
+ layer=['_BatchNorm', 'GroupNorm'])
+ ]):
+ super(RepVGG, self).__init__(init_cfg)
+
+ if isinstance(arch, str):
+ assert arch in self.arch_settings, \
+ f'"arch": "{arch}" is not one of the arch_settings'
+ arch = self.arch_settings[arch]
+ elif not isinstance(arch, dict):
+ raise TypeError('Expect "arch" to be either a string '
+ f'or a dict, got {type(arch)}')
+
+ assert len(arch['num_blocks']) == len(
+ arch['width_factor']) == len(strides) == len(dilations)
+ assert max(out_indices) < len(arch['num_blocks'])
+ if arch['group_layer_map'] is not None:
+ assert max(arch['group_layer_map'].keys()) <= sum(
+ arch['num_blocks'])
+
+ if arch['se_cfg'] is not None:
+ assert isinstance(arch['se_cfg'], dict)
+
+ self.base_channels = base_channels
+ self.arch = arch
+ self.in_channels = in_channels
+ self.out_indices = out_indices
+ self.strides = strides
+ self.dilations = dilations
+ self.deploy = deploy
+ self.frozen_stages = frozen_stages
+ self.conv_cfg = conv_cfg
+ self.norm_cfg = norm_cfg
+ self.act_cfg = act_cfg
+ self.with_cp = with_cp
+ self.norm_eval = norm_eval
+
+ # defaults to 64 to prevert BC-breaking if stem_channels
+ # not in arch dict;
+ # the stem channels should not be larger than that of stage1.
+ channels = min(
+ arch.get('stem_channels', 64),
+ int(self.base_channels * self.arch['width_factor'][0]))
+ self.stem = RepVGGBlock(
+ self.in_channels,
+ channels,
+ stride=2,
+ se_cfg=arch['se_cfg'],
+ with_cp=with_cp,
+ conv_cfg=conv_cfg,
+ norm_cfg=norm_cfg,
+ act_cfg=act_cfg,
+ deploy=deploy)
+
+ next_create_block_idx = 1
+ self.stages = []
+ for i in range(len(arch['num_blocks'])):
+ num_blocks = self.arch['num_blocks'][i]
+ stride = self.strides[i]
+ dilation = self.dilations[i]
+ out_channels = int(self.base_channels * 2**i *
+ self.arch['width_factor'][i])
+
+ stage, next_create_block_idx = self._make_stage(
+ channels, out_channels, num_blocks, stride, dilation,
+ next_create_block_idx, init_cfg)
+ stage_name = f'stage_{i + 1}'
+ self.add_module(stage_name, stage)
+ self.stages.append(stage_name)
+
+ channels = out_channels
+
+ if add_ppf:
+ self.ppf = MTSPPF(
+ out_channels,
+ out_channels,
+ norm_cfg=norm_cfg,
+ act_cfg=act_cfg,
+ kernel_size=5)
+ else:
+ self.ppf = None
+
+ def _make_stage(self, in_channels, out_channels, num_blocks, stride,
+ dilation, next_create_block_idx, init_cfg):
+ strides = [stride] + [1] * (num_blocks - 1)
+ dilations = [dilation] * num_blocks
+
+ blocks = []
+ for i in range(num_blocks):
+ groups = self.arch['group_layer_map'].get(
+ next_create_block_idx,
+ 1) if self.arch['group_layer_map'] is not None else 1
+ blocks.append(
+ RepVGGBlock(
+ in_channels,
+ out_channels,
+ stride=strides[i],
+ padding=dilations[i],
+ dilation=dilations[i],
+ groups=groups,
+ se_cfg=self.arch['se_cfg'],
+ with_cp=self.with_cp,
+ conv_cfg=self.conv_cfg,
+ norm_cfg=self.norm_cfg,
+ act_cfg=self.act_cfg,
+ deploy=self.deploy,
+ init_cfg=init_cfg))
+ in_channels = out_channels
+ next_create_block_idx += 1
+
+ return Sequential(*blocks), next_create_block_idx
+
+ def forward(self, x):
+ x = self.stem(x)
+ outs = []
+ for i, stage_name in enumerate(self.stages):
+ stage = getattr(self, stage_name)
+ x = stage(x)
+ if i + 1 == len(self.stages) and self.ppf is not None:
+ x = self.ppf(x)
+ if i in self.out_indices:
+ outs.append(x)
+
+ return tuple(outs)
+
+ def _freeze_stages(self):
+ if self.frozen_stages >= 0:
+ self.stem.eval()
+ for param in self.stem.parameters():
+ param.requires_grad = False
+ for i in range(self.frozen_stages):
+ stage = getattr(self, f'stage_{i+1}')
+ stage.eval()
+ for param in stage.parameters():
+ param.requires_grad = False
+
+ def train(self, mode=True):
+ super(RepVGG, self).train(mode)
+ self._freeze_stages()
+ if mode and self.norm_eval:
+ for m in self.modules():
+ if isinstance(m, _BatchNorm):
+ m.eval()
+
+ def switch_to_deploy(self):
+ for m in self.modules():
+ if isinstance(m, RepVGGBlock):
+ m.switch_to_deploy()
+ self.deploy = True
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/res2net.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/res2net.py
new file mode 100644
index 0000000000000000000000000000000000000000..491b6f4717d724fb2900ff926204dbf375ea2dd8
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/res2net.py
@@ -0,0 +1,306 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import math
+
+import torch
+import torch.nn as nn
+import torch.utils.checkpoint as cp
+from mmcv.cnn import build_conv_layer, build_norm_layer
+from mmcv.runner import ModuleList, Sequential
+
+from ..builder import BACKBONES
+from .resnet import Bottleneck as _Bottleneck
+from .resnet import ResNet
+
+
+class Bottle2neck(_Bottleneck):
+ expansion = 4
+
+ def __init__(self,
+ in_channels,
+ out_channels,
+ scales=4,
+ base_width=26,
+ base_channels=64,
+ stage_type='normal',
+ **kwargs):
+ """Bottle2neck block for Res2Net."""
+ super(Bottle2neck, self).__init__(in_channels, out_channels, **kwargs)
+ assert scales > 1, 'Res2Net degenerates to ResNet when scales = 1.'
+
+ mid_channels = out_channels // self.expansion
+ width = int(math.floor(mid_channels * (base_width / base_channels)))
+
+ self.norm1_name, norm1 = build_norm_layer(
+ self.norm_cfg, width * scales, postfix=1)
+ self.norm3_name, norm3 = build_norm_layer(
+ self.norm_cfg, self.out_channels, postfix=3)
+
+ self.conv1 = build_conv_layer(
+ self.conv_cfg,
+ self.in_channels,
+ width * scales,
+ kernel_size=1,
+ stride=self.conv1_stride,
+ bias=False)
+ self.add_module(self.norm1_name, norm1)
+
+ if stage_type == 'stage':
+ self.pool = nn.AvgPool2d(
+ kernel_size=3, stride=self.conv2_stride, padding=1)
+
+ self.convs = ModuleList()
+ self.bns = ModuleList()
+ for i in range(scales - 1):
+ self.convs.append(
+ build_conv_layer(
+ self.conv_cfg,
+ width,
+ width,
+ kernel_size=3,
+ stride=self.conv2_stride,
+ padding=self.dilation,
+ dilation=self.dilation,
+ bias=False))
+ self.bns.append(
+ build_norm_layer(self.norm_cfg, width, postfix=i + 1)[1])
+
+ self.conv3 = build_conv_layer(
+ self.conv_cfg,
+ width * scales,
+ self.out_channels,
+ kernel_size=1,
+ bias=False)
+ self.add_module(self.norm3_name, norm3)
+
+ self.stage_type = stage_type
+ self.scales = scales
+ self.width = width
+ delattr(self, 'conv2')
+ delattr(self, self.norm2_name)
+
+ def forward(self, x):
+ """Forward function."""
+
+ def _inner_forward(x):
+ identity = x
+
+ out = self.conv1(x)
+ out = self.norm1(out)
+ out = self.relu(out)
+
+ spx = torch.split(out, self.width, 1)
+ sp = self.convs[0](spx[0].contiguous())
+ sp = self.relu(self.bns[0](sp))
+ out = sp
+ for i in range(1, self.scales - 1):
+ if self.stage_type == 'stage':
+ sp = spx[i]
+ else:
+ sp = sp + spx[i]
+ sp = self.convs[i](sp.contiguous())
+ sp = self.relu(self.bns[i](sp))
+ out = torch.cat((out, sp), 1)
+
+ if self.stage_type == 'normal' and self.scales != 1:
+ out = torch.cat((out, spx[self.scales - 1]), 1)
+ elif self.stage_type == 'stage' and self.scales != 1:
+ out = torch.cat((out, self.pool(spx[self.scales - 1])), 1)
+
+ out = self.conv3(out)
+ out = self.norm3(out)
+
+ if self.downsample is not None:
+ identity = self.downsample(x)
+
+ out += identity
+
+ return out
+
+ if self.with_cp and x.requires_grad:
+ out = cp.checkpoint(_inner_forward, x)
+ else:
+ out = _inner_forward(x)
+
+ out = self.relu(out)
+
+ return out
+
+
+class Res2Layer(Sequential):
+ """Res2Layer to build Res2Net style backbone.
+
+ Args:
+ block (nn.Module): block used to build ResLayer.
+ inplanes (int): inplanes of block.
+ planes (int): planes of block.
+ num_blocks (int): number of blocks.
+ stride (int): stride of the first block. Default: 1
+ avg_down (bool): Use AvgPool instead of stride conv when
+ downsampling in the bottle2neck. Defaults to True.
+ conv_cfg (dict): dictionary to construct and config conv layer.
+ Default: None
+ norm_cfg (dict): dictionary to construct and config norm layer.
+ Default: dict(type='BN')
+ scales (int): Scales used in Res2Net. Default: 4
+ base_width (int): Basic width of each scale. Default: 26
+ """
+
+ def __init__(self,
+ block,
+ in_channels,
+ out_channels,
+ num_blocks,
+ stride=1,
+ avg_down=True,
+ conv_cfg=None,
+ norm_cfg=dict(type='BN'),
+ scales=4,
+ base_width=26,
+ **kwargs):
+ self.block = block
+
+ downsample = None
+ if stride != 1 or in_channels != out_channels:
+ if avg_down:
+ downsample = nn.Sequential(
+ nn.AvgPool2d(
+ kernel_size=stride,
+ stride=stride,
+ ceil_mode=True,
+ count_include_pad=False),
+ build_conv_layer(
+ conv_cfg,
+ in_channels,
+ out_channels,
+ kernel_size=1,
+ stride=1,
+ bias=False),
+ build_norm_layer(norm_cfg, out_channels)[1],
+ )
+ else:
+ downsample = nn.Sequential(
+ build_conv_layer(
+ conv_cfg,
+ in_channels,
+ out_channels,
+ kernel_size=1,
+ stride=stride,
+ bias=False),
+ build_norm_layer(norm_cfg, out_channels)[1],
+ )
+
+ layers = []
+ layers.append(
+ block(
+ in_channels=in_channels,
+ out_channels=out_channels,
+ stride=stride,
+ downsample=downsample,
+ conv_cfg=conv_cfg,
+ norm_cfg=norm_cfg,
+ scales=scales,
+ base_width=base_width,
+ stage_type='stage',
+ **kwargs))
+ in_channels = out_channels
+ for _ in range(1, num_blocks):
+ layers.append(
+ block(
+ in_channels=in_channels,
+ out_channels=out_channels,
+ stride=1,
+ conv_cfg=conv_cfg,
+ norm_cfg=norm_cfg,
+ scales=scales,
+ base_width=base_width,
+ **kwargs))
+ super(Res2Layer, self).__init__(*layers)
+
+
+@BACKBONES.register_module()
+class Res2Net(ResNet):
+ """Res2Net backbone.
+
+ A PyTorch implement of : `Res2Net: A New Multi-scale Backbone
+ Architecture `_
+
+ Args:
+ depth (int): Depth of Res2Net, choose from {50, 101, 152}.
+ scales (int): Scales used in Res2Net. Defaults to 4.
+ base_width (int): Basic width of each scale. Defaults to 26.
+ in_channels (int): Number of input image channels. Defaults to 3.
+ num_stages (int): Number of Res2Net stages. Defaults to 4.
+ strides (Sequence[int]): Strides of the first block of each stage.
+ Defaults to ``(1, 2, 2, 2)``.
+ dilations (Sequence[int]): Dilation of each stage.
+ Defaults to ``(1, 1, 1, 1)``.
+ out_indices (Sequence[int]): Output from which stages.
+ Defaults to ``(3, )``.
+ style (str): "pytorch" or "caffe". If set to "pytorch", the stride-two
+ layer is the 3x3 conv layer, otherwise the stride-two layer is
+ the first 1x1 conv layer. Defaults to "pytorch".
+ deep_stem (bool): Replace 7x7 conv in input stem with 3 3x3 conv.
+ Defaults to True.
+ avg_down (bool): Use AvgPool instead of stride conv when
+ downsampling in the bottle2neck. Defaults to True.
+ frozen_stages (int): Stages to be frozen (stop grad and set eval mode).
+ -1 means not freezing any parameters. Defaults to -1.
+ norm_cfg (dict): Dictionary to construct and config norm layer.
+ Defaults to ``dict(type='BN', requires_grad=True)``.
+ norm_eval (bool): Whether to set norm layers to eval mode, namely,
+ freeze running stats (mean and var). Note: Effect on Batch Norm
+ and its variants only. Defaults to False.
+ with_cp (bool): Use checkpoint or not. Using checkpoint will save some
+ memory while slowing down the training speed. Defaults to False.
+ zero_init_residual (bool): Whether to use zero init for last norm layer
+ in resblocks to let them behave as identity. Defaults to True.
+ init_cfg (dict or list[dict], optional): Initialization config dict.
+ Defaults to None.
+
+ Example:
+ >>> from mmcls.models import Res2Net
+ >>> import torch
+ >>> model = Res2Net(depth=50,
+ ... scales=4,
+ ... base_width=26,
+ ... out_indices=(0, 1, 2, 3))
+ >>> model.eval()
+ >>> inputs = torch.rand(1, 3, 32, 32)
+ >>> level_outputs = model.forward(inputs)
+ >>> for level_out in level_outputs:
+ ... print(tuple(level_out.shape))
+ (1, 256, 8, 8)
+ (1, 512, 4, 4)
+ (1, 1024, 2, 2)
+ (1, 2048, 1, 1)
+ """
+
+ arch_settings = {
+ 50: (Bottle2neck, (3, 4, 6, 3)),
+ 101: (Bottle2neck, (3, 4, 23, 3)),
+ 152: (Bottle2neck, (3, 8, 36, 3))
+ }
+
+ def __init__(self,
+ scales=4,
+ base_width=26,
+ style='pytorch',
+ deep_stem=True,
+ avg_down=True,
+ init_cfg=None,
+ **kwargs):
+ self.scales = scales
+ self.base_width = base_width
+ super(Res2Net, self).__init__(
+ style=style,
+ deep_stem=deep_stem,
+ avg_down=avg_down,
+ init_cfg=init_cfg,
+ **kwargs)
+
+ def make_res_layer(self, **kwargs):
+ return Res2Layer(
+ scales=self.scales,
+ base_width=self.base_width,
+ base_channels=self.base_channels,
+ **kwargs)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/resnest.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/resnest.py
similarity index 99%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/resnest.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/resnest.py
index 2fb5d6c2e28e189903e7666155a44c48bf72d120..0a82398871944cbdb10f1312b56156ce889643e6 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/resnest.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/resnest.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import torch
import torch.nn as nn
import torch.nn.functional as F
@@ -260,7 +261,7 @@ class Bottleneck(_Bottleneck):
class ResNeSt(ResNetV1d):
"""ResNeSt backbone.
- Please refer to the `paper `_ for
+ Please refer to the `paper `__ for
details.
Args:
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/resnet.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/resnet.py
similarity index 90%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/resnet.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/resnet.py
index ee47ff555de1716eb9e89bb9819d19a346ce217c..d01ebe0c546bf421f90c80fb2443086e9da939e6 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/resnet.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/resnet.py
@@ -1,14 +1,20 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+
import torch.nn as nn
import torch.utils.checkpoint as cp
-from mmcv.cnn import (ConvModule, build_conv_layer, build_norm_layer,
- constant_init)
+from mmcv.cnn import (ConvModule, build_activation_layer, build_conv_layer,
+ build_norm_layer, constant_init)
+from mmcv.cnn.bricks import DropPath
+from mmcv.runner import BaseModule
from mmcv.utils.parrots_wrapper import _BatchNorm
from ..builder import BACKBONES
from .base_backbone import BaseBackbone
+eps = 1.0e-5
+
-class BasicBlock(nn.Module):
+class BasicBlock(BaseModule):
"""BasicBlock for ResNet.
Args:
@@ -41,8 +47,11 @@ class BasicBlock(nn.Module):
style='pytorch',
with_cp=False,
conv_cfg=None,
- norm_cfg=dict(type='BN')):
- super(BasicBlock, self).__init__()
+ norm_cfg=dict(type='BN'),
+ drop_path_rate=0.0,
+ act_cfg=dict(type='ReLU', inplace=True),
+ init_cfg=None):
+ super(BasicBlock, self).__init__(init_cfg=init_cfg)
self.in_channels = in_channels
self.out_channels = out_channels
self.expansion = expansion
@@ -80,8 +89,10 @@ class BasicBlock(nn.Module):
bias=False)
self.add_module(self.norm2_name, norm2)
- self.relu = nn.ReLU(inplace=True)
+ self.relu = build_activation_layer(act_cfg)
self.downsample = downsample
+ self.drop_path = DropPath(drop_prob=drop_path_rate
+ ) if drop_path_rate > eps else nn.Identity()
@property
def norm1(self):
@@ -106,6 +117,8 @@ class BasicBlock(nn.Module):
if self.downsample is not None:
identity = self.downsample(x)
+ out = self.drop_path(out)
+
out += identity
return out
@@ -120,7 +133,7 @@ class BasicBlock(nn.Module):
return out
-class Bottleneck(nn.Module):
+class Bottleneck(BaseModule):
"""Bottleneck block for ResNet.
Args:
@@ -153,8 +166,11 @@ class Bottleneck(nn.Module):
style='pytorch',
with_cp=False,
conv_cfg=None,
- norm_cfg=dict(type='BN')):
- super(Bottleneck, self).__init__()
+ norm_cfg=dict(type='BN'),
+ act_cfg=dict(type='ReLU', inplace=True),
+ drop_path_rate=0.0,
+ init_cfg=None):
+ super(Bottleneck, self).__init__(init_cfg=init_cfg)
assert style in ['pytorch', 'caffe']
self.in_channels = in_channels
@@ -210,8 +226,10 @@ class Bottleneck(nn.Module):
bias=False)
self.add_module(self.norm3_name, norm3)
- self.relu = nn.ReLU(inplace=True)
+ self.relu = build_activation_layer(act_cfg)
self.downsample = downsample
+ self.drop_path = DropPath(drop_prob=drop_path_rate
+ ) if drop_path_rate > eps else nn.Identity()
@property
def norm1(self):
@@ -244,6 +262,8 @@ class Bottleneck(nn.Module):
if self.downsample is not None:
identity = self.downsample(x)
+ out = self.drop_path(out)
+
out += identity
return out
@@ -382,7 +402,7 @@ class ResLayer(nn.Sequential):
class ResNet(BaseBackbone):
"""ResNet backbone.
- Please refer to the `paper `_ for
+ Please refer to the `paper `__ for
details.
Args:
@@ -395,10 +415,8 @@ class ResNet(BaseBackbone):
Default: ``(1, 2, 2, 2)``.
dilations (Sequence[int]): Dilation of each stage.
Default: ``(1, 1, 1, 1)``.
- out_indices (Sequence[int]): Output from which stages. If only one
- stage is specified, a single tensor (feature map) is returned,
- otherwise multiple stages are specified, a tuple of tensors will
- be returned. Default: ``(3, )``.
+ out_indices (Sequence[int]): Output from which stages.
+ Default: ``(3, )``.
style (str): `pytorch` or `caffe`. If set to "pytorch", the stride-two
layer is the 3x3 conv layer, otherwise the stride-two layer is
the first 1x1 conv layer.
@@ -466,7 +484,8 @@ class ResNet(BaseBackbone):
type='Constant',
val=1,
layer=['_BatchNorm', 'GroupNorm'])
- ]):
+ ],
+ drop_path_rate=0.0):
super(ResNet, self).__init__(init_cfg)
if depth not in self.arch_settings:
raise KeyError(f'invalid depth {depth} for resnet')
@@ -513,7 +532,8 @@ class ResNet(BaseBackbone):
avg_down=self.avg_down,
with_cp=with_cp,
conv_cfg=conv_cfg,
- norm_cfg=norm_cfg)
+ norm_cfg=norm_cfg,
+ drop_path_rate=drop_path_rate)
_in_channels = _out_channels
_out_channels *= 2
layer_name = f'layer{i + 1}'
@@ -594,10 +614,14 @@ class ResNet(BaseBackbone):
for param in m.parameters():
param.requires_grad = False
- # def init_weights(self, pretrained=None):
def init_weights(self):
super(ResNet, self).init_weights()
+ if (isinstance(self.init_cfg, dict)
+ and self.init_cfg['type'] == 'Pretrained'):
+ # Suppress zero_init_residual if use pretrained model.
+ return
+
if self.zero_init_residual:
for m in self.modules():
if isinstance(m, Bottleneck):
@@ -619,10 +643,7 @@ class ResNet(BaseBackbone):
x = res_layer(x)
if i in self.out_indices:
outs.append(x)
- if len(outs) == 1:
- return outs[0]
- else:
- return tuple(outs)
+ return tuple(outs)
def train(self, mode=True):
super(ResNet, self).train(mode)
@@ -634,10 +655,27 @@ class ResNet(BaseBackbone):
m.eval()
+@BACKBONES.register_module()
+class ResNetV1c(ResNet):
+ """ResNetV1c backbone.
+
+ This variant is described in `Bag of Tricks.
+ `_.
+
+ Compared with default ResNet(ResNetV1b), ResNetV1c replaces the 7x7 conv
+ in the input stem with three 3x3 convs.
+ """
+
+ def __init__(self, **kwargs):
+ super(ResNetV1c, self).__init__(
+ deep_stem=True, avg_down=False, **kwargs)
+
+
@BACKBONES.register_module()
class ResNetV1d(ResNet):
- """ResNetV1d variant described in `Bag of Tricks.
+ """ResNetV1d backbone.
+ This variant is described in `Bag of Tricks.
`_.
Compared with default ResNet(ResNetV1b), ResNetV1d replaces the 7x7 conv in
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/resnet_cifar.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/resnet_cifar.py
similarity index 97%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/resnet_cifar.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/resnet_cifar.py
index d07599405675facccd823056326c76c0379c08d1..54b8a48bfb67c4ceeaa368b3a14f962df6a48736 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/resnet_cifar.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/resnet_cifar.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import torch.nn as nn
from mmcv.cnn import build_conv_layer, build_norm_layer
@@ -77,7 +78,4 @@ class ResNet_CIFAR(ResNet):
x = res_layer(x)
if i in self.out_indices:
outs.append(x)
- if len(outs) == 1:
- return outs[0]
- else:
- return tuple(outs)
+ return tuple(outs)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/resnext.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/resnext.py
similarity index 99%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/resnext.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/resnext.py
index 3549c95ce4b386e2284424b2df11eacab0563ba6..2370b7114ad8196feef6e3f957f2e946e484f292 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/resnext.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/resnext.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
from mmcv.cnn import build_conv_layer, build_norm_layer
from ..builder import BACKBONES
@@ -89,7 +90,7 @@ class Bottleneck(_Bottleneck):
class ResNeXt(ResNet):
"""ResNeXt backbone.
- Please refer to the `paper `_ for
+ Please refer to the `paper `__ for
details.
Args:
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/seresnet.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/seresnet.py
similarity index 98%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/seresnet.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/seresnet.py
index 862698f17f94cea383bf4ed0b19aa1e2a0729f40..0cfc5d1d2e8d6b6e558b6f29e0381b3c41d0aa2b 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/seresnet.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/seresnet.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import torch.utils.checkpoint as cp
from ..builder import BACKBONES
@@ -57,7 +58,7 @@ class SEBottleneck(Bottleneck):
class SEResNet(ResNet):
"""SEResNet backbone.
- Please refer to the `paper `_ for
+ Please refer to the `paper `__ for
details.
Args:
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/seresnext.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/seresnext.py
similarity index 99%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/seresnext.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/seresnext.py
index 8e66a84229a8f4a91ed86494feefe00c5e2f5c7e..aff5cb4934488fb4a34febcf21b288fe7cd7255d 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/seresnext.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/seresnext.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
from mmcv.cnn import build_conv_layer, build_norm_layer
from ..builder import BACKBONES
@@ -95,7 +96,7 @@ class SEBottleneck(_SEBottleneck):
class SEResNeXt(SEResNet):
"""SEResNeXt backbone.
- Please refer to the `paper `_ for
+ Please refer to the `paper `__ for
details.
Args:
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/shufflenet_v1.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/shufflenet_v1.py
similarity index 95%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/shufflenet_v1.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/shufflenet_v1.py
index d1b7e2d3f8a019e7968492cd13705706c5789162..0b6c70f08c7c7e6d76ae0ac464e93db667c00d1d 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/shufflenet_v1.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/shufflenet_v1.py
@@ -1,8 +1,10 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import torch
import torch.nn as nn
import torch.utils.checkpoint as cp
from mmcv.cnn import (ConvModule, build_activation_layer, constant_init,
normal_init)
+from mmcv.runner import BaseModule
from torch.nn.modules.batchnorm import _BatchNorm
from mmcls.models.utils import channel_shuffle, make_divisible
@@ -10,7 +12,7 @@ from ..builder import BACKBONES
from .base_backbone import BaseBackbone
-class ShuffleUnit(nn.Module):
+class ShuffleUnit(BaseModule):
"""ShuffleUnit block.
ShuffleNet unit with pointwise group convolution (GConv) and channel
@@ -22,7 +24,7 @@ class ShuffleUnit(nn.Module):
groups (int): The number of groups to be used in grouped 1x1
convolutions in each ShuffleUnit. Default: 3
first_block (bool): Whether it is the first ShuffleUnit of a
- sequential ShuffleUnits. Default: False, which means not using the
+ sequential ShuffleUnits. Default: True, which means not using the
grouped 1x1 convolution.
combine (str): The ways to combine the input and output
branches. Default: 'add'.
@@ -184,6 +186,7 @@ class ShuffleNetV1(BaseBackbone):
with_cp=False,
init_cfg=None):
super(ShuffleNetV1, self).__init__(init_cfg)
+ self.init_cfg = init_cfg
self.stage_blocks = [4, 8, 4]
self.groups = groups
@@ -250,6 +253,12 @@ class ShuffleNetV1(BaseBackbone):
def init_weights(self):
super(ShuffleNetV1, self).init_weights()
+
+ if (isinstance(self.init_cfg, dict)
+ and self.init_cfg['type'] == 'Pretrained'):
+ # Suppress default init if use pretrained model.
+ return
+
for name, m in self.named_modules():
if isinstance(m, nn.Conv2d):
if 'conv1' in name:
@@ -257,7 +266,7 @@ class ShuffleNetV1(BaseBackbone):
else:
normal_init(m, mean=0, std=1.0 / m.weight.shape[1])
elif isinstance(m, (_BatchNorm, nn.GroupNorm)):
- constant_init(m.weight, val=1, bias=0.0001)
+ constant_init(m, val=1, bias=0.0001)
if isinstance(m, _BatchNorm):
if m.running_mean is not None:
nn.init.constant_(m.running_mean, 0)
@@ -269,7 +278,7 @@ class ShuffleNetV1(BaseBackbone):
out_channels (int): out_channels of the block.
num_blocks (int): Number of blocks.
first_block (bool): Whether is the first ShuffleUnit of a
- sequential ShuffleUnits. Default: False, which means not using
+ sequential ShuffleUnits. Default: False, which means using
the grouped 1x1 convolution.
"""
layers = []
@@ -301,10 +310,7 @@ class ShuffleNetV1(BaseBackbone):
if i in self.out_indices:
outs.append(x)
- if len(outs) == 1:
- return outs[0]
- else:
- return tuple(outs)
+ return tuple(outs)
def train(self, mode=True):
super(ShuffleNetV1, self).train(mode)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/shufflenet_v2.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/shufflenet_v2.py
similarity index 92%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/shufflenet_v2.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/shufflenet_v2.py
index 9e2b5429fb0b33e506e1772b1331afe98ba6244b..bfe7ac8282aa978018db46732810352f52173c28 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/shufflenet_v2.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/shufflenet_v2.py
@@ -1,7 +1,9 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import torch
import torch.nn as nn
import torch.utils.checkpoint as cp
from mmcv.cnn import ConvModule, constant_init, normal_init
+from mmcv.runner import BaseModule
from torch.nn.modules.batchnorm import _BatchNorm
from mmcls.models.utils import channel_shuffle
@@ -9,7 +11,7 @@ from ..builder import BACKBONES
from .base_backbone import BaseBackbone
-class InvertedResidual(nn.Module):
+class InvertedResidual(BaseModule):
"""InvertedResidual block for ShuffleNetV2 backbone.
Args:
@@ -36,8 +38,9 @@ class InvertedResidual(nn.Module):
conv_cfg=None,
norm_cfg=dict(type='BN'),
act_cfg=dict(type='ReLU'),
- with_cp=False):
- super(InvertedResidual, self).__init__()
+ with_cp=False,
+ init_cfg=None):
+ super(InvertedResidual, self).__init__(init_cfg)
self.stride = stride
self.with_cp = with_cp
@@ -112,7 +115,14 @@ class InvertedResidual(nn.Module):
if self.stride > 1:
out = torch.cat((self.branch1(x), self.branch2(x)), dim=1)
else:
- x1, x2 = x.chunk(2, dim=1)
+ # Channel Split operation. using these lines of code to replace
+ # ``chunk(x, 2, dim=1)`` can make it easier to deploy a
+ # shufflenetv2 model by using mmdeploy.
+ channels = x.shape[1]
+ c = channels // 2 + channels % 2
+ x1 = x[:, :c, :, :]
+ x2 = x[:, c:, :, :]
+
out = torch.cat((x1, self.branch2(x2)), dim=1)
out = channel_shuffle(out, 2)
@@ -253,8 +263,14 @@ class ShuffleNetV2(BaseBackbone):
for param in m.parameters():
param.requires_grad = False
- def init_weighs(self):
+ def init_weights(self):
super(ShuffleNetV2, self).init_weights()
+
+ if (isinstance(self.init_cfg, dict)
+ and self.init_cfg['type'] == 'Pretrained'):
+ # Suppress default init if use pretrained model.
+ return
+
for name, m in self.named_modules():
if isinstance(m, nn.Conv2d):
if 'conv1' in name:
@@ -277,10 +293,7 @@ class ShuffleNetV2(BaseBackbone):
if i in self.out_indices:
outs.append(x)
- if len(outs) == 1:
- return outs[0]
- else:
- return tuple(outs)
+ return tuple(outs)
def train(self, mode=True):
super(ShuffleNetV2, self).train(mode)
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/swin_transformer.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/swin_transformer.py
new file mode 100644
index 0000000000000000000000000000000000000000..962d41d6e086ca00ebc6bf72aef705c4887c6d2f
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/swin_transformer.py
@@ -0,0 +1,548 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from copy import deepcopy
+from typing import Sequence
+
+import numpy as np
+import torch
+import torch.nn as nn
+import torch.utils.checkpoint as cp
+from mmcv.cnn import build_norm_layer
+from mmcv.cnn.bricks.transformer import FFN, PatchEmbed, PatchMerging
+from mmcv.cnn.utils.weight_init import trunc_normal_
+from mmcv.runner.base_module import BaseModule, ModuleList
+from mmcv.utils.parrots_wrapper import _BatchNorm
+
+from ..builder import BACKBONES
+from ..utils import (ShiftWindowMSA, resize_pos_embed,
+ resize_relative_position_bias_table, to_2tuple)
+from .base_backbone import BaseBackbone
+
+
+class SwinBlock(BaseModule):
+ """Swin Transformer block.
+
+ Args:
+ embed_dims (int): Number of input channels.
+ num_heads (int): Number of attention heads.
+ window_size (int): The height and width of the window. Defaults to 7.
+ shift (bool): Shift the attention window or not. Defaults to False.
+ ffn_ratio (float): The expansion ratio of feedforward network hidden
+ layer channels. Defaults to 4.
+ drop_path (float): The drop path rate after attention and ffn.
+ Defaults to 0.
+ pad_small_map (bool): If True, pad the small feature map to the window
+ size, which is common used in detection and segmentation. If False,
+ avoid shifting window and shrink the window size to the size of
+ feature map, which is common used in classification.
+ Defaults to False.
+ attn_cfgs (dict): The extra config of Shift Window-MSA.
+ Defaults to empty dict.
+ ffn_cfgs (dict): The extra config of FFN. Defaults to empty dict.
+ norm_cfg (dict): The config of norm layers.
+ Defaults to ``dict(type='LN')``.
+ with_cp (bool): Use checkpoint or not. Using checkpoint will save some
+ memory while slowing down the training speed. Defaults to False.
+ init_cfg (dict, optional): The extra config for initialization.
+ Defaults to None.
+ """
+
+ def __init__(self,
+ embed_dims,
+ num_heads,
+ window_size=7,
+ shift=False,
+ ffn_ratio=4.,
+ drop_path=0.,
+ pad_small_map=False,
+ attn_cfgs=dict(),
+ ffn_cfgs=dict(),
+ norm_cfg=dict(type='LN'),
+ with_cp=False,
+ init_cfg=None):
+
+ super(SwinBlock, self).__init__(init_cfg)
+ self.with_cp = with_cp
+
+ _attn_cfgs = {
+ 'embed_dims': embed_dims,
+ 'num_heads': num_heads,
+ 'shift_size': window_size // 2 if shift else 0,
+ 'window_size': window_size,
+ 'dropout_layer': dict(type='DropPath', drop_prob=drop_path),
+ 'pad_small_map': pad_small_map,
+ **attn_cfgs
+ }
+ self.norm1 = build_norm_layer(norm_cfg, embed_dims)[1]
+ self.attn = ShiftWindowMSA(**_attn_cfgs)
+
+ _ffn_cfgs = {
+ 'embed_dims': embed_dims,
+ 'feedforward_channels': int(embed_dims * ffn_ratio),
+ 'num_fcs': 2,
+ 'ffn_drop': 0,
+ 'dropout_layer': dict(type='DropPath', drop_prob=drop_path),
+ 'act_cfg': dict(type='GELU'),
+ **ffn_cfgs
+ }
+ self.norm2 = build_norm_layer(norm_cfg, embed_dims)[1]
+ self.ffn = FFN(**_ffn_cfgs)
+
+ def forward(self, x, hw_shape):
+
+ def _inner_forward(x):
+ identity = x
+ x = self.norm1(x)
+ x = self.attn(x, hw_shape)
+ x = x + identity
+
+ identity = x
+ x = self.norm2(x)
+ x = self.ffn(x, identity=identity)
+
+ return x
+
+ if self.with_cp and x.requires_grad:
+ x = cp.checkpoint(_inner_forward, x)
+ else:
+ x = _inner_forward(x)
+
+ return x
+
+
+class SwinBlockSequence(BaseModule):
+ """Module with successive Swin Transformer blocks and downsample layer.
+
+ Args:
+ embed_dims (int): Number of input channels.
+ depth (int): Number of successive swin transformer blocks.
+ num_heads (int): Number of attention heads.
+ window_size (int): The height and width of the window. Defaults to 7.
+ downsample (bool): Downsample the output of blocks by patch merging.
+ Defaults to False.
+ downsample_cfg (dict): The extra config of the patch merging layer.
+ Defaults to empty dict.
+ drop_paths (Sequence[float] | float): The drop path rate in each block.
+ Defaults to 0.
+ block_cfgs (Sequence[dict] | dict): The extra config of each block.
+ Defaults to empty dicts.
+ with_cp (bool): Use checkpoint or not. Using checkpoint will save some
+ memory while slowing down the training speed. Defaults to False.
+ pad_small_map (bool): If True, pad the small feature map to the window
+ size, which is common used in detection and segmentation. If False,
+ avoid shifting window and shrink the window size to the size of
+ feature map, which is common used in classification.
+ Defaults to False.
+ init_cfg (dict, optional): The extra config for initialization.
+ Defaults to None.
+ """
+
+ def __init__(self,
+ embed_dims,
+ depth,
+ num_heads,
+ window_size=7,
+ downsample=False,
+ downsample_cfg=dict(),
+ drop_paths=0.,
+ block_cfgs=dict(),
+ with_cp=False,
+ pad_small_map=False,
+ init_cfg=None):
+ super().__init__(init_cfg)
+
+ if not isinstance(drop_paths, Sequence):
+ drop_paths = [drop_paths] * depth
+
+ if not isinstance(block_cfgs, Sequence):
+ block_cfgs = [deepcopy(block_cfgs) for _ in range(depth)]
+
+ self.embed_dims = embed_dims
+ self.blocks = ModuleList()
+ for i in range(depth):
+ _block_cfg = {
+ 'embed_dims': embed_dims,
+ 'num_heads': num_heads,
+ 'window_size': window_size,
+ 'shift': False if i % 2 == 0 else True,
+ 'drop_path': drop_paths[i],
+ 'with_cp': with_cp,
+ 'pad_small_map': pad_small_map,
+ **block_cfgs[i]
+ }
+ block = SwinBlock(**_block_cfg)
+ self.blocks.append(block)
+
+ if downsample:
+ _downsample_cfg = {
+ 'in_channels': embed_dims,
+ 'out_channels': 2 * embed_dims,
+ 'norm_cfg': dict(type='LN'),
+ **downsample_cfg
+ }
+ self.downsample = PatchMerging(**_downsample_cfg)
+ else:
+ self.downsample = None
+
+ def forward(self, x, in_shape, do_downsample=True):
+ for block in self.blocks:
+ x = block(x, in_shape)
+
+ if self.downsample is not None and do_downsample:
+ x, out_shape = self.downsample(x, in_shape)
+ else:
+ out_shape = in_shape
+ return x, out_shape
+
+ @property
+ def out_channels(self):
+ if self.downsample:
+ return self.downsample.out_channels
+ else:
+ return self.embed_dims
+
+
+@BACKBONES.register_module()
+class SwinTransformer(BaseBackbone):
+ """Swin Transformer.
+
+ A PyTorch implement of : `Swin Transformer:
+ Hierarchical Vision Transformer using Shifted Windows
+ `_
+
+ Inspiration from
+ https://github.com/microsoft/Swin-Transformer
+
+ Args:
+ arch (str | dict): Swin Transformer architecture. If use string, choose
+ from 'tiny', 'small', 'base' and 'large'. If use dict, it should
+ have below keys:
+
+ - **embed_dims** (int): The dimensions of embedding.
+ - **depths** (List[int]): The number of blocks in each stage.
+ - **num_heads** (List[int]): The number of heads in attention
+ modules of each stage.
+
+ Defaults to 'tiny'.
+ img_size (int | tuple): The expected input image shape. Because we
+ support dynamic input shape, just set the argument to the most
+ common input image shape. Defaults to 224.
+ patch_size (int | tuple): The patch size in patch embedding.
+ Defaults to 4.
+ in_channels (int): The num of input channels. Defaults to 3.
+ window_size (int): The height and width of the window. Defaults to 7.
+ drop_rate (float): Dropout rate after embedding. Defaults to 0.
+ drop_path_rate (float): Stochastic depth rate. Defaults to 0.1.
+ out_after_downsample (bool): Whether to output the feature map of a
+ stage after the following downsample layer. Defaults to False.
+ use_abs_pos_embed (bool): If True, add absolute position embedding to
+ the patch embedding. Defaults to False.
+ interpolate_mode (str): Select the interpolate mode for absolute
+ position embeding vector resize. Defaults to "bicubic".
+ with_cp (bool): Use checkpoint or not. Using checkpoint will save some
+ memory while slowing down the training speed. Defaults to False.
+ frozen_stages (int): Stages to be frozen (stop grad and set eval mode).
+ -1 means not freezing any parameters. Defaults to -1.
+ norm_eval (bool): Whether to set norm layers to eval mode, namely,
+ freeze running stats (mean and var). Note: Effect on Batch Norm
+ and its variants only. Defaults to False.
+ pad_small_map (bool): If True, pad the small feature map to the window
+ size, which is common used in detection and segmentation. If False,
+ avoid shifting window and shrink the window size to the size of
+ feature map, which is common used in classification.
+ Defaults to False.
+ norm_cfg (dict): Config dict for normalization layer for all output
+ features. Defaults to ``dict(type='LN')``
+ stage_cfgs (Sequence[dict] | dict): Extra config dict for each
+ stage. Defaults to an empty dict.
+ patch_cfg (dict): Extra config dict for patch embedding.
+ Defaults to an empty dict.
+ init_cfg (dict, optional): The Config for initialization.
+ Defaults to None.
+
+ Examples:
+ >>> from mmcls.models import SwinTransformer
+ >>> import torch
+ >>> extra_config = dict(
+ >>> arch='tiny',
+ >>> stage_cfgs=dict(downsample_cfg={'kernel_size': 3,
+ >>> 'expansion_ratio': 3}))
+ >>> self = SwinTransformer(**extra_config)
+ >>> inputs = torch.rand(1, 3, 224, 224)
+ >>> output = self.forward(inputs)
+ >>> print(output.shape)
+ (1, 2592, 4)
+ """
+ arch_zoo = {
+ **dict.fromkeys(['t', 'tiny'],
+ {'embed_dims': 96,
+ 'depths': [2, 2, 6, 2],
+ 'num_heads': [3, 6, 12, 24]}),
+ **dict.fromkeys(['s', 'small'],
+ {'embed_dims': 96,
+ 'depths': [2, 2, 18, 2],
+ 'num_heads': [3, 6, 12, 24]}),
+ **dict.fromkeys(['b', 'base'],
+ {'embed_dims': 128,
+ 'depths': [2, 2, 18, 2],
+ 'num_heads': [4, 8, 16, 32]}),
+ **dict.fromkeys(['l', 'large'],
+ {'embed_dims': 192,
+ 'depths': [2, 2, 18, 2],
+ 'num_heads': [6, 12, 24, 48]}),
+ } # yapf: disable
+
+ _version = 3
+ num_extra_tokens = 0
+
+ def __init__(self,
+ arch='tiny',
+ img_size=224,
+ patch_size=4,
+ in_channels=3,
+ window_size=7,
+ drop_rate=0.,
+ drop_path_rate=0.1,
+ out_indices=(3, ),
+ out_after_downsample=False,
+ use_abs_pos_embed=False,
+ interpolate_mode='bicubic',
+ with_cp=False,
+ frozen_stages=-1,
+ norm_eval=False,
+ pad_small_map=False,
+ norm_cfg=dict(type='LN'),
+ stage_cfgs=dict(),
+ patch_cfg=dict(),
+ init_cfg=None):
+ super(SwinTransformer, self).__init__(init_cfg=init_cfg)
+
+ if isinstance(arch, str):
+ arch = arch.lower()
+ assert arch in set(self.arch_zoo), \
+ f'Arch {arch} is not in default archs {set(self.arch_zoo)}'
+ self.arch_settings = self.arch_zoo[arch]
+ else:
+ essential_keys = {'embed_dims', 'depths', 'num_heads'}
+ assert isinstance(arch, dict) and set(arch) == essential_keys, \
+ f'Custom arch needs a dict with keys {essential_keys}'
+ self.arch_settings = arch
+
+ self.embed_dims = self.arch_settings['embed_dims']
+ self.depths = self.arch_settings['depths']
+ self.num_heads = self.arch_settings['num_heads']
+ self.num_layers = len(self.depths)
+ self.out_indices = out_indices
+ self.out_after_downsample = out_after_downsample
+ self.use_abs_pos_embed = use_abs_pos_embed
+ self.interpolate_mode = interpolate_mode
+ self.frozen_stages = frozen_stages
+
+ _patch_cfg = dict(
+ in_channels=in_channels,
+ input_size=img_size,
+ embed_dims=self.embed_dims,
+ conv_type='Conv2d',
+ kernel_size=patch_size,
+ stride=patch_size,
+ norm_cfg=dict(type='LN'),
+ )
+ _patch_cfg.update(patch_cfg)
+ self.patch_embed = PatchEmbed(**_patch_cfg)
+ self.patch_resolution = self.patch_embed.init_out_size
+
+ if self.use_abs_pos_embed:
+ num_patches = self.patch_resolution[0] * self.patch_resolution[1]
+ self.absolute_pos_embed = nn.Parameter(
+ torch.zeros(1, num_patches, self.embed_dims))
+ self._register_load_state_dict_pre_hook(
+ self._prepare_abs_pos_embed)
+
+ self._register_load_state_dict_pre_hook(
+ self._prepare_relative_position_bias_table)
+
+ self.drop_after_pos = nn.Dropout(p=drop_rate)
+ self.norm_eval = norm_eval
+
+ # stochastic depth
+ total_depth = sum(self.depths)
+ dpr = [
+ x.item() for x in torch.linspace(0, drop_path_rate, total_depth)
+ ] # stochastic depth decay rule
+
+ self.stages = ModuleList()
+ embed_dims = [self.embed_dims]
+ for i, (depth,
+ num_heads) in enumerate(zip(self.depths, self.num_heads)):
+ if isinstance(stage_cfgs, Sequence):
+ stage_cfg = stage_cfgs[i]
+ else:
+ stage_cfg = deepcopy(stage_cfgs)
+ downsample = True if i < self.num_layers - 1 else False
+ _stage_cfg = {
+ 'embed_dims': embed_dims[-1],
+ 'depth': depth,
+ 'num_heads': num_heads,
+ 'window_size': window_size,
+ 'downsample': downsample,
+ 'drop_paths': dpr[:depth],
+ 'with_cp': with_cp,
+ 'pad_small_map': pad_small_map,
+ **stage_cfg
+ }
+
+ stage = SwinBlockSequence(**_stage_cfg)
+ self.stages.append(stage)
+
+ dpr = dpr[depth:]
+ embed_dims.append(stage.out_channels)
+
+ if self.out_after_downsample:
+ self.num_features = embed_dims[1:]
+ else:
+ self.num_features = embed_dims[:-1]
+
+ for i in out_indices:
+ if norm_cfg is not None:
+ norm_layer = build_norm_layer(norm_cfg,
+ self.num_features[i])[1]
+ else:
+ norm_layer = nn.Identity()
+
+ self.add_module(f'norm{i}', norm_layer)
+
+ def init_weights(self):
+ super(SwinTransformer, self).init_weights()
+
+ if (isinstance(self.init_cfg, dict)
+ and self.init_cfg['type'] == 'Pretrained'):
+ # Suppress default init if use pretrained model.
+ return
+
+ if self.use_abs_pos_embed:
+ trunc_normal_(self.absolute_pos_embed, std=0.02)
+
+ def forward(self, x):
+ x, hw_shape = self.patch_embed(x)
+ if self.use_abs_pos_embed:
+ x = x + resize_pos_embed(
+ self.absolute_pos_embed, self.patch_resolution, hw_shape,
+ self.interpolate_mode, self.num_extra_tokens)
+ x = self.drop_after_pos(x)
+
+ outs = []
+ for i, stage in enumerate(self.stages):
+ x, hw_shape = stage(
+ x, hw_shape, do_downsample=self.out_after_downsample)
+ if i in self.out_indices:
+ norm_layer = getattr(self, f'norm{i}')
+ out = norm_layer(x)
+ out = out.view(-1, *hw_shape,
+ self.num_features[i]).permute(0, 3, 1,
+ 2).contiguous()
+ outs.append(out)
+ if stage.downsample is not None and not self.out_after_downsample:
+ x, hw_shape = stage.downsample(x, hw_shape)
+
+ return tuple(outs)
+
+ def _load_from_state_dict(self, state_dict, prefix, local_metadata, *args,
+ **kwargs):
+ """load checkpoints."""
+ # Names of some parameters in has been changed.
+ version = local_metadata.get('version', None)
+ if (version is None
+ or version < 2) and self.__class__ is SwinTransformer:
+ final_stage_num = len(self.stages) - 1
+ state_dict_keys = list(state_dict.keys())
+ for k in state_dict_keys:
+ if k.startswith('norm.') or k.startswith('backbone.norm.'):
+ convert_key = k.replace('norm.', f'norm{final_stage_num}.')
+ state_dict[convert_key] = state_dict[k]
+ del state_dict[k]
+ if (version is None
+ or version < 3) and self.__class__ is SwinTransformer:
+ state_dict_keys = list(state_dict.keys())
+ for k in state_dict_keys:
+ if 'attn_mask' in k:
+ del state_dict[k]
+
+ super()._load_from_state_dict(state_dict, prefix, local_metadata,
+ *args, **kwargs)
+
+ def _freeze_stages(self):
+ if self.frozen_stages >= 0:
+ self.patch_embed.eval()
+ for param in self.patch_embed.parameters():
+ param.requires_grad = False
+
+ for i in range(0, self.frozen_stages + 1):
+ m = self.stages[i]
+ m.eval()
+ for param in m.parameters():
+ param.requires_grad = False
+ for i in self.out_indices:
+ if i <= self.frozen_stages:
+ for param in getattr(self, f'norm{i}').parameters():
+ param.requires_grad = False
+
+ def train(self, mode=True):
+ super(SwinTransformer, self).train(mode)
+ self._freeze_stages()
+ if mode and self.norm_eval:
+ for m in self.modules():
+ # trick: eval have effect on BatchNorm only
+ if isinstance(m, _BatchNorm):
+ m.eval()
+
+ def _prepare_abs_pos_embed(self, state_dict, prefix, *args, **kwargs):
+ name = prefix + 'absolute_pos_embed'
+ if name not in state_dict.keys():
+ return
+
+ ckpt_pos_embed_shape = state_dict[name].shape
+ if self.absolute_pos_embed.shape != ckpt_pos_embed_shape:
+ from mmcls.utils import get_root_logger
+ logger = get_root_logger()
+ logger.info(
+ 'Resize the absolute_pos_embed shape from '
+ f'{ckpt_pos_embed_shape} to {self.absolute_pos_embed.shape}.')
+
+ ckpt_pos_embed_shape = to_2tuple(
+ int(np.sqrt(ckpt_pos_embed_shape[1] - self.num_extra_tokens)))
+ pos_embed_shape = self.patch_embed.init_out_size
+
+ state_dict[name] = resize_pos_embed(state_dict[name],
+ ckpt_pos_embed_shape,
+ pos_embed_shape,
+ self.interpolate_mode,
+ self.num_extra_tokens)
+
+ def _prepare_relative_position_bias_table(self, state_dict, prefix, *args,
+ **kwargs):
+ state_dict_model = self.state_dict()
+ all_keys = list(state_dict_model.keys())
+ for key in all_keys:
+ if 'relative_position_bias_table' in key:
+ ckpt_key = prefix + key
+ if ckpt_key not in state_dict:
+ continue
+ relative_position_bias_table_pretrained = state_dict[ckpt_key]
+ relative_position_bias_table_current = state_dict_model[key]
+ L1, nH1 = relative_position_bias_table_pretrained.size()
+ L2, nH2 = relative_position_bias_table_current.size()
+ if L1 != L2:
+ src_size = int(L1**0.5)
+ dst_size = int(L2**0.5)
+ new_rel_pos_bias = resize_relative_position_bias_table(
+ src_size, dst_size,
+ relative_position_bias_table_pretrained, nH1)
+ from mmcls.utils import get_root_logger
+ logger = get_root_logger()
+ logger.info('Resize the relative_position_bias_table from '
+ f'{state_dict[ckpt_key].shape} to '
+ f'{new_rel_pos_bias.shape}')
+ state_dict[ckpt_key] = new_rel_pos_bias
+
+ # The index buffer need to be re-generated.
+ index_buffer = ckpt_key.replace('bias_table', 'index')
+ del state_dict[index_buffer]
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/swin_transformer_v2.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/swin_transformer_v2.py
new file mode 100644
index 0000000000000000000000000000000000000000..c26b4e6c227c377e0b28a746e95e5a2d80d5cea9
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/swin_transformer_v2.py
@@ -0,0 +1,560 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from copy import deepcopy
+from typing import Sequence
+
+import numpy as np
+import torch
+import torch.nn as nn
+import torch.utils.checkpoint as cp
+from mmcv.cnn import build_norm_layer
+from mmcv.cnn.bricks.transformer import FFN, PatchEmbed
+from mmcv.cnn.utils.weight_init import trunc_normal_
+from mmcv.runner.base_module import BaseModule, ModuleList
+from mmcv.utils.parrots_wrapper import _BatchNorm
+
+from ..builder import BACKBONES
+from ..utils import (PatchMerging, ShiftWindowMSA, WindowMSAV2,
+ resize_pos_embed, to_2tuple)
+from .base_backbone import BaseBackbone
+
+
+class SwinBlockV2(BaseModule):
+ """Swin Transformer V2 block. Use post normalization.
+
+ Args:
+ embed_dims (int): Number of input channels.
+ num_heads (int): Number of attention heads.
+ window_size (int): The height and width of the window. Defaults to 7.
+ shift (bool): Shift the attention window or not. Defaults to False.
+ extra_norm (bool): Whether add extra norm at the end of main branch.
+ ffn_ratio (float): The expansion ratio of feedforward network hidden
+ layer channels. Defaults to 4.
+ drop_path (float): The drop path rate after attention and ffn.
+ Defaults to 0.
+ pad_small_map (bool): If True, pad the small feature map to the window
+ size, which is common used in detection and segmentation. If False,
+ avoid shifting window and shrink the window size to the size of
+ feature map, which is common used in classification.
+ Defaults to False.
+ attn_cfgs (dict): The extra config of Shift Window-MSA.
+ Defaults to empty dict.
+ ffn_cfgs (dict): The extra config of FFN. Defaults to empty dict.
+ norm_cfg (dict): The config of norm layers.
+ Defaults to ``dict(type='LN')``.
+ with_cp (bool): Use checkpoint or not. Using checkpoint will save some
+ memory while slowing down the training speed. Defaults to False.
+ pretrained_window_size (int): Window size in pretrained.
+ init_cfg (dict, optional): The extra config for initialization.
+ Defaults to None.
+ """
+
+ def __init__(self,
+ embed_dims,
+ num_heads,
+ window_size=8,
+ shift=False,
+ extra_norm=False,
+ ffn_ratio=4.,
+ drop_path=0.,
+ pad_small_map=False,
+ attn_cfgs=dict(),
+ ffn_cfgs=dict(),
+ norm_cfg=dict(type='LN'),
+ with_cp=False,
+ pretrained_window_size=0,
+ init_cfg=None):
+
+ super(SwinBlockV2, self).__init__(init_cfg)
+ self.with_cp = with_cp
+ self.extra_norm = extra_norm
+
+ _attn_cfgs = {
+ 'embed_dims': embed_dims,
+ 'num_heads': num_heads,
+ 'shift_size': window_size // 2 if shift else 0,
+ 'window_size': window_size,
+ 'dropout_layer': dict(type='DropPath', drop_prob=drop_path),
+ 'pad_small_map': pad_small_map,
+ **attn_cfgs
+ }
+ # use V2 attention implementation
+ _attn_cfgs.update(
+ window_msa=WindowMSAV2,
+ msa_cfg=dict(
+ pretrained_window_size=to_2tuple(pretrained_window_size)))
+ self.attn = ShiftWindowMSA(**_attn_cfgs)
+ self.norm1 = build_norm_layer(norm_cfg, embed_dims)[1]
+
+ _ffn_cfgs = {
+ 'embed_dims': embed_dims,
+ 'feedforward_channels': int(embed_dims * ffn_ratio),
+ 'num_fcs': 2,
+ 'ffn_drop': 0,
+ 'dropout_layer': dict(type='DropPath', drop_prob=drop_path),
+ 'act_cfg': dict(type='GELU'),
+ 'add_identity': False,
+ **ffn_cfgs
+ }
+ self.ffn = FFN(**_ffn_cfgs)
+ self.norm2 = build_norm_layer(norm_cfg, embed_dims)[1]
+
+ # add extra norm for every n blocks in huge and giant model
+ if self.extra_norm:
+ self.norm3 = build_norm_layer(norm_cfg, embed_dims)[1]
+
+ def forward(self, x, hw_shape):
+
+ def _inner_forward(x):
+ # Use post normalization
+ identity = x
+ x = self.attn(x, hw_shape)
+ x = self.norm1(x)
+ x = x + identity
+
+ identity = x
+ x = self.ffn(x)
+ x = self.norm2(x)
+ x = x + identity
+
+ if self.extra_norm:
+ x = self.norm3(x)
+
+ return x
+
+ if self.with_cp and x.requires_grad:
+ x = cp.checkpoint(_inner_forward, x)
+ else:
+ x = _inner_forward(x)
+
+ return x
+
+
+class SwinBlockV2Sequence(BaseModule):
+ """Module with successive Swin Transformer blocks and downsample layer.
+
+ Args:
+ embed_dims (int): Number of input channels.
+ depth (int): Number of successive swin transformer blocks.
+ num_heads (int): Number of attention heads.
+ window_size (int): The height and width of the window. Defaults to 7.
+ downsample (bool): Downsample the output of blocks by patch merging.
+ Defaults to False.
+ downsample_cfg (dict): The extra config of the patch merging layer.
+ Defaults to empty dict.
+ drop_paths (Sequence[float] | float): The drop path rate in each block.
+ Defaults to 0.
+ block_cfgs (Sequence[dict] | dict): The extra config of each block.
+ Defaults to empty dicts.
+ with_cp (bool): Use checkpoint or not. Using checkpoint will save some
+ memory while slowing down the training speed. Defaults to False.
+ pad_small_map (bool): If True, pad the small feature map to the window
+ size, which is common used in detection and segmentation. If False,
+ avoid shifting window and shrink the window size to the size of
+ feature map, which is common used in classification.
+ Defaults to False.
+ extra_norm_every_n_blocks (int): Add extra norm at the end of main
+ branch every n blocks. Defaults to 0, which means no needs for
+ extra norm layer.
+ pretrained_window_size (int): Window size in pretrained.
+ init_cfg (dict, optional): The extra config for initialization.
+ Defaults to None.
+ """
+
+ def __init__(self,
+ embed_dims,
+ depth,
+ num_heads,
+ window_size=8,
+ downsample=False,
+ downsample_cfg=dict(),
+ drop_paths=0.,
+ block_cfgs=dict(),
+ with_cp=False,
+ pad_small_map=False,
+ extra_norm_every_n_blocks=0,
+ pretrained_window_size=0,
+ init_cfg=None):
+ super().__init__(init_cfg)
+
+ if not isinstance(drop_paths, Sequence):
+ drop_paths = [drop_paths] * depth
+
+ if not isinstance(block_cfgs, Sequence):
+ block_cfgs = [deepcopy(block_cfgs) for _ in range(depth)]
+
+ if downsample:
+ self.out_channels = 2 * embed_dims
+ _downsample_cfg = {
+ 'in_channels': embed_dims,
+ 'out_channels': self.out_channels,
+ 'norm_cfg': dict(type='LN'),
+ **downsample_cfg
+ }
+ self.downsample = PatchMerging(**_downsample_cfg)
+ else:
+ self.out_channels = embed_dims
+ self.downsample = None
+
+ self.blocks = ModuleList()
+ for i in range(depth):
+ extra_norm = True if extra_norm_every_n_blocks and \
+ (i + 1) % extra_norm_every_n_blocks == 0 else False
+ _block_cfg = {
+ 'embed_dims': self.out_channels,
+ 'num_heads': num_heads,
+ 'window_size': window_size,
+ 'shift': False if i % 2 == 0 else True,
+ 'extra_norm': extra_norm,
+ 'drop_path': drop_paths[i],
+ 'with_cp': with_cp,
+ 'pad_small_map': pad_small_map,
+ 'pretrained_window_size': pretrained_window_size,
+ **block_cfgs[i]
+ }
+ block = SwinBlockV2(**_block_cfg)
+ self.blocks.append(block)
+
+ def forward(self, x, in_shape):
+ if self.downsample:
+ x, out_shape = self.downsample(x, in_shape)
+ else:
+ out_shape = in_shape
+
+ for block in self.blocks:
+ x = block(x, out_shape)
+
+ return x, out_shape
+
+
+@BACKBONES.register_module()
+class SwinTransformerV2(BaseBackbone):
+ """Swin Transformer V2.
+
+ A PyTorch implement of : `Swin Transformer V2:
+ Scaling Up Capacity and Resolution
+ `_
+
+ Inspiration from
+ https://github.com/microsoft/Swin-Transformer
+
+ Args:
+ arch (str | dict): Swin Transformer architecture. If use string, choose
+ from 'tiny', 'small', 'base' and 'large'. If use dict, it should
+ have below keys:
+
+ - **embed_dims** (int): The dimensions of embedding.
+ - **depths** (List[int]): The number of blocks in each stage.
+ - **num_heads** (List[int]): The number of heads in attention
+ modules of each stage.
+ - **extra_norm_every_n_blocks** (int): Add extra norm at the end
+ of main branch every n blocks.
+
+ Defaults to 'tiny'.
+ img_size (int | tuple): The expected input image shape. Because we
+ support dynamic input shape, just set the argument to the most
+ common input image shape. Defaults to 224.
+ patch_size (int | tuple): The patch size in patch embedding.
+ Defaults to 4.
+ in_channels (int): The num of input channels. Defaults to 3.
+ window_size (int | Sequence): The height and width of the window.
+ Defaults to 7.
+ drop_rate (float): Dropout rate after embedding. Defaults to 0.
+ drop_path_rate (float): Stochastic depth rate. Defaults to 0.1.
+ use_abs_pos_embed (bool): If True, add absolute position embedding to
+ the patch embedding. Defaults to False.
+ interpolate_mode (str): Select the interpolate mode for absolute
+ position embeding vector resize. Defaults to "bicubic".
+ with_cp (bool): Use checkpoint or not. Using checkpoint will save some
+ memory while slowing down the training speed. Defaults to False.
+ frozen_stages (int): Stages to be frozen (stop grad and set eval mode).
+ -1 means not freezing any parameters. Defaults to -1.
+ norm_eval (bool): Whether to set norm layers to eval mode, namely,
+ freeze running stats (mean and var). Note: Effect on Batch Norm
+ and its variants only. Defaults to False.
+ pad_small_map (bool): If True, pad the small feature map to the window
+ size, which is common used in detection and segmentation. If False,
+ avoid shifting window and shrink the window size to the size of
+ feature map, which is common used in classification.
+ Defaults to False.
+ norm_cfg (dict): Config dict for normalization layer for all output
+ features. Defaults to ``dict(type='LN')``
+ stage_cfgs (Sequence[dict] | dict): Extra config dict for each
+ stage. Defaults to an empty dict.
+ patch_cfg (dict): Extra config dict for patch embedding.
+ Defaults to an empty dict.
+ pretrained_window_sizes (tuple(int)): Pretrained window sizes of
+ each layer.
+ init_cfg (dict, optional): The Config for initialization.
+ Defaults to None.
+
+ Examples:
+ >>> from mmcls.models import SwinTransformerV2
+ >>> import torch
+ >>> extra_config = dict(
+ >>> arch='tiny',
+ >>> stage_cfgs=dict(downsample_cfg={'kernel_size': 3,
+ >>> 'padding': 'same'}))
+ >>> self = SwinTransformerV2(**extra_config)
+ >>> inputs = torch.rand(1, 3, 224, 224)
+ >>> output = self.forward(inputs)
+ >>> print(output.shape)
+ (1, 2592, 4)
+ """
+ arch_zoo = {
+ **dict.fromkeys(['t', 'tiny'],
+ {'embed_dims': 96,
+ 'depths': [2, 2, 6, 2],
+ 'num_heads': [3, 6, 12, 24],
+ 'extra_norm_every_n_blocks': 0}),
+ **dict.fromkeys(['s', 'small'],
+ {'embed_dims': 96,
+ 'depths': [2, 2, 18, 2],
+ 'num_heads': [3, 6, 12, 24],
+ 'extra_norm_every_n_blocks': 0}),
+ **dict.fromkeys(['b', 'base'],
+ {'embed_dims': 128,
+ 'depths': [2, 2, 18, 2],
+ 'num_heads': [4, 8, 16, 32],
+ 'extra_norm_every_n_blocks': 0}),
+ **dict.fromkeys(['l', 'large'],
+ {'embed_dims': 192,
+ 'depths': [2, 2, 18, 2],
+ 'num_heads': [6, 12, 24, 48],
+ 'extra_norm_every_n_blocks': 0}),
+ # head count not certain for huge, and is employed for another
+ # parallel study about self-supervised learning.
+ **dict.fromkeys(['h', 'huge'],
+ {'embed_dims': 352,
+ 'depths': [2, 2, 18, 2],
+ 'num_heads': [8, 16, 32, 64],
+ 'extra_norm_every_n_blocks': 6}),
+ **dict.fromkeys(['g', 'giant'],
+ {'embed_dims': 512,
+ 'depths': [2, 2, 42, 4],
+ 'num_heads': [16, 32, 64, 128],
+ 'extra_norm_every_n_blocks': 6}),
+ } # yapf: disable
+
+ _version = 1
+ num_extra_tokens = 0
+
+ def __init__(self,
+ arch='tiny',
+ img_size=256,
+ patch_size=4,
+ in_channels=3,
+ window_size=8,
+ drop_rate=0.,
+ drop_path_rate=0.1,
+ out_indices=(3, ),
+ use_abs_pos_embed=False,
+ interpolate_mode='bicubic',
+ with_cp=False,
+ frozen_stages=-1,
+ norm_eval=False,
+ pad_small_map=False,
+ norm_cfg=dict(type='LN'),
+ stage_cfgs=dict(downsample_cfg=dict(is_post_norm=True)),
+ patch_cfg=dict(),
+ pretrained_window_sizes=[0, 0, 0, 0],
+ init_cfg=None):
+ super(SwinTransformerV2, self).__init__(init_cfg=init_cfg)
+
+ if isinstance(arch, str):
+ arch = arch.lower()
+ assert arch in set(self.arch_zoo), \
+ f'Arch {arch} is not in default archs {set(self.arch_zoo)}'
+ self.arch_settings = self.arch_zoo[arch]
+ else:
+ essential_keys = {
+ 'embed_dims', 'depths', 'num_heads',
+ 'extra_norm_every_n_blocks'
+ }
+ assert isinstance(arch, dict) and set(arch) == essential_keys, \
+ f'Custom arch needs a dict with keys {essential_keys}'
+ self.arch_settings = arch
+
+ self.embed_dims = self.arch_settings['embed_dims']
+ self.depths = self.arch_settings['depths']
+ self.num_heads = self.arch_settings['num_heads']
+ self.extra_norm_every_n_blocks = self.arch_settings[
+ 'extra_norm_every_n_blocks']
+ self.num_layers = len(self.depths)
+ self.out_indices = out_indices
+ self.use_abs_pos_embed = use_abs_pos_embed
+ self.interpolate_mode = interpolate_mode
+ self.frozen_stages = frozen_stages
+
+ if isinstance(window_size, int):
+ self.window_sizes = [window_size for _ in range(self.num_layers)]
+ elif isinstance(window_size, Sequence):
+ assert len(window_size) == self.num_layers, \
+ f'Length of window_sizes {len(window_size)} is not equal to '\
+ f'length of stages {self.num_layers}.'
+ self.window_sizes = window_size
+ else:
+ raise TypeError('window_size should be a Sequence or int.')
+
+ _patch_cfg = dict(
+ in_channels=in_channels,
+ input_size=img_size,
+ embed_dims=self.embed_dims,
+ conv_type='Conv2d',
+ kernel_size=patch_size,
+ stride=patch_size,
+ norm_cfg=dict(type='LN'),
+ )
+ _patch_cfg.update(patch_cfg)
+ self.patch_embed = PatchEmbed(**_patch_cfg)
+ self.patch_resolution = self.patch_embed.init_out_size
+
+ if self.use_abs_pos_embed:
+ num_patches = self.patch_resolution[0] * self.patch_resolution[1]
+ self.absolute_pos_embed = nn.Parameter(
+ torch.zeros(1, num_patches, self.embed_dims))
+ self._register_load_state_dict_pre_hook(
+ self._prepare_abs_pos_embed)
+
+ self._register_load_state_dict_pre_hook(self._delete_reinit_params)
+
+ self.drop_after_pos = nn.Dropout(p=drop_rate)
+ self.norm_eval = norm_eval
+
+ # stochastic depth
+ total_depth = sum(self.depths)
+ dpr = [
+ x.item() for x in torch.linspace(0, drop_path_rate, total_depth)
+ ] # stochastic depth decay rule
+
+ self.stages = ModuleList()
+ embed_dims = [self.embed_dims]
+ for i, (depth,
+ num_heads) in enumerate(zip(self.depths, self.num_heads)):
+ if isinstance(stage_cfgs, Sequence):
+ stage_cfg = stage_cfgs[i]
+ else:
+ stage_cfg = deepcopy(stage_cfgs)
+ downsample = True if i > 0 else False
+ _stage_cfg = {
+ 'embed_dims': embed_dims[-1],
+ 'depth': depth,
+ 'num_heads': num_heads,
+ 'window_size': self.window_sizes[i],
+ 'downsample': downsample,
+ 'drop_paths': dpr[:depth],
+ 'with_cp': with_cp,
+ 'pad_small_map': pad_small_map,
+ 'extra_norm_every_n_blocks': self.extra_norm_every_n_blocks,
+ 'pretrained_window_size': pretrained_window_sizes[i],
+ **stage_cfg
+ }
+
+ stage = SwinBlockV2Sequence(**_stage_cfg)
+ self.stages.append(stage)
+
+ dpr = dpr[depth:]
+ embed_dims.append(stage.out_channels)
+
+ for i in out_indices:
+ if norm_cfg is not None:
+ norm_layer = build_norm_layer(norm_cfg, embed_dims[i + 1])[1]
+ else:
+ norm_layer = nn.Identity()
+
+ self.add_module(f'norm{i}', norm_layer)
+
+ def init_weights(self):
+ super(SwinTransformerV2, self).init_weights()
+
+ if (isinstance(self.init_cfg, dict)
+ and self.init_cfg['type'] == 'Pretrained'):
+ # Suppress default init if use pretrained model.
+ return
+
+ if self.use_abs_pos_embed:
+ trunc_normal_(self.absolute_pos_embed, std=0.02)
+
+ def forward(self, x):
+ x, hw_shape = self.patch_embed(x)
+
+ if self.use_abs_pos_embed:
+ x = x + resize_pos_embed(
+ self.absolute_pos_embed, self.patch_resolution, hw_shape,
+ self.interpolate_mode, self.num_extra_tokens)
+ x = self.drop_after_pos(x)
+
+ outs = []
+ for i, stage in enumerate(self.stages):
+ x, hw_shape = stage(x, hw_shape)
+ if i in self.out_indices:
+ norm_layer = getattr(self, f'norm{i}')
+ out = norm_layer(x)
+ out = out.view(-1, *hw_shape,
+ stage.out_channels).permute(0, 3, 1,
+ 2).contiguous()
+ outs.append(out)
+
+ return tuple(outs)
+
+ def _freeze_stages(self):
+ if self.frozen_stages >= 0:
+ self.patch_embed.eval()
+ for param in self.patch_embed.parameters():
+ param.requires_grad = False
+
+ for i in range(0, self.frozen_stages + 1):
+ m = self.stages[i]
+ m.eval()
+ for param in m.parameters():
+ param.requires_grad = False
+ for i in self.out_indices:
+ if i <= self.frozen_stages:
+ for param in getattr(self, f'norm{i}').parameters():
+ param.requires_grad = False
+
+ def train(self, mode=True):
+ super(SwinTransformerV2, self).train(mode)
+ self._freeze_stages()
+ if mode and self.norm_eval:
+ for m in self.modules():
+ # trick: eval have effect on BatchNorm only
+ if isinstance(m, _BatchNorm):
+ m.eval()
+
+ def _prepare_abs_pos_embed(self, state_dict, prefix, *args, **kwargs):
+ name = prefix + 'absolute_pos_embed'
+ if name not in state_dict.keys():
+ return
+
+ ckpt_pos_embed_shape = state_dict[name].shape
+ if self.absolute_pos_embed.shape != ckpt_pos_embed_shape:
+ from mmcls.utils import get_root_logger
+ logger = get_root_logger()
+ logger.info(
+ 'Resize the absolute_pos_embed shape from '
+ f'{ckpt_pos_embed_shape} to {self.absolute_pos_embed.shape}.')
+
+ ckpt_pos_embed_shape = to_2tuple(
+ int(np.sqrt(ckpt_pos_embed_shape[1] - self.num_extra_tokens)))
+ pos_embed_shape = self.patch_embed.init_out_size
+
+ state_dict[name] = resize_pos_embed(state_dict[name],
+ ckpt_pos_embed_shape,
+ pos_embed_shape,
+ self.interpolate_mode,
+ self.num_extra_tokens)
+
+ def _delete_reinit_params(self, state_dict, prefix, *args, **kwargs):
+ # delete relative_position_index since we always re-init it
+ relative_position_index_keys = [
+ k for k in state_dict.keys() if 'relative_position_index' in k
+ ]
+ for k in relative_position_index_keys:
+ del state_dict[k]
+
+ # delete relative_coords_table since we always re-init it
+ relative_position_index_keys = [
+ k for k in state_dict.keys() if 'relative_coords_table' in k
+ ]
+ for k in relative_position_index_keys:
+ del state_dict[k]
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/t2t_vit.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/t2t_vit.py
new file mode 100644
index 0000000000000000000000000000000000000000..2edb991e61a547d9108084681056e686dcc1bf9c
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/t2t_vit.py
@@ -0,0 +1,440 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from copy import deepcopy
+from typing import Sequence
+
+import numpy as np
+import torch
+import torch.nn as nn
+from mmcv.cnn import build_norm_layer
+from mmcv.cnn.bricks.transformer import FFN
+from mmcv.cnn.utils.weight_init import trunc_normal_
+from mmcv.runner.base_module import BaseModule, ModuleList
+
+from ..builder import BACKBONES
+from ..utils import MultiheadAttention, resize_pos_embed, to_2tuple
+from .base_backbone import BaseBackbone
+
+
+class T2TTransformerLayer(BaseModule):
+ """Transformer Layer for T2T_ViT.
+
+ Comparing with :obj:`TransformerEncoderLayer` in ViT, it supports
+ different ``input_dims`` and ``embed_dims``.
+
+ Args:
+ embed_dims (int): The feature dimension.
+ num_heads (int): Parallel attention heads.
+ feedforward_channels (int): The hidden dimension for FFNs
+ input_dims (int, optional): The input token dimension.
+ Defaults to None.
+ drop_rate (float): Probability of an element to be zeroed
+ after the feed forward layer. Defaults to 0.
+ attn_drop_rate (float): The drop out rate for attention output weights.
+ Defaults to 0.
+ drop_path_rate (float): Stochastic depth rate. Defaults to 0.
+ num_fcs (int): The number of fully-connected layers for FFNs.
+ Defaults to 2.
+ qkv_bias (bool): enable bias for qkv if True. Defaults to True.
+ qk_scale (float, optional): Override default qk scale of
+ ``(input_dims // num_heads) ** -0.5`` if set. Defaults to None.
+ act_cfg (dict): The activation config for FFNs.
+ Defaluts to ``dict(type='GELU')``.
+ norm_cfg (dict): Config dict for normalization layer.
+ Defaults to ``dict(type='LN')``.
+ init_cfg (dict, optional): Initialization config dict.
+ Defaults to None.
+
+ Notes:
+ In general, ``qk_scale`` should be ``head_dims ** -0.5``, i.e.
+ ``(embed_dims // num_heads) ** -0.5``. However, in the official
+ code, it uses ``(input_dims // num_heads) ** -0.5``, so here we
+ keep the same with the official implementation.
+ """
+
+ def __init__(self,
+ embed_dims,
+ num_heads,
+ feedforward_channels,
+ input_dims=None,
+ drop_rate=0.,
+ attn_drop_rate=0.,
+ drop_path_rate=0.,
+ num_fcs=2,
+ qkv_bias=False,
+ qk_scale=None,
+ act_cfg=dict(type='GELU'),
+ norm_cfg=dict(type='LN'),
+ init_cfg=None):
+ super(T2TTransformerLayer, self).__init__(init_cfg=init_cfg)
+
+ self.v_shortcut = True if input_dims is not None else False
+ input_dims = input_dims or embed_dims
+
+ self.norm1_name, norm1 = build_norm_layer(
+ norm_cfg, input_dims, postfix=1)
+ self.add_module(self.norm1_name, norm1)
+
+ self.attn = MultiheadAttention(
+ input_dims=input_dims,
+ embed_dims=embed_dims,
+ num_heads=num_heads,
+ attn_drop=attn_drop_rate,
+ proj_drop=drop_rate,
+ dropout_layer=dict(type='DropPath', drop_prob=drop_path_rate),
+ qkv_bias=qkv_bias,
+ qk_scale=qk_scale or (input_dims // num_heads)**-0.5,
+ v_shortcut=self.v_shortcut)
+
+ self.norm2_name, norm2 = build_norm_layer(
+ norm_cfg, embed_dims, postfix=2)
+ self.add_module(self.norm2_name, norm2)
+
+ self.ffn = FFN(
+ embed_dims=embed_dims,
+ feedforward_channels=feedforward_channels,
+ num_fcs=num_fcs,
+ ffn_drop=drop_rate,
+ dropout_layer=dict(type='DropPath', drop_prob=drop_path_rate),
+ act_cfg=act_cfg)
+
+ @property
+ def norm1(self):
+ return getattr(self, self.norm1_name)
+
+ @property
+ def norm2(self):
+ return getattr(self, self.norm2_name)
+
+ def forward(self, x):
+ if self.v_shortcut:
+ x = self.attn(self.norm1(x))
+ else:
+ x = x + self.attn(self.norm1(x))
+ x = self.ffn(self.norm2(x), identity=x)
+ return x
+
+
+class T2TModule(BaseModule):
+ """Tokens-to-Token module.
+
+ "Tokens-to-Token module" (T2T Module) can model the local structure
+ information of images and reduce the length of tokens progressively.
+
+ Args:
+ img_size (int): Input image size
+ in_channels (int): Number of input channels
+ embed_dims (int): Embedding dimension
+ token_dims (int): Tokens dimension in T2TModuleAttention.
+ use_performer (bool): If True, use Performer version self-attention to
+ adopt regular self-attention. Defaults to False.
+ init_cfg (dict, optional): The extra config for initialization.
+ Default: None.
+
+ Notes:
+ Usually, ``token_dim`` is set as a small value (32 or 64) to reduce
+ MACs
+ """
+
+ def __init__(
+ self,
+ img_size=224,
+ in_channels=3,
+ embed_dims=384,
+ token_dims=64,
+ use_performer=False,
+ init_cfg=None,
+ ):
+ super(T2TModule, self).__init__(init_cfg)
+
+ self.embed_dims = embed_dims
+
+ self.soft_split0 = nn.Unfold(
+ kernel_size=(7, 7), stride=(4, 4), padding=(2, 2))
+ self.soft_split1 = nn.Unfold(
+ kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
+ self.soft_split2 = nn.Unfold(
+ kernel_size=(3, 3), stride=(2, 2), padding=(1, 1))
+
+ if not use_performer:
+ self.attention1 = T2TTransformerLayer(
+ input_dims=in_channels * 7 * 7,
+ embed_dims=token_dims,
+ num_heads=1,
+ feedforward_channels=token_dims)
+
+ self.attention2 = T2TTransformerLayer(
+ input_dims=token_dims * 3 * 3,
+ embed_dims=token_dims,
+ num_heads=1,
+ feedforward_channels=token_dims)
+
+ self.project = nn.Linear(token_dims * 3 * 3, embed_dims)
+ else:
+ raise NotImplementedError("Performer hasn't been implemented.")
+
+ # there are 3 soft split, stride are 4,2,2 separately
+ out_side = img_size // (4 * 2 * 2)
+ self.init_out_size = [out_side, out_side]
+ self.num_patches = out_side**2
+
+ @staticmethod
+ def _get_unfold_size(unfold: nn.Unfold, input_size):
+ h, w = input_size
+ kernel_size = to_2tuple(unfold.kernel_size)
+ stride = to_2tuple(unfold.stride)
+ padding = to_2tuple(unfold.padding)
+ dilation = to_2tuple(unfold.dilation)
+
+ h_out = (h + 2 * padding[0] - dilation[0] *
+ (kernel_size[0] - 1) - 1) // stride[0] + 1
+ w_out = (w + 2 * padding[1] - dilation[1] *
+ (kernel_size[1] - 1) - 1) // stride[1] + 1
+ return (h_out, w_out)
+
+ def forward(self, x):
+ # step0: soft split
+ hw_shape = self._get_unfold_size(self.soft_split0, x.shape[2:])
+ x = self.soft_split0(x).transpose(1, 2)
+
+ for step in [1, 2]:
+ # re-structurization/reconstruction
+ attn = getattr(self, f'attention{step}')
+ x = attn(x).transpose(1, 2)
+ B, C, _ = x.shape
+ x = x.reshape(B, C, hw_shape[0], hw_shape[1])
+
+ # soft split
+ soft_split = getattr(self, f'soft_split{step}')
+ hw_shape = self._get_unfold_size(soft_split, hw_shape)
+ x = soft_split(x).transpose(1, 2)
+
+ # final tokens
+ x = self.project(x)
+ return x, hw_shape
+
+
+def get_sinusoid_encoding(n_position, embed_dims):
+ """Generate sinusoid encoding table.
+
+ Sinusoid encoding is a kind of relative position encoding method came from
+ `Attention Is All You Need`_.
+ Args:
+ n_position (int): The length of the input token.
+ embed_dims (int): The position embedding dimension.
+ Returns:
+ :obj:`torch.FloatTensor`: The sinusoid encoding table.
+ """
+
+ vec = torch.arange(embed_dims, dtype=torch.float64)
+ vec = (vec - vec % 2) / embed_dims
+ vec = torch.pow(10000, -vec).view(1, -1)
+
+ sinusoid_table = torch.arange(n_position).view(-1, 1) * vec
+ sinusoid_table[:, 0::2].sin_() # dim 2i
+ sinusoid_table[:, 1::2].cos_() # dim 2i+1
+
+ sinusoid_table = sinusoid_table.to(torch.float32)
+
+ return sinusoid_table.unsqueeze(0)
+
+
+@BACKBONES.register_module()
+class T2T_ViT(BaseBackbone):
+ """Tokens-to-Token Vision Transformer (T2T-ViT)
+
+ A PyTorch implementation of `Tokens-to-Token ViT: Training Vision
+ Transformers from Scratch on ImageNet `_
+
+ Args:
+ img_size (int | tuple): The expected input image shape. Because we
+ support dynamic input shape, just set the argument to the most
+ common input image shape. Defaults to 224.
+ in_channels (int): Number of input channels.
+ embed_dims (int): Embedding dimension.
+ num_layers (int): Num of transformer layers in encoder.
+ Defaults to 14.
+ out_indices (Sequence | int): Output from which stages.
+ Defaults to -1, means the last stage.
+ drop_rate (float): Dropout rate after position embedding.
+ Defaults to 0.
+ drop_path_rate (float): stochastic depth rate. Defaults to 0.
+ norm_cfg (dict): Config dict for normalization layer. Defaults to
+ ``dict(type='LN')``.
+ final_norm (bool): Whether to add a additional layer to normalize
+ final feature map. Defaults to True.
+ with_cls_token (bool): Whether concatenating class token into image
+ tokens as transformer input. Defaults to True.
+ output_cls_token (bool): Whether output the cls_token. If set True,
+ ``with_cls_token`` must be True. Defaults to True.
+ interpolate_mode (str): Select the interpolate mode for position
+ embeding vector resize. Defaults to "bicubic".
+ t2t_cfg (dict): Extra config of Tokens-to-Token module.
+ Defaults to an empty dict.
+ layer_cfgs (Sequence | dict): Configs of each transformer layer in
+ encoder. Defaults to an empty dict.
+ init_cfg (dict, optional): The Config for initialization.
+ Defaults to None.
+ """
+ num_extra_tokens = 1 # cls_token
+
+ def __init__(self,
+ img_size=224,
+ in_channels=3,
+ embed_dims=384,
+ num_layers=14,
+ out_indices=-1,
+ drop_rate=0.,
+ drop_path_rate=0.,
+ norm_cfg=dict(type='LN'),
+ final_norm=True,
+ with_cls_token=True,
+ output_cls_token=True,
+ interpolate_mode='bicubic',
+ t2t_cfg=dict(),
+ layer_cfgs=dict(),
+ init_cfg=None):
+ super(T2T_ViT, self).__init__(init_cfg)
+
+ # Token-to-Token Module
+ self.tokens_to_token = T2TModule(
+ img_size=img_size,
+ in_channels=in_channels,
+ embed_dims=embed_dims,
+ **t2t_cfg)
+ self.patch_resolution = self.tokens_to_token.init_out_size
+ num_patches = self.patch_resolution[0] * self.patch_resolution[1]
+
+ # Set cls token
+ if output_cls_token:
+ assert with_cls_token is True, f'with_cls_token must be True if' \
+ f'set output_cls_token to True, but got {with_cls_token}'
+ self.with_cls_token = with_cls_token
+ self.output_cls_token = output_cls_token
+ self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dims))
+
+ # Set position embedding
+ self.interpolate_mode = interpolate_mode
+ sinusoid_table = get_sinusoid_encoding(
+ num_patches + self.num_extra_tokens, embed_dims)
+ self.register_buffer('pos_embed', sinusoid_table)
+ self._register_load_state_dict_pre_hook(self._prepare_pos_embed)
+
+ self.drop_after_pos = nn.Dropout(p=drop_rate)
+
+ if isinstance(out_indices, int):
+ out_indices = [out_indices]
+ assert isinstance(out_indices, Sequence), \
+ f'"out_indices" must be a sequence or int, ' \
+ f'get {type(out_indices)} instead.'
+ for i, index in enumerate(out_indices):
+ if index < 0:
+ out_indices[i] = num_layers + index
+ assert 0 <= out_indices[i] <= num_layers, \
+ f'Invalid out_indices {index}'
+ self.out_indices = out_indices
+
+ # stochastic depth decay rule
+ dpr = [x for x in np.linspace(0, drop_path_rate, num_layers)]
+
+ self.encoder = ModuleList()
+ for i in range(num_layers):
+ if isinstance(layer_cfgs, Sequence):
+ layer_cfg = layer_cfgs[i]
+ else:
+ layer_cfg = deepcopy(layer_cfgs)
+ layer_cfg = {
+ 'embed_dims': embed_dims,
+ 'num_heads': 6,
+ 'feedforward_channels': 3 * embed_dims,
+ 'drop_path_rate': dpr[i],
+ 'qkv_bias': False,
+ 'norm_cfg': norm_cfg,
+ **layer_cfg
+ }
+
+ layer = T2TTransformerLayer(**layer_cfg)
+ self.encoder.append(layer)
+
+ self.final_norm = final_norm
+ if final_norm:
+ self.norm = build_norm_layer(norm_cfg, embed_dims)[1]
+ else:
+ self.norm = nn.Identity()
+
+ def init_weights(self):
+ super().init_weights()
+
+ if (isinstance(self.init_cfg, dict)
+ and self.init_cfg['type'] == 'Pretrained'):
+ # Suppress custom init if use pretrained model.
+ return
+
+ trunc_normal_(self.cls_token, std=.02)
+
+ def _prepare_pos_embed(self, state_dict, prefix, *args, **kwargs):
+ name = prefix + 'pos_embed'
+ if name not in state_dict.keys():
+ return
+
+ ckpt_pos_embed_shape = state_dict[name].shape
+ if self.pos_embed.shape != ckpt_pos_embed_shape:
+ from mmcls.utils import get_root_logger
+ logger = get_root_logger()
+ logger.info(
+ f'Resize the pos_embed shape from {ckpt_pos_embed_shape} '
+ f'to {self.pos_embed.shape}.')
+
+ ckpt_pos_embed_shape = to_2tuple(
+ int(np.sqrt(ckpt_pos_embed_shape[1] - self.num_extra_tokens)))
+ pos_embed_shape = self.tokens_to_token.init_out_size
+
+ state_dict[name] = resize_pos_embed(state_dict[name],
+ ckpt_pos_embed_shape,
+ pos_embed_shape,
+ self.interpolate_mode,
+ self.num_extra_tokens)
+
+ def forward(self, x):
+ B = x.shape[0]
+ x, patch_resolution = self.tokens_to_token(x)
+
+ # stole cls_tokens impl from Phil Wang, thanks
+ cls_tokens = self.cls_token.expand(B, -1, -1)
+ x = torch.cat((cls_tokens, x), dim=1)
+
+ x = x + resize_pos_embed(
+ self.pos_embed,
+ self.patch_resolution,
+ patch_resolution,
+ mode=self.interpolate_mode,
+ num_extra_tokens=self.num_extra_tokens)
+ x = self.drop_after_pos(x)
+
+ if not self.with_cls_token:
+ # Remove class token for transformer encoder input
+ x = x[:, 1:]
+
+ outs = []
+ for i, layer in enumerate(self.encoder):
+ x = layer(x)
+
+ if i == len(self.encoder) - 1 and self.final_norm:
+ x = self.norm(x)
+
+ if i in self.out_indices:
+ B, _, C = x.shape
+ if self.with_cls_token:
+ patch_token = x[:, 1:].reshape(B, *patch_resolution, C)
+ patch_token = patch_token.permute(0, 3, 1, 2)
+ cls_token = x[:, 0]
+ else:
+ patch_token = x.reshape(B, *patch_resolution, C)
+ patch_token = patch_token.permute(0, 3, 1, 2)
+ cls_token = None
+ if self.output_cls_token:
+ out = [patch_token, cls_token]
+ else:
+ out = patch_token
+ outs.append(out)
+
+ return tuple(outs)
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/timm_backbone.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/timm_backbone.py
new file mode 100644
index 0000000000000000000000000000000000000000..1506619a93aac0ea9413c8cdb1e6aef5e37362fd
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/timm_backbone.py
@@ -0,0 +1,112 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+try:
+ import timm
+except ImportError:
+ timm = None
+
+import warnings
+
+from mmcv.cnn.bricks.registry import NORM_LAYERS
+
+from ...utils import get_root_logger
+from ..builder import BACKBONES
+from .base_backbone import BaseBackbone
+
+
+def print_timm_feature_info(feature_info):
+ """Print feature_info of timm backbone to help development and debug.
+
+ Args:
+ feature_info (list[dict] | timm.models.features.FeatureInfo | None):
+ feature_info of timm backbone.
+ """
+ logger = get_root_logger()
+ if feature_info is None:
+ logger.warning('This backbone does not have feature_info')
+ elif isinstance(feature_info, list):
+ for feat_idx, each_info in enumerate(feature_info):
+ logger.info(f'backbone feature_info[{feat_idx}]: {each_info}')
+ else:
+ try:
+ logger.info(f'backbone out_indices: {feature_info.out_indices}')
+ logger.info(f'backbone out_channels: {feature_info.channels()}')
+ logger.info(f'backbone out_strides: {feature_info.reduction()}')
+ except AttributeError:
+ logger.warning('Unexpected format of backbone feature_info')
+
+
+@BACKBONES.register_module()
+class TIMMBackbone(BaseBackbone):
+ """Wrapper to use backbones from timm library.
+
+ More details can be found in
+ `timm `_.
+ See especially the document for `feature extraction
+ `_.
+
+ Args:
+ model_name (str): Name of timm model to instantiate.
+ features_only (bool): Whether to extract feature pyramid (multi-scale
+ feature maps from the deepest layer at each stride). For Vision
+ Transformer models that do not support this argument,
+ set this False. Defaults to False.
+ pretrained (bool): Whether to load pretrained weights.
+ Defaults to False.
+ checkpoint_path (str): Path of checkpoint to load at the last of
+ ``timm.create_model``. Defaults to empty string, which means
+ not loading.
+ in_channels (int): Number of input image channels. Defaults to 3.
+ init_cfg (dict or list[dict], optional): Initialization config dict of
+ OpenMMLab projects. Defaults to None.
+ **kwargs: Other timm & model specific arguments.
+ """
+
+ def __init__(self,
+ model_name,
+ features_only=False,
+ pretrained=False,
+ checkpoint_path='',
+ in_channels=3,
+ init_cfg=None,
+ **kwargs):
+ if timm is None:
+ raise RuntimeError(
+ 'Failed to import timm. Please run "pip install timm". '
+ '"pip install dataclasses" may also be needed for Python 3.6.')
+ if not isinstance(pretrained, bool):
+ raise TypeError('pretrained must be bool, not str for model path')
+ if features_only and checkpoint_path:
+ warnings.warn(
+ 'Using both features_only and checkpoint_path will cause error'
+ ' in timm. See '
+ 'https://github.com/rwightman/pytorch-image-models/issues/488')
+
+ super(TIMMBackbone, self).__init__(init_cfg)
+ if 'norm_layer' in kwargs:
+ kwargs['norm_layer'] = NORM_LAYERS.get(kwargs['norm_layer'])
+ self.timm_model = timm.create_model(
+ model_name=model_name,
+ features_only=features_only,
+ pretrained=pretrained,
+ in_chans=in_channels,
+ checkpoint_path=checkpoint_path,
+ **kwargs)
+
+ # reset classifier
+ if hasattr(self.timm_model, 'reset_classifier'):
+ self.timm_model.reset_classifier(0, '')
+
+ # Hack to use pretrained weights from timm
+ if pretrained or checkpoint_path:
+ self._is_init = True
+
+ feature_info = getattr(self.timm_model, 'feature_info', None)
+ print_timm_feature_info(feature_info)
+
+ def forward(self, x):
+ features = self.timm_model(x)
+ if isinstance(features, (list, tuple)):
+ features = tuple(features)
+ else:
+ features = (features, )
+ return features
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/tnt.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/tnt.py
new file mode 100644
index 0000000000000000000000000000000000000000..b03120b91d6bf2aaf951dc746c2bad5742ec30c9
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/tnt.py
@@ -0,0 +1,368 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import math
+
+import torch
+import torch.nn as nn
+from mmcv.cnn import build_norm_layer
+from mmcv.cnn.bricks.transformer import FFN, MultiheadAttention
+from mmcv.cnn.utils.weight_init import trunc_normal_
+from mmcv.runner.base_module import BaseModule, ModuleList
+
+from ..builder import BACKBONES
+from ..utils import to_2tuple
+from .base_backbone import BaseBackbone
+
+
+class TransformerBlock(BaseModule):
+ """Implement a transformer block in TnTLayer.
+
+ Args:
+ embed_dims (int): The feature dimension
+ num_heads (int): Parallel attention heads
+ ffn_ratio (int): A ratio to calculate the hidden_dims in ffn layer.
+ Default: 4
+ drop_rate (float): Probability of an element to be zeroed
+ after the feed forward layer. Default 0.
+ attn_drop_rate (float): The drop out rate for attention layer.
+ Default 0.
+ drop_path_rate (float): stochastic depth rate. Default 0.
+ num_fcs (int): The number of fully-connected layers for FFNs. Default 2
+ qkv_bias (bool): Enable bias for qkv if True. Default False
+ act_cfg (dict): The activation config for FFNs. Defaults to GELU.
+ norm_cfg (dict): Config dict for normalization layer. Default
+ layer normalization
+ batch_first (bool): Key, Query and Value are shape of
+ (batch, n, embed_dim) or (n, batch, embed_dim).
+ (batch, n, embed_dim) is common case in CV. Default to False
+ init_cfg (dict, optional): Initialization config dict. Default to None
+ """
+
+ def __init__(self,
+ embed_dims,
+ num_heads,
+ ffn_ratio=4,
+ drop_rate=0.,
+ attn_drop_rate=0.,
+ drop_path_rate=0.,
+ num_fcs=2,
+ qkv_bias=False,
+ act_cfg=dict(type='GELU'),
+ norm_cfg=dict(type='LN'),
+ batch_first=True,
+ init_cfg=None):
+ super(TransformerBlock, self).__init__(init_cfg=init_cfg)
+
+ self.norm_attn = build_norm_layer(norm_cfg, embed_dims)[1]
+ self.attn = MultiheadAttention(
+ embed_dims=embed_dims,
+ num_heads=num_heads,
+ attn_drop=attn_drop_rate,
+ proj_drop=drop_rate,
+ dropout_layer=dict(type='DropPath', drop_prob=drop_path_rate),
+ batch_first=batch_first)
+
+ self.norm_ffn = build_norm_layer(norm_cfg, embed_dims)[1]
+ self.ffn = FFN(
+ embed_dims=embed_dims,
+ feedforward_channels=embed_dims * ffn_ratio,
+ num_fcs=num_fcs,
+ ffn_drop=drop_rate,
+ dropout_layer=dict(type='DropPath', drop_prob=drop_path_rate),
+ act_cfg=act_cfg)
+
+ if not qkv_bias:
+ self.attn.attn.in_proj_bias = None
+
+ def forward(self, x):
+ x = self.attn(self.norm_attn(x), identity=x)
+ x = self.ffn(self.norm_ffn(x), identity=x)
+ return x
+
+
+class TnTLayer(BaseModule):
+ """Implement one encoder layer in Transformer in Transformer.
+
+ Args:
+ num_pixel (int): The pixel number in target patch transformed with
+ a linear projection in inner transformer
+ embed_dims_inner (int): Feature dimension in inner transformer block
+ embed_dims_outer (int): Feature dimension in outer transformer block
+ num_heads_inner (int): Parallel attention heads in inner transformer.
+ num_heads_outer (int): Parallel attention heads in outer transformer.
+ inner_block_cfg (dict): Extra config of inner transformer block.
+ Defaults to empty dict.
+ outer_block_cfg (dict): Extra config of outer transformer block.
+ Defaults to empty dict.
+ norm_cfg (dict): Config dict for normalization layer. Default
+ layer normalization
+ init_cfg (dict, optional): Initialization config dict. Default to None
+ """
+
+ def __init__(self,
+ num_pixel,
+ embed_dims_inner,
+ embed_dims_outer,
+ num_heads_inner,
+ num_heads_outer,
+ inner_block_cfg=dict(),
+ outer_block_cfg=dict(),
+ norm_cfg=dict(type='LN'),
+ init_cfg=None):
+ super(TnTLayer, self).__init__(init_cfg=init_cfg)
+
+ self.inner_block = TransformerBlock(
+ embed_dims=embed_dims_inner,
+ num_heads=num_heads_inner,
+ **inner_block_cfg)
+
+ self.norm_proj = build_norm_layer(norm_cfg, embed_dims_inner)[1]
+ self.projection = nn.Linear(
+ embed_dims_inner * num_pixel, embed_dims_outer, bias=True)
+
+ self.outer_block = TransformerBlock(
+ embed_dims=embed_dims_outer,
+ num_heads=num_heads_outer,
+ **outer_block_cfg)
+
+ def forward(self, pixel_embed, patch_embed):
+ pixel_embed = self.inner_block(pixel_embed)
+
+ B, N, C = patch_embed.size()
+ patch_embed[:, 1:] = patch_embed[:, 1:] + self.projection(
+ self.norm_proj(pixel_embed).reshape(B, N - 1, -1))
+ patch_embed = self.outer_block(patch_embed)
+
+ return pixel_embed, patch_embed
+
+
+class PixelEmbed(BaseModule):
+ """Image to Pixel Embedding.
+
+ Args:
+ img_size (int | tuple): The size of input image
+ patch_size (int): The size of one patch
+ in_channels (int): The num of input channels
+ embed_dims_inner (int): The num of channels of the target patch
+ transformed with a linear projection in inner transformer
+ stride (int): The stride of the conv2d layer. We use a conv2d layer
+ and a unfold layer to implement image to pixel embedding.
+ init_cfg (dict, optional): Initialization config dict
+ """
+
+ def __init__(self,
+ img_size=224,
+ patch_size=16,
+ in_channels=3,
+ embed_dims_inner=48,
+ stride=4,
+ init_cfg=None):
+ super(PixelEmbed, self).__init__(init_cfg=init_cfg)
+ img_size = to_2tuple(img_size)
+ patch_size = to_2tuple(patch_size)
+ # patches_resolution property necessary for resizing
+ # positional embedding
+ patches_resolution = [
+ img_size[0] // patch_size[0], img_size[1] // patch_size[1]
+ ]
+ num_patches = patches_resolution[0] * patches_resolution[1]
+
+ self.img_size = img_size
+ self.num_patches = num_patches
+ self.embed_dims_inner = embed_dims_inner
+
+ new_patch_size = [math.ceil(ps / stride) for ps in patch_size]
+ self.new_patch_size = new_patch_size
+
+ self.proj = nn.Conv2d(
+ in_channels,
+ self.embed_dims_inner,
+ kernel_size=7,
+ padding=3,
+ stride=stride)
+ self.unfold = nn.Unfold(
+ kernel_size=new_patch_size, stride=new_patch_size)
+
+ def forward(self, x, pixel_pos):
+ B, C, H, W = x.shape
+ assert H == self.img_size[0] and W == self.img_size[1], \
+ f"Input image size ({H}*{W}) doesn't match model " \
+ f'({self.img_size[0]}*{self.img_size[1]}).'
+ x = self.proj(x)
+ x = self.unfold(x)
+ x = x.transpose(1,
+ 2).reshape(B * self.num_patches, self.embed_dims_inner,
+ self.new_patch_size[0],
+ self.new_patch_size[1])
+ x = x + pixel_pos
+ x = x.reshape(B * self.num_patches, self.embed_dims_inner,
+ -1).transpose(1, 2)
+ return x
+
+
+@BACKBONES.register_module()
+class TNT(BaseBackbone):
+ """Transformer in Transformer.
+
+ A PyTorch implement of: `Transformer in Transformer
+ `_
+
+ Inspiration from
+ https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/tnt.py
+
+ Args:
+ arch (str | dict): Vision Transformer architecture
+ Default: 'b'
+ img_size (int | tuple): Input image size. Default to 224
+ patch_size (int | tuple): The patch size. Deault to 16
+ in_channels (int): Number of input channels. Default to 3
+ ffn_ratio (int): A ratio to calculate the hidden_dims in ffn layer.
+ Default: 4
+ qkv_bias (bool): Enable bias for qkv if True. Default False
+ drop_rate (float): Probability of an element to be zeroed
+ after the feed forward layer. Default 0.
+ attn_drop_rate (float): The drop out rate for attention layer.
+ Default 0.
+ drop_path_rate (float): stochastic depth rate. Default 0.
+ act_cfg (dict): The activation config for FFNs. Defaults to GELU.
+ norm_cfg (dict): Config dict for normalization layer. Default
+ layer normalization
+ first_stride (int): The stride of the conv2d layer. We use a conv2d
+ layer and a unfold layer to implement image to pixel embedding.
+ num_fcs (int): The number of fully-connected layers for FFNs. Default 2
+ init_cfg (dict, optional): Initialization config dict
+ """
+ arch_zoo = {
+ **dict.fromkeys(
+ ['s', 'small'], {
+ 'embed_dims_outer': 384,
+ 'embed_dims_inner': 24,
+ 'num_layers': 12,
+ 'num_heads_outer': 6,
+ 'num_heads_inner': 4
+ }),
+ **dict.fromkeys(
+ ['b', 'base'], {
+ 'embed_dims_outer': 640,
+ 'embed_dims_inner': 40,
+ 'num_layers': 12,
+ 'num_heads_outer': 10,
+ 'num_heads_inner': 4
+ })
+ }
+
+ def __init__(self,
+ arch='b',
+ img_size=224,
+ patch_size=16,
+ in_channels=3,
+ ffn_ratio=4,
+ qkv_bias=False,
+ drop_rate=0.,
+ attn_drop_rate=0.,
+ drop_path_rate=0.,
+ act_cfg=dict(type='GELU'),
+ norm_cfg=dict(type='LN'),
+ first_stride=4,
+ num_fcs=2,
+ init_cfg=[
+ dict(type='TruncNormal', layer='Linear', std=.02),
+ dict(type='Constant', layer='LayerNorm', val=1., bias=0.)
+ ]):
+ super(TNT, self).__init__(init_cfg=init_cfg)
+
+ if isinstance(arch, str):
+ arch = arch.lower()
+ assert arch in set(self.arch_zoo), \
+ f'Arch {arch} is not in default archs {set(self.arch_zoo)}'
+ self.arch_settings = self.arch_zoo[arch]
+ else:
+ essential_keys = {
+ 'embed_dims_outer', 'embed_dims_inner', 'num_layers',
+ 'num_heads_inner', 'num_heads_outer'
+ }
+ assert isinstance(arch, dict) and set(arch) == essential_keys, \
+ f'Custom arch needs a dict with keys {essential_keys}'
+ self.arch_settings = arch
+
+ self.embed_dims_inner = self.arch_settings['embed_dims_inner']
+ self.embed_dims_outer = self.arch_settings['embed_dims_outer']
+ # embed_dims for consistency with other models
+ self.embed_dims = self.embed_dims_outer
+ self.num_layers = self.arch_settings['num_layers']
+ self.num_heads_inner = self.arch_settings['num_heads_inner']
+ self.num_heads_outer = self.arch_settings['num_heads_outer']
+
+ self.pixel_embed = PixelEmbed(
+ img_size=img_size,
+ patch_size=patch_size,
+ in_channels=in_channels,
+ embed_dims_inner=self.embed_dims_inner,
+ stride=first_stride)
+ num_patches = self.pixel_embed.num_patches
+ self.num_patches = num_patches
+ new_patch_size = self.pixel_embed.new_patch_size
+ num_pixel = new_patch_size[0] * new_patch_size[1]
+
+ self.norm1_proj = build_norm_layer(norm_cfg, num_pixel *
+ self.embed_dims_inner)[1]
+ self.projection = nn.Linear(num_pixel * self.embed_dims_inner,
+ self.embed_dims_outer)
+ self.norm2_proj = build_norm_layer(norm_cfg, self.embed_dims_outer)[1]
+
+ self.cls_token = nn.Parameter(torch.zeros(1, 1, self.embed_dims_outer))
+ self.patch_pos = nn.Parameter(
+ torch.zeros(1, num_patches + 1, self.embed_dims_outer))
+ self.pixel_pos = nn.Parameter(
+ torch.zeros(1, self.embed_dims_inner, new_patch_size[0],
+ new_patch_size[1]))
+ self.drop_after_pos = nn.Dropout(p=drop_rate)
+
+ dpr = [
+ x.item()
+ for x in torch.linspace(0, drop_path_rate, self.num_layers)
+ ] # stochastic depth decay rule
+ self.layers = ModuleList()
+ for i in range(self.num_layers):
+ block_cfg = dict(
+ ffn_ratio=ffn_ratio,
+ drop_rate=drop_rate,
+ attn_drop_rate=attn_drop_rate,
+ drop_path_rate=dpr[i],
+ num_fcs=num_fcs,
+ qkv_bias=qkv_bias,
+ norm_cfg=norm_cfg,
+ batch_first=True)
+ self.layers.append(
+ TnTLayer(
+ num_pixel=num_pixel,
+ embed_dims_inner=self.embed_dims_inner,
+ embed_dims_outer=self.embed_dims_outer,
+ num_heads_inner=self.num_heads_inner,
+ num_heads_outer=self.num_heads_outer,
+ inner_block_cfg=block_cfg,
+ outer_block_cfg=block_cfg,
+ norm_cfg=norm_cfg))
+
+ self.norm = build_norm_layer(norm_cfg, self.embed_dims_outer)[1]
+
+ trunc_normal_(self.cls_token, std=.02)
+ trunc_normal_(self.patch_pos, std=.02)
+ trunc_normal_(self.pixel_pos, std=.02)
+
+ def forward(self, x):
+ B = x.shape[0]
+ pixel_embed = self.pixel_embed(x, self.pixel_pos)
+
+ patch_embed = self.norm2_proj(
+ self.projection(
+ self.norm1_proj(pixel_embed.reshape(B, self.num_patches, -1))))
+ patch_embed = torch.cat(
+ (self.cls_token.expand(B, -1, -1), patch_embed), dim=1)
+ patch_embed = patch_embed + self.patch_pos
+ patch_embed = self.drop_after_pos(patch_embed)
+
+ for layer in self.layers:
+ pixel_embed, patch_embed = layer(pixel_embed, patch_embed)
+
+ patch_embed = self.norm(patch_embed)
+ return (patch_embed[:, 0], )
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/twins.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/twins.py
new file mode 100644
index 0000000000000000000000000000000000000000..0e3c47a499227025d01106933ade1493a2733b6a
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/twins.py
@@ -0,0 +1,723 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import math
+
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+from mmcv.cnn import Conv2d, build_norm_layer
+from mmcv.cnn.bricks.drop import build_dropout
+from mmcv.cnn.bricks.transformer import FFN, PatchEmbed
+from mmcv.cnn.utils.weight_init import (constant_init, normal_init,
+ trunc_normal_init)
+from mmcv.runner import BaseModule, ModuleList
+from torch.nn.modules.batchnorm import _BatchNorm
+
+from mmcls.models.builder import BACKBONES
+from mmcls.models.utils.attention import MultiheadAttention
+from mmcls.models.utils.position_encoding import ConditionalPositionEncoding
+
+
+class GlobalSubsampledAttention(MultiheadAttention):
+ """Global Sub-sampled Attention (GSA) module.
+
+ Args:
+ embed_dims (int): The embedding dimension.
+ num_heads (int): Parallel attention heads.
+ input_dims (int, optional): The input dimension, and if None,
+ use ``embed_dims``. Defaults to None.
+ attn_drop (float): Dropout rate of the dropout layer after the
+ attention calculation of query and key. Defaults to 0.
+ proj_drop (float): Dropout rate of the dropout layer after the
+ output projection. Defaults to 0.
+ dropout_layer (dict): The dropout config before adding the shortcut.
+ Defaults to ``dict(type='Dropout', drop_prob=0.)``.
+ qkv_bias (bool): If True, add a learnable bias to q, k, v.
+ Defaults to True.
+ norm_cfg (dict): Config dict for normalization layer.
+ Default: dict(type='LN').
+ qk_scale (float, optional): Override default qk scale of
+ ``head_dim ** -0.5`` if set. Defaults to None.
+ proj_bias (bool) If True, add a learnable bias to output projection.
+ Defaults to True.
+ v_shortcut (bool): Add a shortcut from value to output. It's usually
+ used if ``input_dims`` is different from ``embed_dims``.
+ Defaults to False.
+ sr_ratio (float): The ratio of spatial reduction in attention modules.
+ Defaults to 1.
+ init_cfg (dict, optional): The Config for initialization.
+ Defaults to None.
+ """
+
+ def __init__(self,
+ embed_dims,
+ num_heads,
+ norm_cfg=dict(type='LN'),
+ qkv_bias=True,
+ sr_ratio=1,
+ **kwargs):
+ super(GlobalSubsampledAttention,
+ self).__init__(embed_dims, num_heads, **kwargs)
+
+ self.qkv_bias = qkv_bias
+ self.q = nn.Linear(self.input_dims, embed_dims, bias=qkv_bias)
+ self.kv = nn.Linear(self.input_dims, embed_dims * 2, bias=qkv_bias)
+
+ # remove self.qkv, here split into self.q, self.kv
+ delattr(self, 'qkv')
+
+ self.sr_ratio = sr_ratio
+ if sr_ratio > 1:
+ # use a conv as the spatial-reduction operation, the kernel_size
+ # and stride in conv are equal to the sr_ratio.
+ self.sr = Conv2d(
+ in_channels=embed_dims,
+ out_channels=embed_dims,
+ kernel_size=sr_ratio,
+ stride=sr_ratio)
+ # The ret[0] of build_norm_layer is norm name.
+ self.norm = build_norm_layer(norm_cfg, embed_dims)[1]
+
+ def forward(self, x, hw_shape):
+ B, N, C = x.shape
+ H, W = hw_shape
+ assert H * W == N, 'The product of h and w of hw_shape must be N, ' \
+ 'which is the 2nd dim number of the input Tensor x.'
+
+ q = self.q(x).reshape(B, N, self.num_heads,
+ C // self.num_heads).permute(0, 2, 1, 3)
+
+ if self.sr_ratio > 1:
+ x = x.permute(0, 2, 1).reshape(B, C, *hw_shape) # BNC_2_BCHW
+ x = self.sr(x)
+ x = x.reshape(B, C, -1).permute(0, 2, 1) # BCHW_2_BNC
+ x = self.norm(x)
+
+ kv = self.kv(x).reshape(B, -1, 2, self.num_heads,
+ self.head_dims).permute(2, 0, 3, 1, 4)
+ k, v = kv[0], kv[1]
+
+ attn = (q @ k.transpose(-2, -1)) * self.scale
+ attn = attn.softmax(dim=-1)
+ attn = self.attn_drop(attn)
+
+ x = (attn @ v).transpose(1, 2).reshape(B, N, C)
+ x = self.proj(x)
+ x = self.out_drop(self.proj_drop(x))
+
+ if self.v_shortcut:
+ x = v.squeeze(1) + x
+ return x
+
+
+class GSAEncoderLayer(BaseModule):
+ """Implements one encoder layer with GlobalSubsampledAttention(GSA).
+
+ Args:
+ embed_dims (int): The feature dimension.
+ num_heads (int): Parallel attention heads.
+ feedforward_channels (int): The hidden dimension for FFNs.
+ drop_rate (float): Probability of an element to be zeroed
+ after the feed forward layer. Default: 0.0.
+ attn_drop_rate (float): The drop out rate for attention layer.
+ Default: 0.0.
+ drop_path_rate (float): Stochastic depth rate. Default 0.0.
+ num_fcs (int): The number of fully-connected layers for FFNs.
+ Default: 2.
+ qkv_bias (bool): Enable bias for qkv if True. Default: True
+ act_cfg (dict): The activation config for FFNs.
+ Default: dict(type='GELU').
+ norm_cfg (dict): Config dict for normalization layer.
+ Default: dict(type='LN').
+ sr_ratio (float): The ratio of spatial reduction in attention modules.
+ Defaults to 1.
+ init_cfg (dict, optional): The Config for initialization.
+ Defaults to None.
+ """
+
+ def __init__(self,
+ embed_dims,
+ num_heads,
+ feedforward_channels,
+ drop_rate=0.,
+ attn_drop_rate=0.,
+ drop_path_rate=0.,
+ num_fcs=2,
+ qkv_bias=True,
+ act_cfg=dict(type='GELU'),
+ norm_cfg=dict(type='LN'),
+ sr_ratio=1.,
+ init_cfg=None):
+ super(GSAEncoderLayer, self).__init__(init_cfg=init_cfg)
+
+ self.norm1 = build_norm_layer(norm_cfg, embed_dims, postfix=1)[1]
+ self.attn = GlobalSubsampledAttention(
+ embed_dims=embed_dims,
+ num_heads=num_heads,
+ attn_drop=attn_drop_rate,
+ proj_drop=drop_rate,
+ dropout_layer=dict(type='DropPath', drop_prob=drop_path_rate),
+ qkv_bias=qkv_bias,
+ norm_cfg=norm_cfg,
+ sr_ratio=sr_ratio)
+
+ self.norm2 = build_norm_layer(norm_cfg, embed_dims, postfix=2)[1]
+ self.ffn = FFN(
+ embed_dims=embed_dims,
+ feedforward_channels=feedforward_channels,
+ num_fcs=num_fcs,
+ ffn_drop=drop_rate,
+ dropout_layer=dict(type='DropPath', drop_prob=drop_path_rate),
+ act_cfg=act_cfg,
+ add_identity=False)
+
+ self.drop_path = build_dropout(
+ dict(type='DropPath', drop_prob=drop_path_rate)
+ ) if drop_path_rate > 0. else nn.Identity()
+
+ def forward(self, x, hw_shape):
+ x = x + self.drop_path(self.attn(self.norm1(x), hw_shape))
+ x = x + self.drop_path(self.ffn(self.norm2(x)))
+ return x
+
+
+class LocallyGroupedSelfAttention(BaseModule):
+ """Locally-grouped Self Attention (LSA) module.
+
+ Args:
+ embed_dims (int): Number of input channels.
+ num_heads (int): Number of attention heads. Default: 8
+ qkv_bias (bool, optional): If True, add a learnable bias to q, k, v.
+ Default: False.
+ qk_scale (float | None, optional): Override default qk scale of
+ head_dim ** -0.5 if set. Default: None.
+ attn_drop_rate (float, optional): Dropout ratio of attention weight.
+ Default: 0.0
+ proj_drop_rate (float, optional): Dropout ratio of output. Default: 0.
+ window_size(int): Window size of LSA. Default: 1.
+ init_cfg (dict, optional): The Config for initialization.
+ Defaults to None.
+ """
+
+ def __init__(self,
+ embed_dims,
+ num_heads=8,
+ qkv_bias=False,
+ qk_scale=None,
+ attn_drop_rate=0.,
+ proj_drop_rate=0.,
+ window_size=1,
+ init_cfg=None):
+ super(LocallyGroupedSelfAttention, self).__init__(init_cfg=init_cfg)
+
+ assert embed_dims % num_heads == 0, \
+ f'dim {embed_dims} should be divided by num_heads {num_heads}'
+
+ self.embed_dims = embed_dims
+ self.num_heads = num_heads
+ head_dim = embed_dims // num_heads
+ self.scale = qk_scale or head_dim**-0.5
+
+ self.qkv = nn.Linear(embed_dims, embed_dims * 3, bias=qkv_bias)
+ self.attn_drop = nn.Dropout(attn_drop_rate)
+ self.proj = nn.Linear(embed_dims, embed_dims)
+ self.proj_drop = nn.Dropout(proj_drop_rate)
+ self.window_size = window_size
+
+ def forward(self, x, hw_shape):
+ B, N, C = x.shape
+ H, W = hw_shape
+ x = x.view(B, H, W, C)
+
+ # pad feature maps to multiples of Local-groups
+ pad_l = pad_t = 0
+ pad_r = (self.window_size - W % self.window_size) % self.window_size
+ pad_b = (self.window_size - H % self.window_size) % self.window_size
+ x = F.pad(x, (0, 0, pad_l, pad_r, pad_t, pad_b))
+
+ # calculate attention mask for LSA
+ Hp, Wp = x.shape[1:-1]
+ _h, _w = Hp // self.window_size, Wp // self.window_size
+ mask = torch.zeros((1, Hp, Wp), device=x.device)
+ mask[:, -pad_b:, :].fill_(1)
+ mask[:, :, -pad_r:].fill_(1)
+
+ # [B, _h, _w, window_size, window_size, C]
+ x = x.reshape(B, _h, self.window_size, _w, self.window_size,
+ C).transpose(2, 3)
+ mask = mask.reshape(1, _h, self.window_size, _w,
+ self.window_size).transpose(2, 3).reshape(
+ 1, _h * _w,
+ self.window_size * self.window_size)
+ # [1, _h*_w, window_size*window_size, window_size*window_size]
+ attn_mask = mask.unsqueeze(2) - mask.unsqueeze(3)
+ attn_mask = attn_mask.masked_fill(attn_mask != 0,
+ float(-1000.0)).masked_fill(
+ attn_mask == 0, float(0.0))
+
+ # [3, B, _w*_h, nhead, window_size*window_size, dim]
+ qkv = self.qkv(x).reshape(B, _h * _w,
+ self.window_size * self.window_size, 3,
+ self.num_heads, C // self.num_heads).permute(
+ 3, 0, 1, 4, 2, 5)
+ q, k, v = qkv[0], qkv[1], qkv[2]
+ # [B, _h*_w, n_head, window_size*window_size, window_size*window_size]
+ attn = (q @ k.transpose(-2, -1)) * self.scale
+ attn = attn + attn_mask.unsqueeze(2)
+ attn = attn.softmax(dim=-1)
+ attn = self.attn_drop(attn)
+ attn = (attn @ v).transpose(2, 3).reshape(B, _h, _w, self.window_size,
+ self.window_size, C)
+ x = attn.transpose(2, 3).reshape(B, _h * self.window_size,
+ _w * self.window_size, C)
+ if pad_r > 0 or pad_b > 0:
+ x = x[:, :H, :W, :].contiguous()
+
+ x = x.reshape(B, N, C)
+ x = self.proj(x)
+ x = self.proj_drop(x)
+ return x
+
+
+class LSAEncoderLayer(BaseModule):
+ """Implements one encoder layer with LocallyGroupedSelfAttention(LSA).
+
+ Args:
+ embed_dims (int): The feature dimension.
+ num_heads (int): Parallel attention heads.
+ feedforward_channels (int): The hidden dimension for FFNs.
+ drop_rate (float): Probability of an element to be zeroed
+ after the feed forward layer. Default: 0.0.
+ attn_drop_rate (float, optional): Dropout ratio of attention weight.
+ Default: 0.0
+ drop_path_rate (float): Stochastic depth rate. Default 0.0.
+ num_fcs (int): The number of fully-connected layers for FFNs.
+ Default: 2.
+ qkv_bias (bool): Enable bias for qkv if True. Default: True
+ qk_scale (float | None, optional): Override default qk scale of
+ head_dim ** -0.5 if set. Default: None.
+ act_cfg (dict): The activation config for FFNs.
+ Default: dict(type='GELU').
+ norm_cfg (dict): Config dict for normalization layer.
+ Default: dict(type='LN').
+ window_size (int): Window size of LSA. Default: 1.
+ init_cfg (dict, optional): The Config for initialization.
+ Defaults to None.
+ """
+
+ def __init__(self,
+ embed_dims,
+ num_heads,
+ feedforward_channels,
+ drop_rate=0.,
+ attn_drop_rate=0.,
+ drop_path_rate=0.,
+ num_fcs=2,
+ qkv_bias=True,
+ qk_scale=None,
+ act_cfg=dict(type='GELU'),
+ norm_cfg=dict(type='LN'),
+ window_size=1,
+ init_cfg=None):
+
+ super(LSAEncoderLayer, self).__init__(init_cfg=init_cfg)
+
+ self.norm1 = build_norm_layer(norm_cfg, embed_dims, postfix=1)[1]
+ self.attn = LocallyGroupedSelfAttention(embed_dims, num_heads,
+ qkv_bias, qk_scale,
+ attn_drop_rate, drop_rate,
+ window_size)
+
+ self.norm2 = build_norm_layer(norm_cfg, embed_dims, postfix=2)[1]
+ self.ffn = FFN(
+ embed_dims=embed_dims,
+ feedforward_channels=feedforward_channels,
+ num_fcs=num_fcs,
+ ffn_drop=drop_rate,
+ dropout_layer=dict(type='DropPath', drop_prob=drop_path_rate),
+ act_cfg=act_cfg,
+ add_identity=False)
+
+ self.drop_path = build_dropout(
+ dict(type='DropPath', drop_prob=drop_path_rate)
+ ) if drop_path_rate > 0. else nn.Identity()
+
+ def forward(self, x, hw_shape):
+ x = x + self.drop_path(self.attn(self.norm1(x), hw_shape))
+ x = x + self.drop_path(self.ffn(self.norm2(x)))
+ return x
+
+
+@BACKBONES.register_module()
+class PCPVT(BaseModule):
+ """The backbone of Twins-PCPVT.
+
+ This backbone is the implementation of `Twins: Revisiting the Design
+ of Spatial Attention in Vision Transformers
+ `_.
+
+ Args:
+ arch (dict, str): PCPVT architecture, a str value in arch zoo or a
+ detailed configuration dict with 7 keys, and the length of all the
+ values in dict should be the same:
+
+ - depths (List[int]): The number of encoder layers in each stage.
+ - embed_dims (List[int]): Embedding dimension in each stage.
+ - patch_sizes (List[int]): The patch sizes in each stage.
+ - num_heads (List[int]): Numbers of attention head in each stage.
+ - strides (List[int]): The strides in each stage.
+ - mlp_ratios (List[int]): The ratios of mlp in each stage.
+ - sr_ratios (List[int]): The ratios of GSA-encoder layers in each
+ stage.
+
+ in_channels (int): Number of input channels. Default: 3.
+ out_indices (tuple[int]): Output from which stages.
+ Default: (3, ).
+ qkv_bias (bool): Enable bias for qkv if True. Default: False.
+ drop_rate (float): Probability of an element to be zeroed.
+ Default 0.
+ attn_drop_rate (float): The drop out rate for attention layer.
+ Default 0.0
+ drop_path_rate (float): Stochastic depth rate. Default 0.0
+ norm_cfg (dict): Config dict for normalization layer.
+ Default: dict(type='LN')
+ norm_after_stage(bool, List[bool]): Add extra norm after each stage.
+ Default False.
+ init_cfg (dict, optional): The Config for initialization.
+ Defaults to None.
+
+ Examples:
+ >>> from mmcls.models import PCPVT
+ >>> import torch
+ >>> pcpvt_cfg = {'arch': "small",
+ >>> 'norm_after_stage': [False, False, False, True]}
+ >>> model = PCPVT(**pcpvt_cfg)
+ >>> x = torch.rand(1, 3, 224, 224)
+ >>> outputs = model(x)
+ >>> print(outputs[-1].shape)
+ torch.Size([1, 512, 7, 7])
+ >>> pcpvt_cfg['norm_after_stage'] = [True, True, True, True]
+ >>> pcpvt_cfg['out_indices'] = (0, 1, 2, 3)
+ >>> model = PCPVT(**pcpvt_cfg)
+ >>> outputs = model(x)
+ >>> for feat in outputs:
+ >>> print(feat.shape)
+ torch.Size([1, 64, 56, 56])
+ torch.Size([1, 128, 28, 28])
+ torch.Size([1, 320, 14, 14])
+ torch.Size([1, 512, 7, 7])
+ """
+ arch_zoo = {
+ **dict.fromkeys(['s', 'small'],
+ {'embed_dims': [64, 128, 320, 512],
+ 'depths': [3, 4, 6, 3],
+ 'num_heads': [1, 2, 5, 8],
+ 'patch_sizes': [4, 2, 2, 2],
+ 'strides': [4, 2, 2, 2],
+ 'mlp_ratios': [8, 8, 4, 4],
+ 'sr_ratios': [8, 4, 2, 1]}),
+ **dict.fromkeys(['b', 'base'],
+ {'embed_dims': [64, 128, 320, 512],
+ 'depths': [3, 4, 18, 3],
+ 'num_heads': [1, 2, 5, 8],
+ 'patch_sizes': [4, 2, 2, 2],
+ 'strides': [4, 2, 2, 2],
+ 'mlp_ratios': [8, 8, 4, 4],
+ 'sr_ratios': [8, 4, 2, 1]}),
+ **dict.fromkeys(['l', 'large'],
+ {'embed_dims': [64, 128, 320, 512],
+ 'depths': [3, 8, 27, 3],
+ 'num_heads': [1, 2, 5, 8],
+ 'patch_sizes': [4, 2, 2, 2],
+ 'strides': [4, 2, 2, 2],
+ 'mlp_ratios': [8, 8, 4, 4],
+ 'sr_ratios': [8, 4, 2, 1]}),
+ } # yapf: disable
+
+ essential_keys = {
+ 'embed_dims', 'depths', 'num_heads', 'patch_sizes', 'strides',
+ 'mlp_ratios', 'sr_ratios'
+ }
+
+ def __init__(self,
+ arch,
+ in_channels=3,
+ out_indices=(3, ),
+ qkv_bias=False,
+ drop_rate=0.,
+ attn_drop_rate=0.,
+ drop_path_rate=0.,
+ norm_cfg=dict(type='LN'),
+ norm_after_stage=False,
+ init_cfg=None):
+ super(PCPVT, self).__init__(init_cfg=init_cfg)
+ if isinstance(arch, str):
+ arch = arch.lower()
+ assert arch in set(self.arch_zoo), \
+ f'Arch {arch} is not in default archs {set(self.arch_zoo)}'
+ self.arch_settings = self.arch_zoo[arch]
+ else:
+ assert isinstance(arch, dict) and (
+ set(arch) == self.essential_keys
+ ), f'Custom arch needs a dict with keys {self.essential_keys}.'
+ self.arch_settings = arch
+
+ self.depths = self.arch_settings['depths']
+ self.embed_dims = self.arch_settings['embed_dims']
+ self.patch_sizes = self.arch_settings['patch_sizes']
+ self.strides = self.arch_settings['strides']
+ self.mlp_ratios = self.arch_settings['mlp_ratios']
+ self.num_heads = self.arch_settings['num_heads']
+ self.sr_ratios = self.arch_settings['sr_ratios']
+
+ self.num_extra_tokens = 0 # there is no cls-token in Twins
+ self.num_stage = len(self.depths)
+ for key, value in self.arch_settings.items():
+ assert isinstance(value, list) and len(value) == self.num_stage, (
+ 'Length of setting item in arch dict must be type of list and'
+ ' have the same length.')
+
+ # patch_embeds
+ self.patch_embeds = ModuleList()
+ self.position_encoding_drops = ModuleList()
+ self.stages = ModuleList()
+
+ for i in range(self.num_stage):
+ # use in_channels of the model in the first stage
+ if i == 0:
+ stage_in_channels = in_channels
+ else:
+ stage_in_channels = self.embed_dims[i - 1]
+
+ self.patch_embeds.append(
+ PatchEmbed(
+ in_channels=stage_in_channels,
+ embed_dims=self.embed_dims[i],
+ conv_type='Conv2d',
+ kernel_size=self.patch_sizes[i],
+ stride=self.strides[i],
+ padding='corner',
+ norm_cfg=dict(type='LN')))
+
+ self.position_encoding_drops.append(nn.Dropout(p=drop_rate))
+
+ # PEGs
+ self.position_encodings = ModuleList([
+ ConditionalPositionEncoding(embed_dim, embed_dim)
+ for embed_dim in self.embed_dims
+ ])
+
+ # stochastic depth
+ total_depth = sum(self.depths)
+ self.dpr = [
+ x.item() for x in torch.linspace(0, drop_path_rate, total_depth)
+ ] # stochastic depth decay rule
+ cur = 0
+
+ for k in range(len(self.depths)):
+ _block = ModuleList([
+ GSAEncoderLayer(
+ embed_dims=self.embed_dims[k],
+ num_heads=self.num_heads[k],
+ feedforward_channels=self.mlp_ratios[k] *
+ self.embed_dims[k],
+ attn_drop_rate=attn_drop_rate,
+ drop_rate=drop_rate,
+ drop_path_rate=self.dpr[cur + i],
+ num_fcs=2,
+ qkv_bias=qkv_bias,
+ act_cfg=dict(type='GELU'),
+ norm_cfg=norm_cfg,
+ sr_ratio=self.sr_ratios[k]) for i in range(self.depths[k])
+ ])
+ self.stages.append(_block)
+ cur += self.depths[k]
+
+ self.out_indices = out_indices
+
+ assert isinstance(norm_after_stage, (bool, list))
+ if isinstance(norm_after_stage, bool):
+ self.norm_after_stage = [norm_after_stage] * self.num_stage
+ else:
+ self.norm_after_stage = norm_after_stage
+ assert len(self.norm_after_stage) == self.num_stage, \
+ (f'Number of norm_after_stage({len(self.norm_after_stage)}) should'
+ f' be equal to the number of stages({self.num_stage}).')
+
+ for i, has_norm in enumerate(self.norm_after_stage):
+ assert isinstance(has_norm, bool), 'norm_after_stage should be ' \
+ 'bool or List[bool].'
+ if has_norm and norm_cfg is not None:
+ norm_layer = build_norm_layer(norm_cfg, self.embed_dims[i])[1]
+ else:
+ norm_layer = nn.Identity()
+
+ self.add_module(f'norm_after_stage{i}', norm_layer)
+
+ def init_weights(self):
+ if self.init_cfg is not None:
+ super(PCPVT, self).init_weights()
+ else:
+ for m in self.modules():
+ if isinstance(m, nn.Linear):
+ trunc_normal_init(m, std=.02, bias=0.)
+ elif isinstance(m, (_BatchNorm, nn.GroupNorm, nn.LayerNorm)):
+ constant_init(m, val=1.0, bias=0.)
+ elif isinstance(m, nn.Conv2d):
+ fan_out = m.kernel_size[0] * m.kernel_size[
+ 1] * m.out_channels
+ fan_out //= m.groups
+ normal_init(
+ m, mean=0, std=math.sqrt(2.0 / fan_out), bias=0)
+
+ def forward(self, x):
+ outputs = list()
+
+ b = x.shape[0]
+
+ for i in range(self.num_stage):
+ x, hw_shape = self.patch_embeds[i](x)
+ h, w = hw_shape
+ x = self.position_encoding_drops[i](x)
+ for j, blk in enumerate(self.stages[i]):
+ x = blk(x, hw_shape)
+ if j == 0:
+ x = self.position_encodings[i](x, hw_shape)
+
+ norm_layer = getattr(self, f'norm_after_stage{i}')
+ x = norm_layer(x)
+ x = x.reshape(b, h, w, -1).permute(0, 3, 1, 2).contiguous()
+
+ if i in self.out_indices:
+ outputs.append(x)
+
+ return tuple(outputs)
+
+
+@BACKBONES.register_module()
+class SVT(PCPVT):
+ """The backbone of Twins-SVT.
+
+ This backbone is the implementation of `Twins: Revisiting the Design
+ of Spatial Attention in Vision Transformers
+ `_.
+
+ Args:
+ arch (dict, str): SVT architecture, a str value in arch zoo or a
+ detailed configuration dict with 8 keys, and the length of all the
+ values in dict should be the same:
+
+ - depths (List[int]): The number of encoder layers in each stage.
+ - embed_dims (List[int]): Embedding dimension in each stage.
+ - patch_sizes (List[int]): The patch sizes in each stage.
+ - num_heads (List[int]): Numbers of attention head in each stage.
+ - strides (List[int]): The strides in each stage.
+ - mlp_ratios (List[int]): The ratios of mlp in each stage.
+ - sr_ratios (List[int]): The ratios of GSA-encoder layers in each
+ stage.
+ - windiow_sizes (List[int]): The window sizes in LSA-encoder layers
+ in each stage.
+
+ in_channels (int): Number of input channels. Default: 3.
+ out_indices (tuple[int]): Output from which stages.
+ Default: (3, ).
+ qkv_bias (bool): Enable bias for qkv if True. Default: False.
+ drop_rate (float): Dropout rate. Default 0.
+ attn_drop_rate (float): Dropout ratio of attention weight.
+ Default 0.0
+ drop_path_rate (float): Stochastic depth rate. Default 0.2.
+ norm_cfg (dict): Config dict for normalization layer.
+ Default: dict(type='LN')
+ norm_after_stage(bool, List[bool]): Add extra norm after each stage.
+ Default False.
+ init_cfg (dict, optional): The Config for initialization.
+ Defaults to None.
+
+ Examples:
+ >>> from mmcls.models import SVT
+ >>> import torch
+ >>> svt_cfg = {'arch': "small",
+ >>> 'norm_after_stage': [False, False, False, True]}
+ >>> model = SVT(**svt_cfg)
+ >>> x = torch.rand(1, 3, 224, 224)
+ >>> outputs = model(x)
+ >>> print(outputs[-1].shape)
+ torch.Size([1, 512, 7, 7])
+ >>> svt_cfg["out_indices"] = (0, 1, 2, 3)
+ >>> svt_cfg["norm_after_stage"] = [True, True, True, True]
+ >>> model = SVT(**svt_cfg)
+ >>> output = model(x)
+ >>> for feat in output:
+ >>> print(feat.shape)
+ torch.Size([1, 64, 56, 56])
+ torch.Size([1, 128, 28, 28])
+ torch.Size([1, 320, 14, 14])
+ torch.Size([1, 512, 7, 7])
+ """
+ arch_zoo = {
+ **dict.fromkeys(['s', 'small'],
+ {'embed_dims': [64, 128, 256, 512],
+ 'depths': [2, 2, 10, 4],
+ 'num_heads': [2, 4, 8, 16],
+ 'patch_sizes': [4, 2, 2, 2],
+ 'strides': [4, 2, 2, 2],
+ 'mlp_ratios': [4, 4, 4, 4],
+ 'sr_ratios': [8, 4, 2, 1],
+ 'window_sizes': [7, 7, 7, 7]}),
+ **dict.fromkeys(['b', 'base'],
+ {'embed_dims': [96, 192, 384, 768],
+ 'depths': [2, 2, 18, 2],
+ 'num_heads': [3, 6, 12, 24],
+ 'patch_sizes': [4, 2, 2, 2],
+ 'strides': [4, 2, 2, 2],
+ 'mlp_ratios': [4, 4, 4, 4],
+ 'sr_ratios': [8, 4, 2, 1],
+ 'window_sizes': [7, 7, 7, 7]}),
+ **dict.fromkeys(['l', 'large'],
+ {'embed_dims': [128, 256, 512, 1024],
+ 'depths': [2, 2, 18, 2],
+ 'num_heads': [4, 8, 16, 32],
+ 'patch_sizes': [4, 2, 2, 2],
+ 'strides': [4, 2, 2, 2],
+ 'mlp_ratios': [4, 4, 4, 4],
+ 'sr_ratios': [8, 4, 2, 1],
+ 'window_sizes': [7, 7, 7, 7]}),
+ } # yapf: disable
+
+ essential_keys = {
+ 'embed_dims', 'depths', 'num_heads', 'patch_sizes', 'strides',
+ 'mlp_ratios', 'sr_ratios', 'window_sizes'
+ }
+
+ def __init__(self,
+ arch,
+ in_channels=3,
+ out_indices=(3, ),
+ qkv_bias=False,
+ drop_rate=0.,
+ attn_drop_rate=0.,
+ drop_path_rate=0.0,
+ norm_cfg=dict(type='LN'),
+ norm_after_stage=False,
+ init_cfg=None):
+ super(SVT, self).__init__(arch, in_channels, out_indices, qkv_bias,
+ drop_rate, attn_drop_rate, drop_path_rate,
+ norm_cfg, norm_after_stage, init_cfg)
+
+ self.window_sizes = self.arch_settings['window_sizes']
+
+ for k in range(self.num_stage):
+ for i in range(self.depths[k]):
+ # in even-numbered layers of each stage, replace GSA with LSA
+ if i % 2 == 0:
+ ffn_channels = self.mlp_ratios[k] * self.embed_dims[k]
+ self.stages[k][i] = \
+ LSAEncoderLayer(
+ embed_dims=self.embed_dims[k],
+ num_heads=self.num_heads[k],
+ feedforward_channels=ffn_channels,
+ drop_rate=drop_rate,
+ norm_cfg=norm_cfg,
+ attn_drop_rate=attn_drop_rate,
+ drop_path_rate=self.dpr[sum(self.depths[:k])+i],
+ qkv_bias=qkv_bias,
+ window_size=self.window_sizes[k])
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/van.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/van.py
new file mode 100644
index 0000000000000000000000000000000000000000..925240ed80d6562e7a66af05f5830879518a718c
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/van.py
@@ -0,0 +1,445 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import torch
+import torch.nn as nn
+from mmcv.cnn import Conv2d, build_activation_layer, build_norm_layer
+from mmcv.cnn.bricks import DropPath
+from mmcv.cnn.bricks.transformer import PatchEmbed
+from mmcv.runner import BaseModule, ModuleList
+from mmcv.utils.parrots_wrapper import _BatchNorm
+
+from ..builder import BACKBONES
+from .base_backbone import BaseBackbone
+
+
+class MixFFN(BaseModule):
+ """An implementation of MixFFN of VAN. Refer to
+ mmdetection/mmdet/models/backbones/pvt.py.
+
+ The differences between MixFFN & FFN:
+ 1. Use 1X1 Conv to replace Linear layer.
+ 2. Introduce 3X3 Depth-wise Conv to encode positional information.
+
+ Args:
+ embed_dims (int): The feature dimension. Same as
+ `MultiheadAttention`.
+ feedforward_channels (int): The hidden dimension of FFNs.
+ act_cfg (dict, optional): The activation config for FFNs.
+ Default: dict(type='GELU').
+ ffn_drop (float, optional): Probability of an element to be
+ zeroed in FFN. Default 0.0.
+ init_cfg (obj:`mmcv.ConfigDict`): The Config for initialization.
+ Default: None.
+ """
+
+ def __init__(self,
+ embed_dims,
+ feedforward_channels,
+ act_cfg=dict(type='GELU'),
+ ffn_drop=0.,
+ init_cfg=None):
+ super(MixFFN, self).__init__(init_cfg=init_cfg)
+
+ self.embed_dims = embed_dims
+ self.feedforward_channels = feedforward_channels
+ self.act_cfg = act_cfg
+
+ self.fc1 = Conv2d(
+ in_channels=embed_dims,
+ out_channels=feedforward_channels,
+ kernel_size=1)
+ self.dwconv = Conv2d(
+ in_channels=feedforward_channels,
+ out_channels=feedforward_channels,
+ kernel_size=3,
+ stride=1,
+ padding=1,
+ bias=True,
+ groups=feedforward_channels)
+ self.act = build_activation_layer(act_cfg)
+ self.fc2 = Conv2d(
+ in_channels=feedforward_channels,
+ out_channels=embed_dims,
+ kernel_size=1)
+ self.drop = nn.Dropout(ffn_drop)
+
+ def forward(self, x):
+ x = self.fc1(x)
+ x = self.dwconv(x)
+ x = self.act(x)
+ x = self.drop(x)
+ x = self.fc2(x)
+ x = self.drop(x)
+ return x
+
+
+class LKA(BaseModule):
+ """Large Kernel Attention(LKA) of VAN.
+
+ .. code:: text
+ DW_conv (depth-wise convolution)
+ |
+ |
+ DW_D_conv (depth-wise dilation convolution)
+ |
+ |
+ Transition Convolution (1×1 convolution)
+
+ Args:
+ embed_dims (int): Number of input channels.
+ init_cfg (obj:`mmcv.ConfigDict`): The Config for initialization.
+ Default: None.
+ """
+
+ def __init__(self, embed_dims, init_cfg=None):
+ super(LKA, self).__init__(init_cfg=init_cfg)
+
+ # a spatial local convolution (depth-wise convolution)
+ self.DW_conv = Conv2d(
+ in_channels=embed_dims,
+ out_channels=embed_dims,
+ kernel_size=5,
+ padding=2,
+ groups=embed_dims)
+
+ # a spatial long-range convolution (depth-wise dilation convolution)
+ self.DW_D_conv = Conv2d(
+ in_channels=embed_dims,
+ out_channels=embed_dims,
+ kernel_size=7,
+ stride=1,
+ padding=9,
+ groups=embed_dims,
+ dilation=3)
+
+ self.conv1 = Conv2d(
+ in_channels=embed_dims, out_channels=embed_dims, kernel_size=1)
+
+ def forward(self, x):
+ u = x.clone()
+ attn = self.DW_conv(x)
+ attn = self.DW_D_conv(attn)
+ attn = self.conv1(attn)
+
+ return u * attn
+
+
+class SpatialAttention(BaseModule):
+ """Basic attention module in VANBloack.
+
+ Args:
+ embed_dims (int): Number of input channels.
+ act_cfg (dict, optional): The activation config for FFNs.
+ Default: dict(type='GELU').
+ init_cfg (obj:`mmcv.ConfigDict`): The Config for initialization.
+ Default: None.
+ """
+
+ def __init__(self, embed_dims, act_cfg=dict(type='GELU'), init_cfg=None):
+ super(SpatialAttention, self).__init__(init_cfg=init_cfg)
+
+ self.proj_1 = Conv2d(
+ in_channels=embed_dims, out_channels=embed_dims, kernel_size=1)
+ self.activation = build_activation_layer(act_cfg)
+ self.spatial_gating_unit = LKA(embed_dims)
+ self.proj_2 = Conv2d(
+ in_channels=embed_dims, out_channels=embed_dims, kernel_size=1)
+
+ def forward(self, x):
+ shorcut = x.clone()
+ x = self.proj_1(x)
+ x = self.activation(x)
+ x = self.spatial_gating_unit(x)
+ x = self.proj_2(x)
+ x = x + shorcut
+ return x
+
+
+class VANBlock(BaseModule):
+ """A block of VAN.
+
+ Args:
+ embed_dims (int): Number of input channels.
+ ffn_ratio (float): The expansion ratio of feedforward network hidden
+ layer channels. Defaults to 4.
+ drop_rate (float): Dropout rate after embedding. Defaults to 0.
+ drop_path_rate (float): Stochastic depth rate. Defaults to 0.1.
+ act_cfg (dict, optional): The activation config for FFNs.
+ Default: dict(type='GELU').
+ layer_scale_init_value (float): Init value for Layer Scale.
+ Defaults to 1e-2.
+ init_cfg (obj:`mmcv.ConfigDict`): The Config for initialization.
+ Default: None.
+ """
+
+ def __init__(self,
+ embed_dims,
+ ffn_ratio=4.,
+ drop_rate=0.,
+ drop_path_rate=0.,
+ act_cfg=dict(type='GELU'),
+ norm_cfg=dict(type='BN', eps=1e-5),
+ layer_scale_init_value=1e-2,
+ init_cfg=None):
+ super(VANBlock, self).__init__(init_cfg=init_cfg)
+ self.out_channels = embed_dims
+
+ self.norm1 = build_norm_layer(norm_cfg, embed_dims)[1]
+ self.attn = SpatialAttention(embed_dims, act_cfg=act_cfg)
+ self.drop_path = DropPath(
+ drop_path_rate) if drop_path_rate > 0. else nn.Identity()
+
+ self.norm2 = build_norm_layer(norm_cfg, embed_dims)[1]
+ mlp_hidden_dim = int(embed_dims * ffn_ratio)
+ self.mlp = MixFFN(
+ embed_dims=embed_dims,
+ feedforward_channels=mlp_hidden_dim,
+ act_cfg=act_cfg,
+ ffn_drop=drop_rate)
+ self.layer_scale_1 = nn.Parameter(
+ layer_scale_init_value * torch.ones((embed_dims)),
+ requires_grad=True) if layer_scale_init_value > 0 else None
+ self.layer_scale_2 = nn.Parameter(
+ layer_scale_init_value * torch.ones((embed_dims)),
+ requires_grad=True) if layer_scale_init_value > 0 else None
+
+ def forward(self, x):
+ identity = x
+ x = self.norm1(x)
+ x = self.attn(x)
+ if self.layer_scale_1 is not None:
+ x = self.layer_scale_1.unsqueeze(-1).unsqueeze(-1) * x
+ x = identity + self.drop_path(x)
+
+ identity = x
+ x = self.norm2(x)
+ x = self.mlp(x)
+ if self.layer_scale_2 is not None:
+ x = self.layer_scale_2.unsqueeze(-1).unsqueeze(-1) * x
+ x = identity + self.drop_path(x)
+
+ return x
+
+
+class VANPatchEmbed(PatchEmbed):
+ """Image to Patch Embedding of VAN.
+
+ The differences between VANPatchEmbed & PatchEmbed:
+ 1. Use BN.
+ 2. Do not use 'flatten' and 'transpose'.
+ """
+
+ def __init__(self, *args, norm_cfg=dict(type='BN'), **kwargs):
+ super(VANPatchEmbed, self).__init__(*args, norm_cfg=norm_cfg, **kwargs)
+
+ def forward(self, x):
+ """
+ Args:
+ x (Tensor): Has shape (B, C, H, W). In most case, C is 3.
+ Returns:
+ tuple: Contains merged results and its spatial shape.
+ - x (Tensor): Has shape (B, out_h * out_w, embed_dims)
+ - out_size (tuple[int]): Spatial shape of x, arrange as
+ (out_h, out_w).
+ """
+
+ if self.adaptive_padding:
+ x = self.adaptive_padding(x)
+
+ x = self.projection(x)
+ out_size = (x.shape[2], x.shape[3])
+ if self.norm is not None:
+ x = self.norm(x)
+ return x, out_size
+
+
+@BACKBONES.register_module()
+class VAN(BaseBackbone):
+ """Visual Attention Network.
+
+ A PyTorch implement of : `Visual Attention Network
+ `_
+
+ Inspiration from
+ https://github.com/Visual-Attention-Network/VAN-Classification
+
+ Args:
+ arch (str | dict): Visual Attention Network architecture.
+ If use string, choose from 'b0', 'b1', b2', b3' and etc.,
+ if use dict, it should have below keys:
+
+ - **embed_dims** (List[int]): The dimensions of embedding.
+ - **depths** (List[int]): The number of blocks in each stage.
+ - **ffn_ratios** (List[int]): The number of expansion ratio of
+ feedforward network hidden layer channels.
+
+ Defaults to 'tiny'.
+ patch_sizes (List[int | tuple]): The patch size in patch embeddings.
+ Defaults to [7, 3, 3, 3].
+ in_channels (int): The num of input channels. Defaults to 3.
+ drop_rate (float): Dropout rate after embedding. Defaults to 0.
+ drop_path_rate (float): Stochastic depth rate. Defaults to 0.1.
+ out_indices (Sequence[int]): Output from which stages.
+ Default: ``(3, )``.
+ frozen_stages (int): Stages to be frozen (stop grad and set eval mode).
+ -1 means not freezing any parameters. Defaults to -1.
+ norm_eval (bool): Whether to set norm layers to eval mode, namely,
+ freeze running stats (mean and var). Note: Effect on Batch Norm
+ and its variants only. Defaults to False.
+ norm_cfg (dict): Config dict for normalization layer for all output
+ features. Defaults to ``dict(type='LN')``
+ block_cfgs (Sequence[dict] | dict): The extra config of each block.
+ Defaults to empty dicts.
+ init_cfg (dict, optional): The Config for initialization.
+ Defaults to None.
+
+ Examples:
+ >>> from mmcls.models import VAN
+ >>> import torch
+ >>> model = VAN(arch='b0')
+ >>> inputs = torch.rand(1, 3, 224, 224)
+ >>> outputs = model(inputs)
+ >>> for out in outputs:
+ >>> print(out.size())
+ (1, 256, 7, 7)
+ """
+ arch_zoo = {
+ **dict.fromkeys(['b0', 't', 'tiny'],
+ {'embed_dims': [32, 64, 160, 256],
+ 'depths': [3, 3, 5, 2],
+ 'ffn_ratios': [8, 8, 4, 4]}),
+ **dict.fromkeys(['b1', 's', 'small'],
+ {'embed_dims': [64, 128, 320, 512],
+ 'depths': [2, 2, 4, 2],
+ 'ffn_ratios': [8, 8, 4, 4]}),
+ **dict.fromkeys(['b2', 'b', 'base'],
+ {'embed_dims': [64, 128, 320, 512],
+ 'depths': [3, 3, 12, 3],
+ 'ffn_ratios': [8, 8, 4, 4]}),
+ **dict.fromkeys(['b3', 'l', 'large'],
+ {'embed_dims': [64, 128, 320, 512],
+ 'depths': [3, 5, 27, 3],
+ 'ffn_ratios': [8, 8, 4, 4]}),
+ **dict.fromkeys(['b4'],
+ {'embed_dims': [64, 128, 320, 512],
+ 'depths': [3, 6, 40, 3],
+ 'ffn_ratios': [8, 8, 4, 4]}),
+ **dict.fromkeys(['b5'],
+ {'embed_dims': [96, 192, 480, 768],
+ 'depths': [3, 3, 24, 3],
+ 'ffn_ratios': [8, 8, 4, 4]}),
+ **dict.fromkeys(['b6'],
+ {'embed_dims': [96, 192, 384, 768],
+ 'depths': [6, 6, 90, 6],
+ 'ffn_ratios': [8, 8, 4, 4]}),
+ } # yapf: disable
+
+ def __init__(self,
+ arch='tiny',
+ patch_sizes=[7, 3, 3, 3],
+ in_channels=3,
+ drop_rate=0.,
+ drop_path_rate=0.,
+ out_indices=(3, ),
+ frozen_stages=-1,
+ norm_eval=False,
+ norm_cfg=dict(type='LN'),
+ block_cfgs=dict(),
+ init_cfg=None):
+ super(VAN, self).__init__(init_cfg=init_cfg)
+
+ if isinstance(arch, str):
+ arch = arch.lower()
+ assert arch in set(self.arch_zoo), \
+ f'Arch {arch} is not in default archs {set(self.arch_zoo)}'
+ self.arch_settings = self.arch_zoo[arch]
+ else:
+ essential_keys = {'embed_dims', 'depths', 'ffn_ratios'}
+ assert isinstance(arch, dict) and set(arch) == essential_keys, \
+ f'Custom arch needs a dict with keys {essential_keys}'
+ self.arch_settings = arch
+
+ self.embed_dims = self.arch_settings['embed_dims']
+ self.depths = self.arch_settings['depths']
+ self.ffn_ratios = self.arch_settings['ffn_ratios']
+ self.num_stages = len(self.depths)
+ self.out_indices = out_indices
+ self.frozen_stages = frozen_stages
+ self.norm_eval = norm_eval
+
+ total_depth = sum(self.depths)
+ dpr = [
+ x.item() for x in torch.linspace(0, drop_path_rate, total_depth)
+ ] # stochastic depth decay rule
+
+ cur_block_idx = 0
+ for i, depth in enumerate(self.depths):
+ patch_embed = VANPatchEmbed(
+ in_channels=in_channels if i == 0 else self.embed_dims[i - 1],
+ input_size=None,
+ embed_dims=self.embed_dims[i],
+ kernel_size=patch_sizes[i],
+ stride=patch_sizes[i] // 2 + 1,
+ padding=(patch_sizes[i] // 2, patch_sizes[i] // 2),
+ norm_cfg=dict(type='BN'))
+
+ blocks = ModuleList([
+ VANBlock(
+ embed_dims=self.embed_dims[i],
+ ffn_ratio=self.ffn_ratios[i],
+ drop_rate=drop_rate,
+ drop_path_rate=dpr[cur_block_idx + j],
+ **block_cfgs) for j in range(depth)
+ ])
+ cur_block_idx += depth
+ norm = build_norm_layer(norm_cfg, self.embed_dims[i])[1]
+
+ self.add_module(f'patch_embed{i + 1}', patch_embed)
+ self.add_module(f'blocks{i + 1}', blocks)
+ self.add_module(f'norm{i + 1}', norm)
+
+ def train(self, mode=True):
+ super(VAN, self).train(mode)
+ self._freeze_stages()
+ if mode and self.norm_eval:
+ for m in self.modules():
+ # trick: eval have effect on BatchNorm only
+ if isinstance(m, _BatchNorm):
+ m.eval()
+
+ def _freeze_stages(self):
+ for i in range(0, self.frozen_stages + 1):
+ # freeze patch embed
+ m = getattr(self, f'patch_embed{i + 1}')
+ m.eval()
+ for param in m.parameters():
+ param.requires_grad = False
+
+ # freeze blocks
+ m = getattr(self, f'blocks{i + 1}')
+ m.eval()
+ for param in m.parameters():
+ param.requires_grad = False
+
+ # freeze norm
+ m = getattr(self, f'norm{i + 1}')
+ m.eval()
+ for param in m.parameters():
+ param.requires_grad = False
+
+ def forward(self, x):
+ outs = []
+ for i in range(self.num_stages):
+ patch_embed = getattr(self, f'patch_embed{i + 1}')
+ blocks = getattr(self, f'blocks{i + 1}')
+ norm = getattr(self, f'norm{i + 1}')
+ x, hw_shape = patch_embed(x)
+ for block in blocks:
+ x = block(x)
+ x = x.flatten(2).transpose(1, 2)
+ x = norm(x)
+ x = x.reshape(-1, *hw_shape,
+ block.out_channels).permute(0, 3, 1, 2).contiguous()
+ if i in self.out_indices:
+ outs.append(x)
+
+ return tuple(outs)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/vgg.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/vgg.py
similarity index 91%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/vgg.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/vgg.py
index e01435891b14ded92f65c477e5e8031f4bc4682a..b21151c880dbe1188d75df0169db71b6a1a15077 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/backbones/vgg.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/vgg.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import torch.nn as nn
from mmcv.cnn import ConvModule
from mmcv.utils.parrots_wrapper import _BatchNorm
@@ -45,13 +46,11 @@ class VGG(BaseBackbone):
num_stages (int): VGG stages, normally 5.
dilations (Sequence[int]): Dilation of each stage.
out_indices (Sequence[int], optional): Output from which stages.
- If only one stage is specified, a single tensor (feature map) is
- returned, otherwise multiple stages are specified, a tuple of
- tensors will be returned. When it is None, the default behavior
- depends on whether num_classes is specified. If num_classes <= 0,
- the default value is (4, ), outputing the last feature map before
- classifier. If num_classes > 0, the default value is (5, ),
- outputing the classification score. Default: None.
+ When it is None, the default behavior depends on whether
+ num_classes is specified. If num_classes <= 0, the default value is
+ (4, ), output the last feature map before classifier. If
+ num_classes > 0, the default value is (5, ), output the
+ classification score. Default: None.
frozen_stages (int): Stages to be frozen (all param fixed). -1 means
not freezing any parameters.
norm_eval (bool): Whether to set norm layers to eval mode, namely,
@@ -162,10 +161,8 @@ class VGG(BaseBackbone):
x = x.view(x.size(0), -1)
x = self.classifier(x)
outs.append(x)
- if len(outs) == 1:
- return outs[0]
- else:
- return tuple(outs)
+
+ return tuple(outs)
def _freeze_stages(self):
vgg_layers = getattr(self, self.module_name)
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/vision_transformer.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/vision_transformer.py
new file mode 100644
index 0000000000000000000000000000000000000000..87a70640e24df7be52939756ee17e9b153be7bf9
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/backbones/vision_transformer.py
@@ -0,0 +1,383 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from typing import Sequence
+
+import numpy as np
+import torch
+import torch.nn as nn
+from mmcv.cnn import build_norm_layer
+from mmcv.cnn.bricks.transformer import FFN, PatchEmbed
+from mmcv.cnn.utils.weight_init import trunc_normal_
+from mmcv.runner.base_module import BaseModule, ModuleList
+
+from mmcls.utils import get_root_logger
+from ..builder import BACKBONES
+from ..utils import MultiheadAttention, resize_pos_embed, to_2tuple
+from .base_backbone import BaseBackbone
+
+
+class TransformerEncoderLayer(BaseModule):
+ """Implements one encoder layer in Vision Transformer.
+
+ Args:
+ embed_dims (int): The feature dimension
+ num_heads (int): Parallel attention heads
+ feedforward_channels (int): The hidden dimension for FFNs
+ drop_rate (float): Probability of an element to be zeroed
+ after the feed forward layer. Defaults to 0.
+ attn_drop_rate (float): The drop out rate for attention output weights.
+ Defaults to 0.
+ drop_path_rate (float): Stochastic depth rate. Defaults to 0.
+ num_fcs (int): The number of fully-connected layers for FFNs.
+ Defaults to 2.
+ qkv_bias (bool): enable bias for qkv if True. Defaults to True.
+ act_cfg (dict): The activation config for FFNs.
+ Defaluts to ``dict(type='GELU')``.
+ norm_cfg (dict): Config dict for normalization layer.
+ Defaults to ``dict(type='LN')``.
+ init_cfg (dict, optional): Initialization config dict.
+ Defaults to None.
+ """
+
+ def __init__(self,
+ embed_dims,
+ num_heads,
+ feedforward_channels,
+ drop_rate=0.,
+ attn_drop_rate=0.,
+ drop_path_rate=0.,
+ num_fcs=2,
+ qkv_bias=True,
+ act_cfg=dict(type='GELU'),
+ norm_cfg=dict(type='LN'),
+ init_cfg=None):
+ super(TransformerEncoderLayer, self).__init__(init_cfg=init_cfg)
+
+ self.embed_dims = embed_dims
+
+ self.norm1_name, norm1 = build_norm_layer(
+ norm_cfg, self.embed_dims, postfix=1)
+ self.add_module(self.norm1_name, norm1)
+
+ self.attn = MultiheadAttention(
+ embed_dims=embed_dims,
+ num_heads=num_heads,
+ attn_drop=attn_drop_rate,
+ proj_drop=drop_rate,
+ dropout_layer=dict(type='DropPath', drop_prob=drop_path_rate),
+ qkv_bias=qkv_bias)
+
+ self.norm2_name, norm2 = build_norm_layer(
+ norm_cfg, self.embed_dims, postfix=2)
+ self.add_module(self.norm2_name, norm2)
+
+ self.ffn = FFN(
+ embed_dims=embed_dims,
+ feedforward_channels=feedforward_channels,
+ num_fcs=num_fcs,
+ ffn_drop=drop_rate,
+ dropout_layer=dict(type='DropPath', drop_prob=drop_path_rate),
+ act_cfg=act_cfg)
+
+ @property
+ def norm1(self):
+ return getattr(self, self.norm1_name)
+
+ @property
+ def norm2(self):
+ return getattr(self, self.norm2_name)
+
+ def init_weights(self):
+ super(TransformerEncoderLayer, self).init_weights()
+ for m in self.ffn.modules():
+ if isinstance(m, nn.Linear):
+ nn.init.xavier_uniform_(m.weight)
+ nn.init.normal_(m.bias, std=1e-6)
+
+ def forward(self, x):
+ x = x + self.attn(self.norm1(x))
+ x = self.ffn(self.norm2(x), identity=x)
+ return x
+
+
+@BACKBONES.register_module()
+class VisionTransformer(BaseBackbone):
+ """Vision Transformer.
+
+ A PyTorch implement of : `An Image is Worth 16x16 Words: Transformers
+ for Image Recognition at Scale `_
+
+ Args:
+ arch (str | dict): Vision Transformer architecture. If use string,
+ choose from 'small', 'base', 'large', 'deit-tiny', 'deit-small'
+ and 'deit-base'. If use dict, it should have below keys:
+
+ - **embed_dims** (int): The dimensions of embedding.
+ - **num_layers** (int): The number of transformer encoder layers.
+ - **num_heads** (int): The number of heads in attention modules.
+ - **feedforward_channels** (int): The hidden dimensions in
+ feedforward modules.
+
+ Defaults to 'base'.
+ img_size (int | tuple): The expected input image shape. Because we
+ support dynamic input shape, just set the argument to the most
+ common input image shape. Defaults to 224.
+ patch_size (int | tuple): The patch size in patch embedding.
+ Defaults to 16.
+ in_channels (int): The num of input channels. Defaults to 3.
+ out_indices (Sequence | int): Output from which stages.
+ Defaults to -1, means the last stage.
+ drop_rate (float): Probability of an element to be zeroed.
+ Defaults to 0.
+ drop_path_rate (float): stochastic depth rate. Defaults to 0.
+ qkv_bias (bool): Whether to add bias for qkv in attention modules.
+ Defaults to True.
+ norm_cfg (dict): Config dict for normalization layer.
+ Defaults to ``dict(type='LN')``.
+ final_norm (bool): Whether to add a additional layer to normalize
+ final feature map. Defaults to True.
+ with_cls_token (bool): Whether concatenating class token into image
+ tokens as transformer input. Defaults to True.
+ output_cls_token (bool): Whether output the cls_token. If set True,
+ ``with_cls_token`` must be True. Defaults to True.
+ interpolate_mode (str): Select the interpolate mode for position
+ embeding vector resize. Defaults to "bicubic".
+ patch_cfg (dict): Configs of patch embeding. Defaults to an empty dict.
+ layer_cfgs (Sequence | dict): Configs of each transformer layer in
+ encoder. Defaults to an empty dict.
+ init_cfg (dict, optional): Initialization config dict.
+ Defaults to None.
+ """
+ arch_zoo = {
+ **dict.fromkeys(
+ ['s', 'small'], {
+ 'embed_dims': 768,
+ 'num_layers': 8,
+ 'num_heads': 8,
+ 'feedforward_channels': 768 * 3,
+ }),
+ **dict.fromkeys(
+ ['b', 'base'], {
+ 'embed_dims': 768,
+ 'num_layers': 12,
+ 'num_heads': 12,
+ 'feedforward_channels': 3072
+ }),
+ **dict.fromkeys(
+ ['l', 'large'], {
+ 'embed_dims': 1024,
+ 'num_layers': 24,
+ 'num_heads': 16,
+ 'feedforward_channels': 4096
+ }),
+ **dict.fromkeys(
+ ['deit-t', 'deit-tiny'], {
+ 'embed_dims': 192,
+ 'num_layers': 12,
+ 'num_heads': 3,
+ 'feedforward_channels': 192 * 4
+ }),
+ **dict.fromkeys(
+ ['deit-s', 'deit-small'], {
+ 'embed_dims': 384,
+ 'num_layers': 12,
+ 'num_heads': 6,
+ 'feedforward_channels': 384 * 4
+ }),
+ **dict.fromkeys(
+ ['deit-b', 'deit-base'], {
+ 'embed_dims': 768,
+ 'num_layers': 12,
+ 'num_heads': 12,
+ 'feedforward_channels': 768 * 4
+ }),
+ }
+ # Some structures have multiple extra tokens, like DeiT.
+ num_extra_tokens = 1 # cls_token
+
+ def __init__(self,
+ arch='base',
+ img_size=224,
+ patch_size=16,
+ in_channels=3,
+ out_indices=-1,
+ drop_rate=0.,
+ drop_path_rate=0.,
+ qkv_bias=True,
+ norm_cfg=dict(type='LN', eps=1e-6),
+ final_norm=True,
+ with_cls_token=True,
+ output_cls_token=True,
+ interpolate_mode='bicubic',
+ patch_cfg=dict(),
+ layer_cfgs=dict(),
+ init_cfg=None):
+ super(VisionTransformer, self).__init__(init_cfg)
+
+ if isinstance(arch, str):
+ arch = arch.lower()
+ assert arch in set(self.arch_zoo), \
+ f'Arch {arch} is not in default archs {set(self.arch_zoo)}'
+ self.arch_settings = self.arch_zoo[arch]
+ else:
+ essential_keys = {
+ 'embed_dims', 'num_layers', 'num_heads', 'feedforward_channels'
+ }
+ assert isinstance(arch, dict) and essential_keys <= set(arch), \
+ f'Custom arch needs a dict with keys {essential_keys}'
+ self.arch_settings = arch
+
+ self.embed_dims = self.arch_settings['embed_dims']
+ self.num_layers = self.arch_settings['num_layers']
+ self.img_size = to_2tuple(img_size)
+
+ # Set patch embedding
+ _patch_cfg = dict(
+ in_channels=in_channels,
+ input_size=img_size,
+ embed_dims=self.embed_dims,
+ conv_type='Conv2d',
+ kernel_size=patch_size,
+ stride=patch_size,
+ )
+ _patch_cfg.update(patch_cfg)
+ self.patch_embed = PatchEmbed(**_patch_cfg)
+ self.patch_resolution = self.patch_embed.init_out_size
+ num_patches = self.patch_resolution[0] * self.patch_resolution[1]
+
+ # Set cls token
+ if output_cls_token:
+ assert with_cls_token is True, f'with_cls_token must be True if' \
+ f'set output_cls_token to True, but got {with_cls_token}'
+ self.with_cls_token = with_cls_token
+ self.output_cls_token = output_cls_token
+ self.cls_token = nn.Parameter(torch.zeros(1, 1, self.embed_dims))
+
+ # Set position embedding
+ self.interpolate_mode = interpolate_mode
+ self.pos_embed = nn.Parameter(
+ torch.zeros(1, num_patches + self.num_extra_tokens,
+ self.embed_dims))
+ self._register_load_state_dict_pre_hook(self._prepare_pos_embed)
+
+ self.drop_after_pos = nn.Dropout(p=drop_rate)
+
+ if isinstance(out_indices, int):
+ out_indices = [out_indices]
+ assert isinstance(out_indices, Sequence), \
+ f'"out_indices" must by a sequence or int, ' \
+ f'get {type(out_indices)} instead.'
+ for i, index in enumerate(out_indices):
+ if index < 0:
+ out_indices[i] = self.num_layers + index
+ assert 0 <= out_indices[i] <= self.num_layers, \
+ f'Invalid out_indices {index}'
+ self.out_indices = out_indices
+
+ # stochastic depth decay rule
+ dpr = np.linspace(0, drop_path_rate, self.num_layers)
+
+ self.layers = ModuleList()
+ if isinstance(layer_cfgs, dict):
+ layer_cfgs = [layer_cfgs] * self.num_layers
+ for i in range(self.num_layers):
+ _layer_cfg = dict(
+ embed_dims=self.embed_dims,
+ num_heads=self.arch_settings['num_heads'],
+ feedforward_channels=self.
+ arch_settings['feedforward_channels'],
+ drop_rate=drop_rate,
+ drop_path_rate=dpr[i],
+ qkv_bias=qkv_bias,
+ norm_cfg=norm_cfg)
+ _layer_cfg.update(layer_cfgs[i])
+ self.layers.append(TransformerEncoderLayer(**_layer_cfg))
+
+ self.final_norm = final_norm
+ if final_norm:
+ self.norm1_name, norm1 = build_norm_layer(
+ norm_cfg, self.embed_dims, postfix=1)
+ self.add_module(self.norm1_name, norm1)
+
+ @property
+ def norm1(self):
+ return getattr(self, self.norm1_name)
+
+ def init_weights(self):
+ super(VisionTransformer, self).init_weights()
+
+ if not (isinstance(self.init_cfg, dict)
+ and self.init_cfg['type'] == 'Pretrained'):
+ trunc_normal_(self.pos_embed, std=0.02)
+
+ def _prepare_pos_embed(self, state_dict, prefix, *args, **kwargs):
+ name = prefix + 'pos_embed'
+ if name not in state_dict.keys():
+ return
+
+ ckpt_pos_embed_shape = state_dict[name].shape
+ if self.pos_embed.shape != ckpt_pos_embed_shape:
+ from mmcv.utils import print_log
+ logger = get_root_logger()
+ print_log(
+ f'Resize the pos_embed shape from {ckpt_pos_embed_shape} '
+ f'to {self.pos_embed.shape}.',
+ logger=logger)
+
+ ckpt_pos_embed_shape = to_2tuple(
+ int(np.sqrt(ckpt_pos_embed_shape[1] - self.num_extra_tokens)))
+ pos_embed_shape = self.patch_embed.init_out_size
+
+ state_dict[name] = resize_pos_embed(state_dict[name],
+ ckpt_pos_embed_shape,
+ pos_embed_shape,
+ self.interpolate_mode,
+ self.num_extra_tokens)
+
+ @staticmethod
+ def resize_pos_embed(*args, **kwargs):
+ """Interface for backward-compatibility."""
+ return resize_pos_embed(*args, **kwargs)
+
+ def forward(self, x):
+ B = x.shape[0]
+ x, patch_resolution = self.patch_embed(x)
+
+ # stole cls_tokens impl from Phil Wang, thanks
+ cls_tokens = self.cls_token.expand(B, -1, -1)
+ x = torch.cat((cls_tokens, x), dim=1)
+ x = x + resize_pos_embed(
+ self.pos_embed,
+ self.patch_resolution,
+ patch_resolution,
+ mode=self.interpolate_mode,
+ num_extra_tokens=self.num_extra_tokens)
+ x = self.drop_after_pos(x)
+
+ if not self.with_cls_token:
+ # Remove class token for transformer encoder input
+ x = x[:, 1:]
+
+ outs = []
+ for i, layer in enumerate(self.layers):
+ x = layer(x)
+
+ if i == len(self.layers) - 1 and self.final_norm:
+ x = self.norm1(x)
+
+ if i in self.out_indices:
+ B, _, C = x.shape
+ if self.with_cls_token:
+ patch_token = x[:, 1:].reshape(B, *patch_resolution, C)
+ patch_token = patch_token.permute(0, 3, 1, 2)
+ cls_token = x[:, 0]
+ else:
+ patch_token = x.reshape(B, *patch_resolution, C)
+ patch_token = patch_token.permute(0, 3, 1, 2)
+ cls_token = None
+ if self.output_cls_token:
+ out = [patch_token, cls_token]
+ else:
+ out = patch_token
+ outs.append(out)
+
+ return tuple(outs)
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/builder.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/builder.py
new file mode 100644
index 0000000000000000000000000000000000000000..9b43913ef3c939dac2c05a87762879237f15213f
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/builder.py
@@ -0,0 +1,38 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from mmcv.cnn import MODELS as MMCV_MODELS
+from mmcv.cnn.bricks.registry import ATTENTION as MMCV_ATTENTION
+from mmcv.utils import Registry
+
+MODELS = Registry('models', parent=MMCV_MODELS)
+
+BACKBONES = MODELS
+NECKS = MODELS
+HEADS = MODELS
+LOSSES = MODELS
+CLASSIFIERS = MODELS
+
+ATTENTION = Registry('attention', parent=MMCV_ATTENTION)
+
+
+def build_backbone(cfg):
+ """Build backbone."""
+ return BACKBONES.build(cfg)
+
+
+def build_neck(cfg):
+ """Build neck."""
+ return NECKS.build(cfg)
+
+
+def build_head(cfg):
+ """Build head."""
+ return HEADS.build(cfg)
+
+
+def build_loss(cfg):
+ """Build loss."""
+ return LOSSES.build(cfg)
+
+
+def build_classifier(cfg):
+ return CLASSIFIERS.build(cfg)
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/classifiers/__init__.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/classifiers/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..5fdfb91ff17c7cebed5a10aae59a4e8829ab3a2c
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/classifiers/__init__.py
@@ -0,0 +1,5 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from .base import BaseClassifier
+from .image import ImageClassifier
+
+__all__ = ['BaseClassifier', 'ImageClassifier']
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/classifiers/base.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/classifiers/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..acb5ef3dff0e6d4b7fbbad98ef6bdc0900252088
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/classifiers/base.py
@@ -0,0 +1,224 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from abc import ABCMeta, abstractmethod
+from collections import OrderedDict
+from typing import Sequence
+
+import mmcv
+import torch
+import torch.distributed as dist
+from mmcv.runner import BaseModule, auto_fp16
+
+from mmcls.core.visualization import imshow_infos
+
+
+class BaseClassifier(BaseModule, metaclass=ABCMeta):
+ """Base class for classifiers."""
+
+ def __init__(self, init_cfg=None):
+ super(BaseClassifier, self).__init__(init_cfg)
+ self.fp16_enabled = False
+
+ @property
+ def with_neck(self):
+ return hasattr(self, 'neck') and self.neck is not None
+
+ @property
+ def with_head(self):
+ return hasattr(self, 'head') and self.head is not None
+
+ @abstractmethod
+ def extract_feat(self, imgs, stage=None):
+ pass
+
+ def extract_feats(self, imgs, stage=None):
+ assert isinstance(imgs, Sequence)
+ kwargs = {} if stage is None else {'stage': stage}
+ for img in imgs:
+ yield self.extract_feat(img, **kwargs)
+
+ @abstractmethod
+ def forward_train(self, imgs, **kwargs):
+ """
+ Args:
+ img (list[Tensor]): List of tensors of shape (1, C, H, W).
+ Typically these should be mean centered and std scaled.
+ kwargs (keyword arguments): Specific to concrete implementation.
+ """
+ pass
+
+ @abstractmethod
+ def simple_test(self, img, **kwargs):
+ pass
+
+ def forward_test(self, imgs, **kwargs):
+ """
+ Args:
+ imgs (List[Tensor]): the outer list indicates test-time
+ augmentations and inner Tensor should have a shape NxCxHxW,
+ which contains all images in the batch.
+ """
+ if isinstance(imgs, torch.Tensor):
+ imgs = [imgs]
+ for var, name in [(imgs, 'imgs')]:
+ if not isinstance(var, list):
+ raise TypeError(f'{name} must be a list, but got {type(var)}')
+
+ if len(imgs) == 1:
+ return self.simple_test(imgs[0], **kwargs)
+ else:
+ raise NotImplementedError('aug_test has not been implemented')
+
+ @auto_fp16(apply_to=('img', ))
+ def forward(self, img, return_loss=True, **kwargs):
+ """Calls either forward_train or forward_test depending on whether
+ return_loss=True.
+
+ Note this setting will change the expected inputs. When
+ `return_loss=True`, img and img_meta are single-nested (i.e. Tensor and
+ List[dict]), and when `resturn_loss=False`, img and img_meta should be
+ double nested (i.e. List[Tensor], List[List[dict]]), with the outer
+ list indicating test time augmentations.
+ """
+ if return_loss:
+ return self.forward_train(img, **kwargs)
+ else:
+ return self.forward_test(img, **kwargs)
+
+ def _parse_losses(self, losses):
+ log_vars = OrderedDict()
+ for loss_name, loss_value in losses.items():
+ if isinstance(loss_value, torch.Tensor):
+ log_vars[loss_name] = loss_value.mean()
+ elif isinstance(loss_value, list):
+ log_vars[loss_name] = sum(_loss.mean() for _loss in loss_value)
+ elif isinstance(loss_value, dict):
+ for name, value in loss_value.items():
+ log_vars[name] = value
+ else:
+ raise TypeError(
+ f'{loss_name} is not a tensor or list of tensors')
+
+ loss = sum(_value for _key, _value in log_vars.items()
+ if 'loss' in _key)
+
+ log_vars['loss'] = loss
+ for loss_name, loss_value in log_vars.items():
+ # reduce loss when distributed training
+ if dist.is_available() and dist.is_initialized():
+ loss_value = loss_value.data.clone()
+ dist.all_reduce(loss_value.div_(dist.get_world_size()))
+ log_vars[loss_name] = loss_value.item()
+
+ return loss, log_vars
+
+ def train_step(self, data, optimizer=None, **kwargs):
+ """The iteration step during training.
+
+ This method defines an iteration step during training, except for the
+ back propagation and optimizer updating, which are done in an optimizer
+ hook. Note that in some complicated cases or models, the whole process
+ including back propagation and optimizer updating are also defined in
+ this method, such as GAN.
+
+ Args:
+ data (dict): The output of dataloader.
+ optimizer (:obj:`torch.optim.Optimizer` | dict, optional): The
+ optimizer of runner is passed to ``train_step()``. This
+ argument is unused and reserved.
+
+ Returns:
+ dict: Dict of outputs. The following fields are contained.
+ - loss (torch.Tensor): A tensor for back propagation, which \
+ can be a weighted sum of multiple losses.
+ - log_vars (dict): Dict contains all the variables to be sent \
+ to the logger.
+ - num_samples (int): Indicates the batch size (when the model \
+ is DDP, it means the batch size on each GPU), which is \
+ used for averaging the logs.
+ """
+ losses = self(**data)
+ loss, log_vars = self._parse_losses(losses)
+
+ outputs = dict(
+ loss=loss, log_vars=log_vars, num_samples=len(data['img'].data))
+
+ return outputs
+
+ def val_step(self, data, optimizer=None, **kwargs):
+ """The iteration step during validation.
+
+ This method shares the same signature as :func:`train_step`, but used
+ during val epochs. Note that the evaluation after training epochs is
+ not implemented with this method, but an evaluation hook.
+
+ Args:
+ data (dict): The output of dataloader.
+ optimizer (:obj:`torch.optim.Optimizer` | dict, optional): The
+ optimizer of runner is passed to ``train_step()``. This
+ argument is unused and reserved.
+
+ Returns:
+ dict: Dict of outputs. The following fields are contained.
+ - loss (torch.Tensor): A tensor for back propagation, which \
+ can be a weighted sum of multiple losses.
+ - log_vars (dict): Dict contains all the variables to be sent \
+ to the logger.
+ - num_samples (int): Indicates the batch size (when the model \
+ is DDP, it means the batch size on each GPU), which is \
+ used for averaging the logs.
+ """
+ losses = self(**data)
+ loss, log_vars = self._parse_losses(losses)
+
+ outputs = dict(
+ loss=loss, log_vars=log_vars, num_samples=len(data['img'].data))
+
+ return outputs
+
+ def show_result(self,
+ img,
+ result,
+ text_color='white',
+ font_scale=0.5,
+ row_width=20,
+ show=False,
+ fig_size=(15, 10),
+ win_name='',
+ wait_time=0,
+ out_file=None):
+ """Draw `result` over `img`.
+
+ Args:
+ img (str or ndarray): The image to be displayed.
+ result (dict): The classification results to draw over `img`.
+ text_color (str or tuple or :obj:`Color`): Color of texts.
+ font_scale (float): Font scales of texts.
+ row_width (int): width between each row of results on the image.
+ show (bool): Whether to show the image.
+ Default: False.
+ fig_size (tuple): Image show figure size. Defaults to (15, 10).
+ win_name (str): The window name.
+ wait_time (int): How many seconds to display the image.
+ Defaults to 0.
+ out_file (str or None): The filename to write the image.
+ Default: None.
+
+ Returns:
+ img (ndarray): Image with overlaid results.
+ """
+ img = mmcv.imread(img)
+ img = img.copy()
+
+ img = imshow_infos(
+ img,
+ result,
+ text_color=text_color,
+ font_size=int(font_scale * 50),
+ row_width=row_width,
+ win_name=win_name,
+ show=show,
+ fig_size=fig_size,
+ wait_time=wait_time,
+ out_file=out_file)
+
+ return img
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/classifiers/image.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/classifiers/image.py
new file mode 100644
index 0000000000000000000000000000000000000000..95ffa4665ede28d03ab728de908cc64ec0510c49
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/classifiers/image.py
@@ -0,0 +1,160 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from ..builder import CLASSIFIERS, build_backbone, build_head, build_neck
+from ..heads import MultiLabelClsHead
+from ..utils.augment import Augments
+from .base import BaseClassifier
+
+
+@CLASSIFIERS.register_module()
+class ImageClassifier(BaseClassifier):
+
+ def __init__(self,
+ backbone,
+ neck=None,
+ head=None,
+ pretrained=None,
+ train_cfg=None,
+ init_cfg=None):
+ super(ImageClassifier, self).__init__(init_cfg)
+
+ if pretrained is not None:
+ self.init_cfg = dict(type='Pretrained', checkpoint=pretrained)
+ self.backbone = build_backbone(backbone)
+
+ if neck is not None:
+ self.neck = build_neck(neck)
+
+ if head is not None:
+ self.head = build_head(head)
+
+ self.augments = None
+ if train_cfg is not None:
+ augments_cfg = train_cfg.get('augments', None)
+ if augments_cfg is not None:
+ self.augments = Augments(augments_cfg)
+
+ def forward_dummy(self, img):
+ """Used for computing network flops.
+
+ See `mmclassificaiton/tools/analysis_tools/get_flops.py`
+ """
+ return self.extract_feat(img, stage='pre_logits')
+
+ def extract_feat(self, img, stage='neck'):
+ """Directly extract features from the specified stage.
+
+ Args:
+ img (Tensor): The input images. The shape of it should be
+ ``(num_samples, num_channels, *img_shape)``.
+ stage (str): Which stage to output the feature. Choose from
+ "backbone", "neck" and "pre_logits". Defaults to "neck".
+
+ Returns:
+ tuple | Tensor: The output of specified stage.
+ The output depends on detailed implementation. In general, the
+ output of backbone and neck is a tuple and the output of
+ pre_logits is a tensor.
+
+ Examples:
+ 1. Backbone output
+
+ >>> import torch
+ >>> from mmcv import Config
+ >>> from mmcls.models import build_classifier
+ >>>
+ >>> cfg = Config.fromfile('configs/resnet/resnet18_8xb32_in1k.py').model
+ >>> cfg.backbone.out_indices = (0, 1, 2, 3) # Output multi-scale feature maps
+ >>> model = build_classifier(cfg)
+ >>> outs = model.extract_feat(torch.rand(1, 3, 224, 224), stage='backbone')
+ >>> for out in outs:
+ ... print(out.shape)
+ torch.Size([1, 64, 56, 56])
+ torch.Size([1, 128, 28, 28])
+ torch.Size([1, 256, 14, 14])
+ torch.Size([1, 512, 7, 7])
+
+ 2. Neck output
+
+ >>> import torch
+ >>> from mmcv import Config
+ >>> from mmcls.models import build_classifier
+ >>>
+ >>> cfg = Config.fromfile('configs/resnet/resnet18_8xb32_in1k.py').model
+ >>> cfg.backbone.out_indices = (0, 1, 2, 3) # Output multi-scale feature maps
+ >>> model = build_classifier(cfg)
+ >>>
+ >>> outs = model.extract_feat(torch.rand(1, 3, 224, 224), stage='neck')
+ >>> for out in outs:
+ ... print(out.shape)
+ torch.Size([1, 64])
+ torch.Size([1, 128])
+ torch.Size([1, 256])
+ torch.Size([1, 512])
+
+ 3. Pre-logits output (without the final linear classifier head)
+
+ >>> import torch
+ >>> from mmcv import Config
+ >>> from mmcls.models import build_classifier
+ >>>
+ >>> cfg = Config.fromfile('configs/vision_transformer/vit-base-p16_pt-64xb64_in1k-224.py').model
+ >>> model = build_classifier(cfg)
+ >>>
+ >>> out = model.extract_feat(torch.rand(1, 3, 224, 224), stage='pre_logits')
+ >>> print(out.shape) # The hidden dims in head is 3072
+ torch.Size([1, 3072])
+ """ # noqa: E501
+ assert stage in ['backbone', 'neck', 'pre_logits'], \
+ (f'Invalid output stage "{stage}", please choose from "backbone", '
+ '"neck" and "pre_logits"')
+
+ x = self.backbone(img)
+
+ if stage == 'backbone':
+ return x
+
+ if self.with_neck:
+ x = self.neck(x)
+ if stage == 'neck':
+ return x
+
+ if self.with_head and hasattr(self.head, 'pre_logits'):
+ x = self.head.pre_logits(x)
+ return x
+
+ def forward_train(self, img, gt_label, **kwargs):
+ """Forward computation during training.
+
+ Args:
+ img (Tensor): of shape (N, C, H, W) encoding input images.
+ Typically these should be mean centered and std scaled.
+ gt_label (Tensor): It should be of shape (N, 1) encoding the
+ ground-truth label of input images for single label task. It
+ should be of shape (N, C) encoding the ground-truth label
+ of input images for multi-labels task.
+ Returns:
+ dict[str, Tensor]: a dictionary of loss components
+ """
+ if self.augments is not None:
+ img, gt_label = self.augments(img, gt_label)
+
+ x = self.extract_feat(img)
+
+ losses = dict()
+ loss = self.head.forward_train(x, gt_label)
+
+ losses.update(loss)
+
+ return losses
+
+ def simple_test(self, img, img_metas=None, **kwargs):
+ """Test without augmentation."""
+ x = self.extract_feat(img)
+
+ if isinstance(self.head, MultiLabelClsHead):
+ assert 'softmax' not in kwargs, (
+ 'Please use `sigmoid` instead of `softmax` '
+ 'in multi-label tasks.')
+ res = self.head.simple_test(x, **kwargs)
+
+ return res
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/__init__.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d730161309451f24f9dabd45b104fe5e4e3bdc37
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/__init__.py
@@ -0,0 +1,17 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from .cls_head import ClsHead
+from .conformer_head import ConformerHead
+from .deit_head import DeiTClsHead
+from .efficientformer_head import EfficientFormerClsHead
+from .linear_head import LinearClsHead
+from .multi_label_csra_head import CSRAClsHead
+from .multi_label_head import MultiLabelClsHead
+from .multi_label_linear_head import MultiLabelLinearClsHead
+from .stacked_head import StackedLinearClsHead
+from .vision_transformer_head import VisionTransformerClsHead
+
+__all__ = [
+ 'ClsHead', 'LinearClsHead', 'StackedLinearClsHead', 'MultiLabelClsHead',
+ 'MultiLabelLinearClsHead', 'VisionTransformerClsHead', 'DeiTClsHead',
+ 'ConformerHead', 'EfficientFormerClsHead', 'CSRAClsHead'
+]
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/heads/base_head.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/base_head.py
similarity index 86%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/models/heads/base_head.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/base_head.py
index b319755204aea07c2e353be13ae88be3acdb67cc..e8936f28fe50b0d9b2066176a6e343bc593bf607 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/heads/base_head.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/base_head.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
from abc import ABCMeta, abstractmethod
from mmcv.runner import BaseModule
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/cls_head.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/cls_head.py
new file mode 100644
index 0000000000000000000000000000000000000000..2e430c5a5f100c002e94c55ed067836c5b493ee3
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/cls_head.py
@@ -0,0 +1,116 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import warnings
+
+import torch
+import torch.nn.functional as F
+
+from mmcls.models.losses import Accuracy
+from ..builder import HEADS, build_loss
+from ..utils import is_tracing
+from .base_head import BaseHead
+
+
+@HEADS.register_module()
+class ClsHead(BaseHead):
+ """classification head.
+
+ Args:
+ loss (dict): Config of classification loss.
+ topk (int | tuple): Top-k accuracy.
+ cal_acc (bool): Whether to calculate accuracy during training.
+ If you use Mixup/CutMix or something like that during training,
+ it is not reasonable to calculate accuracy. Defaults to False.
+ """
+
+ def __init__(self,
+ loss=dict(type='CrossEntropyLoss', loss_weight=1.0),
+ topk=(1, ),
+ cal_acc=False,
+ init_cfg=None):
+ super(ClsHead, self).__init__(init_cfg=init_cfg)
+
+ assert isinstance(loss, dict)
+ assert isinstance(topk, (int, tuple))
+ if isinstance(topk, int):
+ topk = (topk, )
+ for _topk in topk:
+ assert _topk > 0, 'Top-k should be larger than 0'
+ self.topk = topk
+
+ self.compute_loss = build_loss(loss)
+ self.compute_accuracy = Accuracy(topk=self.topk)
+ self.cal_acc = cal_acc
+
+ def loss(self, cls_score, gt_label, **kwargs):
+ num_samples = len(cls_score)
+ losses = dict()
+ # compute loss
+ loss = self.compute_loss(
+ cls_score, gt_label, avg_factor=num_samples, **kwargs)
+ if self.cal_acc:
+ # compute accuracy
+ acc = self.compute_accuracy(cls_score, gt_label)
+ assert len(acc) == len(self.topk)
+ losses['accuracy'] = {
+ f'top-{k}': a
+ for k, a in zip(self.topk, acc)
+ }
+ losses['loss'] = loss
+ return losses
+
+ def forward_train(self, cls_score, gt_label, **kwargs):
+ if isinstance(cls_score, tuple):
+ cls_score = cls_score[-1]
+ losses = self.loss(cls_score, gt_label, **kwargs)
+ return losses
+
+ def pre_logits(self, x):
+ if isinstance(x, tuple):
+ x = x[-1]
+
+ warnings.warn(
+ 'The input of ClsHead should be already logits. '
+ 'Please modify the backbone if you want to get pre-logits feature.'
+ )
+ return x
+
+ def simple_test(self, cls_score, softmax=True, post_process=True):
+ """Inference without augmentation.
+
+ Args:
+ cls_score (tuple[Tensor]): The input classification score logits.
+ Multi-stage inputs are acceptable but only the last stage will
+ be used to classify. The shape of every item should be
+ ``(num_samples, num_classes)``.
+ softmax (bool): Whether to softmax the classification score.
+ post_process (bool): Whether to do post processing the
+ inference results. It will convert the output to a list.
+
+ Returns:
+ Tensor | list: The inference results.
+
+ - If no post processing, the output is a tensor with shape
+ ``(num_samples, num_classes)``.
+ - If post processing, the output is a multi-dimentional list of
+ float and the dimensions are ``(num_samples, num_classes)``.
+ """
+ if isinstance(cls_score, tuple):
+ cls_score = cls_score[-1]
+
+ if softmax:
+ pred = (
+ F.softmax(cls_score, dim=1) if cls_score is not None else None)
+ else:
+ pred = cls_score
+
+ if post_process:
+ return self.post_process(pred)
+ else:
+ return pred
+
+ def post_process(self, pred):
+ on_trace = is_tracing()
+ if torch.onnx.is_in_onnx_export() or on_trace:
+ return pred
+ pred = list(pred.detach().cpu().numpy())
+ return pred
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/conformer_head.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/conformer_head.py
new file mode 100644
index 0000000000000000000000000000000000000000..c6557962ae3195e740a52d5e6acbf9999e0c2800
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/conformer_head.py
@@ -0,0 +1,132 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import torch.nn as nn
+import torch.nn.functional as F
+from mmcv.cnn.utils.weight_init import trunc_normal_
+
+from ..builder import HEADS
+from .cls_head import ClsHead
+
+
+@HEADS.register_module()
+class ConformerHead(ClsHead):
+ """Linear classifier head.
+
+ Args:
+ num_classes (int): Number of categories excluding the background
+ category.
+ in_channels (int): Number of channels in the input feature map.
+ init_cfg (dict | optional): The extra init config of layers.
+ Defaults to use ``dict(type='Normal', layer='Linear', std=0.01)``.
+ """
+
+ def __init__(
+ self,
+ num_classes,
+ in_channels, # [conv_dim, trans_dim]
+ init_cfg=dict(type='Normal', layer='Linear', std=0.01),
+ *args,
+ **kwargs):
+ super(ConformerHead, self).__init__(init_cfg=None, *args, **kwargs)
+
+ self.in_channels = in_channels
+ self.num_classes = num_classes
+ self.init_cfg = init_cfg
+
+ if self.num_classes <= 0:
+ raise ValueError(
+ f'num_classes={num_classes} must be a positive integer')
+
+ self.conv_cls_head = nn.Linear(self.in_channels[0], num_classes)
+ self.trans_cls_head = nn.Linear(self.in_channels[1], num_classes)
+
+ def _init_weights(self, m):
+ if isinstance(m, nn.Linear):
+ trunc_normal_(m.weight, std=.02)
+ if isinstance(m, nn.Linear) and m.bias is not None:
+ nn.init.constant_(m.bias, 0)
+
+ def init_weights(self):
+ super(ConformerHead, self).init_weights()
+
+ if (isinstance(self.init_cfg, dict)
+ and self.init_cfg['type'] == 'Pretrained'):
+ # Suppress default init if use pretrained model.
+ return
+ else:
+ self.apply(self._init_weights)
+
+ def pre_logits(self, x):
+ if isinstance(x, tuple):
+ x = x[-1]
+ return x
+
+ def simple_test(self, x, softmax=True, post_process=True):
+ """Inference without augmentation.
+
+ Args:
+ x (tuple[tuple[tensor, tensor]]): The input features.
+ Multi-stage inputs are acceptable but only the last stage will
+ be used to classify. Every item should be a tuple which
+ includes convluation features and transformer features. The
+ shape of them should be ``(num_samples, in_channels[0])`` and
+ ``(num_samples, in_channels[1])``.
+ softmax (bool): Whether to softmax the classification score.
+ post_process (bool): Whether to do post processing the
+ inference results. It will convert the output to a list.
+
+ Returns:
+ Tensor | list: The inference results.
+
+ - If no post processing, the output is a tensor with shape
+ ``(num_samples, num_classes)``.
+ - If post processing, the output is a multi-dimentional list of
+ float and the dimensions are ``(num_samples, num_classes)``.
+ """
+ x = self.pre_logits(x)
+ # There are two outputs in the Conformer model
+ assert len(x) == 2
+
+ conv_cls_score = self.conv_cls_head(x[0])
+ tran_cls_score = self.trans_cls_head(x[1])
+
+ if softmax:
+ cls_score = conv_cls_score + tran_cls_score
+ pred = (
+ F.softmax(cls_score, dim=1) if cls_score is not None else None)
+ if post_process:
+ pred = self.post_process(pred)
+ else:
+ pred = [conv_cls_score, tran_cls_score]
+ if post_process:
+ pred = list(map(self.post_process, pred))
+ return pred
+
+ def forward_train(self, x, gt_label):
+ x = self.pre_logits(x)
+ assert isinstance(x, list) and len(x) == 2, \
+ 'There should be two outputs in the Conformer model'
+
+ conv_cls_score = self.conv_cls_head(x[0])
+ tran_cls_score = self.trans_cls_head(x[1])
+
+ losses = self.loss([conv_cls_score, tran_cls_score], gt_label)
+ return losses
+
+ def loss(self, cls_score, gt_label):
+ num_samples = len(cls_score[0])
+ losses = dict()
+ # compute loss
+ loss = sum([
+ self.compute_loss(score, gt_label, avg_factor=num_samples) /
+ len(cls_score) for score in cls_score
+ ])
+ if self.cal_acc:
+ # compute accuracy
+ acc = self.compute_accuracy(cls_score[0] + cls_score[1], gt_label)
+ assert len(acc) == len(self.topk)
+ losses['accuracy'] = {
+ f'top-{k}': a
+ for k, a in zip(self.topk, acc)
+ }
+ losses['loss'] = loss
+ return losses
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/deit_head.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/deit_head.py
new file mode 100644
index 0000000000000000000000000000000000000000..1e9f22a6e61318c3d047086e33c388676b365823
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/deit_head.py
@@ -0,0 +1,96 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import torch.nn as nn
+import torch.nn.functional as F
+
+from mmcls.utils import get_root_logger
+from ..builder import HEADS
+from .vision_transformer_head import VisionTransformerClsHead
+
+
+@HEADS.register_module()
+class DeiTClsHead(VisionTransformerClsHead):
+ """Distilled Vision Transformer classifier head.
+
+ Comparing with the :class:`VisionTransformerClsHead`, this head adds an
+ extra linear layer to handle the dist token. The final classification score
+ is the average of both linear transformation results of ``cls_token`` and
+ ``dist_token``.
+
+ Args:
+ num_classes (int): Number of categories excluding the background
+ category.
+ in_channels (int): Number of channels in the input feature map.
+ hidden_dim (int): Number of the dimensions for hidden layer.
+ Defaults to None, which means no extra hidden layer.
+ act_cfg (dict): The activation config. Only available during
+ pre-training. Defaults to ``dict(type='Tanh')``.
+ init_cfg (dict): The extra initialization configs. Defaults to
+ ``dict(type='Constant', layer='Linear', val=0)``.
+ """
+
+ def __init__(self, *args, **kwargs):
+ super(DeiTClsHead, self).__init__(*args, **kwargs)
+ if self.hidden_dim is None:
+ head_dist = nn.Linear(self.in_channels, self.num_classes)
+ else:
+ head_dist = nn.Linear(self.hidden_dim, self.num_classes)
+ self.layers.add_module('head_dist', head_dist)
+
+ def pre_logits(self, x):
+ if isinstance(x, tuple):
+ x = x[-1]
+ _, cls_token, dist_token = x
+
+ if self.hidden_dim is None:
+ return cls_token, dist_token
+ else:
+ cls_token = self.layers.act(self.layers.pre_logits(cls_token))
+ dist_token = self.layers.act(self.layers.pre_logits(dist_token))
+ return cls_token, dist_token
+
+ def simple_test(self, x, softmax=True, post_process=True):
+ """Inference without augmentation.
+
+ Args:
+ x (tuple[tuple[tensor, tensor, tensor]]): The input features.
+ Multi-stage inputs are acceptable but only the last stage will
+ be used to classify. Every item should be a tuple which
+ includes patch token, cls token and dist token. The cls token
+ and dist token will be used to classify and the shape of them
+ should be ``(num_samples, in_channels)``.
+ softmax (bool): Whether to softmax the classification score.
+ post_process (bool): Whether to do post processing the
+ inference results. It will convert the output to a list.
+
+ Returns:
+ Tensor | list: The inference results.
+
+ - If no post processing, the output is a tensor with shape
+ ``(num_samples, num_classes)``.
+ - If post processing, the output is a multi-dimentional list of
+ float and the dimensions are ``(num_samples, num_classes)``.
+ """
+ cls_token, dist_token = self.pre_logits(x)
+ cls_score = (self.layers.head(cls_token) +
+ self.layers.head_dist(dist_token)) / 2
+
+ if softmax:
+ pred = F.softmax(
+ cls_score, dim=1) if cls_score is not None else None
+ else:
+ pred = cls_score
+
+ if post_process:
+ return self.post_process(pred)
+ else:
+ return pred
+
+ def forward_train(self, x, gt_label):
+ logger = get_root_logger()
+ logger.warning("MMClassification doesn't support to train the "
+ 'distilled version DeiT.')
+ cls_token, dist_token = self.pre_logits(x)
+ cls_score = (self.layers.head(cls_token) +
+ self.layers.head_dist(dist_token)) / 2
+ losses = self.loss(cls_score, gt_label)
+ return losses
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/efficientformer_head.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/efficientformer_head.py
new file mode 100644
index 0000000000000000000000000000000000000000..3127f12e371233509eb65e397a46a2b5748c936d
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/efficientformer_head.py
@@ -0,0 +1,96 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import torch.nn as nn
+import torch.nn.functional as F
+
+from ..builder import HEADS
+from .cls_head import ClsHead
+
+
+@HEADS.register_module()
+class EfficientFormerClsHead(ClsHead):
+ """EfficientFormer classifier head.
+
+ Args:
+ num_classes (int): Number of categories excluding the background
+ category.
+ in_channels (int): Number of channels in the input feature map.
+ distillation (bool): Whether use a additional distilled head.
+ Defaults to True.
+ init_cfg (dict): The extra initialization configs. Defaults to
+ ``dict(type='Normal', layer='Linear', std=0.01)``.
+ """
+
+ def __init__(self,
+ num_classes,
+ in_channels,
+ distillation=True,
+ init_cfg=dict(type='Normal', layer='Linear', std=0.01),
+ *args,
+ **kwargs):
+ super(EfficientFormerClsHead, self).__init__(
+ init_cfg=init_cfg, *args, **kwargs)
+ self.in_channels = in_channels
+ self.num_classes = num_classes
+ self.dist = distillation
+
+ if self.num_classes <= 0:
+ raise ValueError(
+ f'num_classes={num_classes} must be a positive integer')
+
+ self.head = nn.Linear(self.in_channels, self.num_classes)
+ if self.dist:
+ self.dist_head = nn.Linear(self.in_channels, self.num_classes)
+
+ def pre_logits(self, x):
+ if isinstance(x, tuple):
+ x = x[-1]
+ return x
+
+ def simple_test(self, x, softmax=True, post_process=True):
+ """Inference without augmentation.
+
+ Args:
+ x (tuple[tuple[tensor, tensor]]): The input features.
+ Multi-stage inputs are acceptable but only the last stage will
+ be used to classify. Every item should be a tuple which
+ includes patch token and cls token. The cls token will be used
+ to classify and the shape of it should be
+ ``(num_samples, in_channels)``.
+ softmax (bool): Whether to softmax the classification score.
+ post_process (bool): Whether to do post processing the
+ inference results. It will convert the output to a list.
+
+ Returns:
+ Tensor | list: The inference results.
+
+ - If no post processing, the output is a tensor with shape
+ ``(num_samples, num_classes)``.
+ - If post processing, the output is a multi-dimentional list of
+ float and the dimensions are ``(num_samples, num_classes)``.
+ """
+ x = self.pre_logits(x)
+ cls_score = self.head(x)
+ if self.dist:
+ cls_score = (cls_score + self.dist_head(x)) / 2
+
+ if softmax:
+ pred = (
+ F.softmax(cls_score, dim=1) if cls_score is not None else None)
+ else:
+ pred = cls_score
+
+ if post_process:
+ return self.post_process(pred)
+ else:
+ return pred
+
+ def forward_train(self, x, gt_label, **kwargs):
+ if self.dist:
+ raise NotImplementedError(
+ "MMClassification doesn't support to train"
+ ' the distilled version EfficientFormer.')
+ else:
+ x = self.pre_logits(x)
+ cls_score = self.head(x)
+ losses = self.loss(cls_score, gt_label, **kwargs)
+ return losses
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/linear_head.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/linear_head.py
new file mode 100644
index 0000000000000000000000000000000000000000..113b41b685f61266b66a53bf02f9b84c65e04431
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/linear_head.py
@@ -0,0 +1,81 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import torch.nn as nn
+import torch.nn.functional as F
+
+from ..builder import HEADS
+from .cls_head import ClsHead
+
+
+@HEADS.register_module()
+class LinearClsHead(ClsHead):
+ """Linear classifier head.
+
+ Args:
+ num_classes (int): Number of categories excluding the background
+ category.
+ in_channels (int): Number of channels in the input feature map.
+ init_cfg (dict | optional): The extra init config of layers.
+ Defaults to use dict(type='Normal', layer='Linear', std=0.01).
+ """
+
+ def __init__(self,
+ num_classes,
+ in_channels,
+ init_cfg=dict(type='Normal', layer='Linear', std=0.01),
+ *args,
+ **kwargs):
+ super(LinearClsHead, self).__init__(init_cfg=init_cfg, *args, **kwargs)
+
+ self.in_channels = in_channels
+ self.num_classes = num_classes
+
+ if self.num_classes <= 0:
+ raise ValueError(
+ f'num_classes={num_classes} must be a positive integer')
+
+ self.fc = nn.Linear(self.in_channels, self.num_classes)
+
+ def pre_logits(self, x):
+ if isinstance(x, tuple):
+ x = x[-1]
+ return x
+
+ def simple_test(self, x, softmax=True, post_process=True):
+ """Inference without augmentation.
+
+ Args:
+ x (tuple[Tensor]): The input features.
+ Multi-stage inputs are acceptable but only the last stage will
+ be used to classify. The shape of every item should be
+ ``(num_samples, in_channels)``.
+ softmax (bool): Whether to softmax the classification score.
+ post_process (bool): Whether to do post processing the
+ inference results. It will convert the output to a list.
+
+ Returns:
+ Tensor | list: The inference results.
+
+ - If no post processing, the output is a tensor with shape
+ ``(num_samples, num_classes)``.
+ - If post processing, the output is a multi-dimentional list of
+ float and the dimensions are ``(num_samples, num_classes)``.
+ """
+ x = self.pre_logits(x)
+ cls_score = self.fc(x)
+
+ if softmax:
+ pred = (
+ F.softmax(cls_score, dim=1) if cls_score is not None else None)
+ else:
+ pred = cls_score
+
+ if post_process:
+ return self.post_process(pred)
+ else:
+ return pred
+
+ def forward_train(self, x, gt_label, **kwargs):
+ x = self.pre_logits(x)
+ cls_score = self.fc(x)
+ losses = self.loss(cls_score, gt_label, **kwargs)
+ return losses
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/multi_label_csra_head.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/multi_label_csra_head.py
new file mode 100644
index 0000000000000000000000000000000000000000..f28ba42bdb6af01aade6140e6168e4b712238bc0
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/multi_label_csra_head.py
@@ -0,0 +1,121 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+# Modified from https://github.com/Kevinz-code/CSRA
+import torch
+import torch.nn as nn
+from mmcv.runner import BaseModule, ModuleList
+
+from ..builder import HEADS
+from .multi_label_head import MultiLabelClsHead
+
+
+@HEADS.register_module()
+class CSRAClsHead(MultiLabelClsHead):
+ """Class-specific residual attention classifier head.
+
+ Residual Attention: A Simple but Effective Method for Multi-Label
+ Recognition (ICCV 2021)
+ Please refer to the `paper `__ for
+ details.
+
+ Args:
+ num_classes (int): Number of categories.
+ in_channels (int): Number of channels in the input feature map.
+ num_heads (int): Number of residual at tensor heads.
+ loss (dict): Config of classification loss.
+ lam (float): Lambda that combines global average and max pooling
+ scores.
+ init_cfg (dict | optional): The extra init config of layers.
+ Defaults to use dict(type='Normal', layer='Linear', std=0.01).
+ """
+ temperature_settings = { # softmax temperature settings
+ 1: [1],
+ 2: [1, 99],
+ 4: [1, 2, 4, 99],
+ 6: [1, 2, 3, 4, 5, 99],
+ 8: [1, 2, 3, 4, 5, 6, 7, 99]
+ }
+
+ def __init__(self,
+ num_classes,
+ in_channels,
+ num_heads,
+ lam,
+ loss=dict(
+ type='CrossEntropyLoss',
+ use_sigmoid=True,
+ reduction='mean',
+ loss_weight=1.0),
+ init_cfg=dict(type='Normal', layer='Linear', std=0.01),
+ *args,
+ **kwargs):
+ assert num_heads in self.temperature_settings.keys(
+ ), 'The num of heads is not in temperature setting.'
+ assert lam > 0, 'Lambda should be between 0 and 1.'
+ super(CSRAClsHead, self).__init__(
+ init_cfg=init_cfg, loss=loss, *args, **kwargs)
+ self.temp_list = self.temperature_settings[num_heads]
+ self.csra_heads = ModuleList([
+ CSRAModule(num_classes, in_channels, self.temp_list[i], lam)
+ for i in range(num_heads)
+ ])
+
+ def pre_logits(self, x):
+ if isinstance(x, tuple):
+ x = x[-1]
+ return x
+
+ def simple_test(self, x, post_process=True, **kwargs):
+ logit = 0.
+ x = self.pre_logits(x)
+ for head in self.csra_heads:
+ logit += head(x)
+ if post_process:
+ return self.post_process(logit)
+ else:
+ return logit
+
+ def forward_train(self, x, gt_label, **kwargs):
+ logit = 0.
+ x = self.pre_logits(x)
+ for head in self.csra_heads:
+ logit += head(x)
+ gt_label = gt_label.type_as(logit)
+ _gt_label = torch.abs(gt_label)
+ losses = self.loss(logit, _gt_label, **kwargs)
+ return losses
+
+
+class CSRAModule(BaseModule):
+ """Basic module of CSRA with different temperature.
+
+ Args:
+ num_classes (int): Number of categories.
+ in_channels (int): Number of channels in the input feature map.
+ T (int): Temperature setting.
+ lam (float): Lambda that combines global average and max pooling
+ scores.
+ init_cfg (dict | optional): The extra init config of layers.
+ Defaults to use dict(type='Normal', layer='Linear', std=0.01).
+ """
+
+ def __init__(self, num_classes, in_channels, T, lam, init_cfg=None):
+
+ super(CSRAModule, self).__init__(init_cfg=init_cfg)
+ self.T = T # temperature
+ self.lam = lam # Lambda
+ self.head = nn.Conv2d(in_channels, num_classes, 1, bias=False)
+ self.softmax = nn.Softmax(dim=2)
+
+ def forward(self, x):
+ score = self.head(x) / torch.norm(
+ self.head.weight, dim=1, keepdim=True).transpose(0, 1)
+ score = score.flatten(2)
+ base_logit = torch.mean(score, dim=2)
+
+ if self.T == 99: # max-pooling
+ att_logit = torch.max(score, dim=2)[0]
+ else:
+ score_soft = self.softmax(score * self.T)
+ att_logit = torch.sum(score * score_soft, dim=2)
+
+ return base_logit + self.lam * att_logit
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/multi_label_head.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/multi_label_head.py
new file mode 100644
index 0000000000000000000000000000000000000000..e11a7733192c47f357e7a5a89ec1f4851559b0b2
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/multi_label_head.py
@@ -0,0 +1,99 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import torch
+
+from ..builder import HEADS, build_loss
+from ..utils import is_tracing
+from .base_head import BaseHead
+
+
+@HEADS.register_module()
+class MultiLabelClsHead(BaseHead):
+ """Classification head for multilabel task.
+
+ Args:
+ loss (dict): Config of classification loss.
+ """
+
+ def __init__(self,
+ loss=dict(
+ type='CrossEntropyLoss',
+ use_sigmoid=True,
+ reduction='mean',
+ loss_weight=1.0),
+ init_cfg=None):
+ super(MultiLabelClsHead, self).__init__(init_cfg=init_cfg)
+
+ assert isinstance(loss, dict)
+
+ self.compute_loss = build_loss(loss)
+
+ def loss(self, cls_score, gt_label):
+ gt_label = gt_label.type_as(cls_score)
+ num_samples = len(cls_score)
+ losses = dict()
+
+ # map difficult examples to positive ones
+ _gt_label = torch.abs(gt_label)
+ # compute loss
+ loss = self.compute_loss(cls_score, _gt_label, avg_factor=num_samples)
+ losses['loss'] = loss
+ return losses
+
+ def forward_train(self, cls_score, gt_label, **kwargs):
+ if isinstance(cls_score, tuple):
+ cls_score = cls_score[-1]
+ gt_label = gt_label.type_as(cls_score)
+ losses = self.loss(cls_score, gt_label, **kwargs)
+ return losses
+
+ def pre_logits(self, x):
+ if isinstance(x, tuple):
+ x = x[-1]
+
+ from mmcls.utils import get_root_logger
+ logger = get_root_logger()
+ logger.warning(
+ 'The input of MultiLabelClsHead should be already logits. '
+ 'Please modify the backbone if you want to get pre-logits feature.'
+ )
+ return x
+
+ def simple_test(self, x, sigmoid=True, post_process=True):
+ """Inference without augmentation.
+
+ Args:
+ cls_score (tuple[Tensor]): The input classification score logits.
+ Multi-stage inputs are acceptable but only the last stage will
+ be used to classify. The shape of every item should be
+ ``(num_samples, num_classes)``.
+ sigmoid (bool): Whether to sigmoid the classification score.
+ post_process (bool): Whether to do post processing the
+ inference results. It will convert the output to a list.
+
+ Returns:
+ Tensor | list: The inference results.
+
+ - If no post processing, the output is a tensor with shape
+ ``(num_samples, num_classes)``.
+ - If post processing, the output is a multi-dimentional list of
+ float and the dimensions are ``(num_samples, num_classes)``.
+ """
+ if isinstance(x, tuple):
+ x = x[-1]
+
+ if sigmoid:
+ pred = torch.sigmoid(x) if x is not None else None
+ else:
+ pred = x
+
+ if post_process:
+ return self.post_process(pred)
+ else:
+ return pred
+
+ def post_process(self, pred):
+ on_trace = is_tracing()
+ if torch.onnx.is_in_onnx_export() or on_trace:
+ return pred
+ pred = list(pred.detach().cpu().numpy())
+ return pred
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/multi_label_linear_head.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/multi_label_linear_head.py
new file mode 100644
index 0000000000000000000000000000000000000000..0e9d0684a1b4aff4fa92ba807e550a4de98a6949
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/multi_label_linear_head.py
@@ -0,0 +1,85 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import torch
+import torch.nn as nn
+
+from ..builder import HEADS
+from .multi_label_head import MultiLabelClsHead
+
+
+@HEADS.register_module()
+class MultiLabelLinearClsHead(MultiLabelClsHead):
+ """Linear classification head for multilabel task.
+
+ Args:
+ num_classes (int): Number of categories.
+ in_channels (int): Number of channels in the input feature map.
+ loss (dict): Config of classification loss.
+ init_cfg (dict | optional): The extra init config of layers.
+ Defaults to use dict(type='Normal', layer='Linear', std=0.01).
+ """
+
+ def __init__(self,
+ num_classes,
+ in_channels,
+ loss=dict(
+ type='CrossEntropyLoss',
+ use_sigmoid=True,
+ reduction='mean',
+ loss_weight=1.0),
+ init_cfg=dict(type='Normal', layer='Linear', std=0.01)):
+ super(MultiLabelLinearClsHead, self).__init__(
+ loss=loss, init_cfg=init_cfg)
+
+ if num_classes <= 0:
+ raise ValueError(
+ f'num_classes={num_classes} must be a positive integer')
+
+ self.in_channels = in_channels
+ self.num_classes = num_classes
+
+ self.fc = nn.Linear(self.in_channels, self.num_classes)
+
+ def pre_logits(self, x):
+ if isinstance(x, tuple):
+ x = x[-1]
+ return x
+
+ def forward_train(self, x, gt_label, **kwargs):
+ x = self.pre_logits(x)
+ gt_label = gt_label.type_as(x)
+ cls_score = self.fc(x)
+ losses = self.loss(cls_score, gt_label, **kwargs)
+ return losses
+
+ def simple_test(self, x, sigmoid=True, post_process=True):
+ """Inference without augmentation.
+
+ Args:
+ x (tuple[Tensor]): The input features.
+ Multi-stage inputs are acceptable but only the last stage will
+ be used to classify. The shape of every item should be
+ ``(num_samples, in_channels)``.
+ sigmoid (bool): Whether to sigmoid the classification score.
+ post_process (bool): Whether to do post processing the
+ inference results. It will convert the output to a list.
+
+ Returns:
+ Tensor | list: The inference results.
+
+ - If no post processing, the output is a tensor with shape
+ ``(num_samples, num_classes)``.
+ - If post processing, the output is a multi-dimentional list of
+ float and the dimensions are ``(num_samples, num_classes)``.
+ """
+ x = self.pre_logits(x)
+ cls_score = self.fc(x)
+
+ if sigmoid:
+ pred = torch.sigmoid(cls_score) if cls_score is not None else None
+ else:
+ pred = cls_score
+
+ if post_process:
+ return self.post_process(pred)
+ else:
+ return pred
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/stacked_head.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/stacked_head.py
new file mode 100644
index 0000000000000000000000000000000000000000..bbb0dc24ccbd8e3e0bc6a50710cde416cb7f7f68
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/stacked_head.py
@@ -0,0 +1,163 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from typing import Dict, Sequence
+
+import torch.nn as nn
+import torch.nn.functional as F
+from mmcv.cnn import build_activation_layer, build_norm_layer
+from mmcv.runner import BaseModule, ModuleList
+
+from ..builder import HEADS
+from .cls_head import ClsHead
+
+
+class LinearBlock(BaseModule):
+
+ def __init__(self,
+ in_channels,
+ out_channels,
+ dropout_rate=0.,
+ norm_cfg=None,
+ act_cfg=None,
+ init_cfg=None):
+ super().__init__(init_cfg=init_cfg)
+ self.fc = nn.Linear(in_channels, out_channels)
+
+ self.norm = None
+ self.act = None
+ self.dropout = None
+
+ if norm_cfg is not None:
+ self.norm = build_norm_layer(norm_cfg, out_channels)[1]
+ if act_cfg is not None:
+ self.act = build_activation_layer(act_cfg)
+ if dropout_rate > 0:
+ self.dropout = nn.Dropout(p=dropout_rate)
+
+ def forward(self, x):
+ x = self.fc(x)
+ if self.norm is not None:
+ x = self.norm(x)
+ if self.act is not None:
+ x = self.act(x)
+ if self.dropout is not None:
+ x = self.dropout(x)
+ return x
+
+
+@HEADS.register_module()
+class StackedLinearClsHead(ClsHead):
+ """Classifier head with several hidden fc layer and a output fc layer.
+
+ Args:
+ num_classes (int): Number of categories.
+ in_channels (int): Number of channels in the input feature map.
+ mid_channels (Sequence): Number of channels in the hidden fc layers.
+ dropout_rate (float): Dropout rate after each hidden fc layer,
+ except the last layer. Defaults to 0.
+ norm_cfg (dict, optional): Config dict of normalization layer after
+ each hidden fc layer, except the last layer. Defaults to None.
+ act_cfg (dict, optional): Config dict of activation function after each
+ hidden layer, except the last layer. Defaults to use "ReLU".
+ """
+
+ def __init__(self,
+ num_classes: int,
+ in_channels: int,
+ mid_channels: Sequence,
+ dropout_rate: float = 0.,
+ norm_cfg: Dict = None,
+ act_cfg: Dict = dict(type='ReLU'),
+ **kwargs):
+ super(StackedLinearClsHead, self).__init__(**kwargs)
+ assert num_classes > 0, \
+ f'`num_classes` of StackedLinearClsHead must be a positive ' \
+ f'integer, got {num_classes} instead.'
+ self.num_classes = num_classes
+
+ self.in_channels = in_channels
+
+ assert isinstance(mid_channels, Sequence), \
+ f'`mid_channels` of StackedLinearClsHead should be a sequence, ' \
+ f'instead of {type(mid_channels)}'
+ self.mid_channels = mid_channels
+
+ self.dropout_rate = dropout_rate
+ self.norm_cfg = norm_cfg
+ self.act_cfg = act_cfg
+
+ self._init_layers()
+
+ def _init_layers(self):
+ self.layers = ModuleList()
+ in_channels = self.in_channels
+ for hidden_channels in self.mid_channels:
+ self.layers.append(
+ LinearBlock(
+ in_channels,
+ hidden_channels,
+ dropout_rate=self.dropout_rate,
+ norm_cfg=self.norm_cfg,
+ act_cfg=self.act_cfg))
+ in_channels = hidden_channels
+
+ self.layers.append(
+ LinearBlock(
+ self.mid_channels[-1],
+ self.num_classes,
+ dropout_rate=0.,
+ norm_cfg=None,
+ act_cfg=None))
+
+ def init_weights(self):
+ self.layers.init_weights()
+
+ def pre_logits(self, x):
+ if isinstance(x, tuple):
+ x = x[-1]
+ for layer in self.layers[:-1]:
+ x = layer(x)
+ return x
+
+ @property
+ def fc(self):
+ return self.layers[-1]
+
+ def simple_test(self, x, softmax=True, post_process=True):
+ """Inference without augmentation.
+
+ Args:
+ x (tuple[Tensor]): The input features.
+ Multi-stage inputs are acceptable but only the last stage will
+ be used to classify. The shape of every item should be
+ ``(num_samples, in_channels)``.
+ softmax (bool): Whether to softmax the classification score.
+ post_process (bool): Whether to do post processing the
+ inference results. It will convert the output to a list.
+
+ Returns:
+ Tensor | list: The inference results.
+
+ - If no post processing, the output is a tensor with shape
+ ``(num_samples, num_classes)``.
+ - If post processing, the output is a multi-dimentional list of
+ float and the dimensions are ``(num_samples, num_classes)``.
+ """
+ x = self.pre_logits(x)
+ cls_score = self.fc(x)
+
+ if softmax:
+ pred = (
+ F.softmax(cls_score, dim=1) if cls_score is not None else None)
+ else:
+ pred = cls_score
+
+ if post_process:
+ return self.post_process(pred)
+ else:
+ return pred
+
+ def forward_train(self, x, gt_label, **kwargs):
+ x = self.pre_logits(x)
+ cls_score = self.fc(x)
+ losses = self.loss(cls_score, gt_label, **kwargs)
+ return losses
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/vision_transformer_head.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/vision_transformer_head.py
new file mode 100644
index 0000000000000000000000000000000000000000..d0586cb9d78c03181c5fd286d477b87885b11576
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/heads/vision_transformer_head.py
@@ -0,0 +1,123 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import math
+from collections import OrderedDict
+
+import torch.nn as nn
+import torch.nn.functional as F
+from mmcv.cnn import build_activation_layer
+from mmcv.cnn.utils.weight_init import trunc_normal_
+from mmcv.runner import Sequential
+
+from ..builder import HEADS
+from .cls_head import ClsHead
+
+
+@HEADS.register_module()
+class VisionTransformerClsHead(ClsHead):
+ """Vision Transformer classifier head.
+
+ Args:
+ num_classes (int): Number of categories excluding the background
+ category.
+ in_channels (int): Number of channels in the input feature map.
+ hidden_dim (int): Number of the dimensions for hidden layer.
+ Defaults to None, which means no extra hidden layer.
+ act_cfg (dict): The activation config. Only available during
+ pre-training. Defaults to ``dict(type='Tanh')``.
+ init_cfg (dict): The extra initialization configs. Defaults to
+ ``dict(type='Constant', layer='Linear', val=0)``.
+ """
+
+ def __init__(self,
+ num_classes,
+ in_channels,
+ hidden_dim=None,
+ act_cfg=dict(type='Tanh'),
+ init_cfg=dict(type='Constant', layer='Linear', val=0),
+ *args,
+ **kwargs):
+ super(VisionTransformerClsHead, self).__init__(
+ init_cfg=init_cfg, *args, **kwargs)
+ self.in_channels = in_channels
+ self.num_classes = num_classes
+ self.hidden_dim = hidden_dim
+ self.act_cfg = act_cfg
+
+ if self.num_classes <= 0:
+ raise ValueError(
+ f'num_classes={num_classes} must be a positive integer')
+
+ self._init_layers()
+
+ def _init_layers(self):
+ if self.hidden_dim is None:
+ layers = [('head', nn.Linear(self.in_channels, self.num_classes))]
+ else:
+ layers = [
+ ('pre_logits', nn.Linear(self.in_channels, self.hidden_dim)),
+ ('act', build_activation_layer(self.act_cfg)),
+ ('head', nn.Linear(self.hidden_dim, self.num_classes)),
+ ]
+ self.layers = Sequential(OrderedDict(layers))
+
+ def init_weights(self):
+ super(VisionTransformerClsHead, self).init_weights()
+ # Modified from ClassyVision
+ if hasattr(self.layers, 'pre_logits'):
+ # Lecun norm
+ trunc_normal_(
+ self.layers.pre_logits.weight,
+ std=math.sqrt(1 / self.layers.pre_logits.in_features))
+ nn.init.zeros_(self.layers.pre_logits.bias)
+
+ def pre_logits(self, x):
+ if isinstance(x, tuple):
+ x = x[-1]
+ _, cls_token = x
+ if self.hidden_dim is None:
+ return cls_token
+ else:
+ x = self.layers.pre_logits(cls_token)
+ return self.layers.act(x)
+
+ def simple_test(self, x, softmax=True, post_process=True):
+ """Inference without augmentation.
+
+ Args:
+ x (tuple[tuple[tensor, tensor]]): The input features.
+ Multi-stage inputs are acceptable but only the last stage will
+ be used to classify. Every item should be a tuple which
+ includes patch token and cls token. The cls token will be used
+ to classify and the shape of it should be
+ ``(num_samples, in_channels)``.
+ softmax (bool): Whether to softmax the classification score.
+ post_process (bool): Whether to do post processing the
+ inference results. It will convert the output to a list.
+
+ Returns:
+ Tensor | list: The inference results.
+
+ - If no post processing, the output is a tensor with shape
+ ``(num_samples, num_classes)``.
+ - If post processing, the output is a multi-dimentional list of
+ float and the dimensions are ``(num_samples, num_classes)``.
+ """
+ x = self.pre_logits(x)
+ cls_score = self.layers.head(x)
+
+ if softmax:
+ pred = (
+ F.softmax(cls_score, dim=1) if cls_score is not None else None)
+ else:
+ pred = cls_score
+
+ if post_process:
+ return self.post_process(pred)
+ else:
+ return pred
+
+ def forward_train(self, x, gt_label, **kwargs):
+ x = self.pre_logits(x)
+ cls_score = self.layers.head(x)
+ losses = self.loss(cls_score, gt_label, **kwargs)
+ return losses
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/losses/__init__.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/losses/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..9c9008616978e869084cf10cbcd354bfe2aeda65
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/losses/__init__.py
@@ -0,0 +1,17 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from .accuracy import Accuracy, accuracy
+from .asymmetric_loss import AsymmetricLoss, asymmetric_loss
+from .cross_entropy_loss import (CrossEntropyLoss, binary_cross_entropy,
+ cross_entropy)
+from .focal_loss import FocalLoss, sigmoid_focal_loss
+from .label_smooth_loss import LabelSmoothLoss
+from .seesaw_loss import SeesawLoss
+from .utils import (convert_to_one_hot, reduce_loss, weight_reduce_loss,
+ weighted_loss)
+
+__all__ = [
+ 'accuracy', 'Accuracy', 'asymmetric_loss', 'AsymmetricLoss',
+ 'cross_entropy', 'binary_cross_entropy', 'CrossEntropyLoss', 'reduce_loss',
+ 'weight_reduce_loss', 'LabelSmoothLoss', 'weighted_loss', 'FocalLoss',
+ 'sigmoid_focal_loss', 'convert_to_one_hot', 'SeesawLoss'
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/losses/accuracy.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/losses/accuracy.py
new file mode 100644
index 0000000000000000000000000000000000000000..1b142bc70624051acf719c37f1ba08900b3c9110
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/losses/accuracy.py
@@ -0,0 +1,143 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from numbers import Number
+
+import numpy as np
+import torch
+import torch.nn as nn
+
+
+def accuracy_numpy(pred, target, topk=(1, ), thrs=0.):
+ if isinstance(thrs, Number):
+ thrs = (thrs, )
+ res_single = True
+ elif isinstance(thrs, tuple):
+ res_single = False
+ else:
+ raise TypeError(
+ f'thrs should be a number or tuple, but got {type(thrs)}.')
+
+ res = []
+ maxk = max(topk)
+ num = pred.shape[0]
+
+ static_inds = np.indices((num, maxk))[0]
+ pred_label = pred.argpartition(-maxk, axis=1)[:, -maxk:]
+ pred_score = pred[static_inds, pred_label]
+
+ sort_inds = np.argsort(pred_score, axis=1)[:, ::-1]
+ pred_label = pred_label[static_inds, sort_inds]
+ pred_score = pred_score[static_inds, sort_inds]
+
+ for k in topk:
+ correct_k = pred_label[:, :k] == target.reshape(-1, 1)
+ res_thr = []
+ for thr in thrs:
+ # Only prediction values larger than thr are counted as correct
+ _correct_k = correct_k & (pred_score[:, :k] > thr)
+ _correct_k = np.logical_or.reduce(_correct_k, axis=1)
+ res_thr.append((_correct_k.sum() * 100. / num))
+ if res_single:
+ res.append(res_thr[0])
+ else:
+ res.append(res_thr)
+ return res
+
+
+def accuracy_torch(pred, target, topk=(1, ), thrs=0.):
+ if isinstance(thrs, Number):
+ thrs = (thrs, )
+ res_single = True
+ elif isinstance(thrs, tuple):
+ res_single = False
+ else:
+ raise TypeError(
+ f'thrs should be a number or tuple, but got {type(thrs)}.')
+
+ res = []
+ maxk = max(topk)
+ num = pred.size(0)
+ pred = pred.float()
+ pred_score, pred_label = pred.topk(maxk, dim=1)
+ pred_label = pred_label.t()
+ correct = pred_label.eq(target.view(1, -1).expand_as(pred_label))
+ for k in topk:
+ res_thr = []
+ for thr in thrs:
+ # Only prediction values larger than thr are counted as correct
+ _correct = correct & (pred_score.t() > thr)
+ correct_k = _correct[:k].reshape(-1).float().sum(0, keepdim=True)
+ res_thr.append((correct_k.mul_(100. / num)))
+ if res_single:
+ res.append(res_thr[0])
+ else:
+ res.append(res_thr)
+ return res
+
+
+def accuracy(pred, target, topk=1, thrs=0.):
+ """Calculate accuracy according to the prediction and target.
+
+ Args:
+ pred (torch.Tensor | np.array): The model prediction.
+ target (torch.Tensor | np.array): The target of each prediction
+ topk (int | tuple[int]): If the predictions in ``topk``
+ matches the target, the predictions will be regarded as
+ correct ones. Defaults to 1.
+ thrs (Number | tuple[Number], optional): Predictions with scores under
+ the thresholds are considered negative. Default to 0.
+
+ Returns:
+ torch.Tensor | list[torch.Tensor] | list[list[torch.Tensor]]: Accuracy
+ - torch.Tensor: If both ``topk`` and ``thrs`` is a single value.
+ - list[torch.Tensor]: If one of ``topk`` or ``thrs`` is a tuple.
+ - list[list[torch.Tensor]]: If both ``topk`` and ``thrs`` is a \
+ tuple. And the first dim is ``topk``, the second dim is ``thrs``.
+ """
+ assert isinstance(topk, (int, tuple))
+ if isinstance(topk, int):
+ topk = (topk, )
+ return_single = True
+ else:
+ return_single = False
+
+ assert isinstance(pred, (torch.Tensor, np.ndarray)), \
+ f'The pred should be torch.Tensor or np.ndarray ' \
+ f'instead of {type(pred)}.'
+ assert isinstance(target, (torch.Tensor, np.ndarray)), \
+ f'The target should be torch.Tensor or np.ndarray ' \
+ f'instead of {type(target)}.'
+
+ # torch version is faster in most situations.
+ to_tensor = (lambda x: torch.from_numpy(x)
+ if isinstance(x, np.ndarray) else x)
+ pred = to_tensor(pred)
+ target = to_tensor(target)
+
+ res = accuracy_torch(pred, target, topk, thrs)
+
+ return res[0] if return_single else res
+
+
+class Accuracy(nn.Module):
+
+ def __init__(self, topk=(1, )):
+ """Module to calculate the accuracy.
+
+ Args:
+ topk (tuple): The criterion used to calculate the
+ accuracy. Defaults to (1,).
+ """
+ super().__init__()
+ self.topk = topk
+
+ def forward(self, pred, target):
+ """Forward function to calculate accuracy.
+
+ Args:
+ pred (torch.Tensor): Prediction of models.
+ target (torch.Tensor): Target for each prediction.
+
+ Returns:
+ list[torch.Tensor]: The accuracies under different topk criterions.
+ """
+ return accuracy(pred, target, self.topk)
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/losses/asymmetric_loss.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/losses/asymmetric_loss.py
new file mode 100644
index 0000000000000000000000000000000000000000..1c3b574492692395aa10d6c247780a0fddbb2853
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/losses/asymmetric_loss.py
@@ -0,0 +1,149 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import torch
+import torch.nn as nn
+
+from ..builder import LOSSES
+from .utils import convert_to_one_hot, weight_reduce_loss
+
+
+def asymmetric_loss(pred,
+ target,
+ weight=None,
+ gamma_pos=1.0,
+ gamma_neg=4.0,
+ clip=0.05,
+ reduction='mean',
+ avg_factor=None,
+ use_sigmoid=True,
+ eps=1e-8):
+ r"""asymmetric loss.
+
+ Please refer to the `paper `__ for
+ details.
+
+ Args:
+ pred (torch.Tensor): The prediction with shape (N, \*).
+ target (torch.Tensor): The ground truth label of the prediction with
+ shape (N, \*).
+ weight (torch.Tensor, optional): Sample-wise loss weight with shape
+ (N, ). Defaults to None.
+ gamma_pos (float): positive focusing parameter. Defaults to 0.0.
+ gamma_neg (float): Negative focusing parameter. We usually set
+ gamma_neg > gamma_pos. Defaults to 4.0.
+ clip (float, optional): Probability margin. Defaults to 0.05.
+ reduction (str): The method used to reduce the loss.
+ Options are "none", "mean" and "sum". If reduction is 'none' , loss
+ is same shape as pred and label. Defaults to 'mean'.
+ avg_factor (int, optional): Average factor that is used to average
+ the loss. Defaults to None.
+ use_sigmoid (bool): Whether the prediction uses sigmoid instead
+ of softmax. Defaults to True.
+ eps (float): The minimum value of the argument of logarithm. Defaults
+ to 1e-8.
+
+ Returns:
+ torch.Tensor: Loss.
+ """
+ assert pred.shape == \
+ target.shape, 'pred and target should be in the same shape.'
+
+ if use_sigmoid:
+ pred_sigmoid = pred.sigmoid()
+ else:
+ pred_sigmoid = nn.functional.softmax(pred, dim=-1)
+
+ target = target.type_as(pred)
+
+ if clip and clip > 0:
+ pt = (1 - pred_sigmoid +
+ clip).clamp(max=1) * (1 - target) + pred_sigmoid * target
+ else:
+ pt = (1 - pred_sigmoid) * (1 - target) + pred_sigmoid * target
+ asymmetric_weight = (1 - pt).pow(gamma_pos * target + gamma_neg *
+ (1 - target))
+ loss = -torch.log(pt.clamp(min=eps)) * asymmetric_weight
+ if weight is not None:
+ assert weight.dim() == 1
+ weight = weight.float()
+ if pred.dim() > 1:
+ weight = weight.reshape(-1, 1)
+ loss = weight_reduce_loss(loss, weight, reduction, avg_factor)
+ return loss
+
+
+@LOSSES.register_module()
+class AsymmetricLoss(nn.Module):
+ """asymmetric loss.
+
+ Args:
+ gamma_pos (float): positive focusing parameter.
+ Defaults to 0.0.
+ gamma_neg (float): Negative focusing parameter. We
+ usually set gamma_neg > gamma_pos. Defaults to 4.0.
+ clip (float, optional): Probability margin. Defaults to 0.05.
+ reduction (str): The method used to reduce the loss into
+ a scalar.
+ loss_weight (float): Weight of loss. Defaults to 1.0.
+ use_sigmoid (bool): Whether the prediction uses sigmoid instead
+ of softmax. Defaults to True.
+ eps (float): The minimum value of the argument of logarithm. Defaults
+ to 1e-8.
+ """
+
+ def __init__(self,
+ gamma_pos=0.0,
+ gamma_neg=4.0,
+ clip=0.05,
+ reduction='mean',
+ loss_weight=1.0,
+ use_sigmoid=True,
+ eps=1e-8):
+ super(AsymmetricLoss, self).__init__()
+ self.gamma_pos = gamma_pos
+ self.gamma_neg = gamma_neg
+ self.clip = clip
+ self.reduction = reduction
+ self.loss_weight = loss_weight
+ self.use_sigmoid = use_sigmoid
+ self.eps = eps
+
+ def forward(self,
+ pred,
+ target,
+ weight=None,
+ avg_factor=None,
+ reduction_override=None):
+ r"""asymmetric loss.
+
+ Args:
+ pred (torch.Tensor): The prediction with shape (N, \*).
+ target (torch.Tensor): The ground truth label of the prediction
+ with shape (N, \*), N or (N,1).
+ weight (torch.Tensor, optional): Sample-wise loss weight with shape
+ (N, \*). Defaults to None.
+ avg_factor (int, optional): Average factor that is used to average
+ the loss. Defaults to None.
+ reduction_override (str, optional): The method used to reduce the
+ loss into a scalar. Options are "none", "mean" and "sum".
+ Defaults to None.
+
+ Returns:
+ torch.Tensor: Loss.
+ """
+ assert reduction_override in (None, 'none', 'mean', 'sum')
+ reduction = (
+ reduction_override if reduction_override else self.reduction)
+ if target.dim() == 1 or (target.dim() == 2 and target.shape[1] == 1):
+ target = convert_to_one_hot(target.view(-1, 1), pred.shape[-1])
+ loss_cls = self.loss_weight * asymmetric_loss(
+ pred,
+ target,
+ weight,
+ gamma_pos=self.gamma_pos,
+ gamma_neg=self.gamma_neg,
+ clip=self.clip,
+ reduction=reduction,
+ avg_factor=avg_factor,
+ use_sigmoid=self.use_sigmoid,
+ eps=self.eps)
+ return loss_cls
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/losses/cross_entropy_loss.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/losses/cross_entropy_loss.py
new file mode 100644
index 0000000000000000000000000000000000000000..0b92212a30ab3cc3e24e1618e2c2faff84a31b2a
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/losses/cross_entropy_loss.py
@@ -0,0 +1,209 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import torch.nn as nn
+import torch.nn.functional as F
+
+from ..builder import LOSSES
+from .utils import weight_reduce_loss
+
+
+def cross_entropy(pred,
+ label,
+ weight=None,
+ reduction='mean',
+ avg_factor=None,
+ class_weight=None):
+ """Calculate the CrossEntropy loss.
+
+ Args:
+ pred (torch.Tensor): The prediction with shape (N, C), C is the number
+ of classes.
+ label (torch.Tensor): The gt label of the prediction.
+ weight (torch.Tensor, optional): Sample-wise loss weight.
+ reduction (str): The method used to reduce the loss.
+ avg_factor (int, optional): Average factor that is used to average
+ the loss. Defaults to None.
+ class_weight (torch.Tensor, optional): The weight for each class with
+ shape (C), C is the number of classes. Default None.
+
+ Returns:
+ torch.Tensor: The calculated loss
+ """
+ # element-wise losses
+ loss = F.cross_entropy(pred, label, weight=class_weight, reduction='none')
+
+ # apply weights and do the reduction
+ if weight is not None:
+ weight = weight.float()
+ loss = weight_reduce_loss(
+ loss, weight=weight, reduction=reduction, avg_factor=avg_factor)
+
+ return loss
+
+
+def soft_cross_entropy(pred,
+ label,
+ weight=None,
+ reduction='mean',
+ class_weight=None,
+ avg_factor=None):
+ """Calculate the Soft CrossEntropy loss. The label can be float.
+
+ Args:
+ pred (torch.Tensor): The prediction with shape (N, C), C is the number
+ of classes.
+ label (torch.Tensor): The gt label of the prediction with shape (N, C).
+ When using "mixup", the label can be float.
+ weight (torch.Tensor, optional): Sample-wise loss weight.
+ reduction (str): The method used to reduce the loss.
+ avg_factor (int, optional): Average factor that is used to average
+ the loss. Defaults to None.
+ class_weight (torch.Tensor, optional): The weight for each class with
+ shape (C), C is the number of classes. Default None.
+
+ Returns:
+ torch.Tensor: The calculated loss
+ """
+ # element-wise losses
+ loss = -label * F.log_softmax(pred, dim=-1)
+ if class_weight is not None:
+ loss *= class_weight
+ loss = loss.sum(dim=-1)
+
+ # apply weights and do the reduction
+ if weight is not None:
+ weight = weight.float()
+ loss = weight_reduce_loss(
+ loss, weight=weight, reduction=reduction, avg_factor=avg_factor)
+
+ return loss
+
+
+def binary_cross_entropy(pred,
+ label,
+ weight=None,
+ reduction='mean',
+ avg_factor=None,
+ class_weight=None,
+ pos_weight=None):
+ r"""Calculate the binary CrossEntropy loss with logits.
+
+ Args:
+ pred (torch.Tensor): The prediction with shape (N, \*).
+ label (torch.Tensor): The gt label with shape (N, \*).
+ weight (torch.Tensor, optional): Element-wise weight of loss with shape
+ (N, ). Defaults to None.
+ reduction (str): The method used to reduce the loss.
+ Options are "none", "mean" and "sum". If reduction is 'none' , loss
+ is same shape as pred and label. Defaults to 'mean'.
+ avg_factor (int, optional): Average factor that is used to average
+ the loss. Defaults to None.
+ class_weight (torch.Tensor, optional): The weight for each class with
+ shape (C), C is the number of classes. Default None.
+ pos_weight (torch.Tensor, optional): The positive weight for each
+ class with shape (C), C is the number of classes. Default None.
+
+ Returns:
+ torch.Tensor: The calculated loss
+ """
+ # Ensure that the size of class_weight is consistent with pred and label to
+ # avoid automatic boracast,
+ assert pred.dim() == label.dim()
+
+ if class_weight is not None:
+ N = pred.size()[0]
+ class_weight = class_weight.repeat(N, 1)
+ loss = F.binary_cross_entropy_with_logits(
+ pred,
+ label,
+ weight=class_weight,
+ pos_weight=pos_weight,
+ reduction='none')
+
+ # apply weights and do the reduction
+ if weight is not None:
+ assert weight.dim() == 1
+ weight = weight.float()
+ if pred.dim() > 1:
+ weight = weight.reshape(-1, 1)
+ loss = weight_reduce_loss(
+ loss, weight=weight, reduction=reduction, avg_factor=avg_factor)
+ return loss
+
+
+@LOSSES.register_module()
+class CrossEntropyLoss(nn.Module):
+ """Cross entropy loss.
+
+ Args:
+ use_sigmoid (bool): Whether the prediction uses sigmoid
+ of softmax. Defaults to False.
+ use_soft (bool): Whether to use the soft version of CrossEntropyLoss.
+ Defaults to False.
+ reduction (str): The method used to reduce the loss.
+ Options are "none", "mean" and "sum". Defaults to 'mean'.
+ loss_weight (float): Weight of the loss. Defaults to 1.0.
+ class_weight (List[float], optional): The weight for each class with
+ shape (C), C is the number of classes. Default None.
+ pos_weight (List[float], optional): The positive weight for each
+ class with shape (C), C is the number of classes. Only enabled in
+ BCE loss when ``use_sigmoid`` is True. Default None.
+ """
+
+ def __init__(self,
+ use_sigmoid=False,
+ use_soft=False,
+ reduction='mean',
+ loss_weight=1.0,
+ class_weight=None,
+ pos_weight=None):
+ super(CrossEntropyLoss, self).__init__()
+ self.use_sigmoid = use_sigmoid
+ self.use_soft = use_soft
+ assert not (
+ self.use_soft and self.use_sigmoid
+ ), 'use_sigmoid and use_soft could not be set simultaneously'
+
+ self.reduction = reduction
+ self.loss_weight = loss_weight
+ self.class_weight = class_weight
+ self.pos_weight = pos_weight
+
+ if self.use_sigmoid:
+ self.cls_criterion = binary_cross_entropy
+ elif self.use_soft:
+ self.cls_criterion = soft_cross_entropy
+ else:
+ self.cls_criterion = cross_entropy
+
+ def forward(self,
+ cls_score,
+ label,
+ weight=None,
+ avg_factor=None,
+ reduction_override=None,
+ **kwargs):
+ assert reduction_override in (None, 'none', 'mean', 'sum')
+ reduction = (
+ reduction_override if reduction_override else self.reduction)
+
+ if self.class_weight is not None:
+ class_weight = cls_score.new_tensor(self.class_weight)
+ else:
+ class_weight = None
+
+ # only BCE loss has pos_weight
+ if self.pos_weight is not None and self.use_sigmoid:
+ pos_weight = cls_score.new_tensor(self.pos_weight)
+ kwargs.update({'pos_weight': pos_weight})
+ else:
+ pos_weight = None
+
+ loss_cls = self.loss_weight * self.cls_criterion(
+ cls_score,
+ label,
+ weight,
+ class_weight=class_weight,
+ reduction=reduction,
+ avg_factor=avg_factor,
+ **kwargs)
+ return loss_cls
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/losses/focal_loss.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/losses/focal_loss.py
similarity index 84%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/models/losses/focal_loss.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/models/losses/focal_loss.py
index f8b61653efb8efd24016fd2527cbd505a8b0869b..8bd0c457f251face7fbc13d4ff86e32326be60ef 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/losses/focal_loss.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/losses/focal_loss.py
@@ -1,8 +1,9 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import torch.nn as nn
import torch.nn.functional as F
from ..builder import LOSSES
-from .utils import weight_reduce_loss
+from .utils import convert_to_one_hot, weight_reduce_loss
def sigmoid_focal_loss(pred,
@@ -12,14 +13,14 @@ def sigmoid_focal_loss(pred,
alpha=0.25,
reduction='mean',
avg_factor=None):
- """Sigmoid focal loss.
+ r"""Sigmoid focal loss.
Args:
- pred (torch.Tensor): The prediction with shape (N, *).
+ pred (torch.Tensor): The prediction with shape (N, \*).
target (torch.Tensor): The ground truth label of the prediction with
- shape (N, *).
+ shape (N, \*).
weight (torch.Tensor, optional): Sample-wise loss weight with shape
- (N, ). Dafaults to None.
+ (N, ). Defaults to None.
gamma (float): The gamma for calculating the modulating factor.
Defaults to 2.0.
alpha (float): A balanced form for Focal Loss. Defaults to 0.25.
@@ -82,16 +83,16 @@ class FocalLoss(nn.Module):
weight=None,
avg_factor=None,
reduction_override=None):
- """Sigmoid focal loss.
+ r"""Sigmoid focal loss.
Args:
- pred (torch.Tensor): The prediction with shape (N, *).
+ pred (torch.Tensor): The prediction with shape (N, \*).
target (torch.Tensor): The ground truth label of the prediction
- with shape (N, *).
+ with shape (N, \*), N or (N,1).
weight (torch.Tensor, optional): Sample-wise loss weight with shape
- (N, *). Dafaults to None.
+ (N, \*). Defaults to None.
avg_factor (int, optional): Average factor that is used to average
- the loss. Defaults to None.
+ the loss. Defaults to None.
reduction_override (str, optional): The method used to reduce the
loss into a scalar. Options are "none", "mean" and "sum".
Defaults to None.
@@ -102,6 +103,8 @@ class FocalLoss(nn.Module):
assert reduction_override in (None, 'none', 'mean', 'sum')
reduction = (
reduction_override if reduction_override else self.reduction)
+ if target.dim() == 1 or (target.dim() == 2 and target.shape[1] == 1):
+ target = convert_to_one_hot(target.view(-1, 1), pred.shape[-1])
loss_cls = self.loss_weight * sigmoid_focal_loss(
pred,
target,
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/losses/label_smooth_loss.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/losses/label_smooth_loss.py
similarity index 81%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/models/losses/label_smooth_loss.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/models/losses/label_smooth_loss.py
index f8a26fcdf1d488dd972ef9fb5ff71dc3e88c69d5..daa73444c442df96c28860c96a0f4f1ebb6c4098 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/losses/label_smooth_loss.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/losses/label_smooth_loss.py
@@ -1,5 +1,4 @@
-import warnings
-
+# Copyright (c) OpenMMLab. All rights reserved.
import torch
import torch.nn as nn
@@ -10,9 +9,10 @@ from .utils import convert_to_one_hot
@LOSSES.register_module()
class LabelSmoothLoss(nn.Module):
- r"""Intializer for the label smoothed cross entropy loss.
- Refers to `Rethinking the Inception Architecture for Computer Vision` -
- https://arxiv.org/abs/1512.00567
+ r"""Initializer for the label smoothed cross entropy loss.
+
+ Refers to `Rethinking the Inception Architecture for Computer Vision
+ `_
This decreases gap between output scores and encourages generalization.
Labels provided to forward can be one-hot like vectors (NxC) or class
@@ -24,7 +24,7 @@ class LabelSmoothLoss(nn.Module):
label_smooth_val (float): The degree of label smoothing.
num_classes (int, optional): Number of classes. Defaults to None.
mode (str): Refers to notes, Options are 'original', 'classy_vision',
- 'multi_label'. Defaults to 'classy_vision'
+ 'multi_label'. Defaults to 'original'
reduction (str): The method used to reduce the loss.
Options are "none", "mean" and "sum". Defaults to 'mean'.
loss_weight (float): Weight of the loss. Defaults to 1.0.
@@ -34,7 +34,7 @@ class LabelSmoothLoss(nn.Module):
as the original paper as:
.. math::
- (1-\epsilon)\delta_{k, y} + \frac{\epsilon}{K}
+ (1-\epsilon)\delta_{k, y} + \frac{\epsilon}{K}
where epsilon is the `label_smooth_val`, K is the num_classes and
delta(k,y) is Dirac delta, which equals 1 for k=y and 0 otherwise.
@@ -43,19 +43,19 @@ class LabelSmoothLoss(nn.Module):
method as the facebookresearch/ClassyVision repo as:
.. math::
- \frac{\delta_{k, y} + \epsilon/K}{1+\epsilon}
+ \frac{\delta_{k, y} + \epsilon/K}{1+\epsilon}
if the mode is "multi_label", this will accept labels from multi-label
task and smoothing them as:
.. math::
- (1-2\epsilon)\delta_{k, y} + \epsilon
+ (1-2\epsilon)\delta_{k, y} + \epsilon
"""
def __init__(self,
label_smooth_val,
num_classes=None,
- mode=None,
+ mode='original',
reduction='mean',
loss_weight=1.0):
super().__init__()
@@ -74,14 +74,6 @@ class LabelSmoothLoss(nn.Module):
f'but gets {mode}.'
self.reduction = reduction
- if mode is None:
- warnings.warn(
- 'LabelSmoothLoss mode is not set, use "classy_vision" '
- 'by default. The default value will be changed to '
- '"original" recently. Please set mode manually if want '
- 'to keep "classy_vision".', UserWarning)
- mode = 'classy_vision'
-
accept_mode = {'original', 'classy_vision', 'multi_label'}
assert mode in accept_mode, \
f'LabelSmoothLoss supports mode {accept_mode}, but gets {mode}.'
@@ -124,6 +116,23 @@ class LabelSmoothLoss(nn.Module):
avg_factor=None,
reduction_override=None,
**kwargs):
+ r"""Label smooth loss.
+
+ Args:
+ pred (torch.Tensor): The prediction with shape (N, \*).
+ label (torch.Tensor): The ground truth label of the prediction
+ with shape (N, \*).
+ weight (torch.Tensor, optional): Sample-wise loss weight with shape
+ (N, \*). Defaults to None.
+ avg_factor (int, optional): Average factor that is used to average
+ the loss. Defaults to None.
+ reduction_override (str, optional): The method used to reduce the
+ loss into a scalar. Options are "none", "mean" and "sum".
+ Defaults to None.
+
+ Returns:
+ torch.Tensor: Loss.
+ """
if self.num_classes is not None:
assert self.num_classes == cls_score.shape[1], \
f'num_classes should equal to cls_score.shape[1], ' \
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/losses/seesaw_loss.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/losses/seesaw_loss.py
new file mode 100644
index 0000000000000000000000000000000000000000..14176de61d51c7cb1ebc45411248d19eff371917
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/losses/seesaw_loss.py
@@ -0,0 +1,173 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+# migrate from mmdetection with modifications
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+
+from ..builder import LOSSES
+from .utils import weight_reduce_loss
+
+
+def seesaw_ce_loss(cls_score,
+ labels,
+ weight,
+ cum_samples,
+ num_classes,
+ p,
+ q,
+ eps,
+ reduction='mean',
+ avg_factor=None):
+ """Calculate the Seesaw CrossEntropy loss.
+
+ Args:
+ cls_score (torch.Tensor): The prediction with shape (N, C),
+ C is the number of classes.
+ labels (torch.Tensor): The learning label of the prediction.
+ weight (torch.Tensor): Sample-wise loss weight.
+ cum_samples (torch.Tensor): Cumulative samples for each category.
+ num_classes (int): The number of classes.
+ p (float): The ``p`` in the mitigation factor.
+ q (float): The ``q`` in the compenstation factor.
+ eps (float): The minimal value of divisor to smooth
+ the computation of compensation factor
+ reduction (str, optional): The method used to reduce the loss.
+ avg_factor (int, optional): Average factor that is used to average
+ the loss. Defaults to None.
+
+ Returns:
+ torch.Tensor: The calculated loss
+ """
+ assert cls_score.size(-1) == num_classes
+ assert len(cum_samples) == num_classes
+
+ onehot_labels = F.one_hot(labels, num_classes)
+ seesaw_weights = cls_score.new_ones(onehot_labels.size())
+
+ # mitigation factor
+ if p > 0:
+ sample_ratio_matrix = cum_samples[None, :].clamp(
+ min=1) / cum_samples[:, None].clamp(min=1)
+ index = (sample_ratio_matrix < 1.0).float()
+ sample_weights = sample_ratio_matrix.pow(p) * index + (1 - index
+ ) # M_{ij}
+ mitigation_factor = sample_weights[labels.long(), :]
+ seesaw_weights = seesaw_weights * mitigation_factor
+
+ # compensation factor
+ if q > 0:
+ scores = F.softmax(cls_score.detach(), dim=1)
+ self_scores = scores[
+ torch.arange(0, len(scores)).to(scores.device).long(),
+ labels.long()]
+ score_matrix = scores / self_scores[:, None].clamp(min=eps)
+ index = (score_matrix > 1.0).float()
+ compensation_factor = score_matrix.pow(q) * index + (1 - index)
+ seesaw_weights = seesaw_weights * compensation_factor
+
+ cls_score = cls_score + (seesaw_weights.log() * (1 - onehot_labels))
+
+ loss = F.cross_entropy(cls_score, labels, weight=None, reduction='none')
+
+ if weight is not None:
+ weight = weight.float()
+ loss = weight_reduce_loss(
+ loss, weight=weight, reduction=reduction, avg_factor=avg_factor)
+ return loss
+
+
+@LOSSES.register_module()
+class SeesawLoss(nn.Module):
+ """Implementation of seesaw loss.
+
+ Refers to `Seesaw Loss for Long-Tailed Instance Segmentation (CVPR 2021)
+ `_
+
+ Args:
+ use_sigmoid (bool): Whether the prediction uses sigmoid of softmax.
+ Only False is supported. Defaults to False.
+ p (float): The ``p`` in the mitigation factor.
+ Defaults to 0.8.
+ q (float): The ``q`` in the compenstation factor.
+ Defaults to 2.0.
+ num_classes (int): The number of classes.
+ Default to 1000 for the ImageNet dataset.
+ eps (float): The minimal value of divisor to smooth
+ the computation of compensation factor, default to 1e-2.
+ reduction (str): The method that reduces the loss to a scalar.
+ Options are "none", "mean" and "sum". Default to "mean".
+ loss_weight (float): The weight of the loss. Defaults to 1.0
+ """
+
+ def __init__(self,
+ use_sigmoid=False,
+ p=0.8,
+ q=2.0,
+ num_classes=1000,
+ eps=1e-2,
+ reduction='mean',
+ loss_weight=1.0):
+ super(SeesawLoss, self).__init__()
+ assert not use_sigmoid, '`use_sigmoid` is not supported'
+ self.use_sigmoid = False
+ self.p = p
+ self.q = q
+ self.num_classes = num_classes
+ self.eps = eps
+ self.reduction = reduction
+ self.loss_weight = loss_weight
+
+ self.cls_criterion = seesaw_ce_loss
+
+ # cumulative samples for each category
+ self.register_buffer('cum_samples',
+ torch.zeros(self.num_classes, dtype=torch.float))
+
+ def forward(self,
+ cls_score,
+ labels,
+ weight=None,
+ avg_factor=None,
+ reduction_override=None):
+ """Forward function.
+
+ Args:
+ cls_score (torch.Tensor): The prediction with shape (N, C).
+ labels (torch.Tensor): The learning label of the prediction.
+ weight (torch.Tensor, optional): Sample-wise loss weight.
+ avg_factor (int, optional): Average factor that is used to average
+ the loss. Defaults to None.
+ reduction (str, optional): The method used to reduce the loss.
+ Options are "none", "mean" and "sum".
+ Returns:
+ torch.Tensor: The calculated loss
+ """
+ assert reduction_override in (None, 'none', 'mean', 'sum'), \
+ f'The `reduction_override` should be one of (None, "none", ' \
+ f'"mean", "sum"), but get "{reduction_override}".'
+ assert cls_score.size(0) == labels.view(-1).size(0), \
+ f'Expected `labels` shape [{cls_score.size(0)}], ' \
+ f'but got {list(labels.size())}'
+ reduction = (
+ reduction_override if reduction_override else self.reduction)
+ assert cls_score.size(-1) == self.num_classes, \
+ f'The channel number of output ({cls_score.size(-1)}) does ' \
+ f'not match the `num_classes` of seesaw loss ({self.num_classes}).'
+
+ # accumulate the samples for each category
+ unique_labels = labels.unique()
+ for u_l in unique_labels:
+ inds_ = labels == u_l.item()
+ self.cum_samples[u_l] += inds_.sum()
+
+ if weight is not None:
+ weight = weight.float()
+ else:
+ weight = labels.new_ones(labels.size(), dtype=torch.float)
+
+ # calculate loss_cls_classes
+ loss_cls = self.loss_weight * self.cls_criterion(
+ cls_score, labels, weight, self.cum_samples, self.num_classes,
+ self.p, self.q, self.eps, reduction, avg_factor)
+
+ return loss_cls
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/losses/utils.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/losses/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..a65b68a6590aa3fe10a023022c9c9c9bce51f935
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/losses/utils.py
@@ -0,0 +1,119 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import functools
+
+import torch
+import torch.nn.functional as F
+
+
+def reduce_loss(loss, reduction):
+ """Reduce loss as specified.
+
+ Args:
+ loss (Tensor): Elementwise loss tensor.
+ reduction (str): Options are "none", "mean" and "sum".
+
+ Return:
+ Tensor: Reduced loss tensor.
+ """
+ reduction_enum = F._Reduction.get_enum(reduction)
+ # none: 0, elementwise_mean:1, sum: 2
+ if reduction_enum == 0:
+ return loss
+ elif reduction_enum == 1:
+ return loss.mean()
+ elif reduction_enum == 2:
+ return loss.sum()
+
+
+def weight_reduce_loss(loss, weight=None, reduction='mean', avg_factor=None):
+ """Apply element-wise weight and reduce loss.
+
+ Args:
+ loss (Tensor): Element-wise loss.
+ weight (Tensor): Element-wise weights.
+ reduction (str): Same as built-in losses of PyTorch.
+ avg_factor (float): Average factor when computing the mean of losses.
+
+ Returns:
+ Tensor: Processed loss values.
+ """
+ # if weight is specified, apply element-wise weight
+ if weight is not None:
+ loss = loss * weight
+
+ # if avg_factor is not specified, just reduce the loss
+ if avg_factor is None:
+ loss = reduce_loss(loss, reduction)
+ else:
+ # if reduction is mean, then average the loss by avg_factor
+ if reduction == 'mean':
+ loss = loss.sum() / avg_factor
+ # if reduction is 'none', then do nothing, otherwise raise an error
+ elif reduction != 'none':
+ raise ValueError('avg_factor can not be used with reduction="sum"')
+ return loss
+
+
+def weighted_loss(loss_func):
+ """Create a weighted version of a given loss function.
+
+ To use this decorator, the loss function must have the signature like
+ ``loss_func(pred, target, **kwargs)``. The function only needs to compute
+ element-wise loss without any reduction. This decorator will add weight
+ and reduction arguments to the function. The decorated function will have
+ the signature like ``loss_func(pred, target, weight=None, reduction='mean',
+ avg_factor=None, **kwargs)``.
+
+ :Example:
+
+ >>> import torch
+ >>> @weighted_loss
+ >>> def l1_loss(pred, target):
+ >>> return (pred - target).abs()
+
+ >>> pred = torch.Tensor([0, 2, 3])
+ >>> target = torch.Tensor([1, 1, 1])
+ >>> weight = torch.Tensor([1, 0, 1])
+
+ >>> l1_loss(pred, target)
+ tensor(1.3333)
+ >>> l1_loss(pred, target, weight)
+ tensor(1.)
+ >>> l1_loss(pred, target, reduction='none')
+ tensor([1., 1., 2.])
+ >>> l1_loss(pred, target, weight, avg_factor=2)
+ tensor(1.5000)
+ """
+
+ @functools.wraps(loss_func)
+ def wrapper(pred,
+ target,
+ weight=None,
+ reduction='mean',
+ avg_factor=None,
+ **kwargs):
+ # get element-wise loss
+ loss = loss_func(pred, target, **kwargs)
+ loss = weight_reduce_loss(loss, weight, reduction, avg_factor)
+ return loss
+
+ return wrapper
+
+
+def convert_to_one_hot(targets: torch.Tensor, classes) -> torch.Tensor:
+ """This function converts target class indices to one-hot vectors, given
+ the number of classes.
+
+ Args:
+ targets (Tensor): The ground truth label of the prediction
+ with shape (N, 1)
+ classes (int): the number of classes.
+
+ Returns:
+ Tensor: Processed loss values.
+ """
+ assert (torch.max(targets).item() <
+ classes), 'Class Index must be less than number of classes'
+ one_hot_targets = F.one_hot(
+ targets.long().squeeze(-1), num_classes=classes)
+ return one_hot_targets
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/necks/__init__.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/necks/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..aa5411f01876701cf62c00154accadeeeaf511a0
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/necks/__init__.py
@@ -0,0 +1,6 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from .gap import GlobalAveragePooling
+from .gem import GeneralizedMeanPooling
+from .hr_fuse import HRFuseScales
+
+__all__ = ['GlobalAveragePooling', 'GeneralizedMeanPooling', 'HRFuseScales']
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/necks/gap.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/necks/gap.py
similarity index 96%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/models/necks/gap.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/models/necks/gap.py
index 3b8835c4be92e9cfe106040dbdb2c11f2a16c6ce..f64cce0ffd1ce5f1e3baddf79293be4290d79f09 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/necks/gap.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/necks/gap.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import torch
import torch.nn as nn
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/necks/gem.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/necks/gem.py
new file mode 100644
index 0000000000000000000000000000000000000000..f499357c0d34a5fa9dbb3fc813eb8f02da3dd39d
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/necks/gem.py
@@ -0,0 +1,53 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import torch
+from torch import Tensor, nn
+from torch.nn import functional as F
+from torch.nn.parameter import Parameter
+
+from ..builder import NECKS
+
+
+def gem(x: Tensor, p: Parameter, eps: float = 1e-6, clamp=True) -> Tensor:
+ if clamp:
+ x = x.clamp(min=eps)
+ return F.avg_pool2d(x.pow(p), (x.size(-2), x.size(-1))).pow(1. / p)
+
+
+@NECKS.register_module()
+class GeneralizedMeanPooling(nn.Module):
+ """Generalized Mean Pooling neck.
+
+ Note that we use `view` to remove extra channel after pooling. We do not
+ use `squeeze` as it will also remove the batch dimension when the tensor
+ has a batch dimension of size 1, which can lead to unexpected errors.
+
+ Args:
+ p (float): Parameter value.
+ Default: 3.
+ eps (float): epsilon.
+ Default: 1e-6
+ clamp (bool): Use clamp before pooling.
+ Default: True
+ """
+
+ def __init__(self, p=3., eps=1e-6, clamp=True):
+ assert p >= 1, "'p' must be a value greater then 1"
+ super(GeneralizedMeanPooling, self).__init__()
+ self.p = Parameter(torch.ones(1) * p)
+ self.eps = eps
+ self.clamp = clamp
+
+ def forward(self, inputs):
+ if isinstance(inputs, tuple):
+ outs = tuple([
+ gem(x, p=self.p, eps=self.eps, clamp=self.clamp)
+ for x in inputs
+ ])
+ outs = tuple(
+ [out.view(x.size(0), -1) for out, x in zip(outs, inputs)])
+ elif isinstance(inputs, torch.Tensor):
+ outs = gem(inputs, p=self.p, eps=self.eps, clamp=self.clamp)
+ outs = outs.view(inputs.size(0), -1)
+ else:
+ raise TypeError('neck inputs should be tuple or torch.tensor')
+ return outs
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/necks/hr_fuse.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/necks/hr_fuse.py
new file mode 100644
index 0000000000000000000000000000000000000000..1acc382756b125d310284033807ab3cee7447307
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/necks/hr_fuse.py
@@ -0,0 +1,83 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import torch.nn as nn
+from mmcv.cnn.bricks import ConvModule
+from mmcv.runner import BaseModule
+
+from ..backbones.resnet import Bottleneck, ResLayer
+from ..builder import NECKS
+
+
+@NECKS.register_module()
+class HRFuseScales(BaseModule):
+ """Fuse feature map of multiple scales in HRNet.
+
+ Args:
+ in_channels (list[int]): The input channels of all scales.
+ out_channels (int): The channels of fused feature map.
+ Defaults to 2048.
+ norm_cfg (dict): dictionary to construct norm layers.
+ Defaults to ``dict(type='BN', momentum=0.1)``.
+ init_cfg (dict | list[dict], optional): Initialization config dict.
+ Defaults to ``dict(type='Normal', layer='Linear', std=0.01))``.
+ """
+
+ def __init__(self,
+ in_channels,
+ out_channels=2048,
+ norm_cfg=dict(type='BN', momentum=0.1),
+ init_cfg=dict(type='Normal', layer='Linear', std=0.01)):
+ super(HRFuseScales, self).__init__(init_cfg=init_cfg)
+ self.in_channels = in_channels
+ self.out_channels = out_channels
+ self.norm_cfg = norm_cfg
+
+ block_type = Bottleneck
+ out_channels = [128, 256, 512, 1024]
+
+ # Increase the channels on each resolution
+ # from C, 2C, 4C, 8C to 128, 256, 512, 1024
+ increase_layers = []
+ for i in range(len(in_channels)):
+ increase_layers.append(
+ ResLayer(
+ block_type,
+ in_channels=in_channels[i],
+ out_channels=out_channels[i],
+ num_blocks=1,
+ stride=1,
+ ))
+ self.increase_layers = nn.ModuleList(increase_layers)
+
+ # Downsample feature maps in each scale.
+ downsample_layers = []
+ for i in range(len(in_channels) - 1):
+ downsample_layers.append(
+ ConvModule(
+ in_channels=out_channels[i],
+ out_channels=out_channels[i + 1],
+ kernel_size=3,
+ stride=2,
+ padding=1,
+ norm_cfg=self.norm_cfg,
+ bias=False,
+ ))
+ self.downsample_layers = nn.ModuleList(downsample_layers)
+
+ # The final conv block before final classifier linear layer.
+ self.final_layer = ConvModule(
+ in_channels=out_channels[3],
+ out_channels=self.out_channels,
+ kernel_size=1,
+ norm_cfg=self.norm_cfg,
+ bias=False,
+ )
+
+ def forward(self, x):
+ assert isinstance(x, tuple) and len(x) == len(self.in_channels)
+
+ feat = self.increase_layers[0](x[0])
+ for i in range(len(self.downsample_layers)):
+ feat = self.downsample_layers[i](feat) + \
+ self.increase_layers[i + 1](x[i + 1])
+
+ return (self.final_layer(feat), )
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/__init__.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..05af4db9bcde60cce6f464ad1863876272554d0d
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/__init__.py
@@ -0,0 +1,20 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from .attention import MultiheadAttention, ShiftWindowMSA, WindowMSAV2
+from .augment.augments import Augments
+from .channel_shuffle import channel_shuffle
+from .embed import (HybridEmbed, PatchEmbed, PatchMerging, resize_pos_embed,
+ resize_relative_position_bias_table)
+from .helpers import is_tracing, to_2tuple, to_3tuple, to_4tuple, to_ntuple
+from .inverted_residual import InvertedResidual
+from .layer_scale import LayerScale
+from .make_divisible import make_divisible
+from .position_encoding import ConditionalPositionEncoding
+from .se_layer import SELayer
+
+__all__ = [
+ 'channel_shuffle', 'make_divisible', 'InvertedResidual', 'SELayer',
+ 'to_ntuple', 'to_2tuple', 'to_3tuple', 'to_4tuple', 'PatchEmbed',
+ 'PatchMerging', 'HybridEmbed', 'Augments', 'ShiftWindowMSA', 'is_tracing',
+ 'MultiheadAttention', 'ConditionalPositionEncoding', 'resize_pos_embed',
+ 'resize_relative_position_bias_table', 'WindowMSAV2', 'LayerScale'
+]
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/attention.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/attention.py
new file mode 100644
index 0000000000000000000000000000000000000000..1aae72ae5a799854b898ae269c22f1095afea964
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/attention.py
@@ -0,0 +1,564 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import warnings
+
+import numpy as np
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+from mmcv.cnn.bricks.registry import DROPOUT_LAYERS
+from mmcv.cnn.bricks.transformer import build_dropout
+from mmcv.cnn.utils.weight_init import trunc_normal_
+from mmcv.runner.base_module import BaseModule
+
+from ..builder import ATTENTION
+from .helpers import to_2tuple
+
+
+class WindowMSA(BaseModule):
+ """Window based multi-head self-attention (W-MSA) module with relative
+ position bias.
+
+ Args:
+ embed_dims (int): Number of input channels.
+ window_size (tuple[int]): The height and width of the window.
+ num_heads (int): Number of attention heads.
+ qkv_bias (bool, optional): If True, add a learnable bias to q, k, v.
+ Defaults to True.
+ qk_scale (float, optional): Override default qk scale of
+ ``head_dim ** -0.5`` if set. Defaults to None.
+ attn_drop (float, optional): Dropout ratio of attention weight.
+ Defaults to 0.
+ proj_drop (float, optional): Dropout ratio of output. Defaults to 0.
+ init_cfg (dict, optional): The extra config for initialization.
+ Defaults to None.
+ """
+
+ def __init__(self,
+ embed_dims,
+ window_size,
+ num_heads,
+ qkv_bias=True,
+ qk_scale=None,
+ attn_drop=0.,
+ proj_drop=0.,
+ init_cfg=None):
+
+ super().__init__(init_cfg)
+ self.embed_dims = embed_dims
+ self.window_size = window_size # Wh, Ww
+ self.num_heads = num_heads
+ head_embed_dims = embed_dims // num_heads
+ self.scale = qk_scale or head_embed_dims**-0.5
+
+ # define a parameter table of relative position bias
+ self.relative_position_bias_table = nn.Parameter(
+ torch.zeros((2 * window_size[0] - 1) * (2 * window_size[1] - 1),
+ num_heads)) # 2*Wh-1 * 2*Ww-1, nH
+
+ # About 2x faster than original impl
+ Wh, Ww = self.window_size
+ rel_index_coords = self.double_step_seq(2 * Ww - 1, Wh, 1, Ww)
+ rel_position_index = rel_index_coords + rel_index_coords.T
+ rel_position_index = rel_position_index.flip(1).contiguous()
+ self.register_buffer('relative_position_index', rel_position_index)
+
+ self.qkv = nn.Linear(embed_dims, embed_dims * 3, bias=qkv_bias)
+ self.attn_drop = nn.Dropout(attn_drop)
+ self.proj = nn.Linear(embed_dims, embed_dims)
+ self.proj_drop = nn.Dropout(proj_drop)
+
+ self.softmax = nn.Softmax(dim=-1)
+
+ def init_weights(self):
+ super(WindowMSA, self).init_weights()
+
+ trunc_normal_(self.relative_position_bias_table, std=0.02)
+
+ def forward(self, x, mask=None):
+ """
+ Args:
+
+ x (tensor): input features with shape of (num_windows*B, N, C)
+ mask (tensor, Optional): mask with shape of (num_windows, Wh*Ww,
+ Wh*Ww), value should be between (-inf, 0].
+ """
+ B_, N, C = x.shape
+ qkv = self.qkv(x).reshape(B_, N, 3, self.num_heads,
+ C // self.num_heads).permute(2, 0, 3, 1, 4)
+ q, k, v = qkv[0], qkv[1], qkv[
+ 2] # make torchscript happy (cannot use tensor as tuple)
+
+ q = q * self.scale
+ attn = (q @ k.transpose(-2, -1))
+
+ relative_position_bias = self.relative_position_bias_table[
+ self.relative_position_index.view(-1)].view(
+ self.window_size[0] * self.window_size[1],
+ self.window_size[0] * self.window_size[1],
+ -1) # Wh*Ww,Wh*Ww,nH
+ relative_position_bias = relative_position_bias.permute(
+ 2, 0, 1).contiguous() # nH, Wh*Ww, Wh*Ww
+ attn = attn + relative_position_bias.unsqueeze(0)
+
+ if mask is not None:
+ nW = mask.shape[0]
+ attn = attn.view(B_ // nW, nW, self.num_heads, N,
+ N) + mask.unsqueeze(1).unsqueeze(0)
+ attn = attn.view(-1, self.num_heads, N, N)
+ attn = self.softmax(attn)
+ else:
+ attn = self.softmax(attn)
+
+ attn = self.attn_drop(attn)
+
+ x = (attn @ v).transpose(1, 2).reshape(B_, N, C)
+ x = self.proj(x)
+ x = self.proj_drop(x)
+ return x
+
+ @staticmethod
+ def double_step_seq(step1, len1, step2, len2):
+ seq1 = torch.arange(0, step1 * len1, step1)
+ seq2 = torch.arange(0, step2 * len2, step2)
+ return (seq1[:, None] + seq2[None, :]).reshape(1, -1)
+
+
+class WindowMSAV2(BaseModule):
+ """Window based multi-head self-attention (W-MSA) module with relative
+ position bias.
+
+ Based on implementation on Swin Transformer V2 original repo. Refers to
+ https://github.com/microsoft/Swin-Transformer/blob/main/models/swin_transformer_v2.py
+ for more details.
+
+ Args:
+ embed_dims (int): Number of input channels.
+ window_size (tuple[int]): The height and width of the window.
+ num_heads (int): Number of attention heads.
+ qkv_bias (bool, optional): If True, add a learnable bias to q, k, v.
+ Defaults to True.
+ attn_drop (float, optional): Dropout ratio of attention weight.
+ Defaults to 0.
+ proj_drop (float, optional): Dropout ratio of output. Defaults to 0.
+ pretrained_window_size (tuple(int)): The height and width of the window
+ in pre-training.
+ init_cfg (dict, optional): The extra config for initialization.
+ Defaults to None.
+ """
+
+ def __init__(self,
+ embed_dims,
+ window_size,
+ num_heads,
+ qkv_bias=True,
+ attn_drop=0.,
+ proj_drop=0.,
+ cpb_mlp_hidden_dims=512,
+ pretrained_window_size=(0, 0),
+ init_cfg=None,
+ **kwargs): # accept extra arguments
+
+ super().__init__(init_cfg)
+ self.embed_dims = embed_dims
+ self.window_size = window_size # Wh, Ww
+ self.num_heads = num_heads
+
+ # Use small network for continuous relative position bias
+ self.cpb_mlp = nn.Sequential(
+ nn.Linear(
+ in_features=2, out_features=cpb_mlp_hidden_dims, bias=True),
+ nn.ReLU(inplace=True),
+ nn.Linear(
+ in_features=cpb_mlp_hidden_dims,
+ out_features=num_heads,
+ bias=False))
+
+ # Add learnable scalar for cosine attention
+ self.logit_scale = nn.Parameter(
+ torch.log(10 * torch.ones((num_heads, 1, 1))), requires_grad=True)
+
+ # get relative_coords_table
+ relative_coords_h = torch.arange(
+ -(self.window_size[0] - 1),
+ self.window_size[0],
+ dtype=torch.float32)
+ relative_coords_w = torch.arange(
+ -(self.window_size[1] - 1),
+ self.window_size[1],
+ dtype=torch.float32)
+ relative_coords_table = torch.stack(
+ torch.meshgrid([relative_coords_h, relative_coords_w])).permute(
+ 1, 2, 0).contiguous().unsqueeze(0) # 1, 2*Wh-1, 2*Ww-1, 2
+ if pretrained_window_size[0] > 0:
+ relative_coords_table[:, :, :, 0] /= (
+ pretrained_window_size[0] - 1)
+ relative_coords_table[:, :, :, 1] /= (
+ pretrained_window_size[1] - 1)
+ else:
+ relative_coords_table[:, :, :, 0] /= (self.window_size[0] - 1)
+ relative_coords_table[:, :, :, 1] /= (self.window_size[1] - 1)
+ relative_coords_table *= 8 # normalize to -8, 8
+ relative_coords_table = torch.sign(relative_coords_table) * torch.log2(
+ torch.abs(relative_coords_table) + 1.0) / np.log2(8)
+ self.register_buffer('relative_coords_table', relative_coords_table)
+
+ # get pair-wise relative position index
+ # for each token inside the window
+ indexes_h = torch.arange(self.window_size[0])
+ indexes_w = torch.arange(self.window_size[1])
+ coordinates = torch.stack(
+ torch.meshgrid([indexes_h, indexes_w]), dim=0) # 2, Wh, Ww
+ coordinates = torch.flatten(coordinates, start_dim=1) # 2, Wh*Ww
+ # 2, Wh*Ww, Wh*Ww
+ relative_coordinates = coordinates[:, :, None] - coordinates[:,
+ None, :]
+ relative_coordinates = relative_coordinates.permute(
+ 1, 2, 0).contiguous() # Wh*Ww, Wh*Ww, 2
+
+ relative_coordinates[:, :, 0] += self.window_size[
+ 0] - 1 # shift to start from 0
+ relative_coordinates[:, :, 1] += self.window_size[1] - 1
+ relative_coordinates[:, :, 0] *= 2 * self.window_size[1] - 1
+ relative_position_index = relative_coordinates.sum(-1) # Wh*Ww, Wh*Ww
+ self.register_buffer('relative_position_index',
+ relative_position_index)
+
+ self.qkv = nn.Linear(embed_dims, embed_dims * 3, bias=False)
+ if qkv_bias:
+ self.q_bias = nn.Parameter(torch.zeros(embed_dims))
+ self.v_bias = nn.Parameter(torch.zeros(embed_dims))
+ else:
+ self.q_bias = None
+ self.v_bias = None
+ self.attn_drop = nn.Dropout(attn_drop)
+ self.proj = nn.Linear(embed_dims, embed_dims)
+ self.proj_drop = nn.Dropout(proj_drop)
+
+ self.softmax = nn.Softmax(dim=-1)
+
+ def forward(self, x, mask=None):
+ """
+ Args:
+
+ x (tensor): input features with shape of (num_windows*B, N, C)
+ mask (tensor, Optional): mask with shape of (num_windows, Wh*Ww,
+ Wh*Ww), value should be between (-inf, 0].
+ """
+ B_, N, C = x.shape
+ qkv_bias = None
+ if self.q_bias is not None:
+ qkv_bias = torch.cat(
+ (self.q_bias,
+ torch.zeros_like(self.v_bias,
+ requires_grad=False), self.v_bias))
+ qkv = F.linear(input=x, weight=self.qkv.weight, bias=qkv_bias)
+ qkv = qkv.reshape(B_, N, 3, self.num_heads,
+ C // self.num_heads).permute(2, 0, 3, 1, 4)
+ q, k, v = qkv[0], qkv[1], qkv[
+ 2] # make torchscript happy (cannot use tensor as tuple)
+
+ # cosine attention
+ attn = (
+ F.normalize(q, dim=-1) @ F.normalize(k, dim=-1).transpose(-2, -1))
+ logit_scale = torch.clamp(
+ self.logit_scale, max=np.log(1. / 0.01)).exp()
+ attn = attn * logit_scale
+
+ relative_position_bias_table = self.cpb_mlp(
+ self.relative_coords_table).view(-1, self.num_heads)
+ relative_position_bias = relative_position_bias_table[
+ self.relative_position_index.view(-1)].view(
+ self.window_size[0] * self.window_size[1],
+ self.window_size[0] * self.window_size[1],
+ -1) # Wh*Ww,Wh*Ww,nH
+ relative_position_bias = relative_position_bias.permute(
+ 2, 0, 1).contiguous() # nH, Wh*Ww, Wh*Ww
+ relative_position_bias = 16 * torch.sigmoid(relative_position_bias)
+ attn = attn + relative_position_bias.unsqueeze(0)
+
+ if mask is not None:
+ nW = mask.shape[0]
+ attn = attn.view(B_ // nW, nW, self.num_heads, N,
+ N) + mask.unsqueeze(1).unsqueeze(0)
+ attn = attn.view(-1, self.num_heads, N, N)
+ attn = self.softmax(attn)
+ else:
+ attn = self.softmax(attn)
+
+ attn = self.attn_drop(attn)
+
+ x = (attn @ v).transpose(1, 2).reshape(B_, N, C)
+ x = self.proj(x)
+ x = self.proj_drop(x)
+ return x
+
+
+@ATTENTION.register_module()
+class ShiftWindowMSA(BaseModule):
+ """Shift Window Multihead Self-Attention Module.
+
+ Args:
+ embed_dims (int): Number of input channels.
+ num_heads (int): Number of attention heads.
+ window_size (int): The height and width of the window.
+ shift_size (int, optional): The shift step of each window towards
+ right-bottom. If zero, act as regular window-msa. Defaults to 0.
+ qkv_bias (bool, optional): If True, add a learnable bias to q, k, v.
+ Defaults to True
+ qk_scale (float | None, optional): Override default qk scale of
+ head_dim ** -0.5 if set. Defaults to None.
+ attn_drop (float, optional): Dropout ratio of attention weight.
+ Defaults to 0.0.
+ proj_drop (float, optional): Dropout ratio of output. Defaults to 0.
+ dropout_layer (dict, optional): The dropout_layer used before output.
+ Defaults to dict(type='DropPath', drop_prob=0.).
+ pad_small_map (bool): If True, pad the small feature map to the window
+ size, which is common used in detection and segmentation. If False,
+ avoid shifting window and shrink the window size to the size of
+ feature map, which is common used in classification.
+ Defaults to False.
+ version (str, optional): Version of implementation of Swin
+ Transformers. Defaults to `v1`.
+ init_cfg (dict, optional): The extra config for initialization.
+ Defaults to None.
+ """
+
+ def __init__(self,
+ embed_dims,
+ num_heads,
+ window_size,
+ shift_size=0,
+ qkv_bias=True,
+ qk_scale=None,
+ attn_drop=0,
+ proj_drop=0,
+ dropout_layer=dict(type='DropPath', drop_prob=0.),
+ pad_small_map=False,
+ input_resolution=None,
+ auto_pad=None,
+ window_msa=WindowMSA,
+ msa_cfg=dict(),
+ init_cfg=None):
+ super().__init__(init_cfg)
+
+ if input_resolution is not None or auto_pad is not None:
+ warnings.warn(
+ 'The ShiftWindowMSA in new version has supported auto padding '
+ 'and dynamic input shape in all condition. And the argument '
+ '`auto_pad` and `input_resolution` have been deprecated.',
+ DeprecationWarning)
+
+ self.shift_size = shift_size
+ self.window_size = window_size
+ assert 0 <= self.shift_size < self.window_size
+
+ assert issubclass(window_msa, BaseModule), \
+ 'Expect Window based multi-head self-attention Module is type of' \
+ f'{type(BaseModule)}, but got {type(window_msa)}.'
+ self.w_msa = window_msa(
+ embed_dims=embed_dims,
+ window_size=to_2tuple(self.window_size),
+ num_heads=num_heads,
+ qkv_bias=qkv_bias,
+ qk_scale=qk_scale,
+ attn_drop=attn_drop,
+ proj_drop=proj_drop,
+ **msa_cfg,
+ )
+
+ self.drop = build_dropout(dropout_layer)
+ self.pad_small_map = pad_small_map
+
+ def forward(self, query, hw_shape):
+ B, L, C = query.shape
+ H, W = hw_shape
+ assert L == H * W, f"The query length {L} doesn't match the input "\
+ f'shape ({H}, {W}).'
+ query = query.view(B, H, W, C)
+
+ window_size = self.window_size
+ shift_size = self.shift_size
+
+ if min(H, W) == window_size:
+ # If not pad small feature map, avoid shifting when the window size
+ # is equal to the size of feature map. It's to align with the
+ # behavior of the original implementation.
+ shift_size = shift_size if self.pad_small_map else 0
+ elif min(H, W) < window_size:
+ # In the original implementation, the window size will be shrunk
+ # to the size of feature map. The behavior is different with
+ # swin-transformer for downstream tasks. To support dynamic input
+ # shape, we don't allow this feature.
+ assert self.pad_small_map, \
+ f'The input shape ({H}, {W}) is smaller than the window ' \
+ f'size ({window_size}). Please set `pad_small_map=True`, or ' \
+ 'decrease the `window_size`.'
+
+ pad_r = (window_size - W % window_size) % window_size
+ pad_b = (window_size - H % window_size) % window_size
+ query = F.pad(query, (0, 0, 0, pad_r, 0, pad_b))
+
+ H_pad, W_pad = query.shape[1], query.shape[2]
+
+ # cyclic shift
+ if shift_size > 0:
+ query = torch.roll(
+ query, shifts=(-shift_size, -shift_size), dims=(1, 2))
+
+ attn_mask = self.get_attn_mask((H_pad, W_pad),
+ window_size=window_size,
+ shift_size=shift_size,
+ device=query.device)
+
+ # nW*B, window_size, window_size, C
+ query_windows = self.window_partition(query, window_size)
+ # nW*B, window_size*window_size, C
+ query_windows = query_windows.view(-1, window_size**2, C)
+
+ # W-MSA/SW-MSA (nW*B, window_size*window_size, C)
+ attn_windows = self.w_msa(query_windows, mask=attn_mask)
+
+ # merge windows
+ attn_windows = attn_windows.view(-1, window_size, window_size, C)
+
+ # B H' W' C
+ shifted_x = self.window_reverse(attn_windows, H_pad, W_pad,
+ window_size)
+ # reverse cyclic shift
+ if self.shift_size > 0:
+ x = torch.roll(
+ shifted_x, shifts=(shift_size, shift_size), dims=(1, 2))
+ else:
+ x = shifted_x
+
+ if H != H_pad or W != W_pad:
+ x = x[:, :H, :W, :].contiguous()
+
+ x = x.view(B, H * W, C)
+
+ x = self.drop(x)
+
+ return x
+
+ @staticmethod
+ def window_reverse(windows, H, W, window_size):
+ B = int(windows.shape[0] / (H * W / window_size / window_size))
+ x = windows.view(B, H // window_size, W // window_size, window_size,
+ window_size, -1)
+ x = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(B, H, W, -1)
+ return x
+
+ @staticmethod
+ def window_partition(x, window_size):
+ B, H, W, C = x.shape
+ x = x.view(B, H // window_size, window_size, W // window_size,
+ window_size, C)
+ windows = x.permute(0, 1, 3, 2, 4, 5).contiguous()
+ windows = windows.view(-1, window_size, window_size, C)
+ return windows
+
+ @staticmethod
+ def get_attn_mask(hw_shape, window_size, shift_size, device=None):
+ if shift_size > 0:
+ img_mask = torch.zeros(1, *hw_shape, 1, device=device)
+ h_slices = (slice(0, -window_size), slice(-window_size,
+ -shift_size),
+ slice(-shift_size, None))
+ w_slices = (slice(0, -window_size), slice(-window_size,
+ -shift_size),
+ slice(-shift_size, None))
+ cnt = 0
+ for h in h_slices:
+ for w in w_slices:
+ img_mask[:, h, w, :] = cnt
+ cnt += 1
+
+ # nW, window_size, window_size, 1
+ mask_windows = ShiftWindowMSA.window_partition(
+ img_mask, window_size)
+ mask_windows = mask_windows.view(-1, window_size * window_size)
+ attn_mask = mask_windows.unsqueeze(1) - mask_windows.unsqueeze(2)
+ attn_mask = attn_mask.masked_fill(attn_mask != 0, -100.0)
+ attn_mask = attn_mask.masked_fill(attn_mask == 0, 0.0)
+ else:
+ attn_mask = None
+ return attn_mask
+
+
+class MultiheadAttention(BaseModule):
+ """Multi-head Attention Module.
+
+ This module implements multi-head attention that supports different input
+ dims and embed dims. And it also supports a shortcut from ``value``, which
+ is useful if input dims is not the same with embed dims.
+
+ Args:
+ embed_dims (int): The embedding dimension.
+ num_heads (int): Parallel attention heads.
+ input_dims (int, optional): The input dimension, and if None,
+ use ``embed_dims``. Defaults to None.
+ attn_drop (float): Dropout rate of the dropout layer after the
+ attention calculation of query and key. Defaults to 0.
+ proj_drop (float): Dropout rate of the dropout layer after the
+ output projection. Defaults to 0.
+ dropout_layer (dict): The dropout config before adding the shortcut.
+ Defaults to ``dict(type='Dropout', drop_prob=0.)``.
+ qkv_bias (bool): If True, add a learnable bias to q, k, v.
+ Defaults to True.
+ qk_scale (float, optional): Override default qk scale of
+ ``head_dim ** -0.5`` if set. Defaults to None.
+ proj_bias (bool) If True, add a learnable bias to output projection.
+ Defaults to True.
+ v_shortcut (bool): Add a shortcut from value to output. It's usually
+ used if ``input_dims`` is different from ``embed_dims``.
+ Defaults to False.
+ init_cfg (dict, optional): The Config for initialization.
+ Defaults to None.
+ """
+
+ def __init__(self,
+ embed_dims,
+ num_heads,
+ input_dims=None,
+ attn_drop=0.,
+ proj_drop=0.,
+ dropout_layer=dict(type='Dropout', drop_prob=0.),
+ qkv_bias=True,
+ qk_scale=None,
+ proj_bias=True,
+ v_shortcut=False,
+ init_cfg=None):
+ super(MultiheadAttention, self).__init__(init_cfg=init_cfg)
+
+ self.input_dims = input_dims or embed_dims
+ self.embed_dims = embed_dims
+ self.num_heads = num_heads
+ self.v_shortcut = v_shortcut
+
+ self.head_dims = embed_dims // num_heads
+ self.scale = qk_scale or self.head_dims**-0.5
+
+ self.qkv = nn.Linear(self.input_dims, embed_dims * 3, bias=qkv_bias)
+ self.attn_drop = nn.Dropout(attn_drop)
+ self.proj = nn.Linear(embed_dims, embed_dims, bias=proj_bias)
+ self.proj_drop = nn.Dropout(proj_drop)
+
+ self.out_drop = DROPOUT_LAYERS.build(dropout_layer)
+
+ def forward(self, x):
+ B, N, _ = x.shape
+ qkv = self.qkv(x).reshape(B, N, 3, self.num_heads,
+ self.head_dims).permute(2, 0, 3, 1, 4)
+ q, k, v = qkv[0], qkv[1], qkv[2]
+
+ attn = (q @ k.transpose(-2, -1)) * self.scale
+ attn = attn.softmax(dim=-1)
+ attn = self.attn_drop(attn)
+
+ x = (attn @ v).transpose(1, 2).reshape(B, N, self.embed_dims)
+ x = self.proj(x)
+ x = self.out_drop(self.proj_drop(x))
+
+ if self.v_shortcut:
+ x = v.squeeze(1) + x
+ return x
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/augment/__init__.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/augment/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f92cd54cea4c905e3dcaf1dfa9a850468dcea13
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/augment/__init__.py
@@ -0,0 +1,9 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from .augments import Augments
+from .cutmix import BatchCutMixLayer
+from .identity import Identity
+from .mixup import BatchMixupLayer
+from .resizemix import BatchResizeMixLayer
+
+__all__ = ('Augments', 'BatchCutMixLayer', 'Identity', 'BatchMixupLayer',
+ 'BatchResizeMixLayer')
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/utils/augment/augments.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/augment/augments.py
similarity index 98%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/models/utils/augment/augments.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/augment/augments.py
index 59b9a1fa20b1b07ad5daab2ee04e6dbcc201fa99..8455e935dd998ca2e034ed1f7089c657392809db 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/utils/augment/augments.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/augment/augments.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import random
import numpy as np
@@ -9,6 +10,7 @@ class Augments(object):
"""Data augments.
We implement some data augmentation methods, such as mixup, cutmix.
+
Args:
augments_cfg (list[`mmcv.ConfigDict`] | obj:`mmcv.ConfigDict`):
Config dict of augments
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/augment/builder.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/augment/builder.py
new file mode 100644
index 0000000000000000000000000000000000000000..5d1205ee319347a1d7a4c8ecdf9c3c470bedd065
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/augment/builder.py
@@ -0,0 +1,8 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from mmcv.utils import Registry, build_from_cfg
+
+AUGMENT = Registry('augment')
+
+
+def build_augment(cfg, default_args=None):
+ return build_from_cfg(cfg, AUGMENT, default_args)
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/augment/cutmix.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/augment/cutmix.py
new file mode 100644
index 0000000000000000000000000000000000000000..0d8ba9dd154c3348bdd70d211006385e5716ba87
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/augment/cutmix.py
@@ -0,0 +1,175 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from abc import ABCMeta, abstractmethod
+
+import numpy as np
+import torch
+
+from .builder import AUGMENT
+from .utils import one_hot_encoding
+
+
+class BaseCutMixLayer(object, metaclass=ABCMeta):
+ """Base class for CutMixLayer.
+
+ Args:
+ alpha (float): Parameters for Beta distribution. Positive(>0)
+ num_classes (int): The number of classes
+ prob (float): MixUp probability. It should be in range [0, 1].
+ Default to 1.0
+ cutmix_minmax (List[float], optional): cutmix min/max image ratio.
+ (as percent of image size). When cutmix_minmax is not None, we
+ generate cutmix bounding-box using cutmix_minmax instead of alpha
+ correct_lam (bool): Whether to apply lambda correction when cutmix bbox
+ clipped by image borders. Default to True
+ """
+
+ def __init__(self,
+ alpha,
+ num_classes,
+ prob=1.0,
+ cutmix_minmax=None,
+ correct_lam=True):
+ super(BaseCutMixLayer, self).__init__()
+
+ assert isinstance(alpha, float) and alpha > 0
+ assert isinstance(num_classes, int)
+ assert isinstance(prob, float) and 0.0 <= prob <= 1.0
+
+ self.alpha = alpha
+ self.num_classes = num_classes
+ self.prob = prob
+ self.cutmix_minmax = cutmix_minmax
+ self.correct_lam = correct_lam
+
+ def rand_bbox_minmax(self, img_shape, count=None):
+ """Min-Max CutMix bounding-box Inspired by Darknet cutmix
+ implementation. It generates a random rectangular bbox based on min/max
+ percent values applied to each dimension of the input image.
+
+ Typical defaults for minmax are usually in the .2-.3 for min and
+ .8-.9 range for max.
+
+ Args:
+ img_shape (tuple): Image shape as tuple
+ count (int, optional): Number of bbox to generate. Default to None
+ """
+ assert len(self.cutmix_minmax) == 2
+ img_h, img_w = img_shape[-2:]
+ cut_h = np.random.randint(
+ int(img_h * self.cutmix_minmax[0]),
+ int(img_h * self.cutmix_minmax[1]),
+ size=count)
+ cut_w = np.random.randint(
+ int(img_w * self.cutmix_minmax[0]),
+ int(img_w * self.cutmix_minmax[1]),
+ size=count)
+ yl = np.random.randint(0, img_h - cut_h, size=count)
+ xl = np.random.randint(0, img_w - cut_w, size=count)
+ yu = yl + cut_h
+ xu = xl + cut_w
+ return yl, yu, xl, xu
+
+ def rand_bbox(self, img_shape, lam, margin=0., count=None):
+ """Standard CutMix bounding-box that generates a random square bbox
+ based on lambda value. This implementation includes support for
+ enforcing a border margin as percent of bbox dimensions.
+
+ Args:
+ img_shape (tuple): Image shape as tuple
+ lam (float): Cutmix lambda value
+ margin (float): Percentage of bbox dimension to enforce as margin
+ (reduce amount of box outside image). Default to 0.
+ count (int, optional): Number of bbox to generate. Default to None
+ """
+ ratio = np.sqrt(1 - lam)
+ img_h, img_w = img_shape[-2:]
+ cut_h, cut_w = int(img_h * ratio), int(img_w * ratio)
+ margin_y, margin_x = int(margin * cut_h), int(margin * cut_w)
+ cy = np.random.randint(0 + margin_y, img_h - margin_y, size=count)
+ cx = np.random.randint(0 + margin_x, img_w - margin_x, size=count)
+ yl = np.clip(cy - cut_h // 2, 0, img_h)
+ yh = np.clip(cy + cut_h // 2, 0, img_h)
+ xl = np.clip(cx - cut_w // 2, 0, img_w)
+ xh = np.clip(cx + cut_w // 2, 0, img_w)
+ return yl, yh, xl, xh
+
+ def cutmix_bbox_and_lam(self, img_shape, lam, count=None):
+ """Generate bbox and apply lambda correction.
+
+ Args:
+ img_shape (tuple): Image shape as tuple
+ lam (float): Cutmix lambda value
+ count (int, optional): Number of bbox to generate. Default to None
+ """
+ if self.cutmix_minmax is not None:
+ yl, yu, xl, xu = self.rand_bbox_minmax(img_shape, count=count)
+ else:
+ yl, yu, xl, xu = self.rand_bbox(img_shape, lam, count=count)
+ if self.correct_lam or self.cutmix_minmax is not None:
+ bbox_area = (yu - yl) * (xu - xl)
+ lam = 1. - bbox_area / float(img_shape[-2] * img_shape[-1])
+ return (yl, yu, xl, xu), lam
+
+ @abstractmethod
+ def cutmix(self, imgs, gt_label):
+ pass
+
+
+@AUGMENT.register_module(name='BatchCutMix')
+class BatchCutMixLayer(BaseCutMixLayer):
+ r"""CutMix layer for a batch of data.
+
+ CutMix is a method to improve the network's generalization capability. It's
+ proposed in `CutMix: Regularization Strategy to Train Strong Classifiers
+ with Localizable Features `
+
+ With this method, patches are cut and pasted among training images where
+ the ground truth labels are also mixed proportionally to the area of the
+ patches.
+
+ Args:
+ alpha (float): Parameters for Beta distribution to generate the
+ mixing ratio. It should be a positive number. More details
+ can be found in :class:`BatchMixupLayer`.
+ num_classes (int): The number of classes
+ prob (float): The probability to execute cutmix. It should be in
+ range [0, 1]. Defaults to 1.0.
+ cutmix_minmax (List[float], optional): The min/max area ratio of the
+ patches. If not None, the bounding-box of patches is uniform
+ sampled within this ratio range, and the ``alpha`` will be ignored.
+ Otherwise, the bounding-box is generated according to the
+ ``alpha``. Defaults to None.
+ correct_lam (bool): Whether to apply lambda correction when cutmix bbox
+ clipped by image borders. Defaults to True.
+
+ Note:
+ If the ``cutmix_minmax`` is None, how to generate the bounding-box of
+ patches according to the ``alpha``?
+
+ First, generate a :math:`\lambda`, details can be found in
+ :class:`BatchMixupLayer`. And then, the area ratio of the bounding-box
+ is calculated by:
+
+ .. math::
+ \text{ratio} = \sqrt{1-\lambda}
+ """
+
+ def __init__(self, *args, **kwargs):
+ super(BatchCutMixLayer, self).__init__(*args, **kwargs)
+
+ def cutmix(self, img, gt_label):
+ one_hot_gt_label = one_hot_encoding(gt_label, self.num_classes)
+ lam = np.random.beta(self.alpha, self.alpha)
+ batch_size = img.size(0)
+ index = torch.randperm(batch_size)
+
+ (bby1, bby2, bbx1,
+ bbx2), lam = self.cutmix_bbox_and_lam(img.shape, lam)
+ img[:, :, bby1:bby2, bbx1:bbx2] = \
+ img[index, :, bby1:bby2, bbx1:bbx2]
+ mixed_gt_label = lam * one_hot_gt_label + (
+ 1 - lam) * one_hot_gt_label[index, :]
+ return img, mixed_gt_label
+
+ def __call__(self, img, gt_label):
+ return self.cutmix(img, gt_label)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/utils/augment/identity.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/augment/identity.py
similarity index 83%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/models/utils/augment/identity.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/augment/identity.py
index 414b092b487fd8f5b3a34f844aa4837cfc7288df..ae3a3df52ff2547aeda92390ed8bde12fc64b219 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/utils/augment/identity.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/augment/identity.py
@@ -1,6 +1,6 @@
-import torch.nn.functional as F
-
+# Copyright (c) OpenMMLab. All rights reserved.
from .builder import AUGMENT
+from .utils import one_hot_encoding
@AUGMENT.register_module(name='Identity')
@@ -23,7 +23,7 @@ class Identity(object):
self.prob = prob
def one_hot(self, gt_label):
- return F.one_hot(gt_label, num_classes=self.num_classes)
+ return one_hot_encoding(gt_label, self.num_classes)
def __call__(self, img, gt_label):
return img, self.one_hot(gt_label)
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/augment/mixup.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/augment/mixup.py
new file mode 100644
index 0000000000000000000000000000000000000000..e8899dd3e65cd9152505067757a3b3584bba52ec
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/augment/mixup.py
@@ -0,0 +1,80 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from abc import ABCMeta, abstractmethod
+
+import numpy as np
+import torch
+
+from .builder import AUGMENT
+from .utils import one_hot_encoding
+
+
+class BaseMixupLayer(object, metaclass=ABCMeta):
+ """Base class for MixupLayer.
+
+ Args:
+ alpha (float): Parameters for Beta distribution to generate the
+ mixing ratio. It should be a positive number.
+ num_classes (int): The number of classes.
+ prob (float): MixUp probability. It should be in range [0, 1].
+ Default to 1.0
+ """
+
+ def __init__(self, alpha, num_classes, prob=1.0):
+ super(BaseMixupLayer, self).__init__()
+
+ assert isinstance(alpha, float) and alpha > 0
+ assert isinstance(num_classes, int)
+ assert isinstance(prob, float) and 0.0 <= prob <= 1.0
+
+ self.alpha = alpha
+ self.num_classes = num_classes
+ self.prob = prob
+
+ @abstractmethod
+ def mixup(self, imgs, gt_label):
+ pass
+
+
+@AUGMENT.register_module(name='BatchMixup')
+class BatchMixupLayer(BaseMixupLayer):
+ r"""Mixup layer for a batch of data.
+
+ Mixup is a method to reduces the memorization of corrupt labels and
+ increases the robustness to adversarial examples. It's
+ proposed in `mixup: Beyond Empirical Risk Minimization
+ `
+
+ This method simply linearly mix pairs of data and their labels.
+
+ Args:
+ alpha (float): Parameters for Beta distribution to generate the
+ mixing ratio. It should be a positive number. More details
+ are in the note.
+ num_classes (int): The number of classes.
+ prob (float): The probability to execute mixup. It should be in
+ range [0, 1]. Default sto 1.0.
+
+ Note:
+ The :math:`\alpha` (``alpha``) determines a random distribution
+ :math:`Beta(\alpha, \alpha)`. For each batch of data, we sample
+ a mixing ratio (marked as :math:`\lambda`, ``lam``) from the random
+ distribution.
+ """
+
+ def __init__(self, *args, **kwargs):
+ super(BatchMixupLayer, self).__init__(*args, **kwargs)
+
+ def mixup(self, img, gt_label):
+ one_hot_gt_label = one_hot_encoding(gt_label, self.num_classes)
+ lam = np.random.beta(self.alpha, self.alpha)
+ batch_size = img.size(0)
+ index = torch.randperm(batch_size)
+
+ mixed_img = lam * img + (1 - lam) * img[index, :]
+ mixed_gt_label = lam * one_hot_gt_label + (
+ 1 - lam) * one_hot_gt_label[index, :]
+
+ return mixed_img, mixed_gt_label
+
+ def __call__(self, img, gt_label):
+ return self.mixup(img, gt_label)
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/augment/resizemix.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/augment/resizemix.py
new file mode 100644
index 0000000000000000000000000000000000000000..1506cc379a7c0e07d20518952b75402181f0c9f3
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/augment/resizemix.py
@@ -0,0 +1,93 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import numpy as np
+import torch
+import torch.nn.functional as F
+
+from mmcls.models.utils.augment.builder import AUGMENT
+from .cutmix import BatchCutMixLayer
+from .utils import one_hot_encoding
+
+
+@AUGMENT.register_module(name='BatchResizeMix')
+class BatchResizeMixLayer(BatchCutMixLayer):
+ r"""ResizeMix Random Paste layer for a batch of data.
+
+ The ResizeMix will resize an image to a small patch and paste it on another
+ image. It's proposed in `ResizeMix: Mixing Data with Preserved Object
+ Information and True Labels `_
+
+ Args:
+ alpha (float): Parameters for Beta distribution to generate the
+ mixing ratio. It should be a positive number. More details
+ can be found in :class:`BatchMixupLayer`.
+ num_classes (int): The number of classes.
+ lam_min(float): The minimum value of lam. Defaults to 0.1.
+ lam_max(float): The maximum value of lam. Defaults to 0.8.
+ interpolation (str): algorithm used for upsampling:
+ 'nearest' | 'linear' | 'bilinear' | 'bicubic' | 'trilinear' |
+ 'area'. Default to 'bilinear'.
+ prob (float): The probability to execute resizemix. It should be in
+ range [0, 1]. Defaults to 1.0.
+ cutmix_minmax (List[float], optional): The min/max area ratio of the
+ patches. If not None, the bounding-box of patches is uniform
+ sampled within this ratio range, and the ``alpha`` will be ignored.
+ Otherwise, the bounding-box is generated according to the
+ ``alpha``. Defaults to None.
+ correct_lam (bool): Whether to apply lambda correction when cutmix bbox
+ clipped by image borders. Defaults to True
+ **kwargs: Any other parameters accpeted by :class:`BatchCutMixLayer`.
+
+ Note:
+ The :math:`\lambda` (``lam``) is the mixing ratio. It's a random
+ variable which follows :math:`Beta(\alpha, \alpha)` and is mapped
+ to the range [``lam_min``, ``lam_max``].
+
+ .. math::
+ \lambda = \frac{Beta(\alpha, \alpha)}
+ {\lambda_{max} - \lambda_{min}} + \lambda_{min}
+
+ And the resize ratio of source images is calculated by :math:`\lambda`:
+
+ .. math::
+ \text{ratio} = \sqrt{1-\lambda}
+ """
+
+ def __init__(self,
+ alpha,
+ num_classes,
+ lam_min: float = 0.1,
+ lam_max: float = 0.8,
+ interpolation='bilinear',
+ prob=1.0,
+ cutmix_minmax=None,
+ correct_lam=True,
+ **kwargs):
+ super(BatchResizeMixLayer, self).__init__(
+ alpha=alpha,
+ num_classes=num_classes,
+ prob=prob,
+ cutmix_minmax=cutmix_minmax,
+ correct_lam=correct_lam,
+ **kwargs)
+ self.lam_min = lam_min
+ self.lam_max = lam_max
+ self.interpolation = interpolation
+
+ def cutmix(self, img, gt_label):
+ one_hot_gt_label = one_hot_encoding(gt_label, self.num_classes)
+
+ lam = np.random.beta(self.alpha, self.alpha)
+ lam = lam * (self.lam_max - self.lam_min) + self.lam_min
+ batch_size = img.size(0)
+ index = torch.randperm(batch_size)
+
+ (bby1, bby2, bbx1,
+ bbx2), lam = self.cutmix_bbox_and_lam(img.shape, lam)
+
+ img[:, :, bby1:bby2, bbx1:bbx2] = F.interpolate(
+ img[index],
+ size=(bby2 - bby1, bbx2 - bbx1),
+ mode=self.interpolation)
+ mixed_gt_label = lam * one_hot_gt_label + (
+ 1 - lam) * one_hot_gt_label[index, :]
+ return img, mixed_gt_label
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/augment/utils.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/augment/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..e972d54bf06ff9d94a46f0599cbeb18605cc6a20
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/augment/utils.py
@@ -0,0 +1,24 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import torch.nn.functional as F
+
+
+def one_hot_encoding(gt, num_classes):
+ """Change gt_label to one_hot encoding.
+
+ If the shape has 2 or more
+ dimensions, return it without encoding.
+ Args:
+ gt (Tensor): The gt label with shape (N,) or shape (N, */).
+ num_classes (int): The number of classes.
+ Return:
+ Tensor: One hot gt label.
+ """
+ if gt.ndim == 1:
+ # multi-class classification
+ return F.one_hot(gt, num_classes=num_classes)
+ else:
+ # binary classification
+ # example. [[0], [1], [1]]
+ # multi-label classification
+ # example. [[0, 1, 1], [1, 0, 0], [1, 1, 1]]
+ return gt
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/utils/channel_shuffle.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/channel_shuffle.py
similarity index 94%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/models/utils/channel_shuffle.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/channel_shuffle.py
index 51d6d98c9b15816d8b9eda4b67480b536e9fa161..27006a8065db35a14c4207ce6613104374b064ad 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/utils/channel_shuffle.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/channel_shuffle.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import torch
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/embed.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/embed.py
new file mode 100644
index 0000000000000000000000000000000000000000..ff65fc43583fd8df6d66a1a187035245a85c717e
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/embed.py
@@ -0,0 +1,420 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import warnings
+from typing import Sequence
+
+import numpy as np
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+from mmcv.cnn import build_conv_layer, build_norm_layer
+from mmcv.cnn.bricks.transformer import AdaptivePadding
+from mmcv.runner.base_module import BaseModule
+
+from .helpers import to_2tuple
+
+
+def resize_pos_embed(pos_embed,
+ src_shape,
+ dst_shape,
+ mode='bicubic',
+ num_extra_tokens=1):
+ """Resize pos_embed weights.
+
+ Args:
+ pos_embed (torch.Tensor): Position embedding weights with shape
+ [1, L, C].
+ src_shape (tuple): The resolution of downsampled origin training
+ image, in format (H, W).
+ dst_shape (tuple): The resolution of downsampled new training
+ image, in format (H, W).
+ mode (str): Algorithm used for upsampling. Choose one from 'nearest',
+ 'linear', 'bilinear', 'bicubic' and 'trilinear'.
+ Defaults to 'bicubic'.
+ num_extra_tokens (int): The number of extra tokens, such as cls_token.
+ Defaults to 1.
+
+ Returns:
+ torch.Tensor: The resized pos_embed of shape [1, L_new, C]
+ """
+ if src_shape[0] == dst_shape[0] and src_shape[1] == dst_shape[1]:
+ return pos_embed
+ assert pos_embed.ndim == 3, 'shape of pos_embed must be [1, L, C]'
+ _, L, C = pos_embed.shape
+ src_h, src_w = src_shape
+ assert L == src_h * src_w + num_extra_tokens, \
+ f"The length of `pos_embed` ({L}) doesn't match the expected " \
+ f'shape ({src_h}*{src_w}+{num_extra_tokens}). Please check the' \
+ '`img_size` argument.'
+ extra_tokens = pos_embed[:, :num_extra_tokens]
+
+ src_weight = pos_embed[:, num_extra_tokens:]
+ src_weight = src_weight.reshape(1, src_h, src_w, C).permute(0, 3, 1, 2)
+
+ dst_weight = F.interpolate(
+ src_weight, size=dst_shape, align_corners=False, mode=mode)
+ dst_weight = torch.flatten(dst_weight, 2).transpose(1, 2)
+
+ return torch.cat((extra_tokens, dst_weight), dim=1)
+
+
+def resize_relative_position_bias_table(src_shape, dst_shape, table, num_head):
+ """Resize relative position bias table.
+
+ Args:
+ src_shape (int): The resolution of downsampled origin training
+ image, in format (H, W).
+ dst_shape (int): The resolution of downsampled new training
+ image, in format (H, W).
+ table (tensor): The relative position bias of the pretrained model.
+ num_head (int): Number of attention heads.
+
+ Returns:
+ torch.Tensor: The resized relative position bias table.
+ """
+ from scipy import interpolate
+
+ def geometric_progression(a, r, n):
+ return a * (1.0 - r**n) / (1.0 - r)
+
+ left, right = 1.01, 1.5
+ while right - left > 1e-6:
+ q = (left + right) / 2.0
+ gp = geometric_progression(1, q, src_shape // 2)
+ if gp > dst_shape // 2:
+ right = q
+ else:
+ left = q
+
+ dis = []
+ cur = 1
+ for i in range(src_shape // 2):
+ dis.append(cur)
+ cur += q**(i + 1)
+
+ r_ids = [-_ for _ in reversed(dis)]
+
+ x = r_ids + [0] + dis
+ y = r_ids + [0] + dis
+
+ t = dst_shape // 2.0
+ dx = np.arange(-t, t + 0.1, 1.0)
+ dy = np.arange(-t, t + 0.1, 1.0)
+
+ all_rel_pos_bias = []
+
+ for i in range(num_head):
+ z = table[:, i].view(src_shape, src_shape).float().numpy()
+ f_cubic = interpolate.interp2d(x, y, z, kind='cubic')
+ all_rel_pos_bias.append(
+ torch.Tensor(f_cubic(dx,
+ dy)).contiguous().view(-1,
+ 1).to(table.device))
+ new_rel_pos_bias = torch.cat(all_rel_pos_bias, dim=-1)
+ return new_rel_pos_bias
+
+
+class PatchEmbed(BaseModule):
+ """Image to Patch Embedding.
+
+ We use a conv layer to implement PatchEmbed.
+
+ Args:
+ img_size (int | tuple): The size of input image. Default: 224
+ in_channels (int): The num of input channels. Default: 3
+ embed_dims (int): The dimensions of embedding. Default: 768
+ norm_cfg (dict, optional): Config dict for normalization layer.
+ Default: None
+ conv_cfg (dict, optional): The config dict for conv layers.
+ Default: None
+ init_cfg (`mmcv.ConfigDict`, optional): The Config for initialization.
+ Default: None
+ """
+
+ def __init__(self,
+ img_size=224,
+ in_channels=3,
+ embed_dims=768,
+ norm_cfg=None,
+ conv_cfg=None,
+ init_cfg=None):
+ super(PatchEmbed, self).__init__(init_cfg)
+ warnings.warn('The `PatchEmbed` in mmcls will be deprecated. '
+ 'Please use `mmcv.cnn.bricks.transformer.PatchEmbed`. '
+ "It's more general and supports dynamic input shape")
+
+ if isinstance(img_size, int):
+ img_size = to_2tuple(img_size)
+ elif isinstance(img_size, tuple):
+ if len(img_size) == 1:
+ img_size = to_2tuple(img_size[0])
+ assert len(img_size) == 2, \
+ f'The size of image should have length 1 or 2, ' \
+ f'but got {len(img_size)}'
+
+ self.img_size = img_size
+ self.embed_dims = embed_dims
+
+ # Use conv layer to embed
+ conv_cfg = conv_cfg or dict()
+ _conv_cfg = dict(
+ type='Conv2d', kernel_size=16, stride=16, padding=0, dilation=1)
+ _conv_cfg.update(conv_cfg)
+ self.projection = build_conv_layer(_conv_cfg, in_channels, embed_dims)
+
+ # Calculate how many patches a input image is splited to.
+ h_out, w_out = [(self.img_size[i] + 2 * self.projection.padding[i] -
+ self.projection.dilation[i] *
+ (self.projection.kernel_size[i] - 1) - 1) //
+ self.projection.stride[i] + 1 for i in range(2)]
+
+ self.patches_resolution = (h_out, w_out)
+ self.num_patches = h_out * w_out
+
+ if norm_cfg is not None:
+ self.norm = build_norm_layer(norm_cfg, embed_dims)[1]
+ else:
+ self.norm = None
+
+ def forward(self, x):
+ B, C, H, W = x.shape
+ assert H == self.img_size[0] and W == self.img_size[1], \
+ f"Input image size ({H}*{W}) doesn't " \
+ f'match model ({self.img_size[0]}*{self.img_size[1]}).'
+ # The output size is (B, N, D), where N=H*W/P/P, D is embid_dim
+ x = self.projection(x).flatten(2).transpose(1, 2)
+
+ if self.norm is not None:
+ x = self.norm(x)
+
+ return x
+
+
+# Modified from pytorch-image-models
+class HybridEmbed(BaseModule):
+ """CNN Feature Map Embedding.
+
+ Extract feature map from CNN, flatten,
+ project to embedding dim.
+
+ Args:
+ backbone (nn.Module): CNN backbone
+ img_size (int | tuple): The size of input image. Default: 224
+ feature_size (int | tuple, optional): Size of feature map extracted by
+ CNN backbone. Default: None
+ in_channels (int): The num of input channels. Default: 3
+ embed_dims (int): The dimensions of embedding. Default: 768
+ conv_cfg (dict, optional): The config dict for conv layers.
+ Default: None.
+ init_cfg (`mmcv.ConfigDict`, optional): The Config for initialization.
+ Default: None.
+ """
+
+ def __init__(self,
+ backbone,
+ img_size=224,
+ feature_size=None,
+ in_channels=3,
+ embed_dims=768,
+ conv_cfg=None,
+ init_cfg=None):
+ super(HybridEmbed, self).__init__(init_cfg)
+ assert isinstance(backbone, nn.Module)
+ if isinstance(img_size, int):
+ img_size = to_2tuple(img_size)
+ elif isinstance(img_size, tuple):
+ if len(img_size) == 1:
+ img_size = to_2tuple(img_size[0])
+ assert len(img_size) == 2, \
+ f'The size of image should have length 1 or 2, ' \
+ f'but got {len(img_size)}'
+
+ self.img_size = img_size
+ self.backbone = backbone
+ if feature_size is None:
+ with torch.no_grad():
+ # FIXME this is hacky, but most reliable way of
+ # determining the exact dim of the output feature
+ # map for all networks, the feature metadata has
+ # reliable channel and stride info, but using
+ # stride to calc feature dim requires info about padding of
+ # each stage that isn't captured.
+ training = backbone.training
+ if training:
+ backbone.eval()
+ o = self.backbone(
+ torch.zeros(1, in_channels, img_size[0], img_size[1]))
+ if isinstance(o, (list, tuple)):
+ # last feature if backbone outputs list/tuple of features
+ o = o[-1]
+ feature_size = o.shape[-2:]
+ feature_dim = o.shape[1]
+ backbone.train(training)
+ else:
+ feature_size = to_2tuple(feature_size)
+ if hasattr(self.backbone, 'feature_info'):
+ feature_dim = self.backbone.feature_info.channels()[-1]
+ else:
+ feature_dim = self.backbone.num_features
+ self.num_patches = feature_size[0] * feature_size[1]
+
+ # Use conv layer to embed
+ conv_cfg = conv_cfg or dict()
+ _conv_cfg = dict(
+ type='Conv2d', kernel_size=1, stride=1, padding=0, dilation=1)
+ _conv_cfg.update(conv_cfg)
+ self.projection = build_conv_layer(_conv_cfg, feature_dim, embed_dims)
+
+ def forward(self, x):
+ x = self.backbone(x)
+ if isinstance(x, (list, tuple)):
+ # last feature if backbone outputs list/tuple of features
+ x = x[-1]
+ x = self.projection(x).flatten(2).transpose(1, 2)
+ return x
+
+
+class PatchMerging(BaseModule):
+ """Merge patch feature map. Modified from mmcv, which uses pre-norm layer
+ whereas Swin V2 uses post-norm here. Therefore, add extra parameter to
+ decide whether use post-norm or not.
+
+ This layer groups feature map by kernel_size, and applies norm and linear
+ layers to the grouped feature map ((used in Swin Transformer)).
+ Our implementation uses `nn.Unfold` to
+ merge patches, which is about 25% faster than the original
+ implementation. However, we need to modify pretrained
+ models for compatibility.
+
+ Args:
+ in_channels (int): The num of input channels.
+ to gets fully covered by filter and stride you specified.
+ out_channels (int): The num of output channels.
+ kernel_size (int | tuple, optional): the kernel size in the unfold
+ layer. Defaults to 2.
+ stride (int | tuple, optional): the stride of the sliding blocks in the
+ unfold layer. Defaults to None. (Would be set as `kernel_size`)
+ padding (int | tuple | string ): The padding length of
+ embedding conv. When it is a string, it means the mode
+ of adaptive padding, support "same" and "corner" now.
+ Defaults to "corner".
+ dilation (int | tuple, optional): dilation parameter in the unfold
+ layer. Default: 1.
+ bias (bool, optional): Whether to add bias in linear layer or not.
+ Defaults to False.
+ norm_cfg (dict, optional): Config dict for normalization layer.
+ Defaults to dict(type='LN').
+ is_post_norm (bool): Whether to use post normalization here.
+ Defaults to False.
+ init_cfg (dict, optional): The extra config for initialization.
+ Defaults to None.
+ """
+
+ def __init__(self,
+ in_channels,
+ out_channels,
+ kernel_size=2,
+ stride=None,
+ padding='corner',
+ dilation=1,
+ bias=False,
+ norm_cfg=dict(type='LN'),
+ is_post_norm=False,
+ init_cfg=None):
+ super().__init__(init_cfg=init_cfg)
+ self.in_channels = in_channels
+ self.out_channels = out_channels
+ self.is_post_norm = is_post_norm
+
+ if stride:
+ stride = stride
+ else:
+ stride = kernel_size
+
+ kernel_size = to_2tuple(kernel_size)
+ stride = to_2tuple(stride)
+ dilation = to_2tuple(dilation)
+
+ if isinstance(padding, str):
+ self.adaptive_padding = AdaptivePadding(
+ kernel_size=kernel_size,
+ stride=stride,
+ dilation=dilation,
+ padding=padding)
+ # disable the padding of unfold
+ padding = 0
+ else:
+ self.adaptive_padding = None
+
+ padding = to_2tuple(padding)
+ self.sampler = nn.Unfold(
+ kernel_size=kernel_size,
+ dilation=dilation,
+ padding=padding,
+ stride=stride)
+
+ sample_dim = kernel_size[0] * kernel_size[1] * in_channels
+
+ self.reduction = nn.Linear(sample_dim, out_channels, bias=bias)
+
+ if norm_cfg is not None:
+ # build pre or post norm layer based on different channels
+ if self.is_post_norm:
+ self.norm = build_norm_layer(norm_cfg, out_channels)[1]
+ else:
+ self.norm = build_norm_layer(norm_cfg, sample_dim)[1]
+ else:
+ self.norm = None
+
+ def forward(self, x, input_size):
+ """
+ Args:
+ x (Tensor): Has shape (B, H*W, C_in).
+ input_size (tuple[int]): The spatial shape of x, arrange as (H, W).
+ Default: None.
+
+ Returns:
+ tuple: Contains merged results and its spatial shape.
+
+ - x (Tensor): Has shape (B, Merged_H * Merged_W, C_out)
+ - out_size (tuple[int]): Spatial shape of x, arrange as
+ (Merged_H, Merged_W).
+ """
+ B, L, C = x.shape
+ assert isinstance(input_size, Sequence), f'Expect ' \
+ f'input_size is ' \
+ f'`Sequence` ' \
+ f'but get {input_size}'
+
+ H, W = input_size
+ assert L == H * W, 'input feature has wrong size'
+
+ x = x.view(B, H, W, C).permute([0, 3, 1, 2]) # B, C, H, W
+
+ if self.adaptive_padding:
+ x = self.adaptive_padding(x)
+ H, W = x.shape[-2:]
+
+ # Use nn.Unfold to merge patch. About 25% faster than original method,
+ # but need to modify pretrained model for compatibility
+ # if kernel_size=2 and stride=2, x should has shape (B, 4*C, H/2*W/2)
+ x = self.sampler(x)
+
+ out_h = (H + 2 * self.sampler.padding[0] - self.sampler.dilation[0] *
+ (self.sampler.kernel_size[0] - 1) -
+ 1) // self.sampler.stride[0] + 1
+ out_w = (W + 2 * self.sampler.padding[1] - self.sampler.dilation[1] *
+ (self.sampler.kernel_size[1] - 1) -
+ 1) // self.sampler.stride[1] + 1
+
+ output_size = (out_h, out_w)
+ x = x.transpose(1, 2) # B, H/2*W/2, 4*C
+
+ if self.is_post_norm:
+ # use post-norm here
+ x = self.reduction(x)
+ x = self.norm(x) if self.norm else x
+ else:
+ x = self.norm(x) if self.norm else x
+ x = self.reduction(x)
+
+ return x, output_size
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/helpers.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/helpers.py
new file mode 100644
index 0000000000000000000000000000000000000000..bf55424f700091b0304ed1164ae292c2aa1da7d9
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/helpers.py
@@ -0,0 +1,53 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import collections.abc
+import warnings
+from itertools import repeat
+
+import torch
+from mmcv.utils import digit_version
+
+
+def is_tracing() -> bool:
+ """Determine whether the model is called during the tracing of code with
+ ``torch.jit.trace``."""
+ if digit_version(torch.__version__) >= digit_version('1.6.0'):
+ on_trace = torch.jit.is_tracing()
+ # In PyTorch 1.6, torch.jit.is_tracing has a bug.
+ # Refers to https://github.com/pytorch/pytorch/issues/42448
+ if isinstance(on_trace, bool):
+ return on_trace
+ else:
+ return torch._C._is_tracing()
+ else:
+ warnings.warn(
+ 'torch.jit.is_tracing is only supported after v1.6.0. '
+ 'Therefore is_tracing returns False automatically. Please '
+ 'set on_trace manually if you are using trace.', UserWarning)
+ return False
+
+
+# From PyTorch internals
+def _ntuple(n):
+ """A `to_tuple` function generator.
+
+ It returns a function, this function will repeat the input to a tuple of
+ length ``n`` if the input is not an Iterable object, otherwise, return the
+ input directly.
+
+ Args:
+ n (int): The number of the target length.
+ """
+
+ def parse(x):
+ if isinstance(x, collections.abc.Iterable):
+ return x
+ return tuple(repeat(x, n))
+
+ return parse
+
+
+to_1tuple = _ntuple(1)
+to_2tuple = _ntuple(2)
+to_3tuple = _ntuple(3)
+to_4tuple = _ntuple(4)
+to_ntuple = _ntuple
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/inverted_residual.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/inverted_residual.py
new file mode 100644
index 0000000000000000000000000000000000000000..7c432943b5bd6ee9b6328410835e57c690bdeb1c
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/inverted_residual.py
@@ -0,0 +1,125 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import torch.nn as nn
+import torch.utils.checkpoint as cp
+from mmcv.cnn import ConvModule
+from mmcv.cnn.bricks import DropPath
+from mmcv.runner import BaseModule
+
+from .se_layer import SELayer
+
+
+class InvertedResidual(BaseModule):
+ """Inverted Residual Block.
+
+ Args:
+ in_channels (int): The input channels of this module.
+ out_channels (int): The output channels of this module.
+ mid_channels (int): The input channels of the depthwise convolution.
+ kernel_size (int): The kernel size of the depthwise convolution.
+ Defaults to 3.
+ stride (int): The stride of the depthwise convolution. Defaults to 1.
+ se_cfg (dict, optional): Config dict for se layer. Defaults to None,
+ which means no se layer.
+ conv_cfg (dict): Config dict for convolution layer. Defaults to None,
+ which means using conv2d.
+ norm_cfg (dict): Config dict for normalization layer.
+ Defaults to ``dict(type='BN')``.
+ act_cfg (dict): Config dict for activation layer.
+ Defaults to ``dict(type='ReLU')``.
+ drop_path_rate (float): stochastic depth rate. Defaults to 0.
+ with_cp (bool): Use checkpoint or not. Using checkpoint will save some
+ memory while slowing down the training speed. Defaults to False.
+ init_cfg (dict | list[dict], optional): Initialization config dict.
+ """
+
+ def __init__(self,
+ in_channels,
+ out_channels,
+ mid_channels,
+ kernel_size=3,
+ stride=1,
+ se_cfg=None,
+ conv_cfg=None,
+ norm_cfg=dict(type='BN'),
+ act_cfg=dict(type='ReLU'),
+ drop_path_rate=0.,
+ with_cp=False,
+ init_cfg=None):
+ super(InvertedResidual, self).__init__(init_cfg)
+ self.with_res_shortcut = (stride == 1 and in_channels == out_channels)
+ assert stride in [1, 2]
+ self.with_cp = with_cp
+ self.drop_path = DropPath(
+ drop_path_rate) if drop_path_rate > 0 else nn.Identity()
+ self.with_se = se_cfg is not None
+ self.with_expand_conv = (mid_channels != in_channels)
+
+ if self.with_se:
+ assert isinstance(se_cfg, dict)
+
+ if self.with_expand_conv:
+ self.expand_conv = ConvModule(
+ in_channels=in_channels,
+ out_channels=mid_channels,
+ kernel_size=1,
+ stride=1,
+ padding=0,
+ conv_cfg=conv_cfg,
+ norm_cfg=norm_cfg,
+ act_cfg=act_cfg)
+ self.depthwise_conv = ConvModule(
+ in_channels=mid_channels,
+ out_channels=mid_channels,
+ kernel_size=kernel_size,
+ stride=stride,
+ padding=kernel_size // 2,
+ groups=mid_channels,
+ conv_cfg=conv_cfg,
+ norm_cfg=norm_cfg,
+ act_cfg=act_cfg)
+ if self.with_se:
+ self.se = SELayer(**se_cfg)
+ self.linear_conv = ConvModule(
+ in_channels=mid_channels,
+ out_channels=out_channels,
+ kernel_size=1,
+ stride=1,
+ padding=0,
+ conv_cfg=conv_cfg,
+ norm_cfg=norm_cfg,
+ act_cfg=None)
+
+ def forward(self, x):
+ """Forward function.
+
+ Args:
+ x (torch.Tensor): The input tensor.
+
+ Returns:
+ torch.Tensor: The output tensor.
+ """
+
+ def _inner_forward(x):
+ out = x
+
+ if self.with_expand_conv:
+ out = self.expand_conv(out)
+
+ out = self.depthwise_conv(out)
+
+ if self.with_se:
+ out = self.se(out)
+
+ out = self.linear_conv(out)
+
+ if self.with_res_shortcut:
+ return x + self.drop_path(out)
+ else:
+ return out
+
+ if self.with_cp and x.requires_grad:
+ out = cp.checkpoint(_inner_forward, x)
+ else:
+ out = _inner_forward(x)
+
+ return out
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/layer_scale.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/layer_scale.py
new file mode 100644
index 0000000000000000000000000000000000000000..fbd89bc2f001a181b2c961d053becd2756c2ccda
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/layer_scale.py
@@ -0,0 +1,35 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import torch
+from torch import nn
+
+
+class LayerScale(nn.Module):
+ """LayerScale layer.
+
+ Args:
+ dim (int): Dimension of input features.
+ inplace (bool): inplace: can optionally do the
+ operation in-place. Default: ``False``
+ data_format (str): The input data format, can be 'channels_last'
+ and 'channels_first', representing (B, C, H, W) and
+ (B, N, C) format data respectively.
+ """
+
+ def __init__(self,
+ dim: int,
+ inplace: bool = False,
+ data_format: str = 'channels_last'):
+ super().__init__()
+ assert data_format in ('channels_last', 'channels_first'), \
+ "'data_format' could only be channels_last or channels_first."
+ self.inplace = inplace
+ self.data_format = data_format
+ self.weight = nn.Parameter(torch.ones(dim) * 1e-5)
+
+ def forward(self, x):
+ if self.data_format == 'channels_first':
+ if self.inplace:
+ return x.mul_(self.weight.view(-1, 1, 1))
+ else:
+ return x * self.weight.view(-1, 1, 1)
+ return x.mul_(self.weight) if self.inplace else x * self.weight
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/utils/make_divisible.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/make_divisible.py
similarity index 95%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/models/utils/make_divisible.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/make_divisible.py
index dbf66dc76c32c86d18b066a4721dd96ea884489d..1ec74689e37d4a9d605a595adb0cca1da88aa19a 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/models/utils/make_divisible.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/make_divisible.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
def make_divisible(value, divisor, min_value=None, min_ratio=0.9):
"""Make divisible function.
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/position_encoding.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/position_encoding.py
new file mode 100644
index 0000000000000000000000000000000000000000..99f32de07c366a841cf3774687c0ce968bc0a6c2
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/position_encoding.py
@@ -0,0 +1,41 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import torch.nn as nn
+from mmcv.runner.base_module import BaseModule
+
+
+class ConditionalPositionEncoding(BaseModule):
+ """The Conditional Position Encoding (CPE) module.
+
+ The CPE is the implementation of 'Conditional Positional Encodings
+ for Vision Transformers '_.
+
+ Args:
+ in_channels (int): Number of input channels.
+ embed_dims (int): The feature dimension. Default: 768.
+ stride (int): Stride of conv layer. Default: 1.
+ """
+
+ def __init__(self, in_channels, embed_dims=768, stride=1, init_cfg=None):
+ super(ConditionalPositionEncoding, self).__init__(init_cfg=init_cfg)
+ self.proj = nn.Conv2d(
+ in_channels,
+ embed_dims,
+ kernel_size=3,
+ stride=stride,
+ padding=1,
+ bias=True,
+ groups=embed_dims)
+ self.stride = stride
+
+ def forward(self, x, hw_shape):
+ B, N, C = x.shape
+ H, W = hw_shape
+ feat_token = x
+ # convert (B, N, C) to (B, C, H, W)
+ cnn_feat = feat_token.transpose(1, 2).view(B, C, H, W).contiguous()
+ if self.stride == 1:
+ x = self.proj(cnn_feat) + cnn_feat
+ else:
+ x = self.proj(cnn_feat)
+ x = x.flatten(2).transpose(1, 2)
+ return x
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/se_layer.py b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/se_layer.py
new file mode 100644
index 0000000000000000000000000000000000000000..47a830ac7d811394618497fd30cb1f6b9fa51e25
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/models/utils/se_layer.py
@@ -0,0 +1,80 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import mmcv
+import torch.nn as nn
+from mmcv.cnn import ConvModule
+from mmcv.runner import BaseModule
+
+from .make_divisible import make_divisible
+
+
+class SELayer(BaseModule):
+ """Squeeze-and-Excitation Module.
+
+ Args:
+ channels (int): The input (and output) channels of the SE layer.
+ squeeze_channels (None or int): The intermediate channel number of
+ SElayer. Default: None, means the value of ``squeeze_channels``
+ is ``make_divisible(channels // ratio, divisor)``.
+ ratio (int): Squeeze ratio in SELayer, the intermediate channel will
+ be ``make_divisible(channels // ratio, divisor)``. Only used when
+ ``squeeze_channels`` is None. Default: 16.
+ divisor(int): The divisor to true divide the channel number. Only
+ used when ``squeeze_channels`` is None. Default: 8.
+ conv_cfg (None or dict): Config dict for convolution layer. Default:
+ None, which means using conv2d.
+ return_weight(bool): Whether to return the weight. Default: False.
+ act_cfg (dict or Sequence[dict]): Config dict for activation layer.
+ If act_cfg is a dict, two activation layers will be configurated
+ by this dict. If act_cfg is a sequence of dicts, the first
+ activation layer will be configurated by the first dict and the
+ second activation layer will be configurated by the second dict.
+ Default: (dict(type='ReLU'), dict(type='Sigmoid'))
+ """
+
+ def __init__(self,
+ channels,
+ squeeze_channels=None,
+ ratio=16,
+ divisor=8,
+ bias='auto',
+ conv_cfg=None,
+ act_cfg=(dict(type='ReLU'), dict(type='Sigmoid')),
+ return_weight=False,
+ init_cfg=None):
+ super(SELayer, self).__init__(init_cfg)
+ if isinstance(act_cfg, dict):
+ act_cfg = (act_cfg, act_cfg)
+ assert len(act_cfg) == 2
+ assert mmcv.is_tuple_of(act_cfg, dict)
+ self.global_avgpool = nn.AdaptiveAvgPool2d(1)
+ if squeeze_channels is None:
+ squeeze_channels = make_divisible(channels // ratio, divisor)
+ assert isinstance(squeeze_channels, int) and squeeze_channels > 0, \
+ '"squeeze_channels" should be a positive integer, but get ' + \
+ f'{squeeze_channels} instead.'
+ self.return_weight = return_weight
+ self.conv1 = ConvModule(
+ in_channels=channels,
+ out_channels=squeeze_channels,
+ kernel_size=1,
+ stride=1,
+ bias=bias,
+ conv_cfg=conv_cfg,
+ act_cfg=act_cfg[0])
+ self.conv2 = ConvModule(
+ in_channels=squeeze_channels,
+ out_channels=channels,
+ kernel_size=1,
+ stride=1,
+ bias=bias,
+ conv_cfg=conv_cfg,
+ act_cfg=act_cfg[1])
+
+ def forward(self, x):
+ out = self.global_avgpool(x)
+ out = self.conv1(out)
+ out = self.conv2(out)
+ if self.return_weight:
+ return out
+ else:
+ return x * out
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/utils/__init__.py b/openmmlab_test/mmclassification-0.24.1/mmcls/utils/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..abfea81b773f3cc21f72e6696b51f13656a4fa9e
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/utils/__init__.py
@@ -0,0 +1,12 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from .collect_env import collect_env
+from .device import auto_select_device
+from .distribution import wrap_distributed_model, wrap_non_distributed_model
+from .logger import get_root_logger, load_json_log
+from .setup_env import setup_multi_processes
+
+__all__ = [
+ 'collect_env', 'get_root_logger', 'load_json_log', 'setup_multi_processes',
+ 'wrap_non_distributed_model', 'wrap_distributed_model',
+ 'auto_select_device'
+]
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/utils/collect_env.py b/openmmlab_test/mmclassification-0.24.1/mmcls/utils/collect_env.py
similarity index 89%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/utils/collect_env.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/utils/collect_env.py
index f889ecf6852f3d1bcfe9e177562170f4f97efab1..adb5030f27f70b4ab7b7f5a19b580b72fd8658e5 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/utils/collect_env.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/utils/collect_env.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
from mmcv.utils import collect_env as collect_base_env
from mmcv.utils import get_git_hash
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/utils/device.py b/openmmlab_test/mmclassification-0.24.1/mmcls/utils/device.py
new file mode 100644
index 0000000000000000000000000000000000000000..ee4848adce2e1ffdf0c5b35e3001aa008b83e1c8
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/utils/device.py
@@ -0,0 +1,15 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import mmcv
+import torch
+from mmcv.utils import digit_version
+
+
+def auto_select_device() -> str:
+ mmcv_version = digit_version(mmcv.__version__)
+ if mmcv_version >= digit_version('1.6.0'):
+ from mmcv.device import get_device
+ return get_device()
+ elif torch.cuda.is_available():
+ return 'cuda'
+ else:
+ return 'cpu'
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/utils/distribution.py b/openmmlab_test/mmclassification-0.24.1/mmcls/utils/distribution.py
new file mode 100644
index 0000000000000000000000000000000000000000..d57bd2b53badb60ec209582f11393553c8177348
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/utils/distribution.py
@@ -0,0 +1,68 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+
+
+def wrap_non_distributed_model(model, device='cuda', dim=0, *args, **kwargs):
+ """Wrap module in non-distributed environment by device type.
+
+ - For CUDA, wrap as :obj:`mmcv.parallel.MMDataParallel`.
+ - For MPS, wrap as :obj:`mmcv.device.mps.MPSDataParallel`.
+ - For CPU & IPU, not wrap the model.
+
+ Args:
+ model(:class:`nn.Module`): model to be parallelized.
+ device(str): device type, cuda, cpu or mlu. Defaults to cuda.
+ dim(int): Dimension used to scatter the data. Defaults to 0.
+
+ Returns:
+ model(nn.Module): the model to be parallelized.
+ """
+ if device == 'npu':
+ from mmcv.device.npu import NPUDataParallel
+ model = NPUDataParallel(model.npu(), dim=dim, *args, **kwargs)
+ elif device == 'cuda':
+ from mmcv.parallel import MMDataParallel
+ model = MMDataParallel(model.cuda(), dim=dim, *args, **kwargs)
+ elif device == 'cpu':
+ model = model.cpu()
+ elif device == 'ipu':
+ model = model.cpu()
+ elif device == 'mps':
+ from mmcv.device import mps
+ model = mps.MPSDataParallel(model.to('mps'), dim=dim, *args, **kwargs)
+ else:
+ raise RuntimeError(f'Unavailable device "{device}"')
+
+ return model
+
+
+def wrap_distributed_model(model, device='cuda', *args, **kwargs):
+ """Build DistributedDataParallel module by device type.
+
+ - For CUDA, wrap as :obj:`mmcv.parallel.MMDistributedDataParallel`.
+ - Other device types are not supported by now.
+
+ Args:
+ model(:class:`nn.Module`): module to be parallelized.
+ device(str): device type, mlu or cuda.
+
+ Returns:
+ model(:class:`nn.Module`): the module to be parallelized
+
+ References:
+ .. [1] https://pytorch.org/docs/stable/generated/torch.nn.parallel.
+ DistributedDataParallel.html
+ """
+ if device == 'npu':
+ from mmcv.device.npu import NPUDistributedDataParallel
+ from torch.npu import current_device
+ model = NPUDistributedDataParallel(
+ model.npu(), *args, device_ids=[current_device()], **kwargs)
+ elif device == 'cuda':
+ from mmcv.parallel import MMDistributedDataParallel
+ from torch.cuda import current_device
+ model = MMDistributedDataParallel(
+ model.cuda(), *args, device_ids=[current_device()], **kwargs)
+ else:
+ raise RuntimeError(f'Unavailable device "{device}"')
+
+ return model
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/utils/logger.py b/openmmlab_test/mmclassification-0.24.1/mmcls/utils/logger.py
new file mode 100644
index 0000000000000000000000000000000000000000..2d77fcb9d3e12ae816848b1cba3385e5afe98e16
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/utils/logger.py
@@ -0,0 +1,56 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import json
+import logging
+from collections import defaultdict
+
+from mmcv.utils import get_logger
+
+
+def get_root_logger(log_file=None, log_level=logging.INFO):
+ """Get root logger.
+
+ Args:
+ log_file (str, optional): File path of log. Defaults to None.
+ log_level (int, optional): The level of logger.
+ Defaults to :obj:`logging.INFO`.
+
+ Returns:
+ :obj:`logging.Logger`: The obtained logger
+ """
+ return get_logger('mmcls', log_file, log_level)
+
+
+def load_json_log(json_log):
+ """load and convert json_logs to log_dicts.
+
+ Args:
+ json_log (str): The path of the json log file.
+
+ Returns:
+ dict[int, dict[str, list]]:
+ Key is the epoch, value is a sub dict. The keys in each sub dict
+ are different metrics, e.g. memory, bbox_mAP, and the value is a
+ list of corresponding values in all iterations in this epoch.
+
+ .. code-block:: python
+
+ # An example output
+ {
+ 1: {'iter': [100, 200, 300], 'loss': [6.94, 6.73, 6.53]},
+ 2: {'iter': [100, 200, 300], 'loss': [6.33, 6.20, 6.07]},
+ ...
+ }
+ """
+ log_dict = dict()
+ with open(json_log, 'r') as log_file:
+ for line in log_file:
+ log = json.loads(line.strip())
+ # skip lines without `epoch` field
+ if 'epoch' not in log:
+ continue
+ epoch = log.pop('epoch')
+ if epoch not in log_dict:
+ log_dict[epoch] = defaultdict(list)
+ for k, v in log.items():
+ log_dict[epoch][k].append(v)
+ return log_dict
diff --git a/openmmlab_test/mmclassification-0.24.1/mmcls/utils/setup_env.py b/openmmlab_test/mmclassification-0.24.1/mmcls/utils/setup_env.py
new file mode 100644
index 0000000000000000000000000000000000000000..21def2f0809153a5f755af2431f7e702db625e5c
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/utils/setup_env.py
@@ -0,0 +1,47 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import os
+import platform
+import warnings
+
+import cv2
+import torch.multiprocessing as mp
+
+
+def setup_multi_processes(cfg):
+ """Setup multi-processing environment variables."""
+ # set multi-process start method as `fork` to speed up the training
+ if platform.system() != 'Windows':
+ mp_start_method = cfg.get('mp_start_method', 'fork')
+ current_method = mp.get_start_method(allow_none=True)
+ if current_method is not None and current_method != mp_start_method:
+ warnings.warn(
+ f'Multi-processing start method `{mp_start_method}` is '
+ f'different from the previous setting `{current_method}`.'
+ f'It will be force set to `{mp_start_method}`. You can change '
+ f'this behavior by changing `mp_start_method` in your config.')
+ mp.set_start_method(mp_start_method, force=True)
+
+ # disable opencv multithreading to avoid system being overloaded
+ opencv_num_threads = cfg.get('opencv_num_threads', 0)
+ cv2.setNumThreads(opencv_num_threads)
+
+ # setup OMP threads
+ # This code is referred from https://github.com/pytorch/pytorch/blob/master/torch/distributed/run.py # noqa
+ if 'OMP_NUM_THREADS' not in os.environ and cfg.data.workers_per_gpu > 1:
+ omp_num_threads = 1
+ warnings.warn(
+ f'Setting OMP_NUM_THREADS environment variable for each process '
+ f'to be {omp_num_threads} in default, to avoid your system being '
+ f'overloaded, please further tune the variable for optimal '
+ f'performance in your application as needed.')
+ os.environ['OMP_NUM_THREADS'] = str(omp_num_threads)
+
+ # setup MKL threads
+ if 'MKL_NUM_THREADS' not in os.environ and cfg.data.workers_per_gpu > 1:
+ mkl_num_threads = 1
+ warnings.warn(
+ f'Setting MKL_NUM_THREADS environment variable for each process '
+ f'to be {mkl_num_threads} in default, to avoid your system being '
+ f'overloaded, please further tune the variable for optimal '
+ f'performance in your application as needed.')
+ os.environ['MKL_NUM_THREADS'] = str(mkl_num_threads)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/mmcls/version.py b/openmmlab_test/mmclassification-0.24.1/mmcls/version.py
similarity index 91%
rename from openmmlab_test/mmclassification-speed-benchmark/mmcls/version.py
rename to openmmlab_test/mmclassification-0.24.1/mmcls/version.py
index a9c00fb1de831697b5a35cc9ba0d3c5c899e6ef7..42df038864789a9f489cbe46b8f422f72e6f01cf 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/mmcls/version.py
+++ b/openmmlab_test/mmclassification-0.24.1/mmcls/version.py
@@ -1,6 +1,6 @@
-# Copyright (c) Open-MMLab. All rights reserved.
+# Copyright (c) OpenMMLab. All rights reserved
-__version__ = '0.12.0'
+__version__ = '0.24.1'
def parse_version_info(version_str):
diff --git a/openmmlab_test/mmclassification-0.24.1/model-index.yml b/openmmlab_test/mmclassification-0.24.1/model-index.yml
new file mode 100644
index 0000000000000000000000000000000000000000..56c7dc9729a402a5d711ec5a8ad8c5fed5166fc6
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/model-index.yml
@@ -0,0 +1,34 @@
+Import:
+ - configs/mobilenet_v2/metafile.yml
+ - configs/resnet/metafile.yml
+ - configs/res2net/metafile.yml
+ - configs/resnext/metafile.yml
+ - configs/seresnet/metafile.yml
+ - configs/shufflenet_v1/metafile.yml
+ - configs/shufflenet_v2/metafile.yml
+ - configs/swin_transformer/metafile.yml
+ - configs/swin_transformer_v2/metafile.yml
+ - configs/vgg/metafile.yml
+ - configs/repvgg/metafile.yml
+ - configs/tnt/metafile.yml
+ - configs/vision_transformer/metafile.yml
+ - configs/t2t_vit/metafile.yml
+ - configs/mlp_mixer/metafile.yml
+ - configs/conformer/metafile.yml
+ - configs/regnet/metafile.yml
+ - configs/deit/metafile.yml
+ - configs/twins/metafile.yml
+ - configs/efficientnet/metafile.yml
+ - configs/convnext/metafile.yml
+ - configs/hrnet/metafile.yml
+ - configs/repmlp/metafile.yml
+ - configs/wrn/metafile.yml
+ - configs/van/metafile.yml
+ - configs/cspnet/metafile.yml
+ - configs/convmixer/metafile.yml
+ - configs/densenet/metafile.yml
+ - configs/poolformer/metafile.yml
+ - configs/csra/metafile.yml
+ - configs/mvit/metafile.yml
+ - configs/efficientformer/metafile.yml
+ - configs/hornet/metafile.yml
diff --git a/openmmlab_test/mmclassification-0.24.1/mult_test.sh b/openmmlab_test/mmclassification-0.24.1/mult_test.sh
new file mode 100644
index 0000000000000000000000000000000000000000..ecf6f5fd62be63472e3c6735699b0c50b5afd0a1
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/mult_test.sh
@@ -0,0 +1,7 @@
+export MIOPEN_FIND_MODE=1
+export MIOPEN_USE_APPROXIMATE_PERFORMANCE=0
+export HSA_FORCE_FINE_GRAIN_PCIE=1
+./tools/dist_test.sh configs/vgg/vgg16_8xb32_in1k.py models/vgg16_bn_batch256_imagenet_20210208-7e55cd29.pth 4 --metrics=accuracy --metric-options=topk=5 2>&1 | tee fp16_vgg16.log
+./tools/dist_test.sh configs/resnet/resnet50_8xb32_in1k.py models/resnet50_8xb32_in1k_20210831-ea4938fc.pth 4 --metrics=accuracy --metric-options=topk=5 2>&1 | tee fp16_resnet50.log
+./tools/dist_test.sh configs/shufflenet_v2/shufflenet-v2-1x_16xb64_in1k.py models/shufflenet_v2_batch1024_imagenet_20200812-5bf4721e.pth 4 --metrics=accuracy --metric-options=topk=5 2>&1 | tee fp16_shufflenet_v2.log
+./tools/dist_test.sh configs/mobilenet_v2/mobilenet-v2_8xb32_in1k.py models/mobilenet_v2_batch256_imagenet_20200708-3b2dc3af.pth 4 --metrics=accuracy --metric-options=topk=5 2>&1 | tee fp16_mobilenet_v2.log
diff --git a/openmmlab_test/mmclassification-speed-benchmark/requirements.txt b/openmmlab_test/mmclassification-0.24.1/requirements.txt
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/requirements.txt
rename to openmmlab_test/mmclassification-0.24.1/requirements.txt
diff --git a/openmmlab_test/mmclassification-0.24.1/requirements/docs.txt b/openmmlab_test/mmclassification-0.24.1/requirements/docs.txt
new file mode 100644
index 0000000000000000000000000000000000000000..41b221f05cbc20f22964e92cbebb04664d8025a8
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/requirements/docs.txt
@@ -0,0 +1,6 @@
+docutils==0.17.1
+myst-parser
+-e git+https://github.com/open-mmlab/pytorch_sphinx_theme.git#egg=pytorch_sphinx_theme
+sphinx==4.5.0
+sphinx-copybutton
+sphinx_markdown_tables
diff --git a/openmmlab_test/mmclassification-0.24.1/requirements/mminstall.txt b/openmmlab_test/mmclassification-0.24.1/requirements/mminstall.txt
new file mode 100644
index 0000000000000000000000000000000000000000..be7abf0204d336048aeab7ace2ef3e2af1f705ef
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/requirements/mminstall.txt
@@ -0,0 +1 @@
+mmcv-full>=1.4.2,<1.9.0
diff --git a/openmmlab_test/mmclassification-0.24.1/requirements/optional.txt b/openmmlab_test/mmclassification-0.24.1/requirements/optional.txt
new file mode 100644
index 0000000000000000000000000000000000000000..cc0228041b1d4f3d5d7307b54af33967f1f0119e
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/requirements/optional.txt
@@ -0,0 +1,5 @@
+albumentations>=0.3.2 --no-binary qudida,albumentations
+colorama
+requests
+rich
+scipy
diff --git a/openmmlab_test/mmclassification-0.24.1/requirements/readthedocs.txt b/openmmlab_test/mmclassification-0.24.1/requirements/readthedocs.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3b346257ab44f5fc7128bacbe90c0f0de97fe2dc
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/requirements/readthedocs.txt
@@ -0,0 +1,3 @@
+mmcv>=1.4.2
+torch
+torchvision
diff --git a/openmmlab_test/mmclassification-0.24.1/requirements/runtime.txt b/openmmlab_test/mmclassification-0.24.1/requirements/runtime.txt
new file mode 100644
index 0000000000000000000000000000000000000000..0df372f7fa32f61d3d7721519b75a5dc5655761b
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/requirements/runtime.txt
@@ -0,0 +1,3 @@
+matplotlib>=3.1.0
+numpy
+packaging
diff --git a/openmmlab_test/mmclassification-speed-benchmark/requirements/tests.txt b/openmmlab_test/mmclassification-0.24.1/requirements/tests.txt
similarity index 92%
rename from openmmlab_test/mmclassification-speed-benchmark/requirements/tests.txt
rename to openmmlab_test/mmclassification-0.24.1/requirements/tests.txt
index 4babae51bcaa6355a9893e4b74d1768465d562e1..29d351b5587b380ac9db3d1512806bc46b85de40 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/requirements/tests.txt
+++ b/openmmlab_test/mmclassification-0.24.1/requirements/tests.txt
@@ -2,6 +2,7 @@ codecov
flake8
interrogate
isort==4.3.21
+mmdet
pytest
xdoctest >= 0.10.0
yapf
diff --git a/openmmlab_test/mmclassification-0.24.1/resources/mmcls-logo.png b/openmmlab_test/mmclassification-0.24.1/resources/mmcls-logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..6e65420ab9b63bc8080d1156372e6822e0efe15a
Binary files /dev/null and b/openmmlab_test/mmclassification-0.24.1/resources/mmcls-logo.png differ
diff --git a/openmmlab_test/mmclassification-0.24.1/setup.cfg b/openmmlab_test/mmclassification-0.24.1/setup.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..8c24f5ff0498f9082c29b9afc27d200355ae9e34
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/setup.cfg
@@ -0,0 +1,23 @@
+[bdist_wheel]
+universal=1
+
+[aliases]
+test=pytest
+
+[yapf]
+based_on_style = pep8
+blank_line_before_nested_class_or_def = true
+split_before_expression_after_opening_paren = true
+
+[isort]
+line_length = 79
+multi_line_output = 0
+extra_standard_library = pkg_resources,setuptools
+known_first_party = mmcls
+no_lines_before = STDLIB,LOCALFOLDER
+default_section = THIRDPARTY
+
+[codespell]
+skip = *.ipynb
+quiet-level = 3
+ignore-words-list = patten,confectionary,nd,ty,formating,dows
diff --git a/openmmlab_test/mmclassification-0.24.1/setup.py b/openmmlab_test/mmclassification-0.24.1/setup.py
new file mode 100644
index 0000000000000000000000000000000000000000..e7a9c63f8f57841612befaa184241f7183b5bf70
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/setup.py
@@ -0,0 +1,194 @@
+import os
+import os.path as osp
+import shutil
+import sys
+import warnings
+from setuptools import find_packages, setup
+
+
+def readme():
+ with open('README.md', encoding='utf-8') as f:
+ content = f.read()
+ return content
+
+
+def get_version():
+ version_file = 'mmcls/version.py'
+ with open(version_file, 'r', encoding='utf-8') as f:
+ exec(compile(f.read(), version_file, 'exec'))
+ return locals()['__version__']
+
+
+def parse_requirements(fname='requirements.txt', with_version=True):
+ """Parse the package dependencies listed in a requirements file but strips
+ specific versioning information.
+
+ Args:
+ fname (str): path to requirements file
+ with_version (bool, default=False): if True include version specs
+
+ Returns:
+ List[str]: list of requirements items
+
+ CommandLine:
+ python -c "import setup; print(setup.parse_requirements())"
+ """
+ import re
+ import sys
+ from os.path import exists
+ require_fpath = fname
+
+ def parse_line(line):
+ """Parse information from a line in a requirements text file."""
+ if line.startswith('-r '):
+ # Allow specifying requirements in other files
+ target = line.split(' ')[1]
+ for info in parse_require_file(target):
+ yield info
+ else:
+ info = {'line': line}
+ if line.startswith('-e '):
+ info['package'] = line.split('#egg=')[1]
+ else:
+ # Remove versioning from the package
+ pat = '(' + '|'.join(['>=', '==', '>']) + ')'
+ parts = re.split(pat, line, maxsplit=1)
+ parts = [p.strip() for p in parts]
+
+ info['package'] = parts[0]
+ if len(parts) > 1:
+ op, rest = parts[1:]
+ if ';' in rest:
+ # Handle platform specific dependencies
+ # http://setuptools.readthedocs.io/en/latest/setuptools.html#declaring-platform-specific-dependencies
+ version, platform_deps = map(str.strip,
+ rest.split(';'))
+ info['platform_deps'] = platform_deps
+ else:
+ version = rest # NOQA
+ if '--' in version:
+ # the `extras_require` doesn't accept options.
+ version = version.split('--')[0].strip()
+ info['version'] = (op, version)
+ yield info
+
+ def parse_require_file(fpath):
+ with open(fpath, 'r') as f:
+ for line in f.readlines():
+ line = line.strip()
+ if line and not line.startswith('#'):
+ for info in parse_line(line):
+ yield info
+
+ def gen_packages_items():
+ if exists(require_fpath):
+ for info in parse_require_file(require_fpath):
+ parts = [info['package']]
+ if with_version and 'version' in info:
+ parts.extend(info['version'])
+ if not sys.version.startswith('3.4'):
+ # apparently package_deps are broken in 3.4
+ platform_deps = info.get('platform_deps')
+ if platform_deps is not None:
+ parts.append(';' + platform_deps)
+ item = ''.join(parts)
+ yield item
+
+ packages = list(gen_packages_items())
+ return packages
+
+
+def add_mim_extension():
+ """Add extra files that are required to support MIM into the package.
+
+ These files will be added by creating a symlink to the originals if the
+ package is installed in `editable` mode (e.g. pip install -e .), or by
+ copying from the originals otherwise.
+ """
+
+ # parse installment mode
+ if 'develop' in sys.argv:
+ # installed by `pip install -e .`
+ mode = 'symlink'
+ elif 'sdist' in sys.argv or 'bdist_wheel' in sys.argv:
+ # installed by `pip install .`
+ # or create source distribution by `python setup.py sdist`
+ mode = 'copy'
+ else:
+ return
+
+ filenames = ['tools', 'configs', 'model-index.yml']
+ repo_path = osp.dirname(__file__)
+ mim_path = osp.join(repo_path, 'mmcls', '.mim')
+ os.makedirs(mim_path, exist_ok=True)
+
+ for filename in filenames:
+ if osp.exists(filename):
+ src_path = osp.join(repo_path, filename)
+ tar_path = osp.join(mim_path, filename)
+
+ if osp.isfile(tar_path) or osp.islink(tar_path):
+ os.remove(tar_path)
+ elif osp.isdir(tar_path):
+ shutil.rmtree(tar_path)
+
+ if mode == 'symlink':
+ src_relpath = osp.relpath(src_path, osp.dirname(tar_path))
+ try:
+ os.symlink(src_relpath, tar_path)
+ except OSError:
+ # Creating a symbolic link on windows may raise an
+ # `OSError: [WinError 1314]` due to privilege. If
+ # the error happens, the src file will be copied
+ mode = 'copy'
+ warnings.warn(
+ f'Failed to create a symbolic link for {src_relpath}, '
+ f'and it will be copied to {tar_path}')
+ else:
+ continue
+
+ if mode == 'copy':
+ if osp.isfile(src_path):
+ shutil.copyfile(src_path, tar_path)
+ elif osp.isdir(src_path):
+ shutil.copytree(src_path, tar_path)
+ else:
+ warnings.warn(f'Cannot copy file {src_path}.')
+ else:
+ raise ValueError(f'Invalid mode {mode}')
+
+
+if __name__ == '__main__':
+ add_mim_extension()
+ setup(
+ name='mmcls',
+ version=get_version(),
+ description='OpenMMLab Image Classification Toolbox and Benchmark',
+ long_description=readme(),
+ long_description_content_type='text/markdown',
+ keywords='computer vision, image classification',
+ packages=find_packages(exclude=('configs', 'tools', 'demo')),
+ include_package_data=True,
+ classifiers=[
+ 'Development Status :: 4 - Beta',
+ 'License :: OSI Approved :: Apache Software License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
+ 'Topic :: Scientific/Engineering :: Artificial Intelligence',
+ ],
+ url='https://github.com/open-mmlab/mmclassification',
+ author='MMClassification Contributors',
+ author_email='openmmlab@gmail.com',
+ license='Apache License 2.0',
+ install_requires=parse_requirements('requirements/runtime.txt'),
+ extras_require={
+ 'all': parse_requirements('requirements.txt'),
+ 'tests': parse_requirements('requirements/tests.txt'),
+ 'optional': parse_requirements('requirements/optional.txt'),
+ 'mim': parse_requirements('requirements/mminstall.txt'),
+ },
+ zip_safe=False)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/sing_test.sh b/openmmlab_test/mmclassification-0.24.1/sing_test.sh
similarity index 77%
rename from openmmlab_test/mmclassification-speed-benchmark/sing_test.sh
rename to openmmlab_test/mmclassification-0.24.1/sing_test.sh
index d52a2d328abb95b926a0ad26f7b7f87b913eb5b5..d42f20410d8b9e7926f8c7b1b054333cbb4ab1ec 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/sing_test.sh
+++ b/openmmlab_test/mmclassification-0.24.1/sing_test.sh
@@ -1,6 +1,6 @@
#!/bin/bash
export HIP_VISIBLE_DEVICES=3
-export MIOPEN_FIND_MODE=3
+export MIOPEN_FIND_MODE=1
my_config=$1
python3 tools/train.py $my_config
diff --git a/openmmlab_test/mmclassification-0.24.1/single_process.sh b/openmmlab_test/mmclassification-0.24.1/single_process.sh
new file mode 100644
index 0000000000000000000000000000000000000000..2f3f1603d7f43230c49524623fd60f56f880d9c2
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/single_process.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+lrank=$OMPI_COMM_WORLD_LOCAL_RANK
+comm_rank=$OMPI_COMM_WORLD_RANK
+comm_size=$OMPI_COMM_WORLD_SIZE
+export MASTER_ADDR=${1}
+
+APP="python3 tools/train.py configs/resnet/resnet18_b32x8_imagenet.py --launcher mpi"
+case ${lrank} in
+[0])
+ numactl --cpunodebind=0 --membind=0 ${APP}
+ ;;
+[1])
+
+ numactl --cpunodebind=1 --membind=1 ${APP}
+ ;;
+[2])
+
+ numactl --cpunodebind=2 --membind=2 ${APP}
+ ;;
+[3])
+
+ numactl --cpunodebind=3 --membind=3 ${APP}
+ ;;
+[4])
+
+ numactl --cpunodebind=4 --membind=4 ${APP}
+ ;;
+esac
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tests/data/color.jpg b/openmmlab_test/mmclassification-0.24.1/tests/data/color.jpg
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/tests/data/color.jpg
rename to openmmlab_test/mmclassification-0.24.1/tests/data/color.jpg
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/data/dataset/a/1.JPG b/openmmlab_test/mmclassification-0.24.1/tests/data/dataset/a/1.JPG
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/data/dataset/ann.txt b/openmmlab_test/mmclassification-0.24.1/tests/data/dataset/ann.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a21a9c4272ed71f998acdbf1f68b32c03882706b
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/data/dataset/ann.txt
@@ -0,0 +1,3 @@
+a/1.JPG 0
+b/2.jpeg 1
+b/subb/2.jpeg 1
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/data/dataset/b/2.jpeg b/openmmlab_test/mmclassification-0.24.1/tests/data/dataset/b/2.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/data/dataset/b/subb/3.jpg b/openmmlab_test/mmclassification-0.24.1/tests/data/dataset/b/subb/3.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/data/dataset/classes.txt b/openmmlab_test/mmclassification-0.24.1/tests/data/dataset/classes.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c012a51e609848598ce299aef954746393c49303
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/data/dataset/classes.txt
@@ -0,0 +1,2 @@
+bus
+car
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tests/data/gray.jpg b/openmmlab_test/mmclassification-0.24.1/tests/data/gray.jpg
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/tests/data/gray.jpg
rename to openmmlab_test/mmclassification-0.24.1/tests/data/gray.jpg
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/data/retinanet.py b/openmmlab_test/mmclassification-0.24.1/tests/data/retinanet.py
new file mode 100644
index 0000000000000000000000000000000000000000..e7e6ea0048d0bdecb8e29967ff8ae1ddaa7981c1
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/data/retinanet.py
@@ -0,0 +1,83 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+# small RetinaNet
+num_classes = 3
+
+# model settings
+model = dict(
+ type='RetinaNet',
+ backbone=dict(
+ type='ResNet',
+ depth=50,
+ num_stages=4,
+ out_indices=(0, 1, 2, 3),
+ frozen_stages=1,
+ norm_cfg=dict(type='BN', requires_grad=True),
+ norm_eval=True,
+ style='pytorch',
+ init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')),
+ neck=dict(
+ type='FPN',
+ in_channels=[256, 512, 1024, 2048],
+ out_channels=256,
+ start_level=1,
+ add_extra_convs='on_input',
+ num_outs=5),
+ bbox_head=dict(
+ type='RetinaHead',
+ num_classes=num_classes,
+ in_channels=256,
+ stacked_convs=1,
+ feat_channels=256,
+ anchor_generator=dict(
+ type='AnchorGenerator',
+ octave_base_scale=4,
+ scales_per_octave=3,
+ ratios=[0.5, 1.0, 2.0],
+ strides=[8, 16, 32, 64, 128]),
+ bbox_coder=dict(
+ type='DeltaXYWHBBoxCoder',
+ target_means=[.0, .0, .0, .0],
+ target_stds=[1.0, 1.0, 1.0, 1.0]),
+ loss_cls=dict(
+ type='FocalLoss',
+ use_sigmoid=True,
+ gamma=2.0,
+ alpha=0.25,
+ loss_weight=1.0),
+ loss_bbox=dict(type='L1Loss', loss_weight=1.0)),
+ # model training and testing settings
+ train_cfg=dict(
+ assigner=dict(
+ type='MaxIoUAssigner',
+ pos_iou_thr=0.5,
+ neg_iou_thr=0.4,
+ min_pos_iou=0,
+ ignore_iof_thr=-1),
+ allowed_border=-1,
+ pos_weight=-1,
+ debug=False),
+ test_cfg=dict(
+ nms_pre=1000,
+ min_bbox_size=0,
+ score_thr=0.05,
+ nms=dict(type='nms', iou_threshold=0.5),
+ max_per_img=100))
+
+img_norm_cfg = dict(
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
+test_pipeline = [
+ dict(type='LoadImageFromFile'),
+ dict(
+ type='MultiScaleFlipAug',
+ img_scale=(1333, 800),
+ flip=False,
+ transforms=[
+ dict(type='Resize', keep_ratio=True),
+ dict(type='RandomFlip'),
+ dict(type='Normalize', **img_norm_cfg),
+ dict(type='Pad', size_divisor=32),
+ dict(type='ImageToTensor', keys=['img']),
+ dict(type='Collect', keys=['img']),
+ ])
+]
+data = dict(test=dict(pipeline=test_pipeline))
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/data/test.logjson b/openmmlab_test/mmclassification-0.24.1/tests/data/test.logjson
new file mode 100644
index 0000000000000000000000000000000000000000..dd9a16038e8c0c3354d89376691c2fe72f1d8e82
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/data/test.logjson
@@ -0,0 +1,10 @@
+{"a": "b"}
+{"mode": "train", "epoch": 1, "iter": 10, "lr": 0.01309, "memory": 0, "data_time": 0.0072, "time": 0.00727}
+{"mode": "train", "epoch": 1, "iter": 20, "lr": 0.02764, "memory": 0, "data_time": 0.00044, "time": 0.00046}
+{"mode": "train", "epoch": 1, "iter": 30, "lr": 0.04218, "memory": 0, "data_time": 0.00028, "time": 0.0003}
+{"mode": "train", "epoch": 1, "iter": 40, "lr": 0.05673, "memory": 0, "data_time": 0.00027, "time": 0.00029}
+{"mode": "train", "epoch": 2, "iter": 10, "lr": 0.17309, "memory": 0, "data_time": 0.00048, "time": 0.0005}
+{"mode": "train", "epoch": 2, "iter": 20, "lr": 0.18763, "memory": 0, "data_time": 0.00038, "time": 0.0004}
+{"mode": "train", "epoch": 2, "iter": 30, "lr": 0.20218, "memory": 0, "data_time": 0.00037, "time": 0.00039}
+{"mode": "train", "epoch": 3, "iter": 10, "lr": 0.33305, "memory": 0, "data_time": 0.00045, "time": 0.00046}
+{"mode": "train", "epoch": 3, "iter": 20, "lr": 0.34759, "memory": 0, "data_time": 0.0003, "time": 0.00032}
\ No newline at end of file
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_data/test_builder.py b/openmmlab_test/mmclassification-0.24.1/tests/test_data/test_builder.py
new file mode 100644
index 0000000000000000000000000000000000000000..c911b982211ec657e37d190bfcf5a891540461fc
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_data/test_builder.py
@@ -0,0 +1,272 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import os.path as osp
+from copy import deepcopy
+from unittest.mock import patch
+
+import torch
+from mmcv.utils import digit_version
+
+from mmcls.datasets import ImageNet, build_dataloader, build_dataset
+from mmcls.datasets.dataset_wrappers import (ClassBalancedDataset,
+ ConcatDataset, KFoldDataset,
+ RepeatDataset)
+
+
+class TestDataloaderBuilder():
+
+ @classmethod
+ def setup_class(cls):
+ cls.data = list(range(20))
+ cls.samples_per_gpu = 5
+ cls.workers_per_gpu = 1
+
+ @patch('mmcls.datasets.builder.get_dist_info', return_value=(0, 1))
+ def test_single_gpu(self, _):
+ common_cfg = dict(
+ dataset=self.data,
+ samples_per_gpu=self.samples_per_gpu,
+ workers_per_gpu=self.workers_per_gpu,
+ dist=False)
+
+ # Test default config
+ dataloader = build_dataloader(**common_cfg)
+
+ if digit_version(torch.__version__) >= digit_version('1.8.0'):
+ assert dataloader.persistent_workers
+ elif hasattr(dataloader, 'persistent_workers'):
+ assert not dataloader.persistent_workers
+
+ assert dataloader.batch_size == self.samples_per_gpu
+ assert dataloader.num_workers == self.workers_per_gpu
+ assert not all(
+ torch.cat(list(iter(dataloader))) == torch.tensor(self.data))
+
+ # Test without shuffle
+ dataloader = build_dataloader(**common_cfg, shuffle=False)
+ assert all(
+ torch.cat(list(iter(dataloader))) == torch.tensor(self.data))
+
+ # Test with custom sampler_cfg
+ dataloader = build_dataloader(
+ **common_cfg,
+ sampler_cfg=dict(type='RepeatAugSampler', selected_round=0),
+ shuffle=False)
+ expect = [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6]
+ assert all(torch.cat(list(iter(dataloader))) == torch.tensor(expect))
+
+ @patch('mmcls.datasets.builder.get_dist_info', return_value=(0, 1))
+ def test_multi_gpu(self, _):
+ common_cfg = dict(
+ dataset=self.data,
+ samples_per_gpu=self.samples_per_gpu,
+ workers_per_gpu=self.workers_per_gpu,
+ num_gpus=2,
+ dist=False)
+
+ # Test default config
+ dataloader = build_dataloader(**common_cfg)
+
+ if digit_version(torch.__version__) >= digit_version('1.8.0'):
+ assert dataloader.persistent_workers
+ elif hasattr(dataloader, 'persistent_workers'):
+ assert not dataloader.persistent_workers
+
+ assert dataloader.batch_size == self.samples_per_gpu * 2
+ assert dataloader.num_workers == self.workers_per_gpu * 2
+ assert not all(
+ torch.cat(list(iter(dataloader))) == torch.tensor(self.data))
+
+ # Test without shuffle
+ dataloader = build_dataloader(**common_cfg, shuffle=False)
+ assert all(
+ torch.cat(list(iter(dataloader))) == torch.tensor(self.data))
+
+ # Test with custom sampler_cfg
+ dataloader = build_dataloader(
+ **common_cfg,
+ sampler_cfg=dict(type='RepeatAugSampler', selected_round=0),
+ shuffle=False)
+ expect = torch.tensor(
+ [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6])
+ assert all(torch.cat(list(iter(dataloader))) == expect)
+
+ @patch('mmcls.datasets.builder.get_dist_info', return_value=(1, 2))
+ def test_distributed(self, _):
+ common_cfg = dict(
+ dataset=self.data,
+ samples_per_gpu=self.samples_per_gpu,
+ workers_per_gpu=self.workers_per_gpu,
+ num_gpus=2, # num_gpus will be ignored in distributed environment.
+ dist=True)
+
+ # Test default config
+ dataloader = build_dataloader(**common_cfg)
+
+ if digit_version(torch.__version__) >= digit_version('1.8.0'):
+ assert dataloader.persistent_workers
+ elif hasattr(dataloader, 'persistent_workers'):
+ assert not dataloader.persistent_workers
+
+ assert dataloader.batch_size == self.samples_per_gpu
+ assert dataloader.num_workers == self.workers_per_gpu
+ non_expect = torch.tensor(self.data[1::2])
+ assert not all(torch.cat(list(iter(dataloader))) == non_expect)
+
+ # Test without shuffle
+ dataloader = build_dataloader(**common_cfg, shuffle=False)
+ expect = torch.tensor(self.data[1::2])
+ assert all(torch.cat(list(iter(dataloader))) == expect)
+
+ # Test with custom sampler_cfg
+ dataloader = build_dataloader(
+ **common_cfg,
+ sampler_cfg=dict(type='RepeatAugSampler', selected_round=0),
+ shuffle=False)
+ expect = torch.tensor(
+ [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6][1::2])
+ assert all(torch.cat(list(iter(dataloader))) == expect)
+
+
+class TestDatasetBuilder():
+
+ @classmethod
+ def setup_class(cls):
+ data_prefix = osp.join(osp.dirname(__file__), '../data/dataset')
+ cls.dataset_cfg = dict(
+ type='ImageNet',
+ data_prefix=data_prefix,
+ ann_file=osp.join(data_prefix, 'ann.txt'),
+ pipeline=[],
+ test_mode=False,
+ )
+
+ def test_normal_dataset(self):
+ # Test build
+ dataset = build_dataset(self.dataset_cfg)
+ assert isinstance(dataset, ImageNet)
+ assert dataset.test_mode == self.dataset_cfg['test_mode']
+
+ # Test default_args
+ dataset = build_dataset(self.dataset_cfg, {'test_mode': True})
+ assert dataset.test_mode == self.dataset_cfg['test_mode']
+
+ cp_cfg = deepcopy(self.dataset_cfg)
+ cp_cfg.pop('test_mode')
+ dataset = build_dataset(cp_cfg, {'test_mode': True})
+ assert dataset.test_mode
+
+ def test_concat_dataset(self):
+ # Test build
+ dataset = build_dataset([self.dataset_cfg, self.dataset_cfg])
+ assert isinstance(dataset, ConcatDataset)
+ assert dataset.datasets[0].test_mode == self.dataset_cfg['test_mode']
+
+ # Test default_args
+ dataset = build_dataset([self.dataset_cfg, self.dataset_cfg],
+ {'test_mode': True})
+ assert dataset.datasets[0].test_mode == self.dataset_cfg['test_mode']
+
+ cp_cfg = deepcopy(self.dataset_cfg)
+ cp_cfg.pop('test_mode')
+ dataset = build_dataset([cp_cfg, cp_cfg], {'test_mode': True})
+ assert dataset.datasets[0].test_mode
+
+ def test_repeat_dataset(self):
+ # Test build
+ dataset = build_dataset(
+ dict(type='RepeatDataset', dataset=self.dataset_cfg, times=3))
+ assert isinstance(dataset, RepeatDataset)
+ assert dataset.dataset.test_mode == self.dataset_cfg['test_mode']
+
+ # Test default_args
+ dataset = build_dataset(
+ dict(type='RepeatDataset', dataset=self.dataset_cfg, times=3),
+ {'test_mode': True})
+ assert dataset.dataset.test_mode == self.dataset_cfg['test_mode']
+
+ cp_cfg = deepcopy(self.dataset_cfg)
+ cp_cfg.pop('test_mode')
+ dataset = build_dataset(
+ dict(type='RepeatDataset', dataset=cp_cfg, times=3),
+ {'test_mode': True})
+ assert dataset.dataset.test_mode
+
+ def test_class_balance_dataset(self):
+ # Test build
+ dataset = build_dataset(
+ dict(
+ type='ClassBalancedDataset',
+ dataset=self.dataset_cfg,
+ oversample_thr=1.,
+ ))
+ assert isinstance(dataset, ClassBalancedDataset)
+ assert dataset.dataset.test_mode == self.dataset_cfg['test_mode']
+
+ # Test default_args
+ dataset = build_dataset(
+ dict(
+ type='ClassBalancedDataset',
+ dataset=self.dataset_cfg,
+ oversample_thr=1.,
+ ), {'test_mode': True})
+ assert dataset.dataset.test_mode == self.dataset_cfg['test_mode']
+
+ cp_cfg = deepcopy(self.dataset_cfg)
+ cp_cfg.pop('test_mode')
+ dataset = build_dataset(
+ dict(
+ type='ClassBalancedDataset',
+ dataset=cp_cfg,
+ oversample_thr=1.,
+ ), {'test_mode': True})
+ assert dataset.dataset.test_mode
+
+ def test_kfold_dataset(self):
+ # Test build
+ dataset = build_dataset(
+ dict(
+ type='KFoldDataset',
+ dataset=self.dataset_cfg,
+ fold=0,
+ num_splits=5,
+ test_mode=False,
+ ))
+ assert isinstance(dataset, KFoldDataset)
+ assert not dataset.test_mode
+ assert dataset.dataset.test_mode == self.dataset_cfg['test_mode']
+
+ # Test default_args
+ dataset = build_dataset(
+ dict(
+ type='KFoldDataset',
+ dataset=self.dataset_cfg,
+ fold=0,
+ num_splits=5,
+ test_mode=False,
+ ),
+ default_args={
+ 'test_mode': True,
+ 'classes': [1, 2, 3]
+ })
+ assert not dataset.test_mode
+ assert dataset.dataset.test_mode == self.dataset_cfg['test_mode']
+ assert dataset.dataset.CLASSES == [1, 2, 3]
+
+ cp_cfg = deepcopy(self.dataset_cfg)
+ cp_cfg.pop('test_mode')
+ dataset = build_dataset(
+ dict(
+ type='KFoldDataset',
+ dataset=self.dataset_cfg,
+ fold=0,
+ num_splits=5,
+ ),
+ default_args={
+ 'test_mode': True,
+ 'classes': [1, 2, 3]
+ })
+ # The test_mode in default_args will be passed to KFoldDataset
+ assert dataset.test_mode
+ assert not dataset.dataset.test_mode
+ # Other default_args will be passed to child dataset.
+ assert dataset.dataset.CLASSES == [1, 2, 3]
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_data/test_datasets/test_common.py b/openmmlab_test/mmclassification-0.24.1/tests/test_data/test_datasets/test_common.py
new file mode 100644
index 0000000000000000000000000000000000000000..5ec38184763c3b2928e0b9f3141678d08716a7a2
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_data/test_datasets/test_common.py
@@ -0,0 +1,911 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import os
+import os.path as osp
+import pickle
+import tempfile
+from unittest import TestCase
+from unittest.mock import patch
+
+import numpy as np
+import torch
+
+from mmcls.datasets import DATASETS
+from mmcls.datasets import BaseDataset as _BaseDataset
+from mmcls.datasets import MultiLabelDataset as _MultiLabelDataset
+
+ASSETS_ROOT = osp.abspath(
+ osp.join(osp.dirname(__file__), '../../data/dataset'))
+
+
+class BaseDataset(_BaseDataset):
+
+ def load_annotations(self):
+ pass
+
+
+class MultiLabelDataset(_MultiLabelDataset):
+
+ def load_annotations(self):
+ pass
+
+
+DATASETS.module_dict['BaseDataset'] = BaseDataset
+DATASETS.module_dict['MultiLabelDataset'] = MultiLabelDataset
+
+
+class TestBaseDataset(TestCase):
+ DATASET_TYPE = 'BaseDataset'
+
+ DEFAULT_ARGS = dict(data_prefix='', pipeline=[])
+
+ def test_initialize(self):
+ dataset_class = DATASETS.get(self.DATASET_TYPE)
+
+ with patch.object(dataset_class, 'load_annotations'):
+ # Test default behavior
+ cfg = {**self.DEFAULT_ARGS, 'classes': None, 'ann_file': None}
+ dataset = dataset_class(**cfg)
+ self.assertEqual(dataset.CLASSES, dataset_class.CLASSES)
+ self.assertFalse(dataset.test_mode)
+ self.assertIsNone(dataset.ann_file)
+
+ # Test setting classes as a tuple
+ cfg = {**self.DEFAULT_ARGS, 'classes': ('bus', 'car')}
+ dataset = dataset_class(**cfg)
+ self.assertEqual(dataset.CLASSES, ('bus', 'car'))
+
+ # Test setting classes as a tuple
+ cfg = {**self.DEFAULT_ARGS, 'classes': ['bus', 'car']}
+ dataset = dataset_class(**cfg)
+ self.assertEqual(dataset.CLASSES, ['bus', 'car'])
+
+ # Test setting classes through a file
+ classes_file = osp.join(ASSETS_ROOT, 'classes.txt')
+ cfg = {**self.DEFAULT_ARGS, 'classes': classes_file}
+ dataset = dataset_class(**cfg)
+ self.assertEqual(dataset.CLASSES, ['bus', 'car'])
+ self.assertEqual(dataset.class_to_idx, {'bus': 0, 'car': 1})
+
+ # Test invalid classes
+ cfg = {**self.DEFAULT_ARGS, 'classes': dict(classes=1)}
+ with self.assertRaisesRegex(ValueError, "type "):
+ dataset_class(**cfg)
+
+ def test_get_cat_ids(self):
+ dataset_class = DATASETS.get(self.DATASET_TYPE)
+ fake_ann = [
+ dict(
+ img_prefix='',
+ img_info=dict(),
+ gt_label=np.array(0, dtype=np.int64))
+ ]
+
+ with patch.object(dataset_class, 'load_annotations') as mock_load:
+ mock_load.return_value = fake_ann
+ dataset = dataset_class(**self.DEFAULT_ARGS)
+
+ cat_ids = dataset.get_cat_ids(0)
+ self.assertIsInstance(cat_ids, list)
+ self.assertEqual(len(cat_ids), 1)
+ self.assertIsInstance(cat_ids[0], int)
+
+ def test_evaluate(self):
+ dataset_class = DATASETS.get(self.DATASET_TYPE)
+
+ fake_ann = [
+ dict(gt_label=np.array(0, dtype=np.int64)),
+ dict(gt_label=np.array(0, dtype=np.int64)),
+ dict(gt_label=np.array(1, dtype=np.int64)),
+ dict(gt_label=np.array(2, dtype=np.int64)),
+ dict(gt_label=np.array(1, dtype=np.int64)),
+ dict(gt_label=np.array(0, dtype=np.int64)),
+ ]
+
+ with patch.object(dataset_class, 'load_annotations') as mock_load:
+ mock_load.return_value = fake_ann
+ dataset = dataset_class(**self.DEFAULT_ARGS)
+
+ fake_results = np.array([
+ [0.7, 0.0, 0.3],
+ [0.5, 0.2, 0.3],
+ [0.4, 0.5, 0.1],
+ [0.0, 0.0, 1.0],
+ [0.0, 0.0, 1.0],
+ [0.0, 0.0, 1.0],
+ ])
+
+ eval_results = dataset.evaluate(
+ fake_results,
+ metric=['precision', 'recall', 'f1_score', 'support', 'accuracy'],
+ metric_options={'topk': 1})
+
+ # Test results
+ self.assertAlmostEqual(
+ eval_results['precision'], (1 + 1 + 1 / 3) / 3 * 100.0, places=4)
+ self.assertAlmostEqual(
+ eval_results['recall'], (2 / 3 + 1 / 2 + 1) / 3 * 100.0, places=4)
+ self.assertAlmostEqual(
+ eval_results['f1_score'], (4 / 5 + 2 / 3 + 1 / 2) / 3 * 100.0,
+ places=4)
+ self.assertEqual(eval_results['support'], 6)
+ self.assertAlmostEqual(eval_results['accuracy'], 4 / 6 * 100, places=4)
+
+ # test indices
+ eval_results_ = dataset.evaluate(
+ fake_results[:5],
+ metric=['precision', 'recall', 'f1_score', 'support', 'accuracy'],
+ metric_options={'topk': 1},
+ indices=range(5))
+ self.assertAlmostEqual(
+ eval_results_['precision'], (1 + 1 + 1 / 2) / 3 * 100.0, places=4)
+ self.assertAlmostEqual(
+ eval_results_['recall'], (1 + 1 / 2 + 1) / 3 * 100.0, places=4)
+ self.assertAlmostEqual(
+ eval_results_['f1_score'], (1 + 2 / 3 + 2 / 3) / 3 * 100.0,
+ places=4)
+ self.assertEqual(eval_results_['support'], 5)
+ self.assertAlmostEqual(
+ eval_results_['accuracy'], 4 / 5 * 100, places=4)
+
+ # test input as tensor
+ fake_results_tensor = torch.from_numpy(fake_results)
+ eval_results_ = dataset.evaluate(
+ fake_results_tensor,
+ metric=['precision', 'recall', 'f1_score', 'support', 'accuracy'],
+ metric_options={'topk': 1})
+ assert eval_results_ == eval_results
+
+ # test thr
+ eval_results = dataset.evaluate(
+ fake_results,
+ metric=['precision', 'recall', 'f1_score', 'accuracy'],
+ metric_options={
+ 'thrs': 0.6,
+ 'topk': 1
+ })
+
+ self.assertAlmostEqual(
+ eval_results['precision'], (1 + 0 + 1 / 3) / 3 * 100.0, places=4)
+ self.assertAlmostEqual(
+ eval_results['recall'], (1 / 3 + 0 + 1) / 3 * 100.0, places=4)
+ self.assertAlmostEqual(
+ eval_results['f1_score'], (1 / 2 + 0 + 1 / 2) / 3 * 100.0,
+ places=4)
+ self.assertAlmostEqual(eval_results['accuracy'], 2 / 6 * 100, places=4)
+
+ # thrs must be a number or tuple
+ with self.assertRaises(TypeError):
+ dataset.evaluate(
+ fake_results,
+ metric=['precision', 'recall', 'f1_score', 'accuracy'],
+ metric_options={
+ 'thrs': 'thr',
+ 'topk': 1
+ })
+
+ # test topk and thr as tuple
+ eval_results = dataset.evaluate(
+ fake_results,
+ metric=['precision', 'recall', 'f1_score', 'accuracy'],
+ metric_options={
+ 'thrs': (0.5, 0.6),
+ 'topk': (1, 2)
+ })
+ self.assertEqual(
+ {
+ 'precision_thr_0.50', 'precision_thr_0.60', 'recall_thr_0.50',
+ 'recall_thr_0.60', 'f1_score_thr_0.50', 'f1_score_thr_0.60',
+ 'accuracy_top-1_thr_0.50', 'accuracy_top-1_thr_0.60',
+ 'accuracy_top-2_thr_0.50', 'accuracy_top-2_thr_0.60'
+ }, eval_results.keys())
+
+ self.assertIsInstance(eval_results['precision_thr_0.50'], float)
+ self.assertIsInstance(eval_results['recall_thr_0.50'], float)
+ self.assertIsInstance(eval_results['f1_score_thr_0.50'], float)
+ self.assertIsInstance(eval_results['accuracy_top-1_thr_0.50'], float)
+
+ # test topk is tuple while thrs is number
+ eval_results = dataset.evaluate(
+ fake_results,
+ metric='accuracy',
+ metric_options={
+ 'thrs': 0.5,
+ 'topk': (1, 2)
+ })
+ self.assertEqual({'accuracy_top-1', 'accuracy_top-2'},
+ eval_results.keys())
+ self.assertIsInstance(eval_results['accuracy_top-1'], float)
+
+ # test topk is number while thrs is tuple
+ eval_results = dataset.evaluate(
+ fake_results,
+ metric='accuracy',
+ metric_options={
+ 'thrs': (0.5, 0.6),
+ 'topk': 1
+ })
+ self.assertEqual({'accuracy_thr_0.50', 'accuracy_thr_0.60'},
+ eval_results.keys())
+ self.assertIsInstance(eval_results['accuracy_thr_0.50'], float)
+
+ # test evaluation results for classes
+ eval_results = dataset.evaluate(
+ fake_results,
+ metric=['precision', 'recall', 'f1_score', 'support'],
+ metric_options={'average_mode': 'none'})
+ self.assertEqual(eval_results['precision'].shape, (3, ))
+ self.assertEqual(eval_results['recall'].shape, (3, ))
+ self.assertEqual(eval_results['f1_score'].shape, (3, ))
+ self.assertEqual(eval_results['support'].shape, (3, ))
+
+ # the average_mode method must be valid
+ with self.assertRaises(ValueError):
+ dataset.evaluate(
+ fake_results,
+ metric=['precision', 'recall', 'f1_score', 'support'],
+ metric_options={'average_mode': 'micro'})
+
+ # the metric must be valid for the dataset
+ with self.assertRaisesRegex(ValueError,
+ "{'unknown'} is not supported"):
+ dataset.evaluate(fake_results, metric='unknown')
+
+
+class TestMultiLabelDataset(TestBaseDataset):
+ DATASET_TYPE = 'MultiLabelDataset'
+
+ def test_get_cat_ids(self):
+ dataset_class = DATASETS.get(self.DATASET_TYPE)
+ fake_ann = [
+ dict(
+ img_prefix='',
+ img_info=dict(),
+ gt_label=np.array([0, 1, 1, 0], dtype=np.uint8))
+ ]
+
+ with patch.object(dataset_class, 'load_annotations') as mock_load:
+ mock_load.return_value = fake_ann
+ dataset = dataset_class(**self.DEFAULT_ARGS)
+
+ cat_ids = dataset.get_cat_ids(0)
+ self.assertIsInstance(cat_ids, list)
+ self.assertEqual(len(cat_ids), 2)
+ self.assertIsInstance(cat_ids[0], int)
+ self.assertEqual(cat_ids, [1, 2])
+
+ def test_evaluate(self):
+ dataset_class = DATASETS.get(self.DATASET_TYPE)
+
+ fake_ann = [
+ dict(gt_label=np.array([1, 1, 0, -1], dtype=np.int8)),
+ dict(gt_label=np.array([1, 1, 0, -1], dtype=np.int8)),
+ dict(gt_label=np.array([0, -1, 1, -1], dtype=np.int8)),
+ dict(gt_label=np.array([0, 1, 0, -1], dtype=np.int8)),
+ dict(gt_label=np.array([0, 1, 0, -1], dtype=np.int8)),
+ ]
+
+ with patch.object(dataset_class, 'load_annotations') as mock_load:
+ mock_load.return_value = fake_ann
+ dataset = dataset_class(**self.DEFAULT_ARGS)
+
+ fake_results = np.array([
+ [0.9, 0.8, 0.3, 0.2],
+ [0.1, 0.2, 0.2, 0.1],
+ [0.7, 0.5, 0.9, 0.3],
+ [0.8, 0.1, 0.1, 0.2],
+ [0.8, 0.1, 0.1, 0.2],
+ ])
+
+ # the metric must be valid for the dataset
+ with self.assertRaisesRegex(ValueError,
+ "{'unknown'} is not supported"):
+ dataset.evaluate(fake_results, metric='unknown')
+
+ # only one metric
+ eval_results = dataset.evaluate(fake_results, metric='mAP')
+ self.assertEqual(eval_results.keys(), {'mAP'})
+ self.assertAlmostEqual(eval_results['mAP'], 67.5, places=4)
+
+ # multiple metrics
+ eval_results = dataset.evaluate(
+ fake_results, metric=['mAP', 'CR', 'OF1'])
+ self.assertEqual(eval_results.keys(), {'mAP', 'CR', 'OF1'})
+ self.assertAlmostEqual(eval_results['mAP'], 67.50, places=2)
+ self.assertAlmostEqual(eval_results['CR'], 43.75, places=2)
+ self.assertAlmostEqual(eval_results['OF1'], 42.86, places=2)
+
+
+class TestCustomDataset(TestBaseDataset):
+ DATASET_TYPE = 'CustomDataset'
+
+ def test_load_annotations(self):
+ dataset_class = DATASETS.get(self.DATASET_TYPE)
+
+ # test load without ann_file
+ cfg = {
+ **self.DEFAULT_ARGS,
+ 'data_prefix': ASSETS_ROOT,
+ 'ann_file': None,
+ }
+ dataset = dataset_class(**cfg)
+ self.assertEqual(len(dataset), 3)
+ self.assertEqual(dataset.CLASSES, ['a', 'b']) # auto infer classes
+ self.assertEqual(
+ dataset.data_infos[0], {
+ 'img_prefix': ASSETS_ROOT,
+ 'img_info': {
+ 'filename': 'a/1.JPG'
+ },
+ 'gt_label': np.array(0)
+ })
+ self.assertEqual(
+ dataset.data_infos[2], {
+ 'img_prefix': ASSETS_ROOT,
+ 'img_info': {
+ 'filename': 'b/subb/3.jpg'
+ },
+ 'gt_label': np.array(1)
+ })
+
+ # test ann_file assertion
+ cfg = {
+ **self.DEFAULT_ARGS,
+ 'data_prefix': ASSETS_ROOT,
+ 'ann_file': ['ann_file.txt'],
+ }
+ with self.assertRaisesRegex(TypeError, 'must be a str'):
+ dataset_class(**cfg)
+
+ # test load with ann_file
+ cfg = {
+ **self.DEFAULT_ARGS,
+ 'data_prefix': ASSETS_ROOT,
+ 'ann_file': osp.join(ASSETS_ROOT, 'ann.txt'),
+ }
+ dataset = dataset_class(**cfg)
+ self.assertEqual(len(dataset), 3)
+ # custom dataset won't infer CLASSES from ann_file
+ self.assertEqual(dataset.CLASSES, dataset_class.CLASSES)
+ self.assertEqual(
+ dataset.data_infos[0], {
+ 'img_prefix': ASSETS_ROOT,
+ 'img_info': {
+ 'filename': 'a/1.JPG'
+ },
+ 'gt_label': np.array(0)
+ })
+ self.assertEqual(
+ dataset.data_infos[2], {
+ 'img_prefix': ASSETS_ROOT,
+ 'img_info': {
+ 'filename': 'b/subb/2.jpeg'
+ },
+ 'gt_label': np.array(1)
+ })
+
+ # test extensions filter
+ cfg = {
+ **self.DEFAULT_ARGS, 'data_prefix': ASSETS_ROOT,
+ 'ann_file': None,
+ 'extensions': ('.txt', )
+ }
+ with self.assertRaisesRegex(RuntimeError,
+ 'Supported extensions are: .txt'):
+ dataset_class(**cfg)
+
+ cfg = {
+ **self.DEFAULT_ARGS, 'data_prefix': ASSETS_ROOT,
+ 'ann_file': None,
+ 'extensions': ('.jpeg', )
+ }
+ with self.assertWarnsRegex(UserWarning,
+ 'Supported extensions are: .jpeg'):
+ dataset = dataset_class(**cfg)
+ self.assertEqual(len(dataset), 1)
+ self.assertEqual(
+ dataset.data_infos[0], {
+ 'img_prefix': ASSETS_ROOT,
+ 'img_info': {
+ 'filename': 'b/2.jpeg'
+ },
+ 'gt_label': np.array(1)
+ })
+
+ # test classes check
+ cfg = {
+ **self.DEFAULT_ARGS,
+ 'data_prefix': ASSETS_ROOT,
+ 'classes': ['apple', 'banana'],
+ 'ann_file': None,
+ }
+ dataset = dataset_class(**cfg)
+ self.assertEqual(dataset.CLASSES, ['apple', 'banana'])
+
+ cfg['classes'] = ['apple', 'banana', 'dog']
+ with self.assertRaisesRegex(AssertionError,
+ r"\(2\) doesn't match .* classes \(3\)"):
+ dataset_class(**cfg)
+
+
+class TestImageNet(TestBaseDataset):
+ DATASET_TYPE = 'ImageNet'
+
+ def test_load_annotations(self):
+ dataset_class = DATASETS.get(self.DATASET_TYPE)
+
+ # test classes number
+ cfg = {
+ **self.DEFAULT_ARGS,
+ 'data_prefix': ASSETS_ROOT,
+ 'ann_file': None,
+ }
+ with self.assertRaisesRegex(
+ AssertionError, r"\(2\) doesn't match .* classes \(1000\)"):
+ dataset_class(**cfg)
+
+ # test override classes
+ cfg = {
+ **self.DEFAULT_ARGS,
+ 'data_prefix': ASSETS_ROOT,
+ 'classes': ['cat', 'dog'],
+ 'ann_file': None,
+ }
+ dataset = dataset_class(**cfg)
+ self.assertEqual(len(dataset), 3)
+ self.assertEqual(dataset.CLASSES, ['cat', 'dog'])
+
+
+class TestImageNet21k(TestBaseDataset):
+ DATASET_TYPE = 'ImageNet21k'
+
+ DEFAULT_ARGS = dict(
+ data_prefix=ASSETS_ROOT,
+ pipeline=[],
+ classes=['cat', 'dog'],
+ ann_file=osp.join(ASSETS_ROOT, 'ann.txt'),
+ serialize_data=False)
+
+ def test_initialize(self):
+ super().test_initialize()
+ dataset_class = DATASETS.get(self.DATASET_TYPE)
+
+ # The multi_label option is not implemented not.
+ cfg = {**self.DEFAULT_ARGS, 'multi_label': True}
+ with self.assertRaisesRegex(NotImplementedError, 'not supported'):
+ dataset_class(**cfg)
+
+ # Warn about ann_file
+ cfg = {**self.DEFAULT_ARGS, 'ann_file': None}
+ with self.assertWarnsRegex(UserWarning, 'specify the `ann_file`'):
+ dataset_class(**cfg)
+
+ # Warn about classes
+ cfg = {**self.DEFAULT_ARGS, 'classes': None}
+ with self.assertWarnsRegex(UserWarning, 'specify the `classes`'):
+ dataset_class(**cfg)
+
+ def test_load_annotations(self):
+ dataset_class = DATASETS.get(self.DATASET_TYPE)
+
+ # Test with serialize_data=False
+ cfg = {**self.DEFAULT_ARGS, 'serialize_data': False}
+ dataset = dataset_class(**cfg)
+ self.assertEqual(len(dataset.data_infos), 3)
+ self.assertEqual(len(dataset), 3)
+ self.assertEqual(
+ dataset[0], {
+ 'img_prefix': ASSETS_ROOT,
+ 'img_info': {
+ 'filename': 'a/1.JPG'
+ },
+ 'gt_label': np.array(0)
+ })
+ self.assertEqual(
+ dataset[2], {
+ 'img_prefix': ASSETS_ROOT,
+ 'img_info': {
+ 'filename': 'b/subb/2.jpeg'
+ },
+ 'gt_label': np.array(1)
+ })
+
+ # Test with serialize_data=True
+ cfg = {**self.DEFAULT_ARGS, 'serialize_data': True}
+ dataset = dataset_class(**cfg)
+ self.assertEqual(len(dataset.data_infos), 0) # data_infos is clear.
+ self.assertEqual(len(dataset), 3)
+ self.assertEqual(
+ dataset[0], {
+ 'img_prefix': ASSETS_ROOT,
+ 'img_info': {
+ 'filename': 'a/1.JPG'
+ },
+ 'gt_label': np.array(0)
+ })
+ self.assertEqual(
+ dataset[2], {
+ 'img_prefix': ASSETS_ROOT,
+ 'img_info': {
+ 'filename': 'b/subb/2.jpeg'
+ },
+ 'gt_label': np.array(1)
+ })
+
+
+class TestMNIST(TestBaseDataset):
+ DATASET_TYPE = 'MNIST'
+
+ @classmethod
+ def setUpClass(cls) -> None:
+ super().setUpClass()
+
+ tmpdir = tempfile.TemporaryDirectory()
+ cls.tmpdir = tmpdir
+ data_prefix = tmpdir.name
+ cls.DEFAULT_ARGS = dict(data_prefix=data_prefix, pipeline=[])
+
+ dataset_class = DATASETS.get(cls.DATASET_TYPE)
+
+ def rm_suffix(s):
+ return s[:s.rfind('.')]
+
+ train_image_file = osp.join(
+ data_prefix,
+ rm_suffix(dataset_class.resources['train_image_file'][0]))
+ train_label_file = osp.join(
+ data_prefix,
+ rm_suffix(dataset_class.resources['train_label_file'][0]))
+ test_image_file = osp.join(
+ data_prefix,
+ rm_suffix(dataset_class.resources['test_image_file'][0]))
+ test_label_file = osp.join(
+ data_prefix,
+ rm_suffix(dataset_class.resources['test_label_file'][0]))
+ cls.fake_img = np.random.randint(0, 255, size=(28, 28), dtype=np.uint8)
+ cls.fake_label = np.random.randint(0, 10, size=(1, ), dtype=np.uint8)
+
+ for file in [train_image_file, test_image_file]:
+ magic = b'\x00\x00\x08\x03' # num_dims = 3, type = uint8
+ head = b'\x00\x00\x00\x01' + b'\x00\x00\x00\x1c' * 2 # (1, 28, 28)
+ data = magic + head + cls.fake_img.flatten().tobytes()
+ with open(file, 'wb') as f:
+ f.write(data)
+
+ for file in [train_label_file, test_label_file]:
+ magic = b'\x00\x00\x08\x01' # num_dims = 3, type = uint8
+ head = b'\x00\x00\x00\x01' # (1, )
+ data = magic + head + cls.fake_label.tobytes()
+ with open(file, 'wb') as f:
+ f.write(data)
+
+ def test_load_annotations(self):
+ dataset_class = DATASETS.get(self.DATASET_TYPE)
+
+ with patch.object(dataset_class, 'download'):
+ # Test default behavior
+ dataset = dataset_class(**self.DEFAULT_ARGS)
+ self.assertEqual(len(dataset), 1)
+
+ data_info = dataset[0]
+ np.testing.assert_equal(data_info['img'], self.fake_img)
+ np.testing.assert_equal(data_info['gt_label'], self.fake_label)
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.tmpdir.cleanup()
+
+
+class TestCIFAR10(TestBaseDataset):
+ DATASET_TYPE = 'CIFAR10'
+
+ @classmethod
+ def setUpClass(cls) -> None:
+ super().setUpClass()
+
+ tmpdir = tempfile.TemporaryDirectory()
+ cls.tmpdir = tmpdir
+ data_prefix = tmpdir.name
+ cls.DEFAULT_ARGS = dict(data_prefix=data_prefix, pipeline=[])
+
+ dataset_class = DATASETS.get(cls.DATASET_TYPE)
+ base_folder = osp.join(data_prefix, dataset_class.base_folder)
+ os.mkdir(base_folder)
+
+ cls.fake_imgs = np.random.randint(
+ 0, 255, size=(6, 3 * 32 * 32), dtype=np.uint8)
+ cls.fake_labels = np.random.randint(0, 10, size=(6, ))
+ cls.fake_classes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+
+ batch1 = dict(
+ data=cls.fake_imgs[:2], labels=cls.fake_labels[:2].tolist())
+ with open(osp.join(base_folder, 'data_batch_1'), 'wb') as f:
+ f.write(pickle.dumps(batch1))
+
+ batch2 = dict(
+ data=cls.fake_imgs[2:4], labels=cls.fake_labels[2:4].tolist())
+ with open(osp.join(base_folder, 'data_batch_2'), 'wb') as f:
+ f.write(pickle.dumps(batch2))
+
+ test_batch = dict(
+ data=cls.fake_imgs[4:], labels=cls.fake_labels[4:].tolist())
+ with open(osp.join(base_folder, 'test_batch'), 'wb') as f:
+ f.write(pickle.dumps(test_batch))
+
+ meta = {dataset_class.meta['key']: cls.fake_classes}
+ meta_filename = dataset_class.meta['filename']
+ with open(osp.join(base_folder, meta_filename), 'wb') as f:
+ f.write(pickle.dumps(meta))
+
+ dataset_class.train_list = [['data_batch_1', None],
+ ['data_batch_2', None]]
+ dataset_class.test_list = [['test_batch', None]]
+ dataset_class.meta['md5'] = None
+
+ def test_load_annotations(self):
+ dataset_class = DATASETS.get(self.DATASET_TYPE)
+
+ # Test default behavior
+ dataset = dataset_class(**self.DEFAULT_ARGS)
+ self.assertEqual(len(dataset), 4)
+ self.assertEqual(dataset.CLASSES, self.fake_classes)
+
+ data_info = dataset[0]
+ fake_img = self.fake_imgs[0].reshape(3, 32, 32).transpose(1, 2, 0)
+ np.testing.assert_equal(data_info['img'], fake_img)
+ np.testing.assert_equal(data_info['gt_label'], self.fake_labels[0])
+
+ # Test with test_mode=True
+ cfg = {**self.DEFAULT_ARGS, 'test_mode': True}
+ dataset = dataset_class(**cfg)
+ self.assertEqual(len(dataset), 2)
+
+ data_info = dataset[0]
+ fake_img = self.fake_imgs[4].reshape(3, 32, 32).transpose(1, 2, 0)
+ np.testing.assert_equal(data_info['img'], fake_img)
+ np.testing.assert_equal(data_info['gt_label'], self.fake_labels[4])
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.tmpdir.cleanup()
+
+
+class TestCIFAR100(TestCIFAR10):
+ DATASET_TYPE = 'CIFAR100'
+
+
+class TestVOC(TestMultiLabelDataset):
+ DATASET_TYPE = 'VOC'
+
+ DEFAULT_ARGS = dict(data_prefix='VOC2007', pipeline=[])
+
+
+class TestCUB(TestBaseDataset):
+ DATASET_TYPE = 'CUB'
+
+ @classmethod
+ def setUpClass(cls) -> None:
+ super().setUpClass()
+
+ tmpdir = tempfile.TemporaryDirectory()
+ cls.tmpdir = tmpdir
+ cls.data_prefix = tmpdir.name
+ cls.ann_file = osp.join(cls.data_prefix, 'ann_file.txt')
+ cls.image_class_labels_file = osp.join(cls.data_prefix, 'classes.txt')
+ cls.train_test_split_file = osp.join(cls.data_prefix, 'split.txt')
+ cls.train_test_split_file2 = osp.join(cls.data_prefix, 'split2.txt')
+ cls.DEFAULT_ARGS = dict(
+ data_prefix=cls.data_prefix,
+ pipeline=[],
+ ann_file=cls.ann_file,
+ image_class_labels_file=cls.image_class_labels_file,
+ train_test_split_file=cls.train_test_split_file)
+
+ with open(cls.ann_file, 'w') as f:
+ f.write('\n'.join([
+ '1 1.txt',
+ '2 2.txt',
+ '3 3.txt',
+ ]))
+
+ with open(cls.image_class_labels_file, 'w') as f:
+ f.write('\n'.join([
+ '1 2',
+ '2 3',
+ '3 1',
+ ]))
+
+ with open(cls.train_test_split_file, 'w') as f:
+ f.write('\n'.join([
+ '1 0',
+ '2 1',
+ '3 1',
+ ]))
+
+ with open(cls.train_test_split_file2, 'w') as f:
+ f.write('\n'.join([
+ '1 0',
+ '2 1',
+ ]))
+
+ def test_load_annotations(self):
+ dataset_class = DATASETS.get(self.DATASET_TYPE)
+
+ # Test default behavior
+ dataset = dataset_class(**self.DEFAULT_ARGS)
+ self.assertEqual(len(dataset), 2)
+ self.assertEqual(dataset.CLASSES, dataset_class.CLASSES)
+
+ data_info = dataset[0]
+ np.testing.assert_equal(data_info['img_prefix'], self.data_prefix)
+ np.testing.assert_equal(data_info['img_info'], {'filename': '2.txt'})
+ np.testing.assert_equal(data_info['gt_label'], 3 - 1)
+
+ # Test with test_mode=True
+ cfg = {**self.DEFAULT_ARGS, 'test_mode': True}
+ dataset = dataset_class(**cfg)
+ self.assertEqual(len(dataset), 1)
+
+ data_info = dataset[0]
+ np.testing.assert_equal(data_info['img_prefix'], self.data_prefix)
+ np.testing.assert_equal(data_info['img_info'], {'filename': '1.txt'})
+ np.testing.assert_equal(data_info['gt_label'], 2 - 1)
+
+ # Test if the numbers of line are not match
+ cfg = {
+ **self.DEFAULT_ARGS, 'train_test_split_file':
+ self.train_test_split_file2
+ }
+ with self.assertRaisesRegex(AssertionError, 'should have same length'):
+ dataset_class(**cfg)
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.tmpdir.cleanup()
+
+
+class TestStanfordCars(TestBaseDataset):
+ DATASET_TYPE = 'StanfordCars'
+
+ def test_initialize(self):
+ dataset_class = DATASETS.get(self.DATASET_TYPE)
+
+ with patch.object(dataset_class, 'load_annotations'):
+ # Test with test_mode=False, ann_file is None
+ cfg = {**self.DEFAULT_ARGS, 'test_mode': False, 'ann_file': None}
+ dataset = dataset_class(**cfg)
+ self.assertEqual(dataset.CLASSES, dataset_class.CLASSES)
+ self.assertFalse(dataset.test_mode)
+ self.assertIsNone(dataset.ann_file)
+ self.assertIsNotNone(dataset.train_ann_file)
+
+ # Test with test_mode=False, ann_file is not None
+ cfg = {
+ **self.DEFAULT_ARGS, 'test_mode': False,
+ 'ann_file': 'train_ann_file.mat'
+ }
+ dataset = dataset_class(**cfg)
+ self.assertEqual(dataset.CLASSES, dataset_class.CLASSES)
+ self.assertFalse(dataset.test_mode)
+ self.assertIsNotNone(dataset.ann_file)
+ self.assertEqual(dataset.ann_file, 'train_ann_file.mat')
+ self.assertIsNotNone(dataset.train_ann_file)
+
+ # Test with test_mode=True, ann_file is None
+ cfg = {**self.DEFAULT_ARGS, 'test_mode': True, 'ann_file': None}
+ dataset = dataset_class(**cfg)
+ self.assertEqual(dataset.CLASSES, dataset_class.CLASSES)
+ self.assertTrue(dataset.test_mode)
+ self.assertIsNone(dataset.ann_file)
+ self.assertIsNotNone(dataset.test_ann_file)
+
+ # Test with test_mode=True, ann_file is not None
+ cfg = {
+ **self.DEFAULT_ARGS, 'test_mode': True,
+ 'ann_file': 'test_ann_file.mat'
+ }
+ dataset = dataset_class(**cfg)
+ self.assertEqual(dataset.CLASSES, dataset_class.CLASSES)
+ self.assertTrue(dataset.test_mode)
+ self.assertIsNotNone(dataset.ann_file)
+ self.assertEqual(dataset.ann_file, 'test_ann_file.mat')
+ self.assertIsNotNone(dataset.test_ann_file)
+
+ @classmethod
+ def setUpClass(cls) -> None:
+ super().setUpClass()
+
+ tmpdir = tempfile.TemporaryDirectory()
+ cls.tmpdir = tmpdir
+ cls.data_prefix = tmpdir.name
+ cls.ann_file = None
+ devkit = osp.join(cls.data_prefix, 'devkit')
+ if not osp.exists(devkit):
+ os.mkdir(devkit)
+ cls.train_ann_file = osp.join(devkit, 'cars_train_annos.mat')
+ cls.test_ann_file = osp.join(devkit, 'cars_test_annos_withlabels.mat')
+ cls.DEFAULT_ARGS = dict(
+ data_prefix=cls.data_prefix, pipeline=[], test_mode=False)
+
+ try:
+ import scipy.io as sio
+ except ImportError:
+ raise ImportError(
+ 'please run `pip install scipy` to install package `scipy`.')
+
+ sio.savemat(
+ cls.train_ann_file, {
+ 'annotations': [(
+ (np.array([1]), np.array([10]), np.array(
+ [20]), np.array([50]), 15, np.array(['001.jpg'])),
+ (np.array([2]), np.array([15]), np.array(
+ [240]), np.array([250]), 15, np.array(['002.jpg'])),
+ (np.array([89]), np.array([150]), np.array(
+ [278]), np.array([388]), 150, np.array(['012.jpg'])),
+ )]
+ })
+
+ sio.savemat(
+ cls.test_ann_file, {
+ 'annotations':
+ [((np.array([89]), np.array([150]), np.array(
+ [278]), np.array([388]), 150, np.array(['025.jpg'])),
+ (np.array([155]), np.array([10]), np.array(
+ [200]), np.array([233]), 0, np.array(['111.jpg'])),
+ (np.array([25]), np.array([115]), np.array(
+ [240]), np.array([360]), 15, np.array(['265.jpg'])))]
+ })
+
+ def test_load_annotations(self):
+ dataset_class = DATASETS.get(self.DATASET_TYPE)
+
+ # Test with test_mode=False and ann_file=None
+ dataset = dataset_class(**self.DEFAULT_ARGS)
+ self.assertEqual(len(dataset), 3)
+ self.assertEqual(dataset.CLASSES, dataset_class.CLASSES)
+
+ data_info = dataset[0]
+ np.testing.assert_equal(data_info['img_prefix'],
+ osp.join(self.data_prefix, 'cars_train'))
+ np.testing.assert_equal(data_info['img_info'], {'filename': '001.jpg'})
+ np.testing.assert_equal(data_info['gt_label'], 15 - 1)
+
+ # Test with test_mode=True and ann_file=None
+ cfg = {**self.DEFAULT_ARGS, 'test_mode': True}
+ dataset = dataset_class(**cfg)
+ self.assertEqual(len(dataset), 3)
+
+ data_info = dataset[0]
+ np.testing.assert_equal(data_info['img_prefix'],
+ osp.join(self.data_prefix, 'cars_test'))
+ np.testing.assert_equal(data_info['img_info'], {'filename': '025.jpg'})
+ np.testing.assert_equal(data_info['gt_label'], 150 - 1)
+
+ # Test with test_mode=False, ann_file is not None
+ cfg = {
+ **self.DEFAULT_ARGS, 'test_mode': False,
+ 'ann_file': self.train_ann_file
+ }
+ dataset = dataset_class(**cfg)
+ data_info = dataset[0]
+ np.testing.assert_equal(data_info['img_prefix'],
+ osp.join(self.data_prefix, 'cars_train'))
+ np.testing.assert_equal(data_info['img_info'], {'filename': '001.jpg'})
+ np.testing.assert_equal(data_info['gt_label'], 15 - 1)
+
+ # Test with test_mode=True, ann_file is not None
+ cfg = {
+ **self.DEFAULT_ARGS, 'test_mode': True,
+ 'ann_file': self.test_ann_file
+ }
+ dataset = dataset_class(**cfg)
+ self.assertEqual(len(dataset), 3)
+
+ data_info = dataset[0]
+ np.testing.assert_equal(data_info['img_prefix'],
+ osp.join(self.data_prefix, 'cars_test'))
+ np.testing.assert_equal(data_info['img_info'], {'filename': '025.jpg'})
+ np.testing.assert_equal(data_info['gt_label'], 150 - 1)
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.tmpdir.cleanup()
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_data/test_datasets/test_dataset_utils.py b/openmmlab_test/mmclassification-0.24.1/tests/test_data/test_datasets/test_dataset_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..d29b203d71363e7b69fea8c553750cf6edbfa901
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_data/test_datasets/test_dataset_utils.py
@@ -0,0 +1,22 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import os.path as osp
+import random
+import string
+
+from mmcls.datasets.utils import check_integrity, rm_suffix
+
+
+def test_dataset_utils():
+ # test rm_suffix
+ assert rm_suffix('a.jpg') == 'a'
+ assert rm_suffix('a.bak.jpg') == 'a.bak'
+ assert rm_suffix('a.bak.jpg', suffix='.jpg') == 'a.bak'
+ assert rm_suffix('a.bak.jpg', suffix='.bak.jpg') == 'a'
+
+ # test check_integrity
+ rand_file = ''.join(random.sample(string.ascii_letters, 10))
+ assert not check_integrity(rand_file, md5=None)
+ assert not check_integrity(rand_file, md5=2333)
+ test_file = osp.join(osp.dirname(__file__), '../../data/color.jpg')
+ assert check_integrity(test_file, md5='08252e5100cb321fe74e0e12a724ce14')
+ assert not check_integrity(test_file, md5=2333)
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_data/test_datasets/test_dataset_wrapper.py b/openmmlab_test/mmclassification-0.24.1/tests/test_data/test_datasets/test_dataset_wrapper.py
new file mode 100644
index 0000000000000000000000000000000000000000..fc4e266ba1f51890a7e98e2b672e26bccc271cf8
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_data/test_datasets/test_dataset_wrapper.py
@@ -0,0 +1,192 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import bisect
+import math
+from collections import defaultdict
+from unittest.mock import MagicMock, patch
+
+import numpy as np
+import pytest
+
+from mmcls.datasets import (BaseDataset, ClassBalancedDataset, ConcatDataset,
+ KFoldDataset, RepeatDataset)
+
+
+def mock_evaluate(results,
+ metric='accuracy',
+ metric_options=None,
+ indices=None,
+ logger=None):
+ return dict(
+ results=results,
+ metric=metric,
+ metric_options=metric_options,
+ indices=indices,
+ logger=logger)
+
+
+@patch.multiple(BaseDataset, __abstractmethods__=set())
+def construct_toy_multi_label_dataset(length):
+ BaseDataset.CLASSES = ('foo', 'bar')
+ BaseDataset.__getitem__ = MagicMock(side_effect=lambda idx: idx)
+ dataset = BaseDataset(data_prefix='', pipeline=[], test_mode=True)
+ cat_ids_list = [
+ np.random.randint(0, 80, num).tolist()
+ for num in np.random.randint(1, 20, length)
+ ]
+ dataset.data_infos = MagicMock()
+ dataset.data_infos.__len__.return_value = length
+ dataset.get_cat_ids = MagicMock(side_effect=lambda idx: cat_ids_list[idx])
+ dataset.get_gt_labels = \
+ MagicMock(side_effect=lambda: np.array(cat_ids_list))
+ dataset.evaluate = MagicMock(side_effect=mock_evaluate)
+ return dataset, cat_ids_list
+
+
+@patch.multiple(BaseDataset, __abstractmethods__=set())
+def construct_toy_single_label_dataset(length):
+ BaseDataset.CLASSES = ('foo', 'bar')
+ BaseDataset.__getitem__ = MagicMock(side_effect=lambda idx: idx)
+ dataset = BaseDataset(data_prefix='', pipeline=[], test_mode=True)
+ cat_ids_list = [[np.random.randint(0, 80)] for _ in range(length)]
+ dataset.data_infos = MagicMock()
+ dataset.data_infos.__len__.return_value = length
+ dataset.get_cat_ids = MagicMock(side_effect=lambda idx: cat_ids_list[idx])
+ dataset.get_gt_labels = \
+ MagicMock(side_effect=lambda: cat_ids_list)
+ dataset.evaluate = MagicMock(side_effect=mock_evaluate)
+ return dataset, cat_ids_list
+
+
+@pytest.mark.parametrize('construct_dataset', [
+ 'construct_toy_multi_label_dataset', 'construct_toy_single_label_dataset'
+])
+def test_concat_dataset(construct_dataset):
+ construct_toy_dataset = eval(construct_dataset)
+ dataset_a, cat_ids_list_a = construct_toy_dataset(10)
+ dataset_b, cat_ids_list_b = construct_toy_dataset(20)
+
+ concat_dataset = ConcatDataset([dataset_a, dataset_b])
+ assert concat_dataset[5] == 5
+ assert concat_dataset[25] == 15
+ assert concat_dataset.get_cat_ids(5) == cat_ids_list_a[5]
+ assert concat_dataset.get_cat_ids(25) == cat_ids_list_b[15]
+ assert len(concat_dataset) == len(dataset_a) + len(dataset_b)
+ assert concat_dataset.CLASSES == BaseDataset.CLASSES
+
+
+@pytest.mark.parametrize('construct_dataset', [
+ 'construct_toy_multi_label_dataset', 'construct_toy_single_label_dataset'
+])
+def test_repeat_dataset(construct_dataset):
+ construct_toy_dataset = eval(construct_dataset)
+ dataset, cat_ids_list = construct_toy_dataset(10)
+ repeat_dataset = RepeatDataset(dataset, 10)
+ assert repeat_dataset[5] == 5
+ assert repeat_dataset[15] == 5
+ assert repeat_dataset[27] == 7
+ assert repeat_dataset.get_cat_ids(5) == cat_ids_list[5]
+ assert repeat_dataset.get_cat_ids(15) == cat_ids_list[5]
+ assert repeat_dataset.get_cat_ids(27) == cat_ids_list[7]
+ assert len(repeat_dataset) == 10 * len(dataset)
+ assert repeat_dataset.CLASSES == BaseDataset.CLASSES
+
+
+@pytest.mark.parametrize('construct_dataset', [
+ 'construct_toy_multi_label_dataset', 'construct_toy_single_label_dataset'
+])
+def test_class_balanced_dataset(construct_dataset):
+ construct_toy_dataset = eval(construct_dataset)
+ dataset, cat_ids_list = construct_toy_dataset(10)
+
+ category_freq = defaultdict(int)
+ for cat_ids in cat_ids_list:
+ cat_ids = set(cat_ids)
+ for cat_id in cat_ids:
+ category_freq[cat_id] += 1
+ for k, v in category_freq.items():
+ category_freq[k] = v / len(cat_ids_list)
+
+ mean_freq = np.mean(list(category_freq.values()))
+ repeat_thr = mean_freq
+
+ category_repeat = {
+ cat_id: max(1.0, math.sqrt(repeat_thr / cat_freq))
+ for cat_id, cat_freq in category_freq.items()
+ }
+
+ repeat_factors = []
+ for cat_ids in cat_ids_list:
+ cat_ids = set(cat_ids)
+ repeat_factor = max({category_repeat[cat_id] for cat_id in cat_ids})
+ repeat_factors.append(math.ceil(repeat_factor))
+ repeat_factors_cumsum = np.cumsum(repeat_factors)
+ repeat_factor_dataset = ClassBalancedDataset(dataset, repeat_thr)
+ assert repeat_factor_dataset.CLASSES == BaseDataset.CLASSES
+ assert len(repeat_factor_dataset) == repeat_factors_cumsum[-1]
+ for idx in np.random.randint(0, len(repeat_factor_dataset), 3):
+ assert repeat_factor_dataset[idx] == bisect.bisect_right(
+ repeat_factors_cumsum, idx)
+
+
+@pytest.mark.parametrize('construct_dataset', [
+ 'construct_toy_multi_label_dataset', 'construct_toy_single_label_dataset'
+])
+def test_kfold_dataset(construct_dataset):
+ construct_toy_dataset = eval(construct_dataset)
+ dataset, cat_ids_list = construct_toy_dataset(10)
+
+ # test without random seed
+ train_datasets = [
+ KFoldDataset(dataset, fold=i, num_splits=3, test_mode=False)
+ for i in range(5)
+ ]
+ test_datasets = [
+ KFoldDataset(dataset, fold=i, num_splits=3, test_mode=True)
+ for i in range(5)
+ ]
+
+ assert sum([i.indices for i in test_datasets], []) == list(range(10))
+ for train_set, test_set in zip(train_datasets, test_datasets):
+ train_samples = [train_set[i] for i in range(len(train_set))]
+ test_samples = [test_set[i] for i in range(len(test_set))]
+ assert set(train_samples + test_samples) == set(range(10))
+
+ # test with random seed
+ train_datasets = [
+ KFoldDataset(dataset, fold=i, num_splits=3, test_mode=False, seed=1)
+ for i in range(5)
+ ]
+ test_datasets = [
+ KFoldDataset(dataset, fold=i, num_splits=3, test_mode=True, seed=1)
+ for i in range(5)
+ ]
+
+ assert sum([i.indices for i in test_datasets], []) != list(range(10))
+ assert set(sum([i.indices for i in test_datasets], [])) == set(range(10))
+ for train_set, test_set in zip(train_datasets, test_datasets):
+ train_samples = [train_set[i] for i in range(len(train_set))]
+ test_samples = [test_set[i] for i in range(len(test_set))]
+ assert set(train_samples + test_samples) == set(range(10))
+
+ # test behavior of get_cat_ids method
+ for train_set, test_set in zip(train_datasets, test_datasets):
+ for i in range(len(train_set)):
+ cat_ids = train_set.get_cat_ids(i)
+ assert cat_ids == cat_ids_list[train_set.indices[i]]
+ for i in range(len(test_set)):
+ cat_ids = test_set.get_cat_ids(i)
+ assert cat_ids == cat_ids_list[test_set.indices[i]]
+
+ # test behavior of get_gt_labels method
+ for train_set, test_set in zip(train_datasets, test_datasets):
+ for i in range(len(train_set)):
+ gt_label = train_set.get_gt_labels()[i]
+ assert gt_label == cat_ids_list[train_set.indices[i]]
+ for i in range(len(test_set)):
+ gt_label = test_set.get_gt_labels()[i]
+ assert gt_label == cat_ids_list[test_set.indices[i]]
+
+ # test evaluate
+ for test_set in test_datasets:
+ eval_inputs = test_set.evaluate(None)
+ assert eval_inputs['indices'] == test_set.indices
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_data/test_datasets/test_sampler.py b/openmmlab_test/mmclassification-0.24.1/tests/test_data/test_datasets/test_sampler.py
new file mode 100644
index 0000000000000000000000000000000000000000..683b953a1ef4e203242b3d6ff3db8e1f81f31eee
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_data/test_datasets/test_sampler.py
@@ -0,0 +1,53 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+
+from unittest.mock import MagicMock, patch
+
+import numpy as np
+
+from mmcls.datasets import BaseDataset, RepeatAugSampler, build_sampler
+
+
+@patch.multiple(BaseDataset, __abstractmethods__=set())
+def construct_toy_single_label_dataset(length):
+ BaseDataset.CLASSES = ('foo', 'bar')
+ BaseDataset.__getitem__ = MagicMock(side_effect=lambda idx: idx)
+ dataset = BaseDataset(data_prefix='', pipeline=[], test_mode=True)
+ cat_ids_list = [[np.random.randint(0, 80)] for _ in range(length)]
+ dataset.data_infos = MagicMock()
+ dataset.data_infos.__len__.return_value = length
+ dataset.get_cat_ids = MagicMock(side_effect=lambda idx: cat_ids_list[idx])
+ return dataset, cat_ids_list
+
+
+@patch('mmcls.datasets.samplers.repeat_aug.get_dist_info', return_value=(0, 1))
+def test_sampler_builder(_):
+ assert build_sampler(None) is None
+ dataset = construct_toy_single_label_dataset(1000)[0]
+ build_sampler(dict(type='RepeatAugSampler', dataset=dataset))
+
+
+@patch('mmcls.datasets.samplers.repeat_aug.get_dist_info', return_value=(0, 1))
+def test_rep_aug(_):
+ dataset = construct_toy_single_label_dataset(1000)[0]
+ ra = RepeatAugSampler(dataset, selected_round=0, shuffle=False)
+ ra.set_epoch(0)
+ assert len(ra) == 1000
+ ra = RepeatAugSampler(dataset)
+ assert len(ra) == 768
+ val = None
+ for idx, content in enumerate(ra):
+ if idx % 3 == 0:
+ val = content
+ else:
+ assert val is not None
+ assert content == val
+
+
+@patch('mmcls.datasets.samplers.repeat_aug.get_dist_info', return_value=(0, 2))
+def test_rep_aug_dist(_):
+ dataset = construct_toy_single_label_dataset(1000)[0]
+ ra = RepeatAugSampler(dataset, selected_round=0, shuffle=False)
+ ra.set_epoch(0)
+ assert len(ra) == 1000 // 2
+ ra = RepeatAugSampler(dataset)
+ assert len(ra) == 768 // 2
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tests/test_pipelines/test_auto_augment.py b/openmmlab_test/mmclassification-0.24.1/tests/test_data/test_pipelines/test_auto_augment.py
similarity index 92%
rename from openmmlab_test/mmclassification-speed-benchmark/tests/test_pipelines/test_auto_augment.py
rename to openmmlab_test/mmclassification-0.24.1/tests/test_data/test_pipelines/test_auto_augment.py
index 2342792f70c0a75f50a0c86a49055f92c084a09a..388ff46db0a23a518a934adf16759608d8600000 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/tests/test_pipelines/test_auto_augment.py
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_data/test_pipelines/test_auto_augment.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import copy
import random
@@ -39,6 +40,47 @@ def construct_toy_data_photometric():
return results
+def test_auto_augment():
+ policies = [[
+ dict(type='Posterize', bits=4, prob=0.4),
+ dict(type='Rotate', angle=30., prob=0.6)
+ ]]
+
+ # test assertion for policies
+ with pytest.raises(AssertionError):
+ # policies shouldn't be empty
+ transform = dict(type='AutoAugment', policies=[])
+ build_from_cfg(transform, PIPELINES)
+ with pytest.raises(AssertionError):
+ # policy should have type
+ invalid_policies = copy.deepcopy(policies)
+ invalid_policies[0][0].pop('type')
+ transform = dict(type='AutoAugment', policies=invalid_policies)
+ build_from_cfg(transform, PIPELINES)
+ with pytest.raises(AssertionError):
+ # sub policy should be a non-empty list
+ invalid_policies = copy.deepcopy(policies)
+ invalid_policies[0] = []
+ transform = dict(type='AutoAugment', policies=invalid_policies)
+ build_from_cfg(transform, PIPELINES)
+ with pytest.raises(AssertionError):
+ # policy should be valid in PIPELINES registry.
+ invalid_policies = copy.deepcopy(policies)
+ invalid_policies.append([dict(type='Wrong_policy')])
+ transform = dict(type='AutoAugment', policies=invalid_policies)
+ build_from_cfg(transform, PIPELINES)
+
+ # test hparams
+ transform = dict(
+ type='AutoAugment',
+ policies=policies,
+ hparams=dict(pad_val=15, interpolation='nearest'))
+ pipeline = build_from_cfg(transform, PIPELINES)
+ # use hparams if not set in policies config
+ assert pipeline.policies[0][1]['pad_val'] == 15
+ assert pipeline.policies[0][1]['interpolation'] == 'nearest'
+
+
def test_rand_augment():
policies = [
dict(
@@ -47,12 +89,13 @@ def test_rand_augment():
magnitude_range=(0, 1),
pad_val=128,
prob=1.,
- direction='horizontal'),
+ direction='horizontal',
+ interpolation='nearest'),
dict(type='Invert', prob=1.),
dict(
type='Rotate',
magnitude_key='angle',
- magnitude_range=(0, 30),
+ magnitude_range=(0, 90),
prob=0.)
]
# test assertion for num_policies
@@ -136,6 +179,15 @@ def test_rand_augment():
num_policies=2,
magnitude_level=12)
build_from_cfg(transform, PIPELINES)
+ with pytest.raises(AssertionError):
+ invalid_policies = copy.deepcopy(policies)
+ invalid_policies.append(dict(type='Wrong_policy'))
+ transform = dict(
+ type='RandAugment',
+ policies=invalid_policies,
+ num_policies=2,
+ magnitude_level=12)
+ build_from_cfg(transform, PIPELINES)
with pytest.raises(AssertionError):
invalid_policies = copy.deepcopy(policies)
invalid_policies[2].pop('type')
@@ -306,7 +358,7 @@ def test_rand_augment():
axis=-1)
np.testing.assert_array_equal(results['img'], img_augmented)
- # test case where magnitude_std is negtive
+ # test case where magnitude_std is negative
random.seed(3)
np.random.seed(0)
results = construct_toy_data()
@@ -326,6 +378,32 @@ def test_rand_augment():
axis=-1)
np.testing.assert_array_equal(results['img'], img_augmented)
+ # test hparams
+ random.seed(8)
+ np.random.seed(0)
+ results = construct_toy_data()
+ policies[2]['prob'] = 1.0
+ transform = dict(
+ type='RandAugment',
+ policies=policies,
+ num_policies=2,
+ magnitude_level=12,
+ magnitude_std=-1,
+ hparams=dict(pad_val=15, interpolation='nearest'))
+ pipeline = build_from_cfg(transform, PIPELINES)
+ # apply translate (magnitude=0.4) and rotate (angle=36)
+ results = pipeline(results)
+ img_augmented = np.array(
+ [[128, 128, 128, 15], [128, 128, 5, 2], [15, 9, 9, 6]], dtype=np.uint8)
+ img_augmented = np.stack([img_augmented, img_augmented, img_augmented],
+ axis=-1)
+ np.testing.assert_array_equal(results['img'], img_augmented)
+ # hparams won't override setting in policies config
+ assert pipeline.policies[0]['pad_val'] == 128
+ # use hparams if not set in policies config
+ assert pipeline.policies[2]['pad_val'] == 15
+ assert pipeline.policies[2]['interpolation'] == 'nearest'
+
def test_shear():
# test assertion for invalid type of magnitude
@@ -524,7 +602,7 @@ def test_rotate():
transform = dict(type='Rotate', angle=90., center=0)
build_from_cfg(transform, PIPELINES)
- # test assertion for invalid lenth of center
+ # test assertion for invalid length of center
with pytest.raises(AssertionError):
transform = dict(type='Rotate', angle=90., center=(0, ))
build_from_cfg(transform, PIPELINES)
@@ -682,7 +760,7 @@ def test_equalize(nb_rand_test=100):
def _imequalize(img):
# equalize the image using PIL.ImageOps.equalize
- from PIL import ImageOps, Image
+ from PIL import Image, ImageOps
img = Image.fromarray(img)
equalized_img = np.asarray(ImageOps.equalize(img))
return equalized_img
@@ -704,7 +782,7 @@ def test_equalize(nb_rand_test=100):
transform = dict(type='Equalize', prob=1.)
pipeline = build_from_cfg(transform, PIPELINES)
for _ in range(nb_rand_test):
- img = np.clip(np.random.normal(0, 1, (1000, 1200, 3)) * 260, 0,
+ img = np.clip(np.random.normal(0, 1, (256, 256, 3)) * 260, 0,
255).astype(np.uint8)
results['img'] = img
results = pipeline(copy.deepcopy(results))
@@ -854,8 +932,9 @@ def test_posterize():
def test_contrast(nb_rand_test=100):
def _adjust_contrast(img, factor):
- from PIL.ImageEnhance import Contrast
from PIL import Image
+ from PIL.ImageEnhance import Contrast
+
# Image.fromarray defaultly supports RGB, not BGR.
# convert from BGR to RGB
img = Image.fromarray(img[..., ::-1], mode='RGB')
@@ -903,7 +982,7 @@ def test_contrast(nb_rand_test=100):
prob=1.,
random_negative_prob=0.)
pipeline = build_from_cfg(transform, PIPELINES)
- img = np.clip(np.random.uniform(0, 1, (1200, 1000, 3)) * 260, 0,
+ img = np.clip(np.random.uniform(0, 1, (256, 256, 3)) * 260, 0,
255).astype(np.uint8)
results['img'] = img
results = pipeline(copy.deepcopy(results))
@@ -988,8 +1067,8 @@ def test_brightness(nb_rand_test=100):
def _adjust_brightness(img, factor):
# adjust the brightness of image using
# PIL.ImageEnhance.Brightness
- from PIL.ImageEnhance import Brightness
from PIL import Image
+ from PIL.ImageEnhance import Brightness
img = Image.fromarray(img)
brightened_img = Brightness(img).enhance(factor)
return np.asarray(brightened_img)
@@ -1034,7 +1113,7 @@ def test_brightness(nb_rand_test=100):
prob=1.,
random_negative_prob=0.)
pipeline = build_from_cfg(transform, PIPELINES)
- img = np.clip(np.random.uniform(0, 1, (1200, 1000, 3)) * 260, 0,
+ img = np.clip(np.random.uniform(0, 1, (256, 256, 3)) * 260, 0,
255).astype(np.uint8)
results['img'] = img
results = pipeline(copy.deepcopy(results))
@@ -1050,8 +1129,8 @@ def test_sharpness(nb_rand_test=100):
def _adjust_sharpness(img, factor):
# adjust the sharpness of image using
# PIL.ImageEnhance.Sharpness
- from PIL.ImageEnhance import Sharpness
from PIL import Image
+ from PIL.ImageEnhance import Sharpness
img = Image.fromarray(img)
sharpened_img = Sharpness(img).enhance(factor)
return np.asarray(sharpened_img)
@@ -1096,7 +1175,7 @@ def test_sharpness(nb_rand_test=100):
prob=1.,
random_negative_prob=0.)
pipeline = build_from_cfg(transform, PIPELINES)
- img = np.clip(np.random.uniform(0, 1, (1200, 1000, 3)) * 260, 0,
+ img = np.clip(np.random.uniform(0, 1, (256, 256, 3)) * 260, 0,
255).astype(np.uint8)
results['img'] = img
results = pipeline(copy.deepcopy(results))
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tests/test_pipelines/test_loading.py b/openmmlab_test/mmclassification-0.24.1/tests/test_data/test_pipelines/test_loading.py
similarity index 94%
rename from openmmlab_test/mmclassification-speed-benchmark/tests/test_pipelines/test_loading.py
rename to openmmlab_test/mmclassification-0.24.1/tests/test_data/test_pipelines/test_loading.py
index d3d913d72c17cfbc1fa085f8f355b08d1f2d7213..928fbc842ef36a808d0d86d5a0b2823a91283fed 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/tests/test_pipelines/test_loading.py
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_data/test_pipelines/test_loading.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import copy
import os.path as osp
@@ -10,7 +11,7 @@ class TestLoading(object):
@classmethod
def setup_class(cls):
- cls.data_prefix = osp.join(osp.dirname(__file__), '../data')
+ cls.data_prefix = osp.join(osp.dirname(__file__), '../../data')
def test_load_img(self):
results = dict(
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tests/test_pipelines/test_transform.py b/openmmlab_test/mmclassification-0.24.1/tests/test_data/test_pipelines/test_transform.py
similarity index 89%
rename from openmmlab_test/mmclassification-speed-benchmark/tests/test_pipelines/test_transform.py
rename to openmmlab_test/mmclassification-0.24.1/tests/test_data/test_pipelines/test_transform.py
index 9d751fa7521408409c288a45912f7ee3f5238a3a..b23e84b58498bc4ca2c9b3093b6a42e937df5cd3 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/tests/test_pipelines/test_transform.py
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_data/test_pipelines/test_transform.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import copy
import os.path as osp
import random
@@ -62,6 +63,11 @@ def test_resize():
transform = dict(type='Resize', size=224, interpolation='2333')
build_from_cfg(transform, PIPELINES)
+ # test assertion when resize_short is invalid
+ with pytest.raises(AssertionError):
+ transform = dict(type='Resize', size=224, adaptive_side='False')
+ build_from_cfg(transform, PIPELINES)
+
# test repr
transform = dict(type='Resize', size=224)
resize_module = build_from_cfg(transform, PIPELINES)
@@ -70,7 +76,7 @@ def test_resize():
# read test image
results = dict()
img = mmcv.imread(
- osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color')
+ osp.join(osp.dirname(__file__), '../../data/color.jpg'), 'color')
original_img = copy.deepcopy(img)
results['img'] = img
results['img2'] = copy.deepcopy(img)
@@ -163,6 +169,102 @@ def test_resize():
assert results['img_shape'] == (224, 224, 3)
assert np.allclose(results['img'], resized_img, atol=30)
+ # test resize when size is tuple, the second value is -1
+ # and adaptive_side='long'
+ transform = dict(
+ type='Resize',
+ size=(224, -1),
+ adaptive_side='long',
+ interpolation='bilinear')
+ resize_module = build_from_cfg(transform, PIPELINES)
+ results = reset_results(results, original_img)
+ results = resize_module(results)
+ assert np.equal(results['img'], results['img2']).all()
+ assert results['img_shape'] == (168, 224, 3)
+
+ # test resize when size is tuple, the second value is -1
+ # and adaptive_side='long', h > w
+ transform1 = dict(type='Resize', size=(300, 200), interpolation='bilinear')
+ resize_module1 = build_from_cfg(transform1, PIPELINES)
+ transform2 = dict(
+ type='Resize',
+ size=(224, -1),
+ adaptive_side='long',
+ interpolation='bilinear')
+ resize_module2 = build_from_cfg(transform2, PIPELINES)
+ results = reset_results(results, original_img)
+ results = resize_module1(results)
+ results = resize_module2(results)
+ assert np.equal(results['img'], results['img2']).all()
+ assert results['img_shape'] == (224, 149, 3)
+
+ # test resize when size is tuple, the second value is -1
+ # and adaptive_side='short', h > w
+ transform1 = dict(type='Resize', size=(300, 200), interpolation='bilinear')
+ resize_module1 = build_from_cfg(transform1, PIPELINES)
+ transform2 = dict(
+ type='Resize',
+ size=(224, -1),
+ adaptive_side='short',
+ interpolation='bilinear')
+ resize_module2 = build_from_cfg(transform2, PIPELINES)
+ results = reset_results(results, original_img)
+ results = resize_module1(results)
+ results = resize_module2(results)
+ assert np.equal(results['img'], results['img2']).all()
+ assert results['img_shape'] == (336, 224, 3)
+
+ # test interpolation method checking
+ with pytest.raises(AssertionError):
+ transform = dict(
+ type='Resize', size=(300, 200), backend='cv2', interpolation='box')
+ resize_module = build_from_cfg(transform, PIPELINES)
+
+ with pytest.raises(AssertionError):
+ transform = dict(
+ type='Resize',
+ size=(300, 200),
+ backend='pillow',
+ interpolation='area')
+ resize_module = build_from_cfg(transform, PIPELINES)
+
+
+def test_pad():
+ results = dict()
+ img = mmcv.imread(
+ osp.join(osp.dirname(__file__), '../../data/color.jpg'), 'color')
+ results['img'] = img
+ results['img2'] = copy.deepcopy(img)
+ results['img_shape'] = img.shape
+ results['ori_shape'] = img.shape
+ results['img_fields'] = ['img', 'img2']
+
+ # test assertion if shape is None
+ with pytest.raises(AssertionError):
+ transform = dict(type='Pad', size=None)
+ pad_module = build_from_cfg(transform, PIPELINES)
+ pad_result = pad_module(copy.deepcopy(results))
+ assert np.equal(pad_result['img'], pad_result['img2']).all()
+ assert pad_result['img_shape'] == (400, 400, 3)
+
+ # test if pad is valid
+ transform = dict(type='Pad', size=(400, 400))
+ pad_module = build_from_cfg(transform, PIPELINES)
+ pad_result = pad_module(copy.deepcopy(results))
+ assert isinstance(repr(pad_module), str)
+ assert np.equal(pad_result['img'], pad_result['img2']).all()
+ assert pad_result['img_shape'] == (400, 400, 3)
+ assert np.allclose(pad_result['img'][-100:, :, :], 0)
+
+ # test if pad_to_square is valid
+ transform = dict(type='Pad', pad_to_square=True)
+ pad_module = build_from_cfg(transform, PIPELINES)
+ pad_result = pad_module(copy.deepcopy(results))
+ assert isinstance(repr(pad_module), str)
+ assert np.equal(pad_result['img'], pad_result['img2']).all()
+ assert pad_result['img_shape'] == (400, 400, 3)
+ assert np.allclose(pad_result['img'][-100:, :, :], 0)
+
def test_center_crop():
# test assertion if size is smaller than 0
@@ -220,7 +322,7 @@ def test_center_crop():
# read test image
results = dict()
img = mmcv.imread(
- osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color')
+ osp.join(osp.dirname(__file__), '../../data/color.jpg'), 'color')
original_img = copy.deepcopy(img)
results['img'] = img
results['img2'] = copy.deepcopy(img)
@@ -343,7 +445,7 @@ def test_normalize():
# read data
results = dict()
img = mmcv.imread(
- osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color')
+ osp.join(osp.dirname(__file__), '../../data/color.jpg'), 'color')
original_img = copy.deepcopy(img)
results['img'] = img
results['img2'] = copy.deepcopy(img)
@@ -371,9 +473,9 @@ def test_normalize():
def test_randomcrop():
ori_img = mmcv.imread(
- osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color')
+ osp.join(osp.dirname(__file__), '../../data/color.jpg'), 'color')
ori_img_pil = Image.open(
- osp.join(osp.dirname(__file__), '../data/color.jpg'))
+ osp.join(osp.dirname(__file__), '../../data/color.jpg'))
seed = random.randint(0, 100)
# test crop size is int
@@ -517,9 +619,9 @@ def test_randomcrop():
def test_randomresizedcrop():
ori_img = mmcv.imread(
- osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color')
+ osp.join(osp.dirname(__file__), '../../data/color.jpg'), 'color')
ori_img_pil = Image.open(
- osp.join(osp.dirname(__file__), '../data/color.jpg'))
+ osp.join(osp.dirname(__file__), '../../data/color.jpg'))
seed = random.randint(0, 100)
@@ -900,7 +1002,7 @@ def test_randomflip():
# read test image
results = dict()
img = mmcv.imread(
- osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color')
+ osp.join(osp.dirname(__file__), '../../data/color.jpg'), 'color')
original_img = copy.deepcopy(img)
results['img'] = img
results['img2'] = copy.deepcopy(img)
@@ -928,7 +1030,7 @@ def test_randomflip():
results = flip_module(results)
assert np.equal(results['img'], results['img2']).all()
- # compare hotizontal flip with torchvision
+ # compare horizontal flip with torchvision
transform = dict(type='RandomFlip', flip_prob=1, direction='horizontal')
flip_module = build_from_cfg(transform, PIPELINES)
results = reset_results(results, original_img)
@@ -1077,7 +1179,7 @@ def test_color_jitter():
# read test image
results = dict()
img = mmcv.imread(
- osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color')
+ osp.join(osp.dirname(__file__), '../../data/color.jpg'), 'color')
original_img = copy.deepcopy(img)
results['img'] = img
results['img2'] = copy.deepcopy(img)
@@ -1123,7 +1225,7 @@ def test_lighting():
# read test image
results = dict()
img = mmcv.imread(
- osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color')
+ osp.join(osp.dirname(__file__), '../../data/color.jpg'), 'color')
original_img = copy.deepcopy(img)
results['img'] = img
results['img2'] = copy.deepcopy(img)
@@ -1165,15 +1267,26 @@ def test_lighting():
def test_albu_transform():
results = dict(
- img_prefix=osp.join(osp.dirname(__file__), '../data'),
- img_info=dict(filename='color.jpg'))
+ img_prefix=osp.join(osp.dirname(__file__), '../../data'),
+ img_info=dict(filename='color.jpg'),
+ gt_label=np.array(1))
# Define simple pipeline
load = dict(type='LoadImageFromFile')
load = build_from_cfg(load, PIPELINES)
albu_transform = dict(
- type='Albu', transforms=[dict(type='ChannelShuffle', p=1)])
+ type='Albu',
+ transforms=[
+ dict(type='ChannelShuffle', p=1),
+ dict(
+ type='ShiftScaleRotate',
+ shift_limit=0.0625,
+ scale_limit=0.0,
+ rotate_limit=0,
+ interpolation=1,
+ p=1)
+ ])
albu_transform = build_from_cfg(albu_transform, PIPELINES)
normalize = dict(type='Normalize', mean=[0] * 3, std=[0] * 3, to_rgb=True)
@@ -1185,3 +1298,4 @@ def test_albu_transform():
results = normalize(results)
assert results['img'].dtype == np.float32
+ assert results['gt_label'].shape == np.array(1).shape
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_downstream/test_mmdet_inference.py b/openmmlab_test/mmclassification-0.24.1/tests/test_downstream/test_mmdet_inference.py
new file mode 100644
index 0000000000000000000000000000000000000000..096c5db7d3f3c284451d28c7f776bd2b557040f2
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_downstream/test_mmdet_inference.py
@@ -0,0 +1,118 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import numpy as np
+from mmcv import Config
+from mmdet.apis import inference_detector
+from mmdet.models import build_detector
+
+from mmcls.models import (MobileNetV2, MobileNetV3, RegNet, ResNeSt, ResNet,
+ ResNeXt, SEResNet, SEResNeXt, SwinTransformer,
+ TIMMBackbone)
+from mmcls.models.backbones.timm_backbone import timm
+
+backbone_configs = dict(
+ mobilenetv2=dict(
+ backbone=dict(
+ type='mmcls.MobileNetV2',
+ widen_factor=1.0,
+ norm_cfg=dict(type='GN', num_groups=2, requires_grad=True),
+ out_indices=(4, 7)),
+ out_channels=[96, 1280]),
+ mobilenetv3=dict(
+ backbone=dict(
+ type='mmcls.MobileNetV3',
+ norm_cfg=dict(type='GN', num_groups=2, requires_grad=True),
+ out_indices=range(7, 12)),
+ out_channels=[48, 48, 96, 96, 96]),
+ regnet=dict(
+ backbone=dict(type='mmcls.RegNet', arch='regnetx_400mf'),
+ out_channels=384),
+ resnext=dict(
+ backbone=dict(
+ type='mmcls.ResNeXt', depth=50, groups=32, width_per_group=4),
+ out_channels=2048),
+ resnet=dict(
+ backbone=dict(type='mmcls.ResNet', depth=50), out_channels=2048),
+ seresnet=dict(
+ backbone=dict(type='mmcls.SEResNet', depth=50), out_channels=2048),
+ seresnext=dict(
+ backbone=dict(
+ type='mmcls.SEResNeXt', depth=50, groups=32, width_per_group=4),
+ out_channels=2048),
+ resnest=dict(
+ backbone=dict(
+ type='mmcls.ResNeSt',
+ depth=50,
+ radix=2,
+ reduction_factor=4,
+ out_indices=(0, 1, 2, 3)),
+ out_channels=[256, 512, 1024, 2048]),
+ swin=dict(
+ backbone=dict(
+ type='mmcls.SwinTransformer',
+ arch='small',
+ drop_path_rate=0.2,
+ img_size=800,
+ out_indices=(2, 3)),
+ out_channels=[384, 768]),
+ timm_efficientnet=dict(
+ backbone=dict(
+ type='mmcls.TIMMBackbone',
+ model_name='efficientnet_b1',
+ features_only=True,
+ pretrained=False,
+ out_indices=(1, 2, 3, 4)),
+ out_channels=[24, 40, 112, 320]),
+ timm_resnet=dict(
+ backbone=dict(
+ type='mmcls.TIMMBackbone',
+ model_name='resnet50',
+ features_only=True,
+ pretrained=False,
+ out_indices=(1, 2, 3, 4)),
+ out_channels=[256, 512, 1024, 2048]))
+
+module_mapping = {
+ 'mobilenetv2': MobileNetV2,
+ 'mobilenetv3': MobileNetV3,
+ 'regnet': RegNet,
+ 'resnext': ResNeXt,
+ 'resnet': ResNet,
+ 'seresnext': SEResNeXt,
+ 'seresnet': SEResNet,
+ 'resnest': ResNeSt,
+ 'swin': SwinTransformer,
+ 'timm_efficientnet': TIMMBackbone,
+ 'timm_resnet': TIMMBackbone
+}
+
+
+def test_mmdet_inference():
+ config_path = './tests/data/retinanet.py'
+ rng = np.random.RandomState(0)
+ img1 = rng.rand(100, 100, 3)
+
+ for module_name, backbone_config in backbone_configs.items():
+ module = module_mapping[module_name]
+ if module is TIMMBackbone and timm is None:
+ print(f'skip {module_name} because timm is not available')
+ continue
+ print(f'test {module_name}')
+ config = Config.fromfile(config_path)
+ config.model.backbone = backbone_config['backbone']
+ out_channels = backbone_config['out_channels']
+ if isinstance(out_channels, int):
+ config.model.neck = None
+ config.model.bbox_head.in_channels = out_channels
+ anchor_generator = config.model.bbox_head.anchor_generator
+ anchor_generator.strides = anchor_generator.strides[:1]
+ else:
+ config.model.neck.in_channels = out_channels
+
+ model = build_detector(config.model)
+ assert isinstance(model.backbone, module)
+
+ model.cfg = config
+
+ model.eval()
+ result = inference_detector(model, img1)
+ assert len(result) == config.num_classes
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_metrics/test_losses.py b/openmmlab_test/mmclassification-0.24.1/tests/test_metrics/test_losses.py
new file mode 100644
index 0000000000000000000000000000000000000000..74eec620548206adf615eef3bc7242baad6676c9
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_metrics/test_losses.py
@@ -0,0 +1,362 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import pytest
+import torch
+
+from mmcls.models import build_loss
+
+
+def test_asymmetric_loss():
+ # test asymmetric_loss
+ cls_score = torch.Tensor([[5, -5, 0], [5, -5, 0]])
+ label = torch.Tensor([[1, 0, 1], [0, 1, 0]])
+ weight = torch.tensor([0.5, 0.5])
+
+ loss_cfg = dict(
+ type='AsymmetricLoss',
+ gamma_pos=1.0,
+ gamma_neg=4.0,
+ clip=0.05,
+ reduction='mean',
+ loss_weight=1.0)
+ loss = build_loss(loss_cfg)
+ assert torch.allclose(loss(cls_score, label), torch.tensor(3.80845 / 3))
+
+ # test asymmetric_loss with weight
+ assert torch.allclose(
+ loss(cls_score, label, weight=weight), torch.tensor(3.80845 / 6))
+
+ # test asymmetric_loss without clip
+ loss_cfg = dict(
+ type='AsymmetricLoss',
+ gamma_pos=1.0,
+ gamma_neg=4.0,
+ clip=None,
+ reduction='mean',
+ loss_weight=1.0)
+ loss = build_loss(loss_cfg)
+ assert torch.allclose(loss(cls_score, label), torch.tensor(5.1186 / 3))
+
+ # test asymmetric_loss with softmax for single label task
+ cls_score = torch.Tensor([[5, -5, 0], [5, -5, 0]])
+ label = torch.Tensor([0, 1])
+ weight = torch.tensor([0.5, 0.5])
+ loss_cfg = dict(
+ type='AsymmetricLoss',
+ gamma_pos=0.0,
+ gamma_neg=0.0,
+ clip=None,
+ reduction='mean',
+ loss_weight=1.0,
+ use_sigmoid=False,
+ eps=1e-8)
+ loss = build_loss(loss_cfg)
+ # test asymmetric_loss for single label task without weight
+ assert torch.allclose(loss(cls_score, label), torch.tensor(2.5045))
+ # test asymmetric_loss for single label task with weight
+ assert torch.allclose(
+ loss(cls_score, label, weight=weight), torch.tensor(2.5045 * 0.5))
+
+ # test soft asymmetric_loss with softmax
+ cls_score = torch.Tensor([[5, -5, 0], [5, -5, 0]])
+ label = torch.Tensor([[1, 0, 0], [0, 1, 0]])
+ weight = torch.tensor([0.5, 0.5])
+ loss_cfg = dict(
+ type='AsymmetricLoss',
+ gamma_pos=0.0,
+ gamma_neg=0.0,
+ clip=None,
+ reduction='mean',
+ loss_weight=1.0,
+ use_sigmoid=False,
+ eps=1e-8)
+ loss = build_loss(loss_cfg)
+ # test soft asymmetric_loss with softmax without weight
+ assert torch.allclose(loss(cls_score, label), torch.tensor(2.5045))
+ # test soft asymmetric_loss with softmax with weight
+ assert torch.allclose(
+ loss(cls_score, label, weight=weight), torch.tensor(2.5045 * 0.5))
+
+
+def test_cross_entropy_loss():
+ with pytest.raises(AssertionError):
+ # use_sigmoid and use_soft could not be set simultaneously
+ loss_cfg = dict(
+ type='CrossEntropyLoss', use_sigmoid=True, use_soft=True)
+ loss = build_loss(loss_cfg)
+
+ # test ce_loss
+ cls_score = torch.Tensor([[-1000, 1000], [100, -100]])
+ label = torch.Tensor([0, 1]).long()
+ class_weight = [0.3, 0.7] # class 0 : 0.3, class 1 : 0.7
+ weight = torch.tensor([0.6, 0.4])
+
+ # test ce_loss without class weight
+ loss_cfg = dict(type='CrossEntropyLoss', reduction='mean', loss_weight=1.0)
+ loss = build_loss(loss_cfg)
+ assert torch.allclose(loss(cls_score, label), torch.tensor(1100.))
+ # test ce_loss with weight
+ assert torch.allclose(
+ loss(cls_score, label, weight=weight), torch.tensor(640.))
+
+ # test ce_loss with class weight
+ loss_cfg = dict(
+ type='CrossEntropyLoss',
+ reduction='mean',
+ loss_weight=1.0,
+ class_weight=class_weight)
+ loss = build_loss(loss_cfg)
+ assert torch.allclose(loss(cls_score, label), torch.tensor(370.))
+ # test ce_loss with weight
+ assert torch.allclose(
+ loss(cls_score, label, weight=weight), torch.tensor(208.))
+
+ # test bce_loss
+ cls_score = torch.Tensor([[-200, 100], [500, -1000], [300, -300]])
+ label = torch.Tensor([[1, 0], [0, 1], [1, 0]])
+ weight = torch.Tensor([0.6, 0.4, 0.5])
+ class_weight = [0.1, 0.9] # class 0: 0.1, class 1: 0.9
+ pos_weight = [0.1, 0.2]
+
+ # test bce_loss without class weight
+ loss_cfg = dict(
+ type='CrossEntropyLoss',
+ use_sigmoid=True,
+ reduction='mean',
+ loss_weight=1.0)
+ loss = build_loss(loss_cfg)
+ assert torch.allclose(loss(cls_score, label), torch.tensor(300.))
+ # test ce_loss with weight
+ assert torch.allclose(
+ loss(cls_score, label, weight=weight), torch.tensor(130.))
+
+ # test bce_loss with class weight
+ loss_cfg = dict(
+ type='CrossEntropyLoss',
+ use_sigmoid=True,
+ reduction='mean',
+ loss_weight=1.0,
+ class_weight=class_weight)
+ loss = build_loss(loss_cfg)
+ assert torch.allclose(loss(cls_score, label), torch.tensor(176.667))
+ # test bce_loss with weight
+ assert torch.allclose(
+ loss(cls_score, label, weight=weight), torch.tensor(74.333))
+
+ # test bce loss with pos_weight
+ loss_cfg = dict(
+ type='CrossEntropyLoss',
+ use_sigmoid=True,
+ reduction='mean',
+ loss_weight=1.0,
+ pos_weight=pos_weight)
+ loss = build_loss(loss_cfg)
+ assert torch.allclose(loss(cls_score, label), torch.tensor(136.6667))
+
+ # test soft_ce_loss
+ cls_score = torch.Tensor([[-1000, 1000], [100, -100]])
+ label = torch.Tensor([[1.0, 0.0], [0.0, 1.0]])
+ class_weight = [0.3, 0.7] # class 0 : 0.3, class 1 : 0.7
+ weight = torch.tensor([0.6, 0.4])
+
+ # test soft_ce_loss without class weight
+ loss_cfg = dict(
+ type='CrossEntropyLoss',
+ use_soft=True,
+ reduction='mean',
+ loss_weight=1.0)
+ loss = build_loss(loss_cfg)
+ assert torch.allclose(loss(cls_score, label), torch.tensor(1100.))
+ # test soft_ce_loss with weight
+ assert torch.allclose(
+ loss(cls_score, label, weight=weight), torch.tensor(640.))
+
+ # test soft_ce_loss with class weight
+ loss_cfg = dict(
+ type='CrossEntropyLoss',
+ use_soft=True,
+ reduction='mean',
+ loss_weight=1.0,
+ class_weight=class_weight)
+ loss = build_loss(loss_cfg)
+ assert torch.allclose(loss(cls_score, label), torch.tensor(370.))
+ # test soft_ce_loss with weight
+ assert torch.allclose(
+ loss(cls_score, label, weight=weight), torch.tensor(208.))
+
+
+def test_focal_loss():
+ # test focal_loss
+ cls_score = torch.Tensor([[5, -5, 0], [5, -5, 0]])
+ label = torch.Tensor([[1, 0, 1], [0, 1, 0]])
+ weight = torch.tensor([0.5, 0.5])
+
+ loss_cfg = dict(
+ type='FocalLoss',
+ gamma=2.0,
+ alpha=0.25,
+ reduction='mean',
+ loss_weight=1.0)
+ loss = build_loss(loss_cfg)
+ assert torch.allclose(loss(cls_score, label), torch.tensor(0.8522))
+ # test focal_loss with weight
+ assert torch.allclose(
+ loss(cls_score, label, weight=weight), torch.tensor(0.8522 / 2))
+ # test focal loss for single label task
+ cls_score = torch.Tensor([[5, -5, 0], [5, -5, 0]])
+ label = torch.Tensor([0, 1])
+ weight = torch.tensor([0.5, 0.5])
+ assert torch.allclose(loss(cls_score, label), torch.tensor(0.86664125))
+ # test focal_loss single label with weight
+ assert torch.allclose(
+ loss(cls_score, label, weight=weight), torch.tensor(0.86664125 / 2))
+
+
+def test_label_smooth_loss():
+ # test label_smooth_val assertion
+ with pytest.raises(AssertionError):
+ loss_cfg = dict(type='LabelSmoothLoss', label_smooth_val=1.0)
+ build_loss(loss_cfg)
+
+ with pytest.raises(AssertionError):
+ loss_cfg = dict(type='LabelSmoothLoss', label_smooth_val='str')
+ build_loss(loss_cfg)
+
+ # test reduction assertion
+ with pytest.raises(AssertionError):
+ loss_cfg = dict(
+ type='LabelSmoothLoss', label_smooth_val=0.1, reduction='unknown')
+ build_loss(loss_cfg)
+
+ # test mode assertion
+ with pytest.raises(AssertionError):
+ loss_cfg = dict(
+ type='LabelSmoothLoss', label_smooth_val=0.1, mode='unknown')
+ build_loss(loss_cfg)
+
+ # test original mode label smooth loss
+ cls_score = torch.tensor([[1., -1.]])
+ label = torch.tensor([0])
+
+ loss_cfg = dict(
+ type='LabelSmoothLoss',
+ label_smooth_val=0.1,
+ mode='original',
+ reduction='mean',
+ loss_weight=1.0)
+ loss = build_loss(loss_cfg)
+ correct = 0.2269 # from timm
+ assert loss(cls_score, label) - correct <= 0.0001
+
+ # test classy_vision mode label smooth loss
+ loss_cfg = dict(
+ type='LabelSmoothLoss',
+ label_smooth_val=0.1,
+ mode='classy_vision',
+ reduction='mean',
+ loss_weight=1.0)
+ loss = build_loss(loss_cfg)
+ correct = 0.2178 # from ClassyVision
+ assert loss(cls_score, label) - correct <= 0.0001
+
+ # test multi_label mode label smooth loss
+ cls_score = torch.tensor([[1., -1., 1]])
+ label = torch.tensor([[1, 0, 1]])
+
+ loss_cfg = dict(
+ type='LabelSmoothLoss',
+ label_smooth_val=0.1,
+ mode='multi_label',
+ reduction='mean',
+ loss_weight=1.0)
+ loss = build_loss(loss_cfg)
+ smooth_label = torch.tensor([[0.9, 0.1, 0.9]])
+ correct = torch.binary_cross_entropy_with_logits(cls_score,
+ smooth_label).mean()
+ assert torch.allclose(loss(cls_score, label), correct)
+
+ # test label linear combination smooth loss
+ cls_score = torch.tensor([[1., -1., 0.]])
+ label1 = torch.tensor([[1., 0., 0.]])
+ label2 = torch.tensor([[0., 0., 1.]])
+ label_mix = label1 * 0.6 + label2 * 0.4
+
+ loss_cfg = dict(
+ type='LabelSmoothLoss',
+ label_smooth_val=0.1,
+ mode='original',
+ reduction='mean',
+ num_classes=3,
+ loss_weight=1.0)
+ loss = build_loss(loss_cfg)
+ smooth_label1 = loss.original_smooth_label(label1)
+ smooth_label2 = loss.original_smooth_label(label2)
+ label_smooth_mix = smooth_label1 * 0.6 + smooth_label2 * 0.4
+ correct = (-torch.log_softmax(cls_score, -1) * label_smooth_mix).sum()
+
+ assert loss(cls_score, label_mix) - correct <= 0.0001
+
+ # test label smooth loss with weight
+ cls_score = torch.tensor([[1., -1.], [1., -1.]])
+ label = torch.tensor([0, 1])
+ weight = torch.tensor([0.5, 0.5])
+
+ loss_cfg = dict(
+ type='LabelSmoothLoss',
+ reduction='mean',
+ label_smooth_val=0.1,
+ loss_weight=1.0)
+ loss = build_loss(loss_cfg)
+ assert torch.allclose(
+ loss(cls_score, label, weight=weight),
+ loss(cls_score, label) / 2)
+
+
+# migrate from mmdetection with modifications
+def test_seesaw_loss():
+ # only softmax version of Seesaw Loss is implemented
+ with pytest.raises(AssertionError):
+ loss_cfg = dict(type='SeesawLoss', use_sigmoid=True, loss_weight=1.0)
+ build_loss(loss_cfg)
+
+ # test that cls_score.size(-1) == num_classes
+ loss_cls_cfg = dict(
+ type='SeesawLoss', p=0.0, q=0.0, loss_weight=1.0, num_classes=2)
+ loss_cls = build_loss(loss_cls_cfg)
+ # the length of fake_pred should be num_classe = 4
+ with pytest.raises(AssertionError):
+ fake_pred = torch.Tensor([[-100, 100, -100]])
+ fake_label = torch.Tensor([1]).long()
+ loss_cls(fake_pred, fake_label)
+ # the length of fake_pred should be num_classes + 2 = 4
+ with pytest.raises(AssertionError):
+ fake_pred = torch.Tensor([[-100, 100, -100, 100]])
+ fake_label = torch.Tensor([1]).long()
+ loss_cls(fake_pred, fake_label)
+
+ # test the calculation without p and q
+ loss_cls_cfg = dict(
+ type='SeesawLoss', p=0.0, q=0.0, loss_weight=1.0, num_classes=2)
+ loss_cls = build_loss(loss_cls_cfg)
+ fake_pred = torch.Tensor([[-100, 100]])
+ fake_label = torch.Tensor([1]).long()
+ loss = loss_cls(fake_pred, fake_label)
+ assert torch.allclose(loss, torch.tensor(0.))
+
+ # test the calculation with p and without q
+ loss_cls_cfg = dict(
+ type='SeesawLoss', p=1.0, q=0.0, loss_weight=1.0, num_classes=2)
+ loss_cls = build_loss(loss_cls_cfg)
+ fake_pred = torch.Tensor([[-100, 100]])
+ fake_label = torch.Tensor([0]).long()
+ loss_cls.cum_samples[0] = torch.exp(torch.Tensor([20]))
+ loss = loss_cls(fake_pred, fake_label)
+ assert torch.allclose(loss, torch.tensor(180.))
+
+ # test the calculation with q and without p
+ loss_cls_cfg = dict(
+ type='SeesawLoss', p=0.0, q=1.0, loss_weight=1.0, num_classes=2)
+ loss_cls = build_loss(loss_cls_cfg)
+ fake_pred = torch.Tensor([[-100, 100]])
+ fake_label = torch.Tensor([0]).long()
+ loss = loss_cls(fake_pred, fake_label)
+ assert torch.allclose(loss, torch.tensor(200.) + torch.tensor(100.).log())
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_metrics/test_metrics.py b/openmmlab_test/mmclassification-0.24.1/tests/test_metrics/test_metrics.py
new file mode 100644
index 0000000000000000000000000000000000000000..67acb09599f03baa9f129717369ce363ed7a3180
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_metrics/test_metrics.py
@@ -0,0 +1,93 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from functools import partial
+
+import pytest
+import torch
+
+from mmcls.core import average_performance, mAP
+from mmcls.models.losses.accuracy import Accuracy, accuracy_numpy
+
+
+def test_mAP():
+ target = torch.Tensor([[1, 1, 0, -1], [1, 1, 0, -1], [0, -1, 1, -1],
+ [0, 1, 0, -1]])
+ pred = torch.Tensor([[0.9, 0.8, 0.3, 0.2], [0.1, 0.2, 0.2, 0.1],
+ [0.7, 0.5, 0.9, 0.3], [0.8, 0.1, 0.1, 0.2]])
+
+ # target and pred should both be np.ndarray or torch.Tensor
+ with pytest.raises(TypeError):
+ target_list = target.tolist()
+ _ = mAP(pred, target_list)
+
+ # target and pred should be in the same shape
+ with pytest.raises(AssertionError):
+ target_shorter = target[:-1]
+ _ = mAP(pred, target_shorter)
+
+ assert mAP(pred, target) == pytest.approx(68.75, rel=1e-2)
+
+ target_no_difficult = torch.Tensor([[1, 1, 0, 0], [0, 1, 0, 0],
+ [0, 0, 1, 0], [1, 0, 0, 0]])
+ assert mAP(pred, target_no_difficult) == pytest.approx(70.83, rel=1e-2)
+
+
+def test_average_performance():
+ target = torch.Tensor([[1, 1, 0, -1], [1, 1, 0, -1], [0, -1, 1, -1],
+ [0, 1, 0, -1], [0, 1, 0, -1]])
+ pred = torch.Tensor([[0.9, 0.8, 0.3, 0.2], [0.1, 0.2, 0.2, 0.1],
+ [0.7, 0.5, 0.9, 0.3], [0.8, 0.1, 0.1, 0.2],
+ [0.8, 0.1, 0.1, 0.2]])
+
+ # target and pred should both be np.ndarray or torch.Tensor
+ with pytest.raises(TypeError):
+ target_list = target.tolist()
+ _ = average_performance(pred, target_list)
+
+ # target and pred should be in the same shape
+ with pytest.raises(AssertionError):
+ target_shorter = target[:-1]
+ _ = average_performance(pred, target_shorter)
+
+ assert average_performance(pred, target) == average_performance(
+ pred, target, thr=0.5)
+ assert average_performance(pred, target, thr=0.5, k=2) \
+ == average_performance(pred, target, thr=0.5)
+ assert average_performance(
+ pred, target, thr=0.3) == pytest.approx(
+ (31.25, 43.75, 36.46, 33.33, 42.86, 37.50), rel=1e-2)
+ assert average_performance(
+ pred, target, k=2) == pytest.approx(
+ (43.75, 50.00, 46.67, 40.00, 57.14, 47.06), rel=1e-2)
+
+
+def test_accuracy():
+ pred_tensor = torch.tensor([[0.1, 0.2, 0.4], [0.2, 0.5, 0.3],
+ [0.4, 0.3, 0.1], [0.8, 0.9, 0.0]])
+ target_tensor = torch.tensor([2, 0, 0, 0])
+ pred_array = pred_tensor.numpy()
+ target_array = target_tensor.numpy()
+
+ acc_top1 = 50.
+ acc_top2 = 75.
+
+ compute_acc = Accuracy(topk=1)
+ assert compute_acc(pred_tensor, target_tensor) == acc_top1
+ assert compute_acc(pred_array, target_array) == acc_top1
+
+ compute_acc = Accuracy(topk=(1, ))
+ assert compute_acc(pred_tensor, target_tensor)[0] == acc_top1
+ assert compute_acc(pred_array, target_array)[0] == acc_top1
+
+ compute_acc = Accuracy(topk=(1, 2))
+ assert compute_acc(pred_tensor, target_array)[0] == acc_top1
+ assert compute_acc(pred_tensor, target_tensor)[1] == acc_top2
+ assert compute_acc(pred_array, target_array)[0] == acc_top1
+ assert compute_acc(pred_array, target_array)[1] == acc_top2
+
+ with pytest.raises(AssertionError):
+ compute_acc(pred_tensor, 'other_type')
+
+ # test accuracy_numpy
+ compute_acc = partial(accuracy_numpy, topk=(1, 2))
+ assert compute_acc(pred_array, target_array)[0] == acc_top1
+ assert compute_acc(pred_array, target_array)[1] == acc_top2
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_metrics/test_utils.py b/openmmlab_test/mmclassification-0.24.1/tests/test_metrics/test_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..962a1f8d7647f3a73af48cdaa99a8e5734f1ddb7
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_metrics/test_utils.py
@@ -0,0 +1,49 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import pytest
+import torch
+
+from mmcls.models.losses.utils import convert_to_one_hot
+
+
+def ori_convert_to_one_hot(targets: torch.Tensor, classes) -> torch.Tensor:
+ assert (torch.max(targets).item() <
+ classes), 'Class Index must be less than number of classes'
+ one_hot_targets = torch.zeros((targets.shape[0], classes),
+ dtype=torch.long,
+ device=targets.device)
+ one_hot_targets.scatter_(1, targets.long(), 1)
+ return one_hot_targets
+
+
+def test_convert_to_one_hot():
+ # label should smaller than classes
+ targets = torch.tensor([1, 2, 3, 8, 5])
+ classes = 5
+ with pytest.raises(AssertionError):
+ _ = convert_to_one_hot(targets, classes)
+
+ # test with original impl
+ classes = 10
+ targets = torch.randint(high=classes, size=(10, 1))
+ ori_one_hot_targets = torch.zeros((targets.shape[0], classes),
+ dtype=torch.long,
+ device=targets.device)
+ ori_one_hot_targets.scatter_(1, targets.long(), 1)
+ one_hot_targets = convert_to_one_hot(targets, classes)
+ assert torch.equal(ori_one_hot_targets, one_hot_targets)
+
+
+# test cuda version
+@pytest.mark.skipif(
+ not torch.cuda.is_available(), reason='requires CUDA support')
+def test_convert_to_one_hot_cuda():
+ # test with original impl
+ classes = 10
+ targets = torch.randint(high=classes, size=(10, 1)).cuda()
+ ori_one_hot_targets = torch.zeros((targets.shape[0], classes),
+ dtype=torch.long,
+ device=targets.device)
+ ori_one_hot_targets.scatter_(1, targets.long(), 1)
+ one_hot_targets = convert_to_one_hot(targets, classes)
+ assert torch.equal(ori_one_hot_targets, one_hot_targets)
+ assert ori_one_hot_targets.device == one_hot_targets.device
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/__init__.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..ef101fec61e72abc0eb90266d453b5b22331378d
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/__init__.py
@@ -0,0 +1 @@
+# Copyright (c) OpenMMLab. All rights reserved.
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_conformer.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_conformer.py
new file mode 100644
index 0000000000000000000000000000000000000000..317079a1acde6fed13c8bf39ae421fa35d14d74e
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_conformer.py
@@ -0,0 +1,111 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from copy import deepcopy
+
+import pytest
+import torch
+from torch.nn.modules import GroupNorm
+from torch.nn.modules.batchnorm import _BatchNorm
+
+from mmcls.models.backbones import Conformer
+
+
+def is_norm(modules):
+ """Check if is one of the norms."""
+ if isinstance(modules, (GroupNorm, _BatchNorm)):
+ return True
+ return False
+
+
+def check_norm_state(modules, train_state):
+ """Check if norm layer is in correct train state."""
+ for mod in modules:
+ if isinstance(mod, _BatchNorm):
+ if mod.training != train_state:
+ return False
+ return True
+
+
+def test_conformer_backbone():
+
+ cfg_ori = dict(
+ arch='T',
+ drop_path_rate=0.1,
+ )
+
+ with pytest.raises(AssertionError):
+ # test invalid arch
+ cfg = deepcopy(cfg_ori)
+ cfg['arch'] = 'unknown'
+ Conformer(**cfg)
+
+ with pytest.raises(AssertionError):
+ # test arch without essential keys
+ cfg = deepcopy(cfg_ori)
+ cfg['arch'] = {'embed_dims': 24, 'channel_ratio': 6, 'num_heads': 9}
+ Conformer(**cfg)
+
+ # Test Conformer small model with patch size of 16
+ model = Conformer(**cfg_ori)
+ model.init_weights()
+ model.train()
+
+ assert check_norm_state(model.modules(), True)
+
+ imgs = torch.randn(3, 3, 224, 224)
+ conv_feature, transformer_feature = model(imgs)[-1]
+ assert conv_feature.shape == (3, 64 * 1 * 4
+ ) # base_channels * channel_ratio * 4
+ assert transformer_feature.shape == (3, 384)
+
+ # Test Conformer with irregular input size.
+ model = Conformer(**cfg_ori)
+ model.init_weights()
+ model.train()
+
+ assert check_norm_state(model.modules(), True)
+
+ imgs = torch.randn(3, 3, 241, 241)
+ conv_feature, transformer_feature = model(imgs)[-1]
+ assert conv_feature.shape == (3, 64 * 1 * 4
+ ) # base_channels * channel_ratio * 4
+ assert transformer_feature.shape == (3, 384)
+
+ imgs = torch.randn(3, 3, 321, 221)
+ conv_feature, transformer_feature = model(imgs)[-1]
+ assert conv_feature.shape == (3, 64 * 1 * 4
+ ) # base_channels * channel_ratio * 4
+ assert transformer_feature.shape == (3, 384)
+
+ # Test custom arch Conformer without output cls token
+ cfg = deepcopy(cfg_ori)
+ cfg['arch'] = {
+ 'embed_dims': 128,
+ 'depths': 15,
+ 'num_heads': 16,
+ 'channel_ratio': 3,
+ }
+ cfg['with_cls_token'] = False
+ cfg['base_channels'] = 32
+ model = Conformer(**cfg)
+ conv_feature, transformer_feature = model(imgs)[-1]
+ assert conv_feature.shape == (3, 32 * 3 * 4)
+ assert transformer_feature.shape == (3, 128)
+
+ # Test Conformer with multi out indices
+ cfg = deepcopy(cfg_ori)
+ cfg['out_indices'] = [4, 8, 12]
+ model = Conformer(**cfg)
+ outs = model(imgs)
+ assert len(outs) == 3
+ # stage 1
+ conv_feature, transformer_feature = outs[0]
+ assert conv_feature.shape == (3, 64 * 1)
+ assert transformer_feature.shape == (3, 384)
+ # stage 2
+ conv_feature, transformer_feature = outs[1]
+ assert conv_feature.shape == (3, 64 * 1 * 2)
+ assert transformer_feature.shape == (3, 384)
+ # stage 3
+ conv_feature, transformer_feature = outs[2]
+ assert conv_feature.shape == (3, 64 * 1 * 4)
+ assert transformer_feature.shape == (3, 384)
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_convmixer.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_convmixer.py
new file mode 100644
index 0000000000000000000000000000000000000000..7d2219e298c91ca90782503fa6ef572cebfe7ca4
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_convmixer.py
@@ -0,0 +1,84 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import pytest
+import torch
+
+from mmcls.models.backbones import ConvMixer
+
+
+def test_assertion():
+ with pytest.raises(AssertionError):
+ ConvMixer(arch='unknown')
+
+ with pytest.raises(AssertionError):
+ # ConvMixer arch dict should include essential_keys,
+ ConvMixer(arch=dict(channels=[2, 3, 4, 5]))
+
+ with pytest.raises(AssertionError):
+ # ConvMixer out_indices should be valid depth.
+ ConvMixer(out_indices=-100)
+
+
+def test_convmixer():
+
+ # Test forward
+ model = ConvMixer(arch='768/32')
+ model.init_weights()
+ model.train()
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 1
+ assert feat[0].shape == torch.Size([1, 768, 32, 32])
+
+ # Test forward with multiple outputs
+ model = ConvMixer(arch='768/32', out_indices=range(32))
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 32
+ for f in feat:
+ assert f.shape == torch.Size([1, 768, 32, 32])
+
+ # Test with custom arch
+ model = ConvMixer(
+ arch={
+ 'embed_dims': 99,
+ 'depth': 5,
+ 'patch_size': 5,
+ 'kernel_size': 9
+ },
+ out_indices=range(5))
+ model.init_weights()
+ model.train()
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 5
+ for f in feat:
+ assert f.shape == torch.Size([1, 99, 44, 44])
+
+ # Test with even kernel size arch
+ model = ConvMixer(arch={
+ 'embed_dims': 99,
+ 'depth': 5,
+ 'patch_size': 5,
+ 'kernel_size': 8
+ })
+ model.init_weights()
+ model.train()
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 1
+ assert feat[0].shape == torch.Size([1, 99, 44, 44])
+
+ # Test frozen_stages
+ model = ConvMixer(arch='768/32', frozen_stages=10)
+ model.init_weights()
+ model.train()
+
+ for i in range(10):
+ assert not model.stages[i].training
+
+ for i in range(10, 32):
+ assert model.stages[i].training
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_convnext.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_convnext.py
new file mode 100644
index 0000000000000000000000000000000000000000..35448b458b1a78a47a82bca2f1fdbc74ddc19111
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_convnext.py
@@ -0,0 +1,86 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import pytest
+import torch
+
+from mmcls.models.backbones import ConvNeXt
+
+
+def test_assertion():
+ with pytest.raises(AssertionError):
+ ConvNeXt(arch='unknown')
+
+ with pytest.raises(AssertionError):
+ # ConvNeXt arch dict should include 'embed_dims',
+ ConvNeXt(arch=dict(channels=[2, 3, 4, 5]))
+
+ with pytest.raises(AssertionError):
+ # ConvNeXt arch dict should include 'embed_dims',
+ ConvNeXt(arch=dict(depths=[2, 3, 4], channels=[2, 3, 4, 5]))
+
+
+def test_convnext():
+
+ # Test forward
+ model = ConvNeXt(arch='tiny', out_indices=-1)
+ model.init_weights()
+ model.train()
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 1
+ assert feat[0].shape == torch.Size([1, 768])
+
+ # Test forward with multiple outputs
+ model = ConvNeXt(arch='small', out_indices=(0, 1, 2, 3))
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 4
+ assert feat[0].shape == torch.Size([1, 96])
+ assert feat[1].shape == torch.Size([1, 192])
+ assert feat[2].shape == torch.Size([1, 384])
+ assert feat[3].shape == torch.Size([1, 768])
+
+ # Test with custom arch
+ model = ConvNeXt(
+ arch={
+ 'depths': [2, 3, 4, 5, 6],
+ 'channels': [16, 32, 64, 128, 256]
+ },
+ out_indices=(0, 1, 2, 3, 4))
+ model.init_weights()
+ model.train()
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 5
+ assert feat[0].shape == torch.Size([1, 16])
+ assert feat[1].shape == torch.Size([1, 32])
+ assert feat[2].shape == torch.Size([1, 64])
+ assert feat[3].shape == torch.Size([1, 128])
+ assert feat[4].shape == torch.Size([1, 256])
+
+ # Test without gap before final norm
+ model = ConvNeXt(
+ arch='small', out_indices=(0, 1, 2, 3), gap_before_final_norm=False)
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 4
+ assert feat[0].shape == torch.Size([1, 96, 56, 56])
+ assert feat[1].shape == torch.Size([1, 192, 28, 28])
+ assert feat[2].shape == torch.Size([1, 384, 14, 14])
+ assert feat[3].shape == torch.Size([1, 768, 7, 7])
+
+ # Test frozen_stages
+ model = ConvNeXt(arch='small', out_indices=(0, 1, 2, 3), frozen_stages=2)
+ model.init_weights()
+ model.train()
+
+ for i in range(2):
+ assert not model.downsample_layers[i].training
+ assert not model.stages[i].training
+
+ for i in range(2, 4):
+ assert model.downsample_layers[i].training
+ assert model.stages[i].training
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_cspnet.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_cspnet.py
new file mode 100644
index 0000000000000000000000000000000000000000..ef76264430d1e694c8fbb79141dc9b87f5b83b6c
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_cspnet.py
@@ -0,0 +1,147 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from copy import deepcopy
+from functools import partial
+from unittest import TestCase
+
+import torch
+from mmcv.cnn import ConvModule
+from mmcv.utils.parrots_wrapper import _BatchNorm
+
+from mmcls.models.backbones import CSPDarkNet, CSPResNet, CSPResNeXt
+from mmcls.models.backbones.cspnet import (CSPNet, DarknetBottleneck,
+ ResNetBottleneck, ResNeXtBottleneck)
+
+
+class TestCSPNet(TestCase):
+
+ def setUp(self):
+ self.arch = dict(
+ block_fn=(DarknetBottleneck, ResNetBottleneck, ResNeXtBottleneck),
+ in_channels=(32, 64, 128),
+ out_channels=(64, 128, 256),
+ num_blocks=(1, 2, 8),
+ expand_ratio=(2, 1, 1),
+ bottle_ratio=(3, 1, 1),
+ has_downsampler=True,
+ down_growth=True,
+ block_args=({}, {}, dict(base_channels=32)))
+ self.stem_fn = partial(torch.nn.Conv2d, out_channels=32, kernel_size=3)
+
+ def test_structure(self):
+ # Test with attribute arch_setting.
+ model = CSPNet(arch=self.arch, stem_fn=self.stem_fn, out_indices=[-1])
+ self.assertEqual(len(model.stages), 3)
+ self.assertEqual(type(model.stages[0].blocks[0]), DarknetBottleneck)
+ self.assertEqual(type(model.stages[1].blocks[0]), ResNetBottleneck)
+ self.assertEqual(type(model.stages[2].blocks[0]), ResNeXtBottleneck)
+
+
+class TestCSPDarkNet(TestCase):
+
+ def setUp(self):
+ self.class_name = CSPDarkNet
+ self.cfg = dict(depth=53)
+ self.out_channels = [64, 128, 256, 512, 1024]
+ self.all_out_indices = [0, 1, 2, 3, 4]
+ self.frozen_stages = 2
+ self.stem_down = (1, 1)
+ self.num_stages = 5
+
+ def test_structure(self):
+ # Test invalid default depths
+ with self.assertRaisesRegex(AssertionError, 'depth must be one of'):
+ cfg = deepcopy(self.cfg)
+ cfg['depth'] = 'unknown'
+ self.class_name(**cfg)
+
+ # Test out_indices
+ cfg = deepcopy(self.cfg)
+ cfg['out_indices'] = {1: 1}
+ with self.assertRaisesRegex(AssertionError, "get "):
+ self.class_name(**cfg)
+ cfg['out_indices'] = [0, 13]
+ with self.assertRaisesRegex(AssertionError, 'Invalid out_indices 13'):
+ self.class_name(**cfg)
+
+ # Test model structure
+ cfg = deepcopy(self.cfg)
+ model = self.class_name(**cfg)
+ self.assertEqual(len(model.stages), self.num_stages)
+
+ def test_forward(self):
+ imgs = torch.randn(3, 3, 224, 224)
+
+ # test without output_cls_token
+ cfg = deepcopy(self.cfg)
+ model = self.class_name(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ self.assertEqual(outs[-1].size(), (3, self.out_channels[-1], 7, 7))
+
+ # Test forward with multi out indices
+ cfg = deepcopy(self.cfg)
+ cfg['out_indices'] = self.all_out_indices
+ model = self.class_name(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), len(self.all_out_indices))
+ w, h = 224 / self.stem_down[0], 224 / self.stem_down[1]
+ for i, out in enumerate(outs):
+ self.assertEqual(
+ out.size(),
+ (3, self.out_channels[i], w // 2**(i + 1), h // 2**(i + 1)))
+
+ # Test frozen stages
+ cfg = deepcopy(self.cfg)
+ cfg['frozen_stages'] = self.frozen_stages
+ model = self.class_name(**cfg)
+ model.init_weights()
+ model.train()
+ assert model.stem.training is False
+ for param in model.stem.parameters():
+ assert param.requires_grad is False
+ for i in range(self.frozen_stages + 1):
+ stage = model.stages[i]
+ for mod in stage.modules():
+ if isinstance(mod, _BatchNorm):
+ assert mod.training is False, i
+ for param in stage.parameters():
+ assert param.requires_grad is False
+
+
+class TestCSPResNet(TestCSPDarkNet):
+
+ def setUp(self):
+ self.class_name = CSPResNet
+ self.cfg = dict(depth=50)
+ self.out_channels = [128, 256, 512, 1024]
+ self.all_out_indices = [0, 1, 2, 3]
+ self.frozen_stages = 2
+ self.stem_down = (2, 2)
+ self.num_stages = 4
+
+ def test_deep_stem(self, ):
+ cfg = deepcopy(self.cfg)
+ cfg['deep_stem'] = True
+ model = self.class_name(**cfg)
+ self.assertEqual(len(model.stem), 3)
+ for i in range(3):
+ self.assertEqual(type(model.stem[i]), ConvModule)
+
+
+class TestCSPResNeXt(TestCSPDarkNet):
+
+ def setUp(self):
+ self.class_name = CSPResNeXt
+ self.cfg = dict(depth=50)
+ self.out_channels = [256, 512, 1024, 2048]
+ self.all_out_indices = [0, 1, 2, 3]
+ self.frozen_stages = 2
+ self.stem_down = (2, 2)
+ self.num_stages = 4
+
+
+if __name__ == '__main__':
+ import unittest
+ unittest.main()
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_deit.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_deit.py
new file mode 100644
index 0000000000000000000000000000000000000000..5f11a3ae57b3765bb910558560256fe851d93d3a
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_deit.py
@@ -0,0 +1,131 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import math
+import os
+import tempfile
+from copy import deepcopy
+from unittest import TestCase
+
+import torch
+from mmcv.runner import load_checkpoint, save_checkpoint
+
+from mmcls.models.backbones import DistilledVisionTransformer
+from .utils import timm_resize_pos_embed
+
+
+class TestDeiT(TestCase):
+
+ def setUp(self):
+ self.cfg = dict(
+ arch='deit-base', img_size=224, patch_size=16, drop_rate=0.1)
+
+ def test_init_weights(self):
+ # test weight init cfg
+ cfg = deepcopy(self.cfg)
+ cfg['init_cfg'] = [
+ dict(
+ type='Kaiming',
+ layer='Conv2d',
+ mode='fan_in',
+ nonlinearity='linear')
+ ]
+ model = DistilledVisionTransformer(**cfg)
+ ori_weight = model.patch_embed.projection.weight.clone().detach()
+ # The pos_embed is all zero before initialize
+ self.assertTrue(torch.allclose(model.dist_token, torch.tensor(0.)))
+
+ model.init_weights()
+ initialized_weight = model.patch_embed.projection.weight
+ self.assertFalse(torch.allclose(ori_weight, initialized_weight))
+ self.assertFalse(torch.allclose(model.dist_token, torch.tensor(0.)))
+
+ # test load checkpoint
+ pretrain_pos_embed = model.pos_embed.clone().detach()
+ tmpdir = tempfile.gettempdir()
+ checkpoint = os.path.join(tmpdir, 'test.pth')
+ save_checkpoint(model, checkpoint)
+ cfg = deepcopy(self.cfg)
+ model = DistilledVisionTransformer(**cfg)
+ load_checkpoint(model, checkpoint, strict=True)
+ self.assertTrue(torch.allclose(model.pos_embed, pretrain_pos_embed))
+
+ # test load checkpoint with different img_size
+ cfg = deepcopy(self.cfg)
+ cfg['img_size'] = 384
+ model = DistilledVisionTransformer(**cfg)
+ load_checkpoint(model, checkpoint, strict=True)
+ resized_pos_embed = timm_resize_pos_embed(
+ pretrain_pos_embed, model.pos_embed, num_tokens=2)
+ self.assertTrue(torch.allclose(model.pos_embed, resized_pos_embed))
+
+ os.remove(checkpoint)
+
+ def test_forward(self):
+ imgs = torch.randn(3, 3, 224, 224)
+
+ # test with_cls_token=False
+ cfg = deepcopy(self.cfg)
+ cfg['with_cls_token'] = False
+ cfg['output_cls_token'] = True
+ with self.assertRaisesRegex(AssertionError, 'but got False'):
+ DistilledVisionTransformer(**cfg)
+
+ cfg = deepcopy(self.cfg)
+ cfg['with_cls_token'] = False
+ cfg['output_cls_token'] = False
+ model = DistilledVisionTransformer(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ patch_token = outs[-1]
+ self.assertEqual(patch_token.shape, (3, 768, 14, 14))
+
+ # test with output_cls_token
+ cfg = deepcopy(self.cfg)
+ model = DistilledVisionTransformer(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ patch_token, cls_token, dist_token = outs[-1]
+ self.assertEqual(patch_token.shape, (3, 768, 14, 14))
+ self.assertEqual(cls_token.shape, (3, 768))
+ self.assertEqual(dist_token.shape, (3, 768))
+
+ # test without output_cls_token
+ cfg = deepcopy(self.cfg)
+ cfg['output_cls_token'] = False
+ model = DistilledVisionTransformer(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ patch_token = outs[-1]
+ self.assertEqual(patch_token.shape, (3, 768, 14, 14))
+
+ # Test forward with multi out indices
+ cfg = deepcopy(self.cfg)
+ cfg['out_indices'] = [-3, -2, -1]
+ model = DistilledVisionTransformer(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 3)
+ for out in outs:
+ patch_token, cls_token, dist_token = out
+ self.assertEqual(patch_token.shape, (3, 768, 14, 14))
+ self.assertEqual(cls_token.shape, (3, 768))
+ self.assertEqual(dist_token.shape, (3, 768))
+
+ # Test forward with dynamic input size
+ imgs1 = torch.randn(3, 3, 224, 224)
+ imgs2 = torch.randn(3, 3, 256, 256)
+ imgs3 = torch.randn(3, 3, 256, 309)
+ cfg = deepcopy(self.cfg)
+ model = DistilledVisionTransformer(**cfg)
+ for imgs in [imgs1, imgs2, imgs3]:
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ patch_token, cls_token, dist_token = outs[-1]
+ expect_feat_shape = (math.ceil(imgs.shape[2] / 16),
+ math.ceil(imgs.shape[3] / 16))
+ self.assertEqual(patch_token.shape, (3, 768, *expect_feat_shape))
+ self.assertEqual(cls_token.shape, (3, 768))
+ self.assertEqual(dist_token.shape, (3, 768))
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_densenet.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_densenet.py
new file mode 100644
index 0000000000000000000000000000000000000000..5e4c73bc0b4062f3041d10e2213b598c70f89fce
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_densenet.py
@@ -0,0 +1,95 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import pytest
+import torch
+
+from mmcls.models.backbones import DenseNet
+
+
+def test_assertion():
+ with pytest.raises(AssertionError):
+ DenseNet(arch='unknown')
+
+ with pytest.raises(AssertionError):
+ # DenseNet arch dict should include essential_keys,
+ DenseNet(arch=dict(channels=[2, 3, 4, 5]))
+
+ with pytest.raises(AssertionError):
+ # DenseNet out_indices should be valid depth.
+ DenseNet(out_indices=-100)
+
+
+def test_DenseNet():
+
+ # Test forward
+ model = DenseNet(arch='121')
+ model.init_weights()
+ model.train()
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 1
+ assert feat[0].shape == torch.Size([1, 1024, 7, 7])
+
+ # Test memory efficient option
+ model = DenseNet(arch='121', memory_efficient=True)
+ model.init_weights()
+ model.train()
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 1
+ assert feat[0].shape == torch.Size([1, 1024, 7, 7])
+
+ # Test drop rate
+ model = DenseNet(arch='121', drop_rate=0.05)
+ model.init_weights()
+ model.train()
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 1
+ assert feat[0].shape == torch.Size([1, 1024, 7, 7])
+
+ # Test forward with multiple outputs
+ model = DenseNet(arch='121', out_indices=(0, 1, 2, 3))
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 4
+ assert feat[0].shape == torch.Size([1, 128, 28, 28])
+ assert feat[1].shape == torch.Size([1, 256, 14, 14])
+ assert feat[2].shape == torch.Size([1, 512, 7, 7])
+ assert feat[3].shape == torch.Size([1, 1024, 7, 7])
+
+ # Test with custom arch
+ model = DenseNet(
+ arch={
+ 'growth_rate': 20,
+ 'depths': [4, 8, 12, 16, 20],
+ 'init_channels': 40,
+ },
+ out_indices=(0, 1, 2, 3, 4))
+ model.init_weights()
+ model.train()
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 5
+ assert feat[0].shape == torch.Size([1, 60, 28, 28])
+ assert feat[1].shape == torch.Size([1, 110, 14, 14])
+ assert feat[2].shape == torch.Size([1, 175, 7, 7])
+ assert feat[3].shape == torch.Size([1, 247, 3, 3])
+ assert feat[4].shape == torch.Size([1, 647, 3, 3])
+
+ # Test frozen_stages
+ model = DenseNet(arch='121', out_indices=(0, 1, 2, 3), frozen_stages=2)
+ model.init_weights()
+ model.train()
+
+ for i in range(2):
+ assert not model.stages[i].training
+ assert not model.transitions[i].training
+
+ for i in range(2, 4):
+ assert model.stages[i].training
+ assert model.transitions[i].training
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_efficientformer.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_efficientformer.py
new file mode 100644
index 0000000000000000000000000000000000000000..88aad529c84c95706d88f21d2fff0312d0cb547c
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_efficientformer.py
@@ -0,0 +1,199 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from copy import deepcopy
+from unittest import TestCase
+
+import torch
+from mmcv.cnn import ConvModule
+from torch import nn
+
+from mmcls.models.backbones import EfficientFormer
+from mmcls.models.backbones.efficientformer import (AttentionWithBias, Flat,
+ Meta3D, Meta4D)
+from mmcls.models.backbones.poolformer import Pooling
+
+
+class TestEfficientFormer(TestCase):
+
+ def setUp(self):
+ self.cfg = dict(arch='l1', drop_path_rate=0.1)
+ self.arch = EfficientFormer.arch_settings['l1']
+ self.custom_arch = {
+ 'layers': [1, 1, 1, 4],
+ 'embed_dims': [48, 96, 224, 448],
+ 'downsamples': [False, True, True, True],
+ 'vit_num': 2,
+ }
+ self.custom_cfg = dict(arch=self.custom_arch)
+
+ def test_arch(self):
+ # Test invalid default arch
+ with self.assertRaisesRegex(AssertionError, 'Unavailable arch'):
+ cfg = deepcopy(self.cfg)
+ cfg['arch'] = 'unknown'
+ EfficientFormer(**cfg)
+
+ # Test invalid custom arch
+ with self.assertRaisesRegex(AssertionError, 'must have'):
+ cfg = deepcopy(self.custom_cfg)
+ cfg['arch'].pop('layers')
+ EfficientFormer(**cfg)
+
+ # Test vit_num < 0
+ with self.assertRaisesRegex(AssertionError, "'vit_num' must"):
+ cfg = deepcopy(self.custom_cfg)
+ cfg['arch']['vit_num'] = -1
+ EfficientFormer(**cfg)
+
+ # Test vit_num > last stage layers
+ with self.assertRaisesRegex(AssertionError, "'vit_num' must"):
+ cfg = deepcopy(self.custom_cfg)
+ cfg['arch']['vit_num'] = 10
+ EfficientFormer(**cfg)
+
+ # Test out_ind
+ with self.assertRaisesRegex(AssertionError, '"out_indices" must'):
+ cfg = deepcopy(self.custom_cfg)
+ cfg['out_indices'] = dict
+ EfficientFormer(**cfg)
+
+ # Test custom arch
+ cfg = deepcopy(self.custom_cfg)
+ model = EfficientFormer(**cfg)
+ self.assertEqual(len(model.patch_embed), 2)
+ layers = self.custom_arch['layers']
+ downsamples = self.custom_arch['downsamples']
+ vit_num = self.custom_arch['vit_num']
+
+ for i, stage in enumerate(model.network):
+ if downsamples[i]:
+ self.assertIsInstance(stage[0], ConvModule)
+ self.assertEqual(stage[0].conv.stride, (2, 2))
+ self.assertTrue(hasattr(stage[0].conv, 'bias'))
+ self.assertTrue(isinstance(stage[0].bn, nn.BatchNorm2d))
+
+ if i < len(model.network) - 1:
+ self.assertIsInstance(stage[-1], Meta4D)
+ self.assertIsInstance(stage[-1].token_mixer, Pooling)
+ self.assertEqual(len(stage) - downsamples[i], layers[i])
+ elif vit_num > 0:
+ self.assertIsInstance(stage[-1], Meta3D)
+ self.assertIsInstance(stage[-1].token_mixer, AttentionWithBias)
+ self.assertEqual(len(stage) - downsamples[i] - 1, layers[i])
+ flat_layer_idx = len(stage) - vit_num - downsamples[i]
+ self.assertIsInstance(stage[flat_layer_idx], Flat)
+ count = 0
+ for layer in stage:
+ if isinstance(layer, Meta3D):
+ count += 1
+ self.assertEqual(count, vit_num)
+
+ def test_init_weights(self):
+ # test weight init cfg
+ cfg = deepcopy(self.cfg)
+ cfg['init_cfg'] = [
+ dict(
+ type='Kaiming',
+ layer='Conv2d',
+ mode='fan_in',
+ nonlinearity='linear'),
+ dict(type='Constant', layer=['LayerScale'], val=1e-4)
+ ]
+ model = EfficientFormer(**cfg)
+ ori_weight = model.patch_embed[0].conv.weight.clone().detach()
+ ori_ls_weight = model.network[0][-1].ls1.weight.clone().detach()
+
+ model.init_weights()
+ initialized_weight = model.patch_embed[0].conv.weight
+ initialized_ls_weight = model.network[0][-1].ls1.weight
+ self.assertFalse(torch.allclose(ori_weight, initialized_weight))
+ self.assertFalse(torch.allclose(ori_ls_weight, initialized_ls_weight))
+
+ def test_forward(self):
+ imgs = torch.randn(1, 3, 224, 224)
+
+ # test last stage output
+ cfg = deepcopy(self.cfg)
+ model = EfficientFormer(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ feat = outs[-1]
+ self.assertEqual(feat.shape, (1, 448, 49))
+ assert hasattr(model, 'norm3')
+ assert isinstance(getattr(model, 'norm3'), nn.LayerNorm)
+
+ # test multiple output indices
+ cfg = deepcopy(self.cfg)
+ cfg['out_indices'] = (0, 1, 2, 3)
+ cfg['reshape_last_feat'] = True
+ model = EfficientFormer(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 4)
+ # Test out features shape
+ for dim, stride, out in zip(self.arch['embed_dims'], [1, 2, 4, 8],
+ outs):
+ self.assertEqual(out.shape, (1, dim, 56 // stride, 56 // stride))
+
+ # Test norm layer
+ for i in range(4):
+ assert hasattr(model, f'norm{i}')
+ stage_norm = getattr(model, f'norm{i}')
+ assert isinstance(stage_norm, nn.GroupNorm)
+ assert stage_norm.num_groups == 1
+
+ # Test vit_num == 0
+ cfg = deepcopy(self.custom_cfg)
+ cfg['arch']['vit_num'] = 0
+ cfg['out_indices'] = (0, 1, 2, 3)
+ model = EfficientFormer(**cfg)
+ for i in range(4):
+ assert hasattr(model, f'norm{i}')
+ stage_norm = getattr(model, f'norm{i}')
+ assert isinstance(stage_norm, nn.GroupNorm)
+ assert stage_norm.num_groups == 1
+
+ def test_structure(self):
+ # test drop_path_rate decay
+ cfg = deepcopy(self.cfg)
+ cfg['drop_path_rate'] = 0.2
+ model = EfficientFormer(**cfg)
+ layers = self.arch['layers']
+ for i, block in enumerate(model.network):
+ expect_prob = 0.2 / (sum(layers) - 1) * i
+ if hasattr(block, 'drop_path'):
+ if expect_prob == 0:
+ self.assertIsInstance(block.drop_path, torch.nn.Identity)
+ else:
+ self.assertAlmostEqual(block.drop_path.drop_prob,
+ expect_prob)
+
+ # test with first stage frozen.
+ cfg = deepcopy(self.cfg)
+ frozen_stages = 1
+ cfg['frozen_stages'] = frozen_stages
+ cfg['out_indices'] = (0, 1, 2, 3)
+ model = EfficientFormer(**cfg)
+ model.init_weights()
+ model.train()
+
+ # the patch_embed and first stage should not require grad.
+ self.assertFalse(model.patch_embed.training)
+ for param in model.patch_embed.parameters():
+ self.assertFalse(param.requires_grad)
+ for i in range(frozen_stages):
+ module = model.network[i]
+ for param in module.parameters():
+ self.assertFalse(param.requires_grad)
+ for param in model.norm0.parameters():
+ self.assertFalse(param.requires_grad)
+
+ # the second stage should require grad.
+ for i in range(frozen_stages + 1, 4):
+ module = model.network[i]
+ for param in module.parameters():
+ self.assertTrue(param.requires_grad)
+ if hasattr(model, f'norm{i}'):
+ norm = getattr(model, f'norm{i}')
+ for param in norm.parameters():
+ self.assertTrue(param.requires_grad)
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_efficientnet.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_efficientnet.py
new file mode 100644
index 0000000000000000000000000000000000000000..d424b23054cb6050a1dca3686ab7c3998bb99d87
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_efficientnet.py
@@ -0,0 +1,144 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import pytest
+import torch
+from torch.nn.modules import GroupNorm
+from torch.nn.modules.batchnorm import _BatchNorm
+
+from mmcls.models.backbones import EfficientNet
+
+
+def is_norm(modules):
+ """Check if is one of the norms."""
+ if isinstance(modules, (GroupNorm, _BatchNorm)):
+ return True
+ return False
+
+
+def check_norm_state(modules, train_state):
+ """Check if norm layer is in correct train state."""
+ for mod in modules:
+ if isinstance(mod, _BatchNorm):
+ if mod.training != train_state:
+ return False
+ return True
+
+
+def test_efficientnet_backbone():
+ archs = ['b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b7', 'b8', 'es', 'em', 'el']
+ with pytest.raises(TypeError):
+ # pretrained must be a string path
+ model = EfficientNet()
+ model.init_weights(pretrained=0)
+
+ with pytest.raises(AssertionError):
+ # arch must in arc_settings
+ EfficientNet(arch='others')
+
+ for arch in archs:
+ with pytest.raises(ValueError):
+ # frozen_stages must less than 7
+ EfficientNet(arch=arch, frozen_stages=12)
+
+ # Test EfficientNet
+ model = EfficientNet()
+ model.init_weights()
+ model.train()
+
+ # Test EfficientNet with first stage frozen
+ frozen_stages = 7
+ model = EfficientNet(arch='b0', frozen_stages=frozen_stages)
+ model.init_weights()
+ model.train()
+ for i in range(frozen_stages):
+ layer = model.layers[i]
+ for mod in layer.modules():
+ if isinstance(mod, _BatchNorm):
+ assert mod.training is False
+ for param in layer.parameters():
+ assert param.requires_grad is False
+
+ # Test EfficientNet with norm eval
+ model = EfficientNet(norm_eval=True)
+ model.init_weights()
+ model.train()
+ assert check_norm_state(model.modules(), False)
+
+ # Test EfficientNet forward with 'b0' arch
+ out_channels = [32, 16, 24, 40, 112, 320, 1280]
+ model = EfficientNet(arch='b0', out_indices=(0, 1, 2, 3, 4, 5, 6))
+ model.init_weights()
+ model.train()
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 7
+ assert feat[0].shape == torch.Size([1, out_channels[0], 112, 112])
+ assert feat[1].shape == torch.Size([1, out_channels[1], 112, 112])
+ assert feat[2].shape == torch.Size([1, out_channels[2], 56, 56])
+ assert feat[3].shape == torch.Size([1, out_channels[3], 28, 28])
+ assert feat[4].shape == torch.Size([1, out_channels[4], 14, 14])
+ assert feat[5].shape == torch.Size([1, out_channels[5], 7, 7])
+ assert feat[6].shape == torch.Size([1, out_channels[6], 7, 7])
+
+ # Test EfficientNet forward with 'b0' arch and GroupNorm
+ out_channels = [32, 16, 24, 40, 112, 320, 1280]
+ model = EfficientNet(
+ arch='b0',
+ out_indices=(0, 1, 2, 3, 4, 5, 6),
+ norm_cfg=dict(type='GN', num_groups=2, requires_grad=True))
+ for m in model.modules():
+ if is_norm(m):
+ assert isinstance(m, GroupNorm)
+ model.init_weights()
+ model.train()
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 7
+ assert feat[0].shape == torch.Size([1, out_channels[0], 112, 112])
+ assert feat[1].shape == torch.Size([1, out_channels[1], 112, 112])
+ assert feat[2].shape == torch.Size([1, out_channels[2], 56, 56])
+ assert feat[3].shape == torch.Size([1, out_channels[3], 28, 28])
+ assert feat[4].shape == torch.Size([1, out_channels[4], 14, 14])
+ assert feat[5].shape == torch.Size([1, out_channels[5], 7, 7])
+ assert feat[6].shape == torch.Size([1, out_channels[6], 7, 7])
+
+ # Test EfficientNet forward with 'es' arch
+ out_channels = [32, 24, 32, 48, 144, 192, 1280]
+ model = EfficientNet(arch='es', out_indices=(0, 1, 2, 3, 4, 5, 6))
+ model.init_weights()
+ model.train()
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 7
+ assert feat[0].shape == torch.Size([1, out_channels[0], 112, 112])
+ assert feat[1].shape == torch.Size([1, out_channels[1], 112, 112])
+ assert feat[2].shape == torch.Size([1, out_channels[2], 56, 56])
+ assert feat[3].shape == torch.Size([1, out_channels[3], 28, 28])
+ assert feat[4].shape == torch.Size([1, out_channels[4], 14, 14])
+ assert feat[5].shape == torch.Size([1, out_channels[5], 7, 7])
+ assert feat[6].shape == torch.Size([1, out_channels[6], 7, 7])
+
+ # Test EfficientNet forward with 'es' arch and GroupNorm
+ out_channels = [32, 24, 32, 48, 144, 192, 1280]
+ model = EfficientNet(
+ arch='es',
+ out_indices=(0, 1, 2, 3, 4, 5, 6),
+ norm_cfg=dict(type='GN', num_groups=2, requires_grad=True))
+ for m in model.modules():
+ if is_norm(m):
+ assert isinstance(m, GroupNorm)
+ model.init_weights()
+ model.train()
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 7
+ assert feat[0].shape == torch.Size([1, out_channels[0], 112, 112])
+ assert feat[1].shape == torch.Size([1, out_channels[1], 112, 112])
+ assert feat[2].shape == torch.Size([1, out_channels[2], 56, 56])
+ assert feat[3].shape == torch.Size([1, out_channels[3], 28, 28])
+ assert feat[4].shape == torch.Size([1, out_channels[4], 14, 14])
+ assert feat[5].shape == torch.Size([1, out_channels[5], 7, 7])
+ assert feat[6].shape == torch.Size([1, out_channels[6], 7, 7])
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_hornet.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_hornet.py
new file mode 100644
index 0000000000000000000000000000000000000000..5fdd84b34c3989763a80a9aa276dac4a5d66d382
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_hornet.py
@@ -0,0 +1,174 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import math
+from copy import deepcopy
+from itertools import chain
+from unittest import TestCase
+
+import pytest
+import torch
+from mmcv.utils import digit_version
+from mmcv.utils.parrots_wrapper import _BatchNorm
+from torch import nn
+
+from mmcls.models.backbones import HorNet
+
+
+def check_norm_state(modules, train_state):
+ """Check if norm layer is in correct train state."""
+ for mod in modules:
+ if isinstance(mod, _BatchNorm):
+ if mod.training != train_state:
+ return False
+ return True
+
+
+@pytest.mark.skipif(
+ digit_version(torch.__version__) < digit_version('1.7.0'),
+ reason='torch.fft is not available before 1.7.0')
+class TestHorNet(TestCase):
+
+ def setUp(self):
+ self.cfg = dict(
+ arch='t', drop_path_rate=0.1, gap_before_final_norm=False)
+
+ def test_arch(self):
+ # Test invalid default arch
+ with self.assertRaisesRegex(AssertionError, 'not in default archs'):
+ cfg = deepcopy(self.cfg)
+ cfg['arch'] = 'unknown'
+ HorNet(**cfg)
+
+ # Test invalid custom arch
+ with self.assertRaisesRegex(AssertionError, 'Custom arch needs'):
+ cfg = deepcopy(self.cfg)
+ cfg['arch'] = {
+ 'depths': [1, 1, 1, 1],
+ 'orders': [1, 1, 1, 1],
+ }
+ HorNet(**cfg)
+
+ # Test custom arch
+ cfg = deepcopy(self.cfg)
+ base_dim = 64
+ depths = [2, 3, 18, 2]
+ embed_dims = [base_dim, base_dim * 2, base_dim * 4, base_dim * 8]
+ cfg['arch'] = {
+ 'base_dim':
+ base_dim,
+ 'depths':
+ depths,
+ 'orders': [2, 3, 4, 5],
+ 'dw_cfg': [
+ dict(type='DW', kernel_size=7),
+ dict(type='DW', kernel_size=7),
+ dict(type='GF', h=14, w=8),
+ dict(type='GF', h=7, w=4)
+ ],
+ }
+ model = HorNet(**cfg)
+
+ for i in range(len(depths)):
+ stage = model.stages[i]
+ self.assertEqual(stage[-1].out_channels, embed_dims[i])
+ self.assertEqual(len(stage), depths[i])
+
+ def test_init_weights(self):
+ # test weight init cfg
+ cfg = deepcopy(self.cfg)
+ cfg['init_cfg'] = [
+ dict(
+ type='Kaiming',
+ layer='Conv2d',
+ mode='fan_in',
+ nonlinearity='linear')
+ ]
+ model = HorNet(**cfg)
+ ori_weight = model.downsample_layers[0][0].weight.clone().detach()
+
+ model.init_weights()
+ initialized_weight = model.downsample_layers[0][0].weight
+ self.assertFalse(torch.allclose(ori_weight, initialized_weight))
+
+ def test_forward(self):
+ imgs = torch.randn(3, 3, 224, 224)
+
+ cfg = deepcopy(self.cfg)
+ model = HorNet(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ feat = outs[-1]
+ self.assertEqual(feat.shape, (3, 512, 7, 7))
+
+ # test multiple output indices
+ cfg = deepcopy(self.cfg)
+ cfg['out_indices'] = (0, 1, 2, 3)
+ model = HorNet(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 4)
+ for emb_size, stride, out in zip([64, 128, 256, 512], [1, 2, 4, 8],
+ outs):
+ self.assertEqual(out.shape,
+ (3, emb_size, 56 // stride, 56 // stride))
+
+ # test with dynamic input shape
+ imgs1 = torch.randn(3, 3, 224, 224)
+ imgs2 = torch.randn(3, 3, 256, 256)
+ imgs3 = torch.randn(3, 3, 256, 309)
+ cfg = deepcopy(self.cfg)
+ model = HorNet(**cfg)
+ for imgs in [imgs1, imgs2, imgs3]:
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ feat = outs[-1]
+ expect_feat_shape = (math.floor(imgs.shape[2] / 32),
+ math.floor(imgs.shape[3] / 32))
+ self.assertEqual(feat.shape, (3, 512, *expect_feat_shape))
+
+ def test_structure(self):
+ # test drop_path_rate decay
+ cfg = deepcopy(self.cfg)
+ cfg['drop_path_rate'] = 0.2
+ model = HorNet(**cfg)
+ depths = model.arch_settings['depths']
+ stages = model.stages
+ blocks = chain(*[stage for stage in stages])
+ total_depth = sum(depths)
+ dpr = [
+ x.item()
+ for x in torch.linspace(0, cfg['drop_path_rate'], total_depth)
+ ]
+ for i, (block, expect_prob) in enumerate(zip(blocks, dpr)):
+ if expect_prob == 0:
+ assert isinstance(block.drop_path, nn.Identity)
+ else:
+ self.assertAlmostEqual(block.drop_path.drop_prob, expect_prob)
+
+ # test VAN with first stage frozen.
+ cfg = deepcopy(self.cfg)
+ frozen_stages = 0
+ cfg['frozen_stages'] = frozen_stages
+ cfg['out_indices'] = (0, 1, 2, 3)
+ model = HorNet(**cfg)
+ model.init_weights()
+ model.train()
+
+ # the patch_embed and first stage should not require grad.
+ for i in range(frozen_stages + 1):
+ down = model.downsample_layers[i]
+ for param in down.parameters():
+ self.assertFalse(param.requires_grad)
+ blocks = model.stages[i]
+ for param in blocks.parameters():
+ self.assertFalse(param.requires_grad)
+
+ # the second stage should require grad.
+ for i in range(frozen_stages + 1, 4):
+ down = model.downsample_layers[i]
+ for param in down.parameters():
+ self.assertTrue(param.requires_grad)
+ blocks = model.stages[i]
+ for param in blocks.parameters():
+ self.assertTrue(param.requires_grad)
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_hrnet.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_hrnet.py
new file mode 100644
index 0000000000000000000000000000000000000000..cb9909a8923614bcf9fadfc017c326d2132d8cc9
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_hrnet.py
@@ -0,0 +1,93 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import pytest
+import torch
+from torch.nn.modules import GroupNorm
+from torch.nn.modules.batchnorm import _BatchNorm
+
+from mmcls.models.backbones import HRNet
+
+
+def is_norm(modules):
+ """Check if is one of the norms."""
+ if isinstance(modules, (GroupNorm, _BatchNorm)):
+ return True
+ return False
+
+
+def check_norm_state(modules, train_state):
+ """Check if norm layer is in correct train state."""
+ for mod in modules:
+ if isinstance(mod, _BatchNorm):
+ if mod.training != train_state:
+ return False
+ return True
+
+
+@pytest.mark.parametrize('base_channels', [18, 30, 32, 40, 44, 48, 64])
+def test_hrnet_arch_zoo(base_channels):
+
+ cfg_ori = dict(arch=f'w{base_channels}')
+
+ # Test HRNet model with input size of 224
+ model = HRNet(**cfg_ori)
+ model.init_weights()
+ model.train()
+
+ assert check_norm_state(model.modules(), True)
+
+ imgs = torch.randn(3, 3, 224, 224)
+ outs = model(imgs)
+ out_channels = base_channels
+ out_size = 56
+ assert isinstance(outs, tuple)
+ for out in outs:
+ assert out.shape == (3, out_channels, out_size, out_size)
+ out_channels = out_channels * 2
+ out_size = out_size // 2
+
+
+def test_hrnet_custom_arch():
+
+ cfg_ori = dict(
+ extra=dict(
+ stage1=dict(
+ num_modules=1,
+ num_branches=1,
+ block='BOTTLENECK',
+ num_blocks=(4, ),
+ num_channels=(64, )),
+ stage2=dict(
+ num_modules=1,
+ num_branches=2,
+ block='BASIC',
+ num_blocks=(4, 4),
+ num_channels=(32, 64)),
+ stage3=dict(
+ num_modules=4,
+ num_branches=3,
+ block='BOTTLENECK',
+ num_blocks=(4, 4, 2),
+ num_channels=(32, 64, 128)),
+ stage4=dict(
+ num_modules=3,
+ num_branches=4,
+ block='BASIC',
+ num_blocks=(4, 3, 4, 4),
+ num_channels=(32, 64, 152, 256)),
+ ), )
+
+ # Test HRNet model with input size of 224
+ model = HRNet(**cfg_ori)
+ model.init_weights()
+ model.train()
+
+ assert check_norm_state(model.modules(), True)
+
+ imgs = torch.randn(3, 3, 224, 224)
+ outs = model(imgs)
+ out_channels = (32, 64, 152, 256)
+ out_size = 56
+ assert isinstance(outs, tuple)
+ for out, out_channel in zip(outs, out_channels):
+ assert out.shape == (3, out_channel, out_size, out_size)
+ out_size = out_size // 2
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_mlp_mixer.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_mlp_mixer.py
new file mode 100644
index 0000000000000000000000000000000000000000..d065a6805c8154f67ccea7573463b65fbe04c3f0
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_mlp_mixer.py
@@ -0,0 +1,119 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from copy import deepcopy
+from unittest import TestCase
+
+import torch
+from torch.nn.modules import GroupNorm
+from torch.nn.modules.batchnorm import _BatchNorm
+
+from mmcls.models.backbones import MlpMixer
+
+
+def is_norm(modules):
+ """Check if is one of the norms."""
+ if isinstance(modules, (GroupNorm, _BatchNorm)):
+ return True
+ return False
+
+
+def check_norm_state(modules, train_state):
+ """Check if norm layer is in correct train state."""
+ for mod in modules:
+ if isinstance(mod, _BatchNorm):
+ if mod.training != train_state:
+ return False
+ return True
+
+
+class TestMLPMixer(TestCase):
+
+ def setUp(self):
+ self.cfg = dict(
+ arch='b',
+ img_size=224,
+ patch_size=16,
+ drop_rate=0.1,
+ init_cfg=[
+ dict(
+ type='Kaiming',
+ layer='Conv2d',
+ mode='fan_in',
+ nonlinearity='linear')
+ ])
+
+ def test_arch(self):
+ # Test invalid default arch
+ with self.assertRaisesRegex(AssertionError, 'not in default archs'):
+ cfg = deepcopy(self.cfg)
+ cfg['arch'] = 'unknown'
+ MlpMixer(**cfg)
+
+ # Test invalid custom arch
+ with self.assertRaisesRegex(AssertionError, 'Custom arch needs'):
+ cfg = deepcopy(self.cfg)
+ cfg['arch'] = {
+ 'embed_dims': 24,
+ 'num_layers': 16,
+ 'tokens_mlp_dims': 4096
+ }
+ MlpMixer(**cfg)
+
+ # Test custom arch
+ cfg = deepcopy(self.cfg)
+ cfg['arch'] = {
+ 'embed_dims': 128,
+ 'num_layers': 6,
+ 'tokens_mlp_dims': 256,
+ 'channels_mlp_dims': 1024
+ }
+ model = MlpMixer(**cfg)
+ self.assertEqual(model.embed_dims, 128)
+ self.assertEqual(model.num_layers, 6)
+ for layer in model.layers:
+ self.assertEqual(layer.token_mix.feedforward_channels, 256)
+ self.assertEqual(layer.channel_mix.feedforward_channels, 1024)
+
+ def test_init_weights(self):
+ # test weight init cfg
+ cfg = deepcopy(self.cfg)
+ cfg['init_cfg'] = [
+ dict(
+ type='Kaiming',
+ layer='Conv2d',
+ mode='fan_in',
+ nonlinearity='linear')
+ ]
+ model = MlpMixer(**cfg)
+ ori_weight = model.patch_embed.projection.weight.clone().detach()
+ model.init_weights()
+ initialized_weight = model.patch_embed.projection.weight
+ self.assertFalse(torch.allclose(ori_weight, initialized_weight))
+
+ def test_forward(self):
+ imgs = torch.randn(3, 3, 224, 224)
+
+ # test forward with single out indices
+ cfg = deepcopy(self.cfg)
+ model = MlpMixer(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ feat = outs[-1]
+ self.assertEqual(feat.shape, (3, 768, 196))
+
+ # test forward with multi out indices
+ cfg = deepcopy(self.cfg)
+ cfg['out_indices'] = [-3, -2, -1]
+ model = MlpMixer(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 3)
+ for feat in outs:
+ self.assertEqual(feat.shape, (3, 768, 196))
+
+ # test with invalid input shape
+ imgs2 = torch.randn(3, 3, 256, 256)
+ cfg = deepcopy(self.cfg)
+ model = MlpMixer(**cfg)
+ with self.assertRaisesRegex(AssertionError, 'dynamic input shape.'):
+ model(imgs2)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_mobilenet_v2.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_mobilenet_v2.py
similarity index 97%
rename from openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_mobilenet_v2.py
rename to openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_mobilenet_v2.py
index 1610f1281ee08760baf7624a27689a9bd7b7f9f9..9ea75570260ebe7091f99579c342e73e35ae5d73 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_mobilenet_v2.py
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_mobilenet_v2.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import pytest
import torch
from torch.nn.modules import GroupNorm
@@ -154,7 +155,8 @@ def test_mobilenetv2_backbone():
imgs = torch.randn(1, 3, 224, 224)
feat = model(imgs)
- assert feat.shape == torch.Size((1, 2560, 7, 7))
+ assert len(feat) == 1
+ assert feat[0].shape == torch.Size((1, 2560, 7, 7))
# Test MobileNetV2 forward with out_indices=None
model = MobileNetV2(widen_factor=1.0)
@@ -163,7 +165,8 @@ def test_mobilenetv2_backbone():
imgs = torch.randn(1, 3, 224, 224)
feat = model(imgs)
- assert feat.shape == torch.Size((1, 1280, 7, 7))
+ assert len(feat) == 1
+ assert feat[0].shape == torch.Size((1, 1280, 7, 7))
# Test MobileNetV2 forward with dict(type='ReLU')
model = MobileNetV2(
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_mobilenet_v3.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_mobilenet_v3.py
new file mode 100644
index 0000000000000000000000000000000000000000..b122dbd76943a9803e5873aca3b38af5bcd87bea
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_mobilenet_v3.py
@@ -0,0 +1,175 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import pytest
+import torch
+from torch.nn.modules import GroupNorm
+from torch.nn.modules.batchnorm import _BatchNorm
+
+from mmcls.models.backbones import MobileNetV3
+from mmcls.models.utils import InvertedResidual
+
+
+def is_norm(modules):
+ """Check if is one of the norms."""
+ if isinstance(modules, (GroupNorm, _BatchNorm)):
+ return True
+ return False
+
+
+def check_norm_state(modules, train_state):
+ """Check if norm layer is in correct train state."""
+ for mod in modules:
+ if isinstance(mod, _BatchNorm):
+ if mod.training != train_state:
+ return False
+ return True
+
+
+def test_mobilenetv3_backbone():
+ with pytest.raises(TypeError):
+ # pretrained must be a string path
+ model = MobileNetV3()
+ model.init_weights(pretrained=0)
+
+ with pytest.raises(AssertionError):
+ # arch must in [small, large]
+ MobileNetV3(arch='others')
+
+ with pytest.raises(ValueError):
+ # frozen_stages must less than 13 when arch is small
+ MobileNetV3(arch='small', frozen_stages=13)
+
+ with pytest.raises(ValueError):
+ # frozen_stages must less than 17 when arch is large
+ MobileNetV3(arch='large', frozen_stages=17)
+
+ with pytest.raises(ValueError):
+ # max out_indices must less than 13 when arch is small
+ MobileNetV3(arch='small', out_indices=(13, ))
+
+ with pytest.raises(ValueError):
+ # max out_indices must less than 17 when arch is large
+ MobileNetV3(arch='large', out_indices=(17, ))
+
+ # Test MobileNetV3
+ model = MobileNetV3()
+ model.init_weights()
+ model.train()
+
+ # Test MobileNetV3 with first stage frozen
+ frozen_stages = 1
+ model = MobileNetV3(frozen_stages=frozen_stages)
+ model.init_weights()
+ model.train()
+ for i in range(0, frozen_stages + 1):
+ layer = getattr(model, f'layer{i}')
+ for mod in layer.modules():
+ if isinstance(mod, _BatchNorm):
+ assert mod.training is False
+ for param in layer.parameters():
+ assert param.requires_grad is False
+
+ # Test MobileNetV3 with norm eval
+ model = MobileNetV3(norm_eval=True, out_indices=range(0, 12))
+ model.init_weights()
+ model.train()
+ assert check_norm_state(model.modules(), False)
+
+ # Test MobileNetV3 forward with small arch
+ model = MobileNetV3(out_indices=(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12))
+ model.init_weights()
+ model.train()
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 13
+ assert feat[0].shape == torch.Size([1, 16, 112, 112])
+ assert feat[1].shape == torch.Size([1, 16, 56, 56])
+ assert feat[2].shape == torch.Size([1, 24, 28, 28])
+ assert feat[3].shape == torch.Size([1, 24, 28, 28])
+ assert feat[4].shape == torch.Size([1, 40, 14, 14])
+ assert feat[5].shape == torch.Size([1, 40, 14, 14])
+ assert feat[6].shape == torch.Size([1, 40, 14, 14])
+ assert feat[7].shape == torch.Size([1, 48, 14, 14])
+ assert feat[8].shape == torch.Size([1, 48, 14, 14])
+ assert feat[9].shape == torch.Size([1, 96, 7, 7])
+ assert feat[10].shape == torch.Size([1, 96, 7, 7])
+ assert feat[11].shape == torch.Size([1, 96, 7, 7])
+ assert feat[12].shape == torch.Size([1, 576, 7, 7])
+
+ # Test MobileNetV3 forward with small arch and GroupNorm
+ model = MobileNetV3(
+ out_indices=(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12),
+ norm_cfg=dict(type='GN', num_groups=2, requires_grad=True))
+ for m in model.modules():
+ if is_norm(m):
+ assert isinstance(m, GroupNorm)
+ model.init_weights()
+ model.train()
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 13
+ assert feat[0].shape == torch.Size([1, 16, 112, 112])
+ assert feat[1].shape == torch.Size([1, 16, 56, 56])
+ assert feat[2].shape == torch.Size([1, 24, 28, 28])
+ assert feat[3].shape == torch.Size([1, 24, 28, 28])
+ assert feat[4].shape == torch.Size([1, 40, 14, 14])
+ assert feat[5].shape == torch.Size([1, 40, 14, 14])
+ assert feat[6].shape == torch.Size([1, 40, 14, 14])
+ assert feat[7].shape == torch.Size([1, 48, 14, 14])
+ assert feat[8].shape == torch.Size([1, 48, 14, 14])
+ assert feat[9].shape == torch.Size([1, 96, 7, 7])
+ assert feat[10].shape == torch.Size([1, 96, 7, 7])
+ assert feat[11].shape == torch.Size([1, 96, 7, 7])
+ assert feat[12].shape == torch.Size([1, 576, 7, 7])
+
+ # Test MobileNetV3 forward with large arch
+ model = MobileNetV3(
+ arch='large',
+ out_indices=(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16))
+ model.init_weights()
+ model.train()
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 17
+ assert feat[0].shape == torch.Size([1, 16, 112, 112])
+ assert feat[1].shape == torch.Size([1, 16, 112, 112])
+ assert feat[2].shape == torch.Size([1, 24, 56, 56])
+ assert feat[3].shape == torch.Size([1, 24, 56, 56])
+ assert feat[4].shape == torch.Size([1, 40, 28, 28])
+ assert feat[5].shape == torch.Size([1, 40, 28, 28])
+ assert feat[6].shape == torch.Size([1, 40, 28, 28])
+ assert feat[7].shape == torch.Size([1, 80, 14, 14])
+ assert feat[8].shape == torch.Size([1, 80, 14, 14])
+ assert feat[9].shape == torch.Size([1, 80, 14, 14])
+ assert feat[10].shape == torch.Size([1, 80, 14, 14])
+ assert feat[11].shape == torch.Size([1, 112, 14, 14])
+ assert feat[12].shape == torch.Size([1, 112, 14, 14])
+ assert feat[13].shape == torch.Size([1, 160, 7, 7])
+ assert feat[14].shape == torch.Size([1, 160, 7, 7])
+ assert feat[15].shape == torch.Size([1, 160, 7, 7])
+ assert feat[16].shape == torch.Size([1, 960, 7, 7])
+
+ # Test MobileNetV3 forward with large arch
+ model = MobileNetV3(arch='large', out_indices=(0, ))
+ model.init_weights()
+ model.train()
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 1
+ assert feat[0].shape == torch.Size([1, 16, 112, 112])
+
+ # Test MobileNetV3 with checkpoint forward
+ model = MobileNetV3(with_cp=True)
+ for m in model.modules():
+ if isinstance(m, InvertedResidual):
+ assert m.with_cp
+ model.init_weights()
+ model.train()
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 1
+ assert feat[0].shape == torch.Size([1, 576, 7, 7])
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_mvit.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_mvit.py
new file mode 100644
index 0000000000000000000000000000000000000000..a37e93f55b2cc03225f83f9c7a81f89f1d5bcf8d
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_mvit.py
@@ -0,0 +1,185 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from copy import deepcopy
+from unittest import TestCase
+
+import torch
+
+from mmcls.models.backbones import MViT
+
+
+class TestMViT(TestCase):
+
+ def setUp(self):
+ self.cfg = dict(arch='tiny', img_size=224, drop_path_rate=0.1)
+
+ def test_arch(self):
+ # Test invalid default arch
+ with self.assertRaisesRegex(AssertionError, 'not in default archs'):
+ cfg = deepcopy(self.cfg)
+ cfg['arch'] = 'unknown'
+ MViT(**cfg)
+
+ # Test invalid custom arch
+ with self.assertRaisesRegex(AssertionError, 'Custom arch needs'):
+ cfg = deepcopy(self.cfg)
+ cfg['arch'] = {
+ 'embed_dims': 96,
+ 'num_layers': 10,
+ }
+ MViT(**cfg)
+
+ # Test custom arch
+ cfg = deepcopy(self.cfg)
+ embed_dims = 96
+ num_layers = 10
+ num_heads = 1
+ downscale_indices = (2, 5, 7)
+ cfg['arch'] = {
+ 'embed_dims': embed_dims,
+ 'num_layers': num_layers,
+ 'num_heads': num_heads,
+ 'downscale_indices': downscale_indices
+ }
+ model = MViT(**cfg)
+ self.assertEqual(len(model.blocks), num_layers)
+ for i, block in enumerate(model.blocks):
+ if i in downscale_indices:
+ num_heads *= 2
+ embed_dims *= 2
+ self.assertEqual(block.out_dims, embed_dims)
+ self.assertEqual(block.attn.num_heads, num_heads)
+
+ def test_init_weights(self):
+ # test weight init cfg
+ cfg = deepcopy(self.cfg)
+ cfg['use_abs_pos_embed'] = True
+ cfg['init_cfg'] = [
+ dict(
+ type='Kaiming',
+ layer='Conv2d',
+ mode='fan_in',
+ nonlinearity='linear')
+ ]
+ model = MViT(**cfg)
+ ori_weight = model.patch_embed.projection.weight.clone().detach()
+ # The pos_embed is all zero before initialize
+ self.assertTrue(torch.allclose(model.pos_embed, torch.tensor(0.)))
+
+ model.init_weights()
+ initialized_weight = model.patch_embed.projection.weight
+ self.assertFalse(torch.allclose(ori_weight, initialized_weight))
+ self.assertFalse(torch.allclose(model.pos_embed, torch.tensor(0.)))
+ self.assertFalse(
+ torch.allclose(model.blocks[0].attn.rel_pos_h, torch.tensor(0.)))
+ self.assertFalse(
+ torch.allclose(model.blocks[0].attn.rel_pos_w, torch.tensor(0.)))
+
+ # test rel_pos_zero_init
+ cfg = deepcopy(self.cfg)
+ cfg['rel_pos_zero_init'] = True
+ model = MViT(**cfg)
+ model.init_weights()
+ self.assertTrue(
+ torch.allclose(model.blocks[0].attn.rel_pos_h, torch.tensor(0.)))
+ self.assertTrue(
+ torch.allclose(model.blocks[0].attn.rel_pos_w, torch.tensor(0.)))
+
+ def test_forward(self):
+ imgs = torch.randn(1, 3, 224, 224)
+
+ cfg = deepcopy(self.cfg)
+ model = MViT(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ feat = outs[-1]
+ self.assertEqual(feat.shape, (1, 768, 7, 7))
+
+ # test multiple output indices
+ cfg = deepcopy(self.cfg)
+ cfg['out_scales'] = (0, 1, 2, 3)
+ model = MViT(**cfg)
+ model.init_weights()
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 4)
+ for stride, out in zip([1, 2, 4, 8], outs):
+ self.assertEqual(out.shape,
+ (1, 96 * stride, 56 // stride, 56 // stride))
+
+ # test dim_mul_in_attention = False
+ cfg = deepcopy(self.cfg)
+ cfg['out_scales'] = (0, 1, 2, 3)
+ cfg['dim_mul_in_attention'] = False
+ model = MViT(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 4)
+ for dim_mul, stride, out in zip([2, 4, 8, 8], [1, 2, 4, 8], outs):
+ self.assertEqual(out.shape,
+ (1, 96 * dim_mul, 56 // stride, 56 // stride))
+
+ # test rel_pos_spatial = False
+ cfg = deepcopy(self.cfg)
+ cfg['out_scales'] = (0, 1, 2, 3)
+ cfg['rel_pos_spatial'] = False
+ cfg['img_size'] = None
+ model = MViT(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 4)
+ for stride, out in zip([1, 2, 4, 8], outs):
+ self.assertEqual(out.shape,
+ (1, 96 * stride, 56 // stride, 56 // stride))
+
+ # test residual_pooling = False
+ cfg = deepcopy(self.cfg)
+ cfg['out_scales'] = (0, 1, 2, 3)
+ cfg['residual_pooling'] = False
+ model = MViT(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 4)
+ for stride, out in zip([1, 2, 4, 8], outs):
+ self.assertEqual(out.shape,
+ (1, 96 * stride, 56 // stride, 56 // stride))
+
+ # test use_abs_pos_embed = True
+ cfg = deepcopy(self.cfg)
+ cfg['out_scales'] = (0, 1, 2, 3)
+ cfg['use_abs_pos_embed'] = True
+ model = MViT(**cfg)
+ model.init_weights()
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 4)
+ for stride, out in zip([1, 2, 4, 8], outs):
+ self.assertEqual(out.shape,
+ (1, 96 * stride, 56 // stride, 56 // stride))
+
+ # test dynamic inputs shape
+ cfg = deepcopy(self.cfg)
+ cfg['out_scales'] = (0, 1, 2, 3)
+ model = MViT(**cfg)
+ imgs = torch.randn(1, 3, 352, 260)
+ h_resolution = (352 + 2 * 3 - 7) // 4 + 1
+ w_resolution = (260 + 2 * 3 - 7) // 4 + 1
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 4)
+ expect_h = h_resolution
+ expect_w = w_resolution
+ for i, out in enumerate(outs):
+ self.assertEqual(out.shape, (1, 96 * 2**i, expect_h, expect_w))
+ expect_h = (expect_h + 2 * 1 - 3) // 2 + 1
+ expect_w = (expect_w + 2 * 1 - 3) // 2 + 1
+
+ def test_structure(self):
+ # test drop_path_rate decay
+ cfg = deepcopy(self.cfg)
+ cfg['drop_path_rate'] = 0.2
+ model = MViT(**cfg)
+ for i, block in enumerate(model.blocks):
+ expect_prob = 0.2 / (model.num_layers - 1) * i
+ if expect_prob > 0:
+ self.assertAlmostEqual(block.drop_path.drop_prob, expect_prob)
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_poolformer.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_poolformer.py
new file mode 100644
index 0000000000000000000000000000000000000000..8e60b81f68cdf2a0fe4512e3cc3461caf0dd4180
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_poolformer.py
@@ -0,0 +1,143 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from copy import deepcopy
+from unittest import TestCase
+
+import torch
+
+from mmcls.models.backbones import PoolFormer
+from mmcls.models.backbones.poolformer import PoolFormerBlock
+
+
+class TestPoolFormer(TestCase):
+
+ def setUp(self):
+ arch = 's12'
+ self.cfg = dict(arch=arch, drop_path_rate=0.1)
+ self.arch = PoolFormer.arch_settings[arch]
+
+ def test_arch(self):
+ # Test invalid default arch
+ with self.assertRaisesRegex(AssertionError, 'Unavailable arch'):
+ cfg = deepcopy(self.cfg)
+ cfg['arch'] = 'unknown'
+ PoolFormer(**cfg)
+
+ # Test invalid custom arch
+ with self.assertRaisesRegex(AssertionError, 'must have "layers"'):
+ cfg = deepcopy(self.cfg)
+ cfg['arch'] = {
+ 'embed_dims': 96,
+ 'num_heads': [3, 6, 12, 16],
+ }
+ PoolFormer(**cfg)
+
+ # Test custom arch
+ cfg = deepcopy(self.cfg)
+ layers = [2, 2, 4, 2]
+ embed_dims = [6, 12, 6, 12]
+ mlp_ratios = [2, 3, 4, 4]
+ layer_scale_init_value = 1e-4
+ cfg['arch'] = dict(
+ layers=layers,
+ embed_dims=embed_dims,
+ mlp_ratios=mlp_ratios,
+ layer_scale_init_value=layer_scale_init_value,
+ )
+ model = PoolFormer(**cfg)
+ for i, stage in enumerate(model.network):
+ if not isinstance(stage, PoolFormerBlock):
+ continue
+ self.assertEqual(len(stage), layers[i])
+ self.assertEqual(stage[0].mlp.fc1.in_channels, embed_dims[i])
+ self.assertEqual(stage[0].mlp.fc1.out_channels,
+ embed_dims[i] * mlp_ratios[i])
+ self.assertTrue(
+ torch.allclose(stage[0].layer_scale_1,
+ torch.tensor(layer_scale_init_value)))
+ self.assertTrue(
+ torch.allclose(stage[0].layer_scale_2,
+ torch.tensor(layer_scale_init_value)))
+
+ def test_init_weights(self):
+ # test weight init cfg
+ cfg = deepcopy(self.cfg)
+ cfg['init_cfg'] = [
+ dict(
+ type='Kaiming',
+ layer='Conv2d',
+ mode='fan_in',
+ nonlinearity='linear')
+ ]
+ model = PoolFormer(**cfg)
+ ori_weight = model.patch_embed.proj.weight.clone().detach()
+
+ model.init_weights()
+ initialized_weight = model.patch_embed.proj.weight
+ self.assertFalse(torch.allclose(ori_weight, initialized_weight))
+
+ def test_forward(self):
+ imgs = torch.randn(1, 3, 224, 224)
+
+ cfg = deepcopy(self.cfg)
+ model = PoolFormer(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ feat = outs[-1]
+ self.assertEqual(feat.shape, (1, 512, 7, 7))
+
+ # test multiple output indices
+ cfg = deepcopy(self.cfg)
+ cfg['out_indices'] = (0, 2, 4, 6)
+ model = PoolFormer(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 4)
+ for dim, stride, out in zip(self.arch['embed_dims'], [1, 2, 4, 8],
+ outs):
+ self.assertEqual(out.shape, (1, dim, 56 // stride, 56 // stride))
+
+ def test_structure(self):
+ # test drop_path_rate decay
+ cfg = deepcopy(self.cfg)
+ cfg['drop_path_rate'] = 0.2
+ model = PoolFormer(**cfg)
+ layers = self.arch['layers']
+ for i, block in enumerate(model.network):
+ expect_prob = 0.2 / (sum(layers) - 1) * i
+ if hasattr(block, 'drop_path'):
+ if expect_prob == 0:
+ self.assertIsInstance(block.drop_path, torch.nn.Identity)
+ else:
+ self.assertAlmostEqual(block.drop_path.drop_prob,
+ expect_prob)
+
+ # test with first stage frozen.
+ cfg = deepcopy(self.cfg)
+ frozen_stages = 1
+ cfg['frozen_stages'] = frozen_stages
+ cfg['out_indices'] = (0, 2, 4, 6)
+ model = PoolFormer(**cfg)
+ model.init_weights()
+ model.train()
+
+ # the patch_embed and first stage should not require grad.
+ self.assertFalse(model.patch_embed.training)
+ for param in model.patch_embed.parameters():
+ self.assertFalse(param.requires_grad)
+ for i in range(frozen_stages):
+ module = model.network[i]
+ for param in module.parameters():
+ self.assertFalse(param.requires_grad)
+ for param in model.norm0.parameters():
+ self.assertFalse(param.requires_grad)
+
+ # the second stage should require grad.
+ for i in range(frozen_stages + 1, 7):
+ module = model.network[i]
+ for param in module.parameters():
+ self.assertTrue(param.requires_grad)
+ if hasattr(model, f'norm{i}'):
+ norm = getattr(model, f'norm{i}')
+ for param in norm.parameters():
+ self.assertTrue(param.requires_grad)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_regnet.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_regnet.py
similarity index 90%
rename from openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_regnet.py
rename to openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_regnet.py
index 465e5033b6d233e0c07cb3135d86fda0188e5e16..67de1c8733938dba82c14163e65b686022575a42 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_regnet.py
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_regnet.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import pytest
import torch
@@ -44,8 +45,9 @@ def test_regnet_backbone(arch_name, arch, out_channels):
imgs = torch.randn(1, 3, 224, 224)
feat = model(imgs)
- assert isinstance(feat, torch.Tensor)
- assert feat.shape == (1, out_channels[-1], 7, 7)
+ assert len(feat) == 1
+ assert isinstance(feat[0], torch.Tensor)
+ assert feat[0].shape == (1, out_channels[-1], 7, 7)
# output feature map of all stages
model = RegNet(arch_name, out_indices=(0, 1, 2, 3))
@@ -69,8 +71,9 @@ def test_custom_arch(arch_name, arch, out_channels):
imgs = torch.randn(1, 3, 224, 224)
feat = model(imgs)
- assert isinstance(feat, torch.Tensor)
- assert feat.shape == (1, out_channels[-1], 7, 7)
+ assert len(feat) == 1
+ assert isinstance(feat[0], torch.Tensor)
+ assert feat[0].shape == (1, out_channels[-1], 7, 7)
# output feature map of all stages
model = RegNet(arch, out_indices=(0, 1, 2, 3))
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_repmlp.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_repmlp.py
new file mode 100644
index 0000000000000000000000000000000000000000..dcab2cfbf50a09f11fed549dd1b088c5f03a46a4
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_repmlp.py
@@ -0,0 +1,172 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import os
+import tempfile
+from copy import deepcopy
+from unittest import TestCase
+
+import torch
+from mmcv.runner import load_checkpoint, save_checkpoint
+
+from mmcls.models.backbones import RepMLPNet
+
+
+class TestRepMLP(TestCase):
+
+ def setUp(self):
+ # default model setting
+ self.cfg = dict(
+ arch='b',
+ img_size=224,
+ out_indices=(3, ),
+ reparam_conv_kernels=(1, 3),
+ final_norm=True)
+
+ # default model setting and output stage channels
+ self.model_forward_settings = [
+ dict(model_name='B', out_sizes=(96, 192, 384, 768)),
+ ]
+
+ # temp ckpt path
+ self.ckpt_path = os.path.join(tempfile.gettempdir(), 'ckpt.pth')
+
+ def test_arch(self):
+ # Test invalid arch data type
+ with self.assertRaisesRegex(AssertionError, 'arch needs a dict'):
+ cfg = deepcopy(self.cfg)
+ cfg['arch'] = [96, 192, 384, 768]
+ RepMLPNet(**cfg)
+
+ # Test invalid default arch
+ with self.assertRaisesRegex(AssertionError, 'not in default archs'):
+ cfg = deepcopy(self.cfg)
+ cfg['arch'] = 'A'
+ RepMLPNet(**cfg)
+
+ # Test invalid custom arch
+ with self.assertRaisesRegex(AssertionError, 'Custom arch needs'):
+ cfg = deepcopy(self.cfg)
+ cfg['arch'] = {
+ 'channels': [96, 192, 384, 768],
+ 'depths': [2, 2, 12, 2]
+ }
+ RepMLPNet(**cfg)
+
+ # test len(arch['depths']) equals to len(arch['channels'])
+ # equals to len(arch['sharesets_nums'])
+ with self.assertRaisesRegex(AssertionError, 'Length of setting'):
+ cfg = deepcopy(self.cfg)
+ cfg['arch'] = {
+ 'channels': [96, 192, 384, 768],
+ 'depths': [2, 2, 12, 2],
+ 'sharesets_nums': [1, 4, 32]
+ }
+ RepMLPNet(**cfg)
+
+ # Test custom arch
+ cfg = deepcopy(self.cfg)
+ channels = [96, 192, 384, 768]
+ depths = [2, 2, 12, 2]
+ sharesets_nums = [1, 4, 32, 128]
+ cfg['arch'] = {
+ 'channels': channels,
+ 'depths': depths,
+ 'sharesets_nums': sharesets_nums
+ }
+ cfg['out_indices'] = (0, 1, 2, 3)
+ model = RepMLPNet(**cfg)
+ for i, stage in enumerate(model.stages):
+ self.assertEqual(len(stage), depths[i])
+ self.assertEqual(stage[0].repmlp_block.channels, channels[i])
+ self.assertEqual(stage[0].repmlp_block.deploy, False)
+ self.assertEqual(stage[0].repmlp_block.num_sharesets,
+ sharesets_nums[i])
+
+ def test_init(self):
+ # test weight init cfg
+ cfg = deepcopy(self.cfg)
+ cfg['init_cfg'] = [
+ dict(
+ type='Kaiming',
+ layer='Conv2d',
+ mode='fan_in',
+ nonlinearity='linear')
+ ]
+ model = RepMLPNet(**cfg)
+ ori_weight = model.patch_embed.projection.weight.clone().detach()
+
+ model.init_weights()
+ initialized_weight = model.patch_embed.projection.weight
+ self.assertFalse(torch.allclose(ori_weight, initialized_weight))
+
+ def test_forward(self):
+ imgs = torch.randn(1, 3, 224, 224)
+ cfg = deepcopy(self.cfg)
+ model = RepMLPNet(**cfg)
+ feat = model(imgs)
+ self.assertTrue(isinstance(feat, tuple))
+ self.assertEqual(len(feat), 1)
+ self.assertTrue(isinstance(feat[0], torch.Tensor))
+ self.assertEqual(feat[0].shape, torch.Size((1, 768, 7, 7)))
+
+ imgs = torch.randn(1, 3, 256, 256)
+ with self.assertRaisesRegex(AssertionError, "doesn't support dynamic"):
+ model(imgs)
+
+ # Test RepMLPNet model forward
+ for model_test_setting in self.model_forward_settings:
+ model = RepMLPNet(
+ model_test_setting['model_name'],
+ out_indices=(0, 1, 2, 3),
+ final_norm=False)
+ model.init_weights()
+
+ model.train()
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ self.assertEqual(
+ feat[0].shape,
+ torch.Size((1, model_test_setting['out_sizes'][1], 28, 28)))
+ self.assertEqual(
+ feat[1].shape,
+ torch.Size((1, model_test_setting['out_sizes'][2], 14, 14)))
+ self.assertEqual(
+ feat[2].shape,
+ torch.Size((1, model_test_setting['out_sizes'][3], 7, 7)))
+ self.assertEqual(
+ feat[3].shape,
+ torch.Size((1, model_test_setting['out_sizes'][3], 7, 7)))
+
+ def test_deploy_(self):
+ # Test output before and load from deploy checkpoint
+ imgs = torch.randn((1, 3, 224, 224))
+ cfg = dict(
+ arch='b', out_indices=(
+ 1,
+ 3,
+ ), reparam_conv_kernels=(1, 3, 5))
+ model = RepMLPNet(**cfg)
+
+ model.eval()
+ feats = model(imgs)
+ model.switch_to_deploy()
+ for m in model.modules():
+ if hasattr(m, 'deploy'):
+ self.assertTrue(m.deploy)
+ model.eval()
+ feats_ = model(imgs)
+ assert len(feats) == len(feats_)
+ for i in range(len(feats)):
+ self.assertTrue(
+ torch.allclose(
+ feats[i].sum(), feats_[i].sum(), rtol=0.1, atol=0.1))
+
+ cfg['deploy'] = True
+ model_deploy = RepMLPNet(**cfg)
+ model_deploy.eval()
+ save_checkpoint(model, self.ckpt_path)
+ load_checkpoint(model_deploy, self.ckpt_path, strict=True)
+ feats__ = model_deploy(imgs)
+
+ assert len(feats_) == len(feats__)
+ for i in range(len(feats)):
+ self.assertTrue(torch.allclose(feats__[i], feats_[i]))
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_repvgg.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_repvgg.py
new file mode 100644
index 0000000000000000000000000000000000000000..beecdffc906f08b4cddc7316f4ebee8c5b662f68
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_repvgg.py
@@ -0,0 +1,350 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import os
+import tempfile
+
+import pytest
+import torch
+from mmcv.runner import load_checkpoint, save_checkpoint
+from torch import nn
+from torch.nn.modules import GroupNorm
+from torch.nn.modules.batchnorm import _BatchNorm
+
+from mmcls.models.backbones import RepVGG
+from mmcls.models.backbones.repvgg import RepVGGBlock
+from mmcls.models.utils import SELayer
+
+
+def check_norm_state(modules, train_state):
+ """Check if norm layer is in correct train state."""
+ for mod in modules:
+ if isinstance(mod, _BatchNorm):
+ if mod.training != train_state:
+ return False
+ return True
+
+
+def is_norm(modules):
+ """Check if is one of the norms."""
+ if isinstance(modules, (GroupNorm, _BatchNorm)):
+ return True
+ return False
+
+
+def is_repvgg_block(modules):
+ if isinstance(modules, RepVGGBlock):
+ return True
+ return False
+
+
+def test_repvgg_repvggblock():
+ # Test RepVGGBlock with in_channels != out_channels, stride = 1
+ block = RepVGGBlock(5, 10, stride=1)
+ block.eval()
+ x = torch.randn(1, 5, 16, 16)
+ x_out_not_deploy = block(x)
+ assert block.branch_norm is None
+ assert not hasattr(block, 'branch_reparam')
+ assert hasattr(block, 'branch_1x1')
+ assert hasattr(block, 'branch_3x3')
+ assert hasattr(block, 'branch_norm')
+ assert block.se_cfg is None
+ assert x_out_not_deploy.shape == torch.Size((1, 10, 16, 16))
+ block.switch_to_deploy()
+ assert block.deploy is True
+ x_out_deploy = block(x)
+ assert x_out_deploy.shape == torch.Size((1, 10, 16, 16))
+ assert torch.allclose(x_out_not_deploy, x_out_deploy, atol=1e-5, rtol=1e-4)
+
+ # Test RepVGGBlock with in_channels == out_channels, stride = 1
+ block = RepVGGBlock(12, 12, stride=1)
+ block.eval()
+ x = torch.randn(1, 12, 8, 8)
+ x_out_not_deploy = block(x)
+ assert isinstance(block.branch_norm, nn.BatchNorm2d)
+ assert not hasattr(block, 'branch_reparam')
+ assert x_out_not_deploy.shape == torch.Size((1, 12, 8, 8))
+ block.switch_to_deploy()
+ assert block.deploy is True
+ x_out_deploy = block(x)
+ assert x_out_deploy.shape == torch.Size((1, 12, 8, 8))
+ assert torch.allclose(x_out_not_deploy, x_out_deploy, atol=1e-5, rtol=1e-4)
+
+ # Test RepVGGBlock with in_channels == out_channels, stride = 2
+ block = RepVGGBlock(16, 16, stride=2)
+ block.eval()
+ x = torch.randn(1, 16, 8, 8)
+ x_out_not_deploy = block(x)
+ assert block.branch_norm is None
+ assert x_out_not_deploy.shape == torch.Size((1, 16, 4, 4))
+ block.switch_to_deploy()
+ assert block.deploy is True
+ x_out_deploy = block(x)
+ assert x_out_deploy.shape == torch.Size((1, 16, 4, 4))
+ assert torch.allclose(x_out_not_deploy, x_out_deploy, atol=1e-5, rtol=1e-4)
+
+ # Test RepVGGBlock with padding == dilation == 2
+ block = RepVGGBlock(14, 14, stride=1, padding=2, dilation=2)
+ block.eval()
+ x = torch.randn(1, 14, 16, 16)
+ x_out_not_deploy = block(x)
+ assert isinstance(block.branch_norm, nn.BatchNorm2d)
+ assert x_out_not_deploy.shape == torch.Size((1, 14, 16, 16))
+ block.switch_to_deploy()
+ assert block.deploy is True
+ x_out_deploy = block(x)
+ assert x_out_deploy.shape == torch.Size((1, 14, 16, 16))
+ assert torch.allclose(x_out_not_deploy, x_out_deploy, atol=1e-5, rtol=1e-4)
+
+ # Test RepVGGBlock with groups = 2
+ block = RepVGGBlock(4, 4, stride=1, groups=2)
+ block.eval()
+ x = torch.randn(1, 4, 5, 6)
+ x_out_not_deploy = block(x)
+ assert x_out_not_deploy.shape == torch.Size((1, 4, 5, 6))
+ block.switch_to_deploy()
+ assert block.deploy is True
+ x_out_deploy = block(x)
+ assert x_out_deploy.shape == torch.Size((1, 4, 5, 6))
+ assert torch.allclose(x_out_not_deploy, x_out_deploy, atol=1e-5, rtol=1e-4)
+
+ # Test RepVGGBlock with se
+ se_cfg = dict(ratio=4, divisor=1)
+ block = RepVGGBlock(18, 18, stride=1, se_cfg=se_cfg)
+ block.train()
+ x = torch.randn(1, 18, 5, 5)
+ x_out_not_deploy = block(x)
+ assert isinstance(block.se_layer, SELayer)
+ assert x_out_not_deploy.shape == torch.Size((1, 18, 5, 5))
+
+ # Test RepVGGBlock with checkpoint forward
+ block = RepVGGBlock(24, 24, stride=1, with_cp=True)
+ assert block.with_cp
+ x = torch.randn(1, 24, 7, 7)
+ x_out = block(x)
+ assert x_out.shape == torch.Size((1, 24, 7, 7))
+
+ # Test RepVGGBlock with deploy == True
+ block = RepVGGBlock(8, 8, stride=1, deploy=True)
+ assert isinstance(block.branch_reparam, nn.Conv2d)
+ assert not hasattr(block, 'branch_3x3')
+ assert not hasattr(block, 'branch_1x1')
+ assert not hasattr(block, 'branch_norm')
+ x = torch.randn(1, 8, 16, 16)
+ x_out = block(x)
+ assert x_out.shape == torch.Size((1, 8, 16, 16))
+
+
+def test_repvgg_backbone():
+ with pytest.raises(TypeError):
+ # arch must be str or dict
+ RepVGG(arch=[4, 6, 16, 1])
+
+ with pytest.raises(AssertionError):
+ # arch must in arch_settings
+ RepVGG(arch='A3')
+
+ with pytest.raises(KeyError):
+ # arch must have num_blocks and width_factor
+ arch = dict(num_blocks=[2, 4, 14, 1])
+ RepVGG(arch=arch)
+
+ # len(arch['num_blocks']) == len(arch['width_factor'])
+ # == len(strides) == len(dilations)
+ with pytest.raises(AssertionError):
+ arch = dict(num_blocks=[2, 4, 14, 1], width_factor=[0.75, 0.75, 0.75])
+ RepVGG(arch=arch)
+
+ # len(strides) must equal to 4
+ with pytest.raises(AssertionError):
+ RepVGG('A0', strides=(1, 1, 1))
+
+ # len(dilations) must equal to 4
+ with pytest.raises(AssertionError):
+ RepVGG('A0', strides=(1, 1, 1, 1), dilations=(1, 1, 2))
+
+ # max(out_indices) < len(arch['num_blocks'])
+ with pytest.raises(AssertionError):
+ RepVGG('A0', out_indices=(5, ))
+
+ # max(arch['group_idx'].keys()) <= sum(arch['num_blocks'])
+ with pytest.raises(AssertionError):
+ arch = dict(
+ num_blocks=[2, 4, 14, 1],
+ width_factor=[0.75, 0.75, 0.75],
+ group_idx={22: 2})
+ RepVGG(arch=arch)
+
+ # Test RepVGG norm state
+ model = RepVGG('A0')
+ model.train()
+ assert check_norm_state(model.modules(), True)
+
+ # Test RepVGG with first stage frozen
+ frozen_stages = 1
+ model = RepVGG('A0', frozen_stages=frozen_stages)
+ model.train()
+ for param in model.stem.parameters():
+ assert param.requires_grad is False
+ for i in range(0, frozen_stages):
+ stage_name = model.stages[i]
+ stage = model.__getattr__(stage_name)
+ for mod in stage:
+ if isinstance(mod, _BatchNorm):
+ assert mod.training is False
+ for param in stage.parameters():
+ assert param.requires_grad is False
+
+ # Test RepVGG with norm_eval
+ model = RepVGG('A0', norm_eval=True)
+ model.train()
+ assert check_norm_state(model.modules(), False)
+
+ # Test RepVGG forward with layer 3 forward
+ model = RepVGG('A0', out_indices=(3, ))
+ model.init_weights()
+ model.eval()
+
+ for m in model.modules():
+ if is_norm(m):
+ assert isinstance(m, _BatchNorm)
+
+ imgs = torch.randn(1, 3, 32, 32)
+ feat = model(imgs)
+ assert isinstance(feat, tuple)
+ assert len(feat) == 1
+ assert isinstance(feat[0], torch.Tensor)
+ assert feat[0].shape == torch.Size((1, 1280, 1, 1))
+
+ # Test with custom arch
+ cfg = dict(
+ num_blocks=[3, 5, 7, 3],
+ width_factor=[1, 1, 1, 1],
+ group_layer_map=None,
+ se_cfg=None,
+ stem_channels=16)
+ model = RepVGG(arch=cfg, out_indices=(3, ))
+ model.eval()
+ assert model.stem.out_channels == min(16, 64 * 1)
+
+ imgs = torch.randn(1, 3, 32, 32)
+ feat = model(imgs)
+ assert isinstance(feat, tuple)
+ assert len(feat) == 1
+ assert isinstance(feat[0], torch.Tensor)
+ assert feat[0].shape == torch.Size((1, 512, 1, 1))
+
+ # Test RepVGG forward
+ model_test_settings = [
+ dict(model_name='A0', out_sizes=(48, 96, 192, 1280)),
+ dict(model_name='A1', out_sizes=(64, 128, 256, 1280)),
+ dict(model_name='A2', out_sizes=(96, 192, 384, 1408)),
+ dict(model_name='B0', out_sizes=(64, 128, 256, 1280)),
+ dict(model_name='B1', out_sizes=(128, 256, 512, 2048)),
+ dict(model_name='B1g2', out_sizes=(128, 256, 512, 2048)),
+ dict(model_name='B1g4', out_sizes=(128, 256, 512, 2048)),
+ dict(model_name='B2', out_sizes=(160, 320, 640, 2560)),
+ dict(model_name='B2g2', out_sizes=(160, 320, 640, 2560)),
+ dict(model_name='B2g4', out_sizes=(160, 320, 640, 2560)),
+ dict(model_name='B3', out_sizes=(192, 384, 768, 2560)),
+ dict(model_name='B3g2', out_sizes=(192, 384, 768, 2560)),
+ dict(model_name='B3g4', out_sizes=(192, 384, 768, 2560)),
+ dict(model_name='D2se', out_sizes=(160, 320, 640, 2560))
+ ]
+
+ choose_models = ['A0', 'B1', 'B1g2']
+ # Test RepVGG model forward
+ for model_test_setting in model_test_settings:
+ if model_test_setting['model_name'] not in choose_models:
+ continue
+ model = RepVGG(
+ model_test_setting['model_name'], out_indices=(0, 1, 2, 3))
+ model.init_weights()
+ model.eval()
+
+ # Test Norm
+ for m in model.modules():
+ if is_norm(m):
+ assert isinstance(m, _BatchNorm)
+
+ imgs = torch.randn(1, 3, 32, 32)
+ feat = model(imgs)
+ assert feat[0].shape == torch.Size(
+ (1, model_test_setting['out_sizes'][0], 8, 8))
+ assert feat[1].shape == torch.Size(
+ (1, model_test_setting['out_sizes'][1], 4, 4))
+ assert feat[2].shape == torch.Size(
+ (1, model_test_setting['out_sizes'][2], 2, 2))
+ assert feat[3].shape == torch.Size(
+ (1, model_test_setting['out_sizes'][3], 1, 1))
+
+ # Test eval of "train" mode and "deploy" mode
+ gap = nn.AdaptiveAvgPool2d(output_size=(1))
+ fc = nn.Linear(model_test_setting['out_sizes'][3], 10)
+ model.eval()
+ feat = model(imgs)
+ pred = fc(gap(feat[3]).flatten(1))
+ model.switch_to_deploy()
+ for m in model.modules():
+ if isinstance(m, RepVGGBlock):
+ assert m.deploy is True
+ feat_deploy = model(imgs)
+ pred_deploy = fc(gap(feat_deploy[3]).flatten(1))
+ for i in range(4):
+ torch.allclose(feat[i], feat_deploy[i])
+ torch.allclose(pred, pred_deploy)
+
+ # Test RepVGG forward with add_ppf
+ model = RepVGG('A0', out_indices=(3, ), add_ppf=True)
+ model.init_weights()
+ model.train()
+
+ for m in model.modules():
+ if is_norm(m):
+ assert isinstance(m, _BatchNorm)
+
+ imgs = torch.randn(1, 3, 64, 64)
+ feat = model(imgs)
+ assert isinstance(feat, tuple)
+ assert len(feat) == 1
+ assert isinstance(feat[0], torch.Tensor)
+ assert feat[0].shape == torch.Size((1, 1280, 2, 2))
+
+ # Test RepVGG forward with 'stem_channels' not in arch
+ arch = dict(
+ num_blocks=[2, 4, 14, 1],
+ width_factor=[0.75, 0.75, 0.75, 2.5],
+ group_layer_map=None,
+ se_cfg=None)
+ model = RepVGG(arch, add_ppf=True)
+ model.stem.in_channels = min(64, 64 * 0.75)
+ model.init_weights()
+ model.train()
+
+ for m in model.modules():
+ if is_norm(m):
+ assert isinstance(m, _BatchNorm)
+
+ imgs = torch.randn(1, 3, 64, 64)
+ feat = model(imgs)
+ assert isinstance(feat, tuple)
+ assert len(feat) == 1
+ assert isinstance(feat[0], torch.Tensor)
+ assert feat[0].shape == torch.Size((1, 1280, 2, 2))
+
+
+def test_repvgg_load():
+ # Test output before and load from deploy checkpoint
+ model = RepVGG('A1', out_indices=(0, 1, 2, 3))
+ inputs = torch.randn((1, 3, 32, 32))
+ ckpt_path = os.path.join(tempfile.gettempdir(), 'ckpt.pth')
+ model.switch_to_deploy()
+ model.eval()
+ outputs = model(inputs)
+
+ model_deploy = RepVGG('A1', out_indices=(0, 1, 2, 3), deploy=True)
+ save_checkpoint(model, ckpt_path)
+ load_checkpoint(model_deploy, ckpt_path, strict=True)
+
+ outputs_load = model_deploy(inputs)
+ for feat, feat_load in zip(outputs, outputs_load):
+ assert torch.allclose(feat, feat_load)
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_res2net.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_res2net.py
new file mode 100644
index 0000000000000000000000000000000000000000..173d3e628e03a08e61276f8bd65fd778d9c1ca94
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_res2net.py
@@ -0,0 +1,71 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import pytest
+import torch
+from mmcv.utils.parrots_wrapper import _BatchNorm
+
+from mmcls.models.backbones import Res2Net
+
+
+def check_norm_state(modules, train_state):
+ """Check if norm layer is in correct train state."""
+ for mod in modules:
+ if isinstance(mod, _BatchNorm):
+ if mod.training != train_state:
+ return False
+ return True
+
+
+def test_resnet_cifar():
+ # Only support depth 50, 101 and 152
+ with pytest.raises(KeyError):
+ Res2Net(depth=18)
+
+ # test the feature map size when depth is 50
+ # and deep_stem=True, avg_down=True
+ model = Res2Net(
+ depth=50, out_indices=(0, 1, 2, 3), deep_stem=True, avg_down=True)
+ model.init_weights()
+ model.train()
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model.stem(imgs)
+ assert feat.shape == (1, 64, 112, 112)
+ feat = model(imgs)
+ assert len(feat) == 4
+ assert feat[0].shape == (1, 256, 56, 56)
+ assert feat[1].shape == (1, 512, 28, 28)
+ assert feat[2].shape == (1, 1024, 14, 14)
+ assert feat[3].shape == (1, 2048, 7, 7)
+
+ # test the feature map size when depth is 101
+ # and deep_stem=False, avg_down=False
+ model = Res2Net(
+ depth=101, out_indices=(0, 1, 2, 3), deep_stem=False, avg_down=False)
+ model.init_weights()
+ model.train()
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model.conv1(imgs)
+ assert feat.shape == (1, 64, 112, 112)
+ feat = model(imgs)
+ assert len(feat) == 4
+ assert feat[0].shape == (1, 256, 56, 56)
+ assert feat[1].shape == (1, 512, 28, 28)
+ assert feat[2].shape == (1, 1024, 14, 14)
+ assert feat[3].shape == (1, 2048, 7, 7)
+
+ # Test Res2Net with first stage frozen
+ frozen_stages = 1
+ model = Res2Net(depth=50, frozen_stages=frozen_stages, deep_stem=False)
+ model.init_weights()
+ model.train()
+ assert check_norm_state([model.norm1], False)
+ for param in model.conv1.parameters():
+ assert param.requires_grad is False
+ for i in range(1, frozen_stages + 1):
+ layer = getattr(model, f'layer{i}')
+ for mod in layer.modules():
+ if isinstance(mod, _BatchNorm):
+ assert mod.training is False
+ for param in layer.parameters():
+ assert param.requires_grad is False
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_resnest.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_resnest.py
similarity index 96%
rename from openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_resnest.py
rename to openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_resnest.py
index 41d82f1b4f73186a0182d2c963da98d70ee68fda..7a0b250ddbba83615303a4fc6a26e23c272850fc 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_resnest.py
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_resnest.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import pytest
import torch
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_resnet.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_resnet.py
similarity index 91%
rename from openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_resnet.py
rename to openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_resnet.py
index 5adc5d4e51048e8b5e5e3f5b108be1c7c4fba3a8..8ff8bc8fe1ac6e12a12b0f0c6e7c83da46e76f27 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_resnet.py
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_resnet.py
@@ -1,10 +1,11 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import pytest
import torch
import torch.nn as nn
from mmcv.cnn import ConvModule
from mmcv.utils.parrots_wrapper import _BatchNorm
-from mmcls.models.backbones import ResNet, ResNetV1d
+from mmcls.models.backbones import ResNet, ResNetV1c, ResNetV1d
from mmcls.models.backbones.resnet import (BasicBlock, Bottleneck, ResLayer,
get_expansion)
@@ -455,6 +456,19 @@ def test_resnet():
assert feat[2].shape == (1, 1024, 14, 14)
assert feat[3].shape == (1, 2048, 7, 7)
+ # Test ResNet50 with DropPath forward
+ model = ResNet(50, out_indices=(0, 1, 2, 3), drop_path_rate=0.5)
+ model.init_weights()
+ model.train()
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 4
+ assert feat[0].shape == (1, 256, 56, 56)
+ assert feat[1].shape == (1, 512, 28, 28)
+ assert feat[2].shape == (1, 1024, 14, 14)
+ assert feat[3].shape == (1, 2048, 7, 7)
+
# Test ResNet50 with layers 1, 2, 3 out forward
model = ResNet(50, out_indices=(0, 1, 2))
model.init_weights()
@@ -474,7 +488,8 @@ def test_resnet():
imgs = torch.randn(1, 3, 224, 224)
feat = model(imgs)
- assert feat.shape == (1, 2048, 7, 7)
+ assert len(feat) == 1
+ assert feat[0].shape == (1, 2048, 7, 7)
# Test ResNet50 with checkpoint forward
model = ResNet(50, out_indices=(0, 1, 2, 3), with_cp=True)
@@ -511,6 +526,45 @@ def test_resnet():
assert not all_zeros(m.norm2)
+def test_resnet_v1c():
+ model = ResNetV1c(depth=50, out_indices=(0, 1, 2, 3))
+ model.init_weights()
+ model.train()
+
+ assert len(model.stem) == 3
+ for i in range(3):
+ assert isinstance(model.stem[i], ConvModule)
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model.stem(imgs)
+ assert feat.shape == (1, 64, 112, 112)
+ feat = model(imgs)
+ assert len(feat) == 4
+ assert feat[0].shape == (1, 256, 56, 56)
+ assert feat[1].shape == (1, 512, 28, 28)
+ assert feat[2].shape == (1, 1024, 14, 14)
+ assert feat[3].shape == (1, 2048, 7, 7)
+
+ # Test ResNet50V1d with first stage frozen
+ frozen_stages = 1
+ model = ResNetV1d(depth=50, frozen_stages=frozen_stages)
+ assert len(model.stem) == 3
+ for i in range(3):
+ assert isinstance(model.stem[i], ConvModule)
+ model.init_weights()
+ model.train()
+ check_norm_state(model.stem, False)
+ for param in model.stem.parameters():
+ assert param.requires_grad is False
+ for i in range(1, frozen_stages + 1):
+ layer = getattr(model, f'layer{i}')
+ for mod in layer.modules():
+ if isinstance(mod, _BatchNorm):
+ assert mod.training is False
+ for param in layer.parameters():
+ assert param.requires_grad is False
+
+
def test_resnet_v1d():
model = ResNetV1d(depth=50, out_indices=(0, 1, 2, 3))
model.init_weights()
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_resnet_cifar.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_resnet_cifar.py
similarity index 97%
rename from openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_resnet_cifar.py
rename to openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_resnet_cifar.py
index 533c2e05a35a763b40d2045c2dc864feb4b4311e..af7bba61ec507186ea5780c5efd2e9d71d413191 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_resnet_cifar.py
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_resnet_cifar.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import pytest
import torch
from mmcv.utils.parrots_wrapper import _BatchNorm
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_resnext.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_resnext.py
similarity index 93%
rename from openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_resnext.py
rename to openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_resnext.py
index ee9de0bd906308223e41f0d4eb4d90e68e95b782..4ee15f93305369650b997cf22c90027ae0b647a0 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_resnext.py
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_resnext.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import pytest
import torch
@@ -56,4 +57,5 @@ def test_resnext():
imgs = torch.randn(1, 3, 224, 224)
feat = model(imgs)
- assert feat.shape == torch.Size([1, 2048, 7, 7])
+ assert len(feat) == 1
+ assert feat[0].shape == torch.Size([1, 2048, 7, 7])
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_seresnet.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_seresnet.py
similarity index 98%
rename from openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_seresnet.py
rename to openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_seresnet.py
index 557270b714f40d3f413a6362d6a5eb7b332f6807..32670209cffb872c6d10579c827046116700a73e 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_seresnet.py
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_seresnet.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import pytest
import torch
from torch.nn.modules import AvgPool2d
@@ -210,7 +211,8 @@ def test_seresnet():
imgs = torch.randn(1, 3, 224, 224)
feat = model(imgs)
- assert feat.shape == torch.Size([1, 2048, 7, 7])
+ assert len(feat) == 1
+ assert feat[0].shape == torch.Size([1, 2048, 7, 7])
# Test SEResNet50 with checkpoint forward
model = SEResNet(50, out_indices=(0, 1, 2, 3), with_cp=True)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_seresnext.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_seresnext.py
similarity index 94%
rename from openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_seresnext.py
rename to openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_seresnext.py
index bb5e49487000027fdc2047d8cd06f8babae24c47..2431c0708de9930673753bb5fd60e268a6b8c22d 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_seresnext.py
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_seresnext.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import pytest
import torch
@@ -69,4 +70,5 @@ def test_seresnext():
imgs = torch.randn(1, 3, 224, 224)
feat = model(imgs)
- assert feat.shape == torch.Size([1, 2048, 7, 7])
+ assert len(feat) == 1
+ assert feat[0].shape == torch.Size([1, 2048, 7, 7])
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_shufflenet_v1.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_shufflenet_v1.py
similarity index 97%
rename from openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_shufflenet_v1.py
rename to openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_shufflenet_v1.py
index 5ef267e6f281b71a60a50bd7b809ac1aa6e00718..97beee7abb73aa74e09a9c3db3d3bef1fbac7cb5 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_shufflenet_v1.py
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_shufflenet_v1.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import pytest
import torch
from torch.nn.modules import GroupNorm
@@ -227,8 +228,9 @@ def test_shufflenetv1_backbone():
imgs = torch.randn(1, 3, 224, 224)
feat = model(imgs)
- assert isinstance(feat, torch.Tensor)
- assert feat.shape == torch.Size((1, 960, 7, 7))
+ assert len(feat) == 1
+ assert isinstance(feat[0], torch.Tensor)
+ assert feat[0].shape == torch.Size((1, 960, 7, 7))
# Test ShuffleNetV1 forward with checkpoint forward
model = ShuffleNetV1(groups=3, with_cp=True)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_shufflenet_v2.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_shufflenet_v2.py
similarity index 97%
rename from openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_shufflenet_v2.py
rename to openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_shufflenet_v2.py
index ee564faffc0b5338d0dc61cc4e97b54fc56ae1d8..b7ab495552bc7f931f24ed398ad920f332d75357 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_shufflenet_v2.py
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_shufflenet_v2.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import pytest
import torch
from torch.nn.modules import GroupNorm
@@ -178,8 +179,9 @@ def test_shufflenetv2_backbone():
imgs = torch.randn(1, 3, 224, 224)
feat = model(imgs)
- assert isinstance(feat, torch.Tensor)
- assert feat.shape == torch.Size((1, 464, 7, 7))
+ assert len(feat) == 1
+ assert isinstance(feat[0], torch.Tensor)
+ assert feat[0].shape == torch.Size((1, 464, 7, 7))
# Test ShuffleNetV2 forward with layers 1 2 forward
model = ShuffleNetV2(widen_factor=1.0, out_indices=(1, 2))
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_swin_transformer.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_swin_transformer.py
new file mode 100644
index 0000000000000000000000000000000000000000..33947304bd8a0346c50faf00890cd921da3df4c2
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_swin_transformer.py
@@ -0,0 +1,255 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import math
+import os
+import tempfile
+from copy import deepcopy
+from itertools import chain
+from unittest import TestCase
+
+import torch
+from mmcv.runner import load_checkpoint, save_checkpoint
+from mmcv.utils.parrots_wrapper import _BatchNorm
+
+from mmcls.models.backbones import SwinTransformer
+from mmcls.models.backbones.swin_transformer import SwinBlock
+from .utils import timm_resize_pos_embed
+
+
+def check_norm_state(modules, train_state):
+ """Check if norm layer is in correct train state."""
+ for mod in modules:
+ if isinstance(mod, _BatchNorm):
+ if mod.training != train_state:
+ return False
+ return True
+
+
+class TestSwinTransformer(TestCase):
+
+ def setUp(self):
+ self.cfg = dict(
+ arch='b', img_size=224, patch_size=4, drop_path_rate=0.1)
+
+ def test_arch(self):
+ # Test invalid default arch
+ with self.assertRaisesRegex(AssertionError, 'not in default archs'):
+ cfg = deepcopy(self.cfg)
+ cfg['arch'] = 'unknown'
+ SwinTransformer(**cfg)
+
+ # Test invalid custom arch
+ with self.assertRaisesRegex(AssertionError, 'Custom arch needs'):
+ cfg = deepcopy(self.cfg)
+ cfg['arch'] = {
+ 'embed_dims': 96,
+ 'num_heads': [3, 6, 12, 16],
+ }
+ SwinTransformer(**cfg)
+
+ # Test custom arch
+ cfg = deepcopy(self.cfg)
+ depths = [2, 2, 4, 2]
+ num_heads = [6, 12, 6, 12]
+ cfg['arch'] = {
+ 'embed_dims': 256,
+ 'depths': depths,
+ 'num_heads': num_heads
+ }
+ model = SwinTransformer(**cfg)
+ for i, stage in enumerate(model.stages):
+ self.assertEqual(stage.embed_dims, 256 * (2**i))
+ self.assertEqual(len(stage.blocks), depths[i])
+ self.assertEqual(stage.blocks[0].attn.w_msa.num_heads,
+ num_heads[i])
+
+ def test_init_weights(self):
+ # test weight init cfg
+ cfg = deepcopy(self.cfg)
+ cfg['use_abs_pos_embed'] = True
+ cfg['init_cfg'] = [
+ dict(
+ type='Kaiming',
+ layer='Conv2d',
+ mode='fan_in',
+ nonlinearity='linear')
+ ]
+ model = SwinTransformer(**cfg)
+ ori_weight = model.patch_embed.projection.weight.clone().detach()
+ # The pos_embed is all zero before initialize
+ self.assertTrue(
+ torch.allclose(model.absolute_pos_embed, torch.tensor(0.)))
+
+ model.init_weights()
+ initialized_weight = model.patch_embed.projection.weight
+ self.assertFalse(torch.allclose(ori_weight, initialized_weight))
+ self.assertFalse(
+ torch.allclose(model.absolute_pos_embed, torch.tensor(0.)))
+
+ pretrain_pos_embed = model.absolute_pos_embed.clone().detach()
+
+ tmpdir = tempfile.gettempdir()
+ # Save v3 checkpoints
+ checkpoint_v2 = os.path.join(tmpdir, 'v3.pth')
+ save_checkpoint(model, checkpoint_v2)
+ # Save v1 checkpoints
+ setattr(model, 'norm', model.norm3)
+ setattr(model.stages[0].blocks[1].attn, 'attn_mask',
+ torch.zeros(64, 49, 49))
+ model._version = 1
+ del model.norm3
+ checkpoint_v1 = os.path.join(tmpdir, 'v1.pth')
+ save_checkpoint(model, checkpoint_v1)
+
+ # test load v1 checkpoint
+ cfg = deepcopy(self.cfg)
+ cfg['use_abs_pos_embed'] = True
+ model = SwinTransformer(**cfg)
+ load_checkpoint(model, checkpoint_v1, strict=True)
+
+ # test load v3 checkpoint
+ cfg = deepcopy(self.cfg)
+ cfg['use_abs_pos_embed'] = True
+ model = SwinTransformer(**cfg)
+ load_checkpoint(model, checkpoint_v2, strict=True)
+
+ # test load v3 checkpoint with different img_size
+ cfg = deepcopy(self.cfg)
+ cfg['img_size'] = 384
+ cfg['use_abs_pos_embed'] = True
+ model = SwinTransformer(**cfg)
+ load_checkpoint(model, checkpoint_v2, strict=True)
+ resized_pos_embed = timm_resize_pos_embed(
+ pretrain_pos_embed, model.absolute_pos_embed, num_tokens=0)
+ self.assertTrue(
+ torch.allclose(model.absolute_pos_embed, resized_pos_embed))
+
+ os.remove(checkpoint_v1)
+ os.remove(checkpoint_v2)
+
+ def test_forward(self):
+ imgs = torch.randn(1, 3, 224, 224)
+
+ cfg = deepcopy(self.cfg)
+ model = SwinTransformer(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ feat = outs[-1]
+ self.assertEqual(feat.shape, (1, 1024, 7, 7))
+
+ # test with window_size=12
+ cfg = deepcopy(self.cfg)
+ cfg['window_size'] = 12
+ model = SwinTransformer(**cfg)
+ outs = model(torch.randn(1, 3, 384, 384))
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ feat = outs[-1]
+ self.assertEqual(feat.shape, (1, 1024, 12, 12))
+ with self.assertRaisesRegex(AssertionError, r'the window size \(12\)'):
+ model(torch.randn(1, 3, 224, 224))
+
+ # test with pad_small_map=True
+ cfg = deepcopy(self.cfg)
+ cfg['window_size'] = 12
+ cfg['pad_small_map'] = True
+ model = SwinTransformer(**cfg)
+ outs = model(torch.randn(1, 3, 224, 224))
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ feat = outs[-1]
+ self.assertEqual(feat.shape, (1, 1024, 7, 7))
+
+ # test multiple output indices
+ cfg = deepcopy(self.cfg)
+ cfg['out_indices'] = (0, 1, 2, 3)
+ model = SwinTransformer(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 4)
+ for stride, out in zip([1, 2, 4, 8], outs):
+ self.assertEqual(out.shape,
+ (1, 128 * stride, 56 // stride, 56 // stride))
+
+ # test with checkpoint forward
+ cfg = deepcopy(self.cfg)
+ cfg['with_cp'] = True
+ model = SwinTransformer(**cfg)
+ for m in model.modules():
+ if isinstance(m, SwinBlock):
+ self.assertTrue(m.with_cp)
+ model.init_weights()
+ model.train()
+
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ feat = outs[-1]
+ self.assertEqual(feat.shape, (1, 1024, 7, 7))
+
+ # test with dynamic input shape
+ imgs1 = torch.randn(1, 3, 224, 224)
+ imgs2 = torch.randn(1, 3, 256, 256)
+ imgs3 = torch.randn(1, 3, 256, 309)
+ cfg = deepcopy(self.cfg)
+ model = SwinTransformer(**cfg)
+ for imgs in [imgs1, imgs2, imgs3]:
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ feat = outs[-1]
+ expect_feat_shape = (math.ceil(imgs.shape[2] / 32),
+ math.ceil(imgs.shape[3] / 32))
+ self.assertEqual(feat.shape, (1, 1024, *expect_feat_shape))
+
+ def test_structure(self):
+ # test drop_path_rate decay
+ cfg = deepcopy(self.cfg)
+ cfg['drop_path_rate'] = 0.2
+ model = SwinTransformer(**cfg)
+ depths = model.arch_settings['depths']
+ blocks = chain(*[stage.blocks for stage in model.stages])
+ for i, block in enumerate(blocks):
+ expect_prob = 0.2 / (sum(depths) - 1) * i
+ self.assertAlmostEqual(block.ffn.dropout_layer.drop_prob,
+ expect_prob)
+ self.assertAlmostEqual(block.attn.drop.drop_prob, expect_prob)
+
+ # test Swin-Transformer with norm_eval=True
+ cfg = deepcopy(self.cfg)
+ cfg['norm_eval'] = True
+ cfg['norm_cfg'] = dict(type='BN')
+ cfg['stage_cfgs'] = dict(block_cfgs=dict(norm_cfg=dict(type='BN')))
+ model = SwinTransformer(**cfg)
+ model.init_weights()
+ model.train()
+ self.assertTrue(check_norm_state(model.modules(), False))
+
+ # test Swin-Transformer with first stage frozen.
+ cfg = deepcopy(self.cfg)
+ frozen_stages = 0
+ cfg['frozen_stages'] = frozen_stages
+ cfg['out_indices'] = (0, 1, 2, 3)
+ model = SwinTransformer(**cfg)
+ model.init_weights()
+ model.train()
+
+ # the patch_embed and first stage should not require grad.
+ self.assertFalse(model.patch_embed.training)
+ for param in model.patch_embed.parameters():
+ self.assertFalse(param.requires_grad)
+ for i in range(frozen_stages + 1):
+ stage = model.stages[i]
+ for param in stage.parameters():
+ self.assertFalse(param.requires_grad)
+ for param in model.norm0.parameters():
+ self.assertFalse(param.requires_grad)
+
+ # the second stage should require grad.
+ for i in range(frozen_stages + 1, 4):
+ stage = model.stages[i]
+ for param in stage.parameters():
+ self.assertTrue(param.requires_grad)
+ norm = getattr(model, f'norm{i}')
+ for param in norm.parameters():
+ self.assertTrue(param.requires_grad)
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_swin_transformer_v2.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_swin_transformer_v2.py
new file mode 100644
index 0000000000000000000000000000000000000000..1fd43140c3d334a3f4dfc725c4d0cf2666e11cd2
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_swin_transformer_v2.py
@@ -0,0 +1,243 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import math
+import os
+import tempfile
+from copy import deepcopy
+from itertools import chain
+from unittest import TestCase
+
+import torch
+from mmcv.runner import load_checkpoint, save_checkpoint
+from mmcv.utils.parrots_wrapper import _BatchNorm
+
+from mmcls.models.backbones import SwinTransformerV2
+from mmcls.models.backbones.swin_transformer import SwinBlock
+from .utils import timm_resize_pos_embed
+
+
+def check_norm_state(modules, train_state):
+ """Check if norm layer is in correct train state."""
+ for mod in modules:
+ if isinstance(mod, _BatchNorm):
+ if mod.training != train_state:
+ return False
+ return True
+
+
+class TestSwinTransformerV2(TestCase):
+
+ def setUp(self):
+ self.cfg = dict(
+ arch='b', img_size=256, patch_size=4, drop_path_rate=0.1)
+
+ def test_arch(self):
+ # Test invalid default arch
+ with self.assertRaisesRegex(AssertionError, 'not in default archs'):
+ cfg = deepcopy(self.cfg)
+ cfg['arch'] = 'unknown'
+ SwinTransformerV2(**cfg)
+
+ # Test invalid custom arch
+ with self.assertRaisesRegex(AssertionError, 'Custom arch needs'):
+ cfg = deepcopy(self.cfg)
+ cfg['arch'] = {
+ 'embed_dims': 96,
+ 'num_heads': [3, 6, 12, 16],
+ }
+ SwinTransformerV2(**cfg)
+
+ # Test custom arch
+ cfg = deepcopy(self.cfg)
+ depths = [2, 2, 6, 2]
+ num_heads = [6, 12, 6, 12]
+ cfg['arch'] = {
+ 'embed_dims': 256,
+ 'depths': depths,
+ 'num_heads': num_heads,
+ 'extra_norm_every_n_blocks': 2
+ }
+ model = SwinTransformerV2(**cfg)
+ for i, stage in enumerate(model.stages):
+ self.assertEqual(stage.out_channels, 256 * (2**i))
+ self.assertEqual(len(stage.blocks), depths[i])
+ self.assertEqual(stage.blocks[0].attn.w_msa.num_heads,
+ num_heads[i])
+ self.assertIsInstance(model.stages[2].blocks[5], torch.nn.Module)
+
+ def test_init_weights(self):
+ # test weight init cfg
+ cfg = deepcopy(self.cfg)
+ cfg['use_abs_pos_embed'] = True
+ cfg['init_cfg'] = [
+ dict(
+ type='Kaiming',
+ layer='Conv2d',
+ mode='fan_in',
+ nonlinearity='linear')
+ ]
+ model = SwinTransformerV2(**cfg)
+ ori_weight = model.patch_embed.projection.weight.clone().detach()
+ # The pos_embed is all zero before initialize
+ self.assertTrue(
+ torch.allclose(model.absolute_pos_embed, torch.tensor(0.)))
+
+ model.init_weights()
+ initialized_weight = model.patch_embed.projection.weight
+ self.assertFalse(torch.allclose(ori_weight, initialized_weight))
+ self.assertFalse(
+ torch.allclose(model.absolute_pos_embed, torch.tensor(0.)))
+
+ pretrain_pos_embed = model.absolute_pos_embed.clone().detach()
+
+ tmpdir = tempfile.TemporaryDirectory()
+ # Save checkpoints
+ checkpoint = os.path.join(tmpdir.name, 'checkpoint.pth')
+ save_checkpoint(model, checkpoint)
+
+ # test load checkpoint
+ cfg = deepcopy(self.cfg)
+ cfg['use_abs_pos_embed'] = True
+ model = SwinTransformerV2(**cfg)
+ load_checkpoint(model, checkpoint, strict=False)
+
+ # test load checkpoint with different img_size
+ cfg = deepcopy(self.cfg)
+ cfg['img_size'] = 384
+ cfg['use_abs_pos_embed'] = True
+ model = SwinTransformerV2(**cfg)
+ load_checkpoint(model, checkpoint, strict=False)
+ resized_pos_embed = timm_resize_pos_embed(
+ pretrain_pos_embed, model.absolute_pos_embed, num_tokens=0)
+ self.assertTrue(
+ torch.allclose(model.absolute_pos_embed, resized_pos_embed))
+
+ tmpdir.cleanup()
+
+ def test_forward(self):
+ imgs = torch.randn(1, 3, 256, 256)
+
+ cfg = deepcopy(self.cfg)
+ model = SwinTransformerV2(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ feat = outs[-1]
+ self.assertEqual(feat.shape, (1, 1024, 8, 8))
+
+ # test with window_size=12
+ cfg = deepcopy(self.cfg)
+ cfg['window_size'] = 12
+ model = SwinTransformerV2(**cfg)
+ outs = model(torch.randn(1, 3, 384, 384))
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ feat = outs[-1]
+ self.assertEqual(feat.shape, (1, 1024, 12, 12))
+ with self.assertRaisesRegex(AssertionError, r'the window size \(12\)'):
+ model(torch.randn(1, 3, 256, 256))
+
+ # test with pad_small_map=True
+ cfg = deepcopy(self.cfg)
+ cfg['window_size'] = 12
+ cfg['pad_small_map'] = True
+ model = SwinTransformerV2(**cfg)
+ outs = model(torch.randn(1, 3, 256, 256))
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ feat = outs[-1]
+ self.assertEqual(feat.shape, (1, 1024, 8, 8))
+
+ # test multiple output indices
+ cfg = deepcopy(self.cfg)
+ cfg['out_indices'] = (0, 1, 2, 3)
+ model = SwinTransformerV2(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 4)
+ for stride, out in zip([1, 2, 4, 8], outs):
+ self.assertEqual(out.shape,
+ (1, 128 * stride, 64 // stride, 64 // stride))
+
+ # test with checkpoint forward
+ cfg = deepcopy(self.cfg)
+ cfg['with_cp'] = True
+ model = SwinTransformerV2(**cfg)
+ for m in model.modules():
+ if isinstance(m, SwinBlock):
+ self.assertTrue(m.with_cp)
+ model.init_weights()
+ model.train()
+
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ feat = outs[-1]
+ self.assertEqual(feat.shape, (1, 1024, 8, 8))
+
+ # test with dynamic input shape
+ imgs1 = torch.randn(1, 3, 224, 224)
+ imgs2 = torch.randn(1, 3, 256, 256)
+ imgs3 = torch.randn(1, 3, 256, 309)
+ cfg = deepcopy(self.cfg)
+ cfg['pad_small_map'] = True
+ model = SwinTransformerV2(**cfg)
+ for imgs in [imgs1, imgs2, imgs3]:
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ feat = outs[-1]
+ expect_feat_shape = (math.ceil(imgs.shape[2] / 32),
+ math.ceil(imgs.shape[3] / 32))
+ self.assertEqual(feat.shape, (1, 1024, *expect_feat_shape))
+
+ def test_structure(self):
+ # test drop_path_rate decay
+ cfg = deepcopy(self.cfg)
+ cfg['drop_path_rate'] = 0.2
+ model = SwinTransformerV2(**cfg)
+ depths = model.arch_settings['depths']
+ blocks = chain(*[stage.blocks for stage in model.stages])
+ for i, block in enumerate(blocks):
+ expect_prob = 0.2 / (sum(depths) - 1) * i
+ self.assertAlmostEqual(block.ffn.dropout_layer.drop_prob,
+ expect_prob)
+ self.assertAlmostEqual(block.attn.drop.drop_prob, expect_prob)
+
+ # test Swin-Transformer V2 with norm_eval=True
+ cfg = deepcopy(self.cfg)
+ cfg['norm_eval'] = True
+ cfg['norm_cfg'] = dict(type='BN')
+ cfg['stage_cfgs'] = dict(block_cfgs=dict(norm_cfg=dict(type='BN')))
+ model = SwinTransformerV2(**cfg)
+ model.init_weights()
+ model.train()
+ self.assertTrue(check_norm_state(model.modules(), False))
+
+ # test Swin-Transformer V2 with first stage frozen.
+ cfg = deepcopy(self.cfg)
+ frozen_stages = 0
+ cfg['frozen_stages'] = frozen_stages
+ cfg['out_indices'] = (0, 1, 2, 3)
+ model = SwinTransformerV2(**cfg)
+ model.init_weights()
+ model.train()
+
+ # the patch_embed and first stage should not require grad.
+ self.assertFalse(model.patch_embed.training)
+ for param in model.patch_embed.parameters():
+ self.assertFalse(param.requires_grad)
+ for i in range(frozen_stages + 1):
+ stage = model.stages[i]
+ for param in stage.parameters():
+ self.assertFalse(param.requires_grad)
+ for param in model.norm0.parameters():
+ self.assertFalse(param.requires_grad)
+
+ # the second stage should require grad.
+ for i in range(frozen_stages + 1, 4):
+ stage = model.stages[i]
+ for param in stage.parameters():
+ self.assertTrue(param.requires_grad)
+ norm = getattr(model, f'norm{i}')
+ for param in norm.parameters():
+ self.assertTrue(param.requires_grad)
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_t2t_vit.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_t2t_vit.py
new file mode 100644
index 0000000000000000000000000000000000000000..f3103c65555e32a920f0aa71164fbad0f2adef66
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_t2t_vit.py
@@ -0,0 +1,188 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import math
+import os
+import tempfile
+from copy import deepcopy
+from unittest import TestCase
+
+import numpy as np
+import torch
+from mmcv.runner import load_checkpoint, save_checkpoint
+
+from mmcls.models.backbones import T2T_ViT
+from mmcls.models.backbones.t2t_vit import get_sinusoid_encoding
+from .utils import timm_resize_pos_embed
+
+
+class TestT2TViT(TestCase):
+
+ def setUp(self):
+ self.cfg = dict(
+ img_size=224,
+ in_channels=3,
+ embed_dims=384,
+ t2t_cfg=dict(
+ token_dims=64,
+ use_performer=False,
+ ),
+ num_layers=14,
+ drop_path_rate=0.1)
+
+ def test_structure(self):
+ # The performer hasn't been implemented
+ cfg = deepcopy(self.cfg)
+ cfg['t2t_cfg']['use_performer'] = True
+ with self.assertRaises(NotImplementedError):
+ T2T_ViT(**cfg)
+
+ # Test out_indices
+ cfg = deepcopy(self.cfg)
+ cfg['out_indices'] = {1: 1}
+ with self.assertRaisesRegex(AssertionError, "get "):
+ T2T_ViT(**cfg)
+ cfg['out_indices'] = [0, 15]
+ with self.assertRaisesRegex(AssertionError, 'Invalid out_indices 15'):
+ T2T_ViT(**cfg)
+
+ # Test model structure
+ cfg = deepcopy(self.cfg)
+ model = T2T_ViT(**cfg)
+ self.assertEqual(len(model.encoder), 14)
+ dpr_inc = 0.1 / (14 - 1)
+ dpr = 0
+ for layer in model.encoder:
+ self.assertEqual(layer.attn.embed_dims, 384)
+ # The default mlp_ratio is 3
+ self.assertEqual(layer.ffn.feedforward_channels, 384 * 3)
+ self.assertAlmostEqual(layer.attn.out_drop.drop_prob, dpr)
+ self.assertAlmostEqual(layer.ffn.dropout_layer.drop_prob, dpr)
+ dpr += dpr_inc
+
+ def test_init_weights(self):
+ # test weight init cfg
+ cfg = deepcopy(self.cfg)
+ cfg['init_cfg'] = [dict(type='TruncNormal', layer='Linear', std=.02)]
+ model = T2T_ViT(**cfg)
+ ori_weight = model.tokens_to_token.project.weight.clone().detach()
+
+ model.init_weights()
+ initialized_weight = model.tokens_to_token.project.weight
+ self.assertFalse(torch.allclose(ori_weight, initialized_weight))
+
+ # test load checkpoint
+ pretrain_pos_embed = model.pos_embed.clone().detach()
+ tmpdir = tempfile.gettempdir()
+ checkpoint = os.path.join(tmpdir, 'test.pth')
+ save_checkpoint(model, checkpoint)
+ cfg = deepcopy(self.cfg)
+ model = T2T_ViT(**cfg)
+ load_checkpoint(model, checkpoint, strict=True)
+ self.assertTrue(torch.allclose(model.pos_embed, pretrain_pos_embed))
+
+ # test load checkpoint with different img_size
+ cfg = deepcopy(self.cfg)
+ cfg['img_size'] = 384
+ model = T2T_ViT(**cfg)
+ load_checkpoint(model, checkpoint, strict=True)
+ resized_pos_embed = timm_resize_pos_embed(pretrain_pos_embed,
+ model.pos_embed)
+ self.assertTrue(torch.allclose(model.pos_embed, resized_pos_embed))
+
+ os.remove(checkpoint)
+
+ def test_forward(self):
+ imgs = torch.randn(1, 3, 224, 224)
+
+ # test with_cls_token=False
+ cfg = deepcopy(self.cfg)
+ cfg['with_cls_token'] = False
+ cfg['output_cls_token'] = True
+ with self.assertRaisesRegex(AssertionError, 'but got False'):
+ T2T_ViT(**cfg)
+
+ cfg = deepcopy(self.cfg)
+ cfg['with_cls_token'] = False
+ cfg['output_cls_token'] = False
+ model = T2T_ViT(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ patch_token = outs[-1]
+ self.assertEqual(patch_token.shape, (1, 384, 14, 14))
+
+ # test with output_cls_token
+ cfg = deepcopy(self.cfg)
+ model = T2T_ViT(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ patch_token, cls_token = outs[-1]
+ self.assertEqual(patch_token.shape, (1, 384, 14, 14))
+ self.assertEqual(cls_token.shape, (1, 384))
+
+ # test without output_cls_token
+ cfg = deepcopy(self.cfg)
+ cfg['output_cls_token'] = False
+ model = T2T_ViT(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ patch_token = outs[-1]
+ self.assertEqual(patch_token.shape, (1, 384, 14, 14))
+
+ # Test forward with multi out indices
+ cfg = deepcopy(self.cfg)
+ cfg['out_indices'] = [-3, -2, -1]
+ model = T2T_ViT(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 3)
+ for out in outs:
+ patch_token, cls_token = out
+ self.assertEqual(patch_token.shape, (1, 384, 14, 14))
+ self.assertEqual(cls_token.shape, (1, 384))
+
+ # Test forward with dynamic input size
+ imgs1 = torch.randn(1, 3, 224, 224)
+ imgs2 = torch.randn(1, 3, 256, 256)
+ imgs3 = torch.randn(1, 3, 256, 309)
+ cfg = deepcopy(self.cfg)
+ model = T2T_ViT(**cfg)
+ for imgs in [imgs1, imgs2, imgs3]:
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ patch_token, cls_token = outs[-1]
+ expect_feat_shape = (math.ceil(imgs.shape[2] / 16),
+ math.ceil(imgs.shape[3] / 16))
+ self.assertEqual(patch_token.shape, (1, 384, *expect_feat_shape))
+ self.assertEqual(cls_token.shape, (1, 384))
+
+
+def test_get_sinusoid_encoding():
+ # original numpy based third-party implementation copied from mmcls
+ # https://github.com/jadore801120/attention-is-all-you-need-pytorch/blob/master/transformer/Models.py#L31
+ def get_sinusoid_encoding_numpy(n_position, d_hid):
+
+ def get_position_angle_vec(position):
+ return [
+ position / np.power(10000, 2 * (hid_j // 2) / d_hid)
+ for hid_j in range(d_hid)
+ ]
+
+ sinusoid_table = np.array(
+ [get_position_angle_vec(pos_i) for pos_i in range(n_position)])
+ sinusoid_table[:, 0::2] = np.sin(sinusoid_table[:, 0::2]) # dim 2i
+ sinusoid_table[:, 1::2] = np.cos(sinusoid_table[:, 1::2]) # dim 2i+1
+
+ return torch.FloatTensor(sinusoid_table).unsqueeze(0)
+
+ n_positions = [128, 256, 512, 1024]
+ embed_dims = [128, 256, 512, 1024]
+ for n_position in n_positions:
+ for embed_dim in embed_dims:
+ out_mmcls = get_sinusoid_encoding(n_position, embed_dim)
+ out_numpy = get_sinusoid_encoding_numpy(n_position, embed_dim)
+ error = (out_mmcls - out_numpy).abs().max()
+ assert error < 1e-9, 'Test case n_position=%d, embed_dim=%d failed'
+ return
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_timm_backbone.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_timm_backbone.py
new file mode 100644
index 0000000000000000000000000000000000000000..46283091ecca811b6b18308038d6a28a9d2660b7
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_timm_backbone.py
@@ -0,0 +1,204 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import pytest
+import torch
+from torch import nn
+from torch.nn.modules.batchnorm import _BatchNorm
+
+from mmcls.models.backbones import TIMMBackbone
+
+
+def check_norm_state(modules, train_state):
+ """Check if norm layer is in correct train state."""
+ for mod in modules:
+ if isinstance(mod, _BatchNorm):
+ if mod.training != train_state:
+ return False
+ return True
+
+
+def test_timm_backbone():
+ """Test timm backbones, features_only=False (default)."""
+ with pytest.raises(TypeError):
+ # TIMMBackbone has 1 required positional argument: 'model_name'
+ model = TIMMBackbone(pretrained=True)
+
+ with pytest.raises(TypeError):
+ # pretrained must be bool
+ model = TIMMBackbone(model_name='resnet18', pretrained='model.pth')
+
+ # Test resnet18 from timm
+ model = TIMMBackbone(model_name='resnet18')
+ model.init_weights()
+ model.train()
+ assert check_norm_state(model.modules(), True)
+ assert isinstance(model.timm_model.global_pool.pool, nn.Identity)
+ assert isinstance(model.timm_model.fc, nn.Identity)
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 1
+ assert feat[0].shape == torch.Size((1, 512, 7, 7))
+
+ # Test efficientnet_b1 with pretrained weights
+ model = TIMMBackbone(model_name='efficientnet_b1', pretrained=True)
+ model.init_weights()
+ model.train()
+ assert isinstance(model.timm_model.global_pool.pool, nn.Identity)
+ assert isinstance(model.timm_model.classifier, nn.Identity)
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 1
+ assert feat[0].shape == torch.Size((1, 1280, 7, 7))
+
+ # Test vit_tiny_patch16_224 with pretrained weights
+ model = TIMMBackbone(model_name='vit_tiny_patch16_224', pretrained=True)
+ model.init_weights()
+ model.train()
+ assert isinstance(model.timm_model.head, nn.Identity)
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 1
+ # Disable the test since TIMM's behavior changes between 0.5.4 and 0.5.5
+ # assert feat[0].shape == torch.Size((1, 197, 192))
+
+
+def test_timm_backbone_features_only():
+ """Test timm backbones, features_only=True."""
+ # Test different norm_layer, can be: 'SyncBN', 'BN2d', 'GN', 'LN', 'IN'
+ # Test resnet18 from timm, norm_layer='BN2d'
+ model = TIMMBackbone(
+ model_name='resnet18',
+ features_only=True,
+ pretrained=False,
+ output_stride=32,
+ norm_layer='BN2d')
+
+ # Test resnet18 from timm, norm_layer='SyncBN'
+ model = TIMMBackbone(
+ model_name='resnet18',
+ features_only=True,
+ pretrained=False,
+ output_stride=32,
+ norm_layer='SyncBN')
+
+ # Test resnet18 from timm, output_stride=32
+ model = TIMMBackbone(
+ model_name='resnet18',
+ features_only=True,
+ pretrained=False,
+ output_stride=32)
+ model.init_weights()
+ model.train()
+ assert check_norm_state(model.modules(), True)
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feats = model(imgs)
+ assert len(feats) == 5
+ assert feats[0].shape == torch.Size((1, 64, 112, 112))
+ assert feats[1].shape == torch.Size((1, 64, 56, 56))
+ assert feats[2].shape == torch.Size((1, 128, 28, 28))
+ assert feats[3].shape == torch.Size((1, 256, 14, 14))
+ assert feats[4].shape == torch.Size((1, 512, 7, 7))
+
+ # Test resnet18 from timm, output_stride=32, out_indices=(1, 2, 3)
+ model = TIMMBackbone(
+ model_name='resnet18',
+ features_only=True,
+ pretrained=False,
+ output_stride=32,
+ out_indices=(1, 2, 3))
+ imgs = torch.randn(1, 3, 224, 224)
+ feats = model(imgs)
+ assert len(feats) == 3
+ assert feats[0].shape == torch.Size((1, 64, 56, 56))
+ assert feats[1].shape == torch.Size((1, 128, 28, 28))
+ assert feats[2].shape == torch.Size((1, 256, 14, 14))
+
+ # Test resnet18 from timm, output_stride=16
+ model = TIMMBackbone(
+ model_name='resnet18',
+ features_only=True,
+ pretrained=False,
+ output_stride=16)
+ imgs = torch.randn(1, 3, 224, 224)
+ feats = model(imgs)
+ assert len(feats) == 5
+ assert feats[0].shape == torch.Size((1, 64, 112, 112))
+ assert feats[1].shape == torch.Size((1, 64, 56, 56))
+ assert feats[2].shape == torch.Size((1, 128, 28, 28))
+ assert feats[3].shape == torch.Size((1, 256, 14, 14))
+ assert feats[4].shape == torch.Size((1, 512, 14, 14))
+
+ # Test resnet18 from timm, output_stride=8
+ model = TIMMBackbone(
+ model_name='resnet18',
+ features_only=True,
+ pretrained=False,
+ output_stride=8)
+ imgs = torch.randn(1, 3, 224, 224)
+ feats = model(imgs)
+ assert len(feats) == 5
+ assert feats[0].shape == torch.Size((1, 64, 112, 112))
+ assert feats[1].shape == torch.Size((1, 64, 56, 56))
+ assert feats[2].shape == torch.Size((1, 128, 28, 28))
+ assert feats[3].shape == torch.Size((1, 256, 28, 28))
+ assert feats[4].shape == torch.Size((1, 512, 28, 28))
+
+ # Test efficientnet_b1 with pretrained weights
+ model = TIMMBackbone(
+ model_name='efficientnet_b1', features_only=True, pretrained=True)
+ imgs = torch.randn(1, 3, 64, 64)
+ feats = model(imgs)
+ assert len(feats) == 5
+ assert feats[0].shape == torch.Size((1, 16, 32, 32))
+ assert feats[1].shape == torch.Size((1, 24, 16, 16))
+ assert feats[2].shape == torch.Size((1, 40, 8, 8))
+ assert feats[3].shape == torch.Size((1, 112, 4, 4))
+ assert feats[4].shape == torch.Size((1, 320, 2, 2))
+
+ # Test resnetv2_50x1_bitm from timm, output_stride=8
+ model = TIMMBackbone(
+ model_name='resnetv2_50x1_bitm',
+ features_only=True,
+ pretrained=False,
+ output_stride=8)
+ imgs = torch.randn(1, 3, 8, 8)
+ feats = model(imgs)
+ assert len(feats) == 5
+ assert feats[0].shape == torch.Size((1, 64, 4, 4))
+ assert feats[1].shape == torch.Size((1, 256, 2, 2))
+ assert feats[2].shape == torch.Size((1, 512, 1, 1))
+ assert feats[3].shape == torch.Size((1, 1024, 1, 1))
+ assert feats[4].shape == torch.Size((1, 2048, 1, 1))
+
+ # Test resnetv2_50x3_bitm from timm, output_stride=8
+ model = TIMMBackbone(
+ model_name='resnetv2_50x3_bitm',
+ features_only=True,
+ pretrained=False,
+ output_stride=8)
+ imgs = torch.randn(1, 3, 8, 8)
+ feats = model(imgs)
+ assert len(feats) == 5
+ assert feats[0].shape == torch.Size((1, 192, 4, 4))
+ assert feats[1].shape == torch.Size((1, 768, 2, 2))
+ assert feats[2].shape == torch.Size((1, 1536, 1, 1))
+ assert feats[3].shape == torch.Size((1, 3072, 1, 1))
+ assert feats[4].shape == torch.Size((1, 6144, 1, 1))
+
+ # Test resnetv2_101x1_bitm from timm, output_stride=8
+ model = TIMMBackbone(
+ model_name='resnetv2_101x1_bitm',
+ features_only=True,
+ pretrained=False,
+ output_stride=8)
+ imgs = torch.randn(1, 3, 8, 8)
+ feats = model(imgs)
+ assert len(feats) == 5
+ assert feats[0].shape == torch.Size((1, 64, 4, 4))
+ assert feats[1].shape == torch.Size((1, 256, 2, 2))
+ assert feats[2].shape == torch.Size((1, 512, 1, 1))
+ assert feats[3].shape == torch.Size((1, 1024, 1, 1))
+ assert feats[4].shape == torch.Size((1, 2048, 1, 1))
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_tnt.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_tnt.py
new file mode 100644
index 0000000000000000000000000000000000000000..2feffd6a757dd0f01fef988a9ec15efe7c0d8339
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_tnt.py
@@ -0,0 +1,50 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import pytest
+import torch
+from torch.nn.modules.batchnorm import _BatchNorm
+
+from mmcls.models.backbones import TNT
+
+
+def check_norm_state(modules, train_state):
+ """Check if norm layer is in correct train state."""
+ for mod in modules:
+ if isinstance(mod, _BatchNorm):
+ if mod.training != train_state:
+ return False
+ return True
+
+
+def test_tnt_backbone():
+ with pytest.raises(TypeError):
+ # pretrained must be a string path
+ model = TNT()
+ model.init_weights(pretrained=0)
+
+ # Test tnt_base_patch16_224
+ model = TNT()
+ model.init_weights()
+ model.train()
+ assert check_norm_state(model.modules(), True)
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 1
+ assert feat[0].shape == torch.Size((1, 640))
+
+ # Test tnt with embed_dims=768
+ arch = {
+ 'embed_dims_outer': 768,
+ 'embed_dims_inner': 48,
+ 'num_layers': 12,
+ 'num_heads_outer': 6,
+ 'num_heads_inner': 4
+ }
+ model = TNT(arch=arch)
+ model.init_weights()
+ model.train()
+
+ imgs = torch.randn(1, 3, 224, 224)
+ feat = model(imgs)
+ assert len(feat) == 1
+ assert feat[0].shape == torch.Size((1, 768))
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_twins.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_twins.py
new file mode 100644
index 0000000000000000000000000000000000000000..b692584315eb3623881450ab2ac561c55c335e09
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_twins.py
@@ -0,0 +1,243 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import copy
+
+import pytest
+import torch
+import torch.nn as nn
+
+from mmcls.models.backbones.twins import (PCPVT, SVT,
+ GlobalSubsampledAttention,
+ LocallyGroupedSelfAttention)
+
+
+def test_LSA_module():
+ lsa = LocallyGroupedSelfAttention(embed_dims=32, window_size=3)
+ outs = lsa(torch.randn(1, 3136, 32), (56, 56))
+ assert outs.shape == torch.Size([1, 3136, 32])
+
+
+def test_GSA_module():
+ gsa = GlobalSubsampledAttention(embed_dims=32, num_heads=8)
+ outs = gsa(torch.randn(1, 3136, 32), (56, 56))
+ assert outs.shape == torch.Size([1, 3136, 32])
+
+
+def test_pcpvt():
+ # test init
+ path = 'PATH_THAT_DO_NOT_EXIST'
+
+ # init_cfg loads pretrain from an non-existent file
+ model = PCPVT('s', init_cfg=dict(type='Pretrained', checkpoint=path))
+ assert model.init_cfg == dict(type='Pretrained', checkpoint=path)
+
+ # Test loading a checkpoint from an non-existent file
+ with pytest.raises(OSError):
+ model.init_weights()
+
+ # init_cfg=123, whose type is unsupported
+ model = PCPVT('s', init_cfg=123)
+ with pytest.raises(TypeError):
+ model.init_weights()
+
+ H, W = (64, 64)
+ temp = torch.randn((1, 3, H, W))
+
+ # test output last feat
+ model = PCPVT('small')
+ model.init_weights()
+ outs = model(temp)
+ assert len(outs) == 1
+ assert outs[-1].shape == (1, 512, H // 32, W // 32)
+
+ # test with mutil outputs
+ model = PCPVT('small', out_indices=(0, 1, 2, 3))
+ model.init_weights()
+ outs = model(temp)
+ assert len(outs) == 4
+ assert outs[0].shape == (1, 64, H // 4, W // 4)
+ assert outs[1].shape == (1, 128, H // 8, W // 8)
+ assert outs[2].shape == (1, 320, H // 16, W // 16)
+ assert outs[3].shape == (1, 512, H // 32, W // 32)
+
+ # test with arch of dict
+ arch = {
+ 'embed_dims': [64, 128, 320, 512],
+ 'depths': [3, 4, 18, 3],
+ 'num_heads': [1, 2, 5, 8],
+ 'patch_sizes': [4, 2, 2, 2],
+ 'strides': [4, 2, 2, 2],
+ 'mlp_ratios': [8, 8, 4, 4],
+ 'sr_ratios': [8, 4, 2, 1]
+ }
+
+ pcpvt_arch = copy.deepcopy(arch)
+ model = PCPVT(pcpvt_arch, out_indices=(0, 1, 2, 3))
+ model.init_weights()
+ outs = model(temp)
+ assert len(outs) == 4
+ assert outs[0].shape == (1, 64, H // 4, W // 4)
+ assert outs[1].shape == (1, 128, H // 8, W // 8)
+ assert outs[2].shape == (1, 320, H // 16, W // 16)
+ assert outs[3].shape == (1, 512, H // 32, W // 32)
+
+ # assert length of arch value not equal
+ pcpvt_arch = copy.deepcopy(arch)
+ pcpvt_arch['sr_ratios'] = [8, 4, 2]
+ with pytest.raises(AssertionError):
+ model = PCPVT(pcpvt_arch, out_indices=(0, 1, 2, 3))
+
+ # assert lack arch essential_keys
+ pcpvt_arch = copy.deepcopy(arch)
+ del pcpvt_arch['sr_ratios']
+ with pytest.raises(AssertionError):
+ model = PCPVT(pcpvt_arch, out_indices=(0, 1, 2, 3))
+
+ # assert arch value not list
+ pcpvt_arch = copy.deepcopy(arch)
+ pcpvt_arch['sr_ratios'] = 1
+ with pytest.raises(AssertionError):
+ model = PCPVT(pcpvt_arch, out_indices=(0, 1, 2, 3))
+
+ pcpvt_arch = copy.deepcopy(arch)
+ pcpvt_arch['sr_ratios'] = '1, 2, 3, 4'
+ with pytest.raises(AssertionError):
+ model = PCPVT(pcpvt_arch, out_indices=(0, 1, 2, 3))
+
+ # test norm_after_stage is bool True
+ model = PCPVT('small', norm_after_stage=True, norm_cfg=dict(type='LN'))
+ for i in range(model.num_stage):
+ assert hasattr(model, f'norm_after_stage{i}')
+ assert isinstance(getattr(model, f'norm_after_stage{i}'), nn.LayerNorm)
+
+ # test norm_after_stage is bool Flase
+ model = PCPVT('small', norm_after_stage=False)
+ for i in range(model.num_stage):
+ assert hasattr(model, f'norm_after_stage{i}')
+ assert isinstance(getattr(model, f'norm_after_stage{i}'), nn.Identity)
+
+ # test norm_after_stage is bool list
+ norm_after_stage = [False, True, False, True]
+ model = PCPVT('small', norm_after_stage=norm_after_stage)
+ assert len(norm_after_stage) == model.num_stage
+ for i in range(model.num_stage):
+ assert hasattr(model, f'norm_after_stage{i}')
+ norm_layer = getattr(model, f'norm_after_stage{i}')
+ if norm_after_stage[i]:
+ assert isinstance(norm_layer, nn.LayerNorm)
+ else:
+ assert isinstance(norm_layer, nn.Identity)
+
+ # test norm_after_stage is not bool list
+ norm_after_stage = [False, 'True', False, True]
+ with pytest.raises(AssertionError):
+ model = PCPVT('small', norm_after_stage=norm_after_stage)
+
+
+def test_svt():
+ # test init
+ path = 'PATH_THAT_DO_NOT_EXIST'
+
+ # init_cfg loads pretrain from an non-existent file
+ model = SVT('s', init_cfg=dict(type='Pretrained', checkpoint=path))
+ assert model.init_cfg == dict(type='Pretrained', checkpoint=path)
+
+ # Test loading a checkpoint from an non-existent file
+ with pytest.raises(OSError):
+ model.init_weights()
+
+ # init_cfg=123, whose type is unsupported
+ model = SVT('s', init_cfg=123)
+ with pytest.raises(TypeError):
+ model.init_weights()
+
+ # Test feature map output
+ H, W = (64, 64)
+ temp = torch.randn((1, 3, H, W))
+
+ model = SVT('s')
+ model.init_weights()
+ outs = model(temp)
+ assert len(outs) == 1
+ assert outs[-1].shape == (1, 512, H // 32, W // 32)
+
+ # test with mutil outputs
+ model = SVT('small', out_indices=(0, 1, 2, 3))
+ model.init_weights()
+ outs = model(temp)
+ assert len(outs) == 4
+ assert outs[0].shape == (1, 64, H // 4, W // 4)
+ assert outs[1].shape == (1, 128, H // 8, W // 8)
+ assert outs[2].shape == (1, 256, H // 16, W // 16)
+ assert outs[3].shape == (1, 512, H // 32, W // 32)
+
+ # test with arch of dict
+ arch = {
+ 'embed_dims': [96, 192, 384, 768],
+ 'depths': [2, 2, 18, 2],
+ 'num_heads': [3, 6, 12, 24],
+ 'patch_sizes': [4, 2, 2, 2],
+ 'strides': [4, 2, 2, 2],
+ 'mlp_ratios': [4, 4, 4, 4],
+ 'sr_ratios': [8, 4, 2, 1],
+ 'window_sizes': [7, 7, 7, 7]
+ }
+ model = SVT(arch, out_indices=(0, 1, 2, 3))
+ model.init_weights()
+ outs = model(temp)
+ assert len(outs) == 4
+ assert outs[0].shape == (1, 96, H // 4, W // 4)
+ assert outs[1].shape == (1, 192, H // 8, W // 8)
+ assert outs[2].shape == (1, 384, H // 16, W // 16)
+ assert outs[3].shape == (1, 768, H // 32, W // 32)
+
+ # assert length of arch value not equal
+ svt_arch = copy.deepcopy(arch)
+ svt_arch['sr_ratios'] = [8, 4, 2]
+ with pytest.raises(AssertionError):
+ model = SVT(svt_arch, out_indices=(0, 1, 2, 3))
+
+ # assert lack arch essential_keys
+ svt_arch = copy.deepcopy(arch)
+ del svt_arch['window_sizes']
+ with pytest.raises(AssertionError):
+ model = SVT(svt_arch, out_indices=(0, 1, 2, 3))
+
+ # assert arch value not list
+ svt_arch = copy.deepcopy(arch)
+ svt_arch['sr_ratios'] = 1
+ with pytest.raises(AssertionError):
+ model = SVT(svt_arch, out_indices=(0, 1, 2, 3))
+
+ svt_arch = copy.deepcopy(arch)
+ svt_arch['sr_ratios'] = '1, 2, 3, 4'
+ with pytest.raises(AssertionError):
+ model = SVT(svt_arch, out_indices=(0, 1, 2, 3))
+
+ # test norm_after_stage is bool True
+ model = SVT('small', norm_after_stage=True, norm_cfg=dict(type='LN'))
+ for i in range(model.num_stage):
+ assert hasattr(model, f'norm_after_stage{i}')
+ assert isinstance(getattr(model, f'norm_after_stage{i}'), nn.LayerNorm)
+
+ # test norm_after_stage is bool Flase
+ model = SVT('small', norm_after_stage=False)
+ for i in range(model.num_stage):
+ assert hasattr(model, f'norm_after_stage{i}')
+ assert isinstance(getattr(model, f'norm_after_stage{i}'), nn.Identity)
+
+ # test norm_after_stage is bool list
+ norm_after_stage = [False, True, False, True]
+ model = SVT('small', norm_after_stage=norm_after_stage)
+ assert len(norm_after_stage) == model.num_stage
+ for i in range(model.num_stage):
+ assert hasattr(model, f'norm_after_stage{i}')
+ norm_layer = getattr(model, f'norm_after_stage{i}')
+ if norm_after_stage[i]:
+ assert isinstance(norm_layer, nn.LayerNorm)
+ else:
+ assert isinstance(norm_layer, nn.Identity)
+
+ # test norm_after_stage is not bool list
+ norm_after_stage = [False, 'True', False, True]
+ with pytest.raises(AssertionError):
+ model = SVT('small', norm_after_stage=norm_after_stage)
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_van.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_van.py
new file mode 100644
index 0000000000000000000000000000000000000000..136ce9737371baf7b6c434d0deb61588c9ac1fd6
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_van.py
@@ -0,0 +1,188 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import math
+from copy import deepcopy
+from itertools import chain
+from unittest import TestCase
+
+import torch
+from mmcv.utils.parrots_wrapper import _BatchNorm
+from torch import nn
+
+from mmcls.models.backbones import VAN
+
+
+def check_norm_state(modules, train_state):
+ """Check if norm layer is in correct train state."""
+ for mod in modules:
+ if isinstance(mod, _BatchNorm):
+ if mod.training != train_state:
+ return False
+ return True
+
+
+class TestVAN(TestCase):
+
+ def setUp(self):
+ self.cfg = dict(arch='t', drop_path_rate=0.1)
+
+ def test_arch(self):
+ # Test invalid default arch
+ with self.assertRaisesRegex(AssertionError, 'not in default archs'):
+ cfg = deepcopy(self.cfg)
+ cfg['arch'] = 'unknown'
+ VAN(**cfg)
+
+ # Test invalid custom arch
+ with self.assertRaisesRegex(AssertionError, 'Custom arch needs'):
+ cfg = deepcopy(self.cfg)
+ cfg['arch'] = {
+ 'embed_dims': [32, 64, 160, 256],
+ 'ffn_ratios': [8, 8, 4, 4],
+ }
+ VAN(**cfg)
+
+ # Test custom arch
+ cfg = deepcopy(self.cfg)
+ embed_dims = [32, 64, 160, 256]
+ depths = [3, 3, 5, 2]
+ ffn_ratios = [8, 8, 4, 4]
+ cfg['arch'] = {
+ 'embed_dims': embed_dims,
+ 'depths': depths,
+ 'ffn_ratios': ffn_ratios
+ }
+ model = VAN(**cfg)
+
+ for i in range(len(depths)):
+ stage = getattr(model, f'blocks{i + 1}')
+ self.assertEqual(stage[-1].out_channels, embed_dims[i])
+ self.assertEqual(len(stage), depths[i])
+
+ def test_init_weights(self):
+ # test weight init cfg
+ cfg = deepcopy(self.cfg)
+ cfg['init_cfg'] = [
+ dict(
+ type='Kaiming',
+ layer='Conv2d',
+ mode='fan_in',
+ nonlinearity='linear')
+ ]
+ model = VAN(**cfg)
+ ori_weight = model.patch_embed1.projection.weight.clone().detach()
+
+ model.init_weights()
+ initialized_weight = model.patch_embed1.projection.weight
+ self.assertFalse(torch.allclose(ori_weight, initialized_weight))
+
+ def test_forward(self):
+ imgs = torch.randn(3, 3, 224, 224)
+
+ cfg = deepcopy(self.cfg)
+ model = VAN(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ feat = outs[-1]
+ self.assertEqual(feat.shape, (3, 256, 7, 7))
+
+ # test with patch_sizes
+ cfg = deepcopy(self.cfg)
+ cfg['patch_sizes'] = [7, 5, 5, 5]
+ model = VAN(**cfg)
+ outs = model(torch.randn(3, 3, 224, 224))
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ feat = outs[-1]
+ self.assertEqual(feat.shape, (3, 256, 3, 3))
+
+ # test multiple output indices
+ cfg = deepcopy(self.cfg)
+ cfg['out_indices'] = (0, 1, 2, 3)
+ model = VAN(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 4)
+ for emb_size, stride, out in zip([32, 64, 160, 256], [1, 2, 4, 8],
+ outs):
+ self.assertEqual(out.shape,
+ (3, emb_size, 56 // stride, 56 // stride))
+
+ # test with dynamic input shape
+ imgs1 = torch.randn(3, 3, 224, 224)
+ imgs2 = torch.randn(3, 3, 256, 256)
+ imgs3 = torch.randn(3, 3, 256, 309)
+ cfg = deepcopy(self.cfg)
+ model = VAN(**cfg)
+ for imgs in [imgs1, imgs2, imgs3]:
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ feat = outs[-1]
+ expect_feat_shape = (math.ceil(imgs.shape[2] / 32),
+ math.ceil(imgs.shape[3] / 32))
+ self.assertEqual(feat.shape, (3, 256, *expect_feat_shape))
+
+ def test_structure(self):
+ # test drop_path_rate decay
+ cfg = deepcopy(self.cfg)
+ cfg['drop_path_rate'] = 0.2
+ model = VAN(**cfg)
+ depths = model.arch_settings['depths']
+ stages = [model.blocks1, model.blocks2, model.blocks3, model.blocks4]
+ blocks = chain(*[stage for stage in stages])
+ total_depth = sum(depths)
+ dpr = [
+ x.item()
+ for x in torch.linspace(0, cfg['drop_path_rate'], total_depth)
+ ]
+ for i, (block, expect_prob) in enumerate(zip(blocks, dpr)):
+ if expect_prob == 0:
+ assert isinstance(block.drop_path, nn.Identity)
+ else:
+ self.assertAlmostEqual(block.drop_path.drop_prob, expect_prob)
+
+ # test VAN with norm_eval=True
+ cfg = deepcopy(self.cfg)
+ cfg['norm_eval'] = True
+ cfg['norm_cfg'] = dict(type='BN')
+ model = VAN(**cfg)
+ model.init_weights()
+ model.train()
+ self.assertTrue(check_norm_state(model.modules(), False))
+
+ # test VAN with first stage frozen.
+ cfg = deepcopy(self.cfg)
+ frozen_stages = 0
+ cfg['frozen_stages'] = frozen_stages
+ cfg['out_indices'] = (0, 1, 2, 3)
+ model = VAN(**cfg)
+ model.init_weights()
+ model.train()
+
+ # the patch_embed and first stage should not require grad.
+ self.assertFalse(model.patch_embed1.training)
+ for param in model.patch_embed1.parameters():
+ self.assertFalse(param.requires_grad)
+ for i in range(frozen_stages + 1):
+ patch = getattr(model, f'patch_embed{i+1}')
+ for param in patch.parameters():
+ self.assertFalse(param.requires_grad)
+ blocks = getattr(model, f'blocks{i + 1}')
+ for param in blocks.parameters():
+ self.assertFalse(param.requires_grad)
+ norm = getattr(model, f'norm{i + 1}')
+ for param in norm.parameters():
+ self.assertFalse(param.requires_grad)
+
+ # the second stage should require grad.
+ for i in range(frozen_stages + 1, 4):
+ patch = getattr(model, f'patch_embed{i + 1}')
+ for param in patch.parameters():
+ self.assertTrue(param.requires_grad)
+ blocks = getattr(model, f'blocks{i+1}')
+ for param in blocks.parameters():
+ self.assertTrue(param.requires_grad)
+ norm = getattr(model, f'norm{i + 1}')
+ for param in norm.parameters():
+ self.assertTrue(param.requires_grad)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_vgg.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_vgg.py
similarity index 95%
rename from openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_vgg.py
rename to openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_vgg.py
index 7696833e4034c625b835ef68e24e0a136004270f..4e8177922bbe02666b6311acd4d3c7665dbe0663 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/tests/test_backbones/test_vgg.py
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_vgg.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import pytest
import torch
from mmcv.utils.parrots_wrapper import _BatchNorm
@@ -124,7 +125,8 @@ def test_vgg():
imgs = torch.randn(1, 3, 224, 224)
feat = model(imgs)
- assert feat.shape == (1, 512, 7, 7)
+ assert len(feat) == 1
+ assert feat[0].shape == (1, 512, 7, 7)
# Test VGG19 with classification score out forward
model = VGG(19, num_classes=10)
@@ -133,4 +135,5 @@ def test_vgg():
imgs = torch.randn(1, 3, 224, 224)
feat = model(imgs)
- assert feat.shape == (1, 10)
+ assert len(feat) == 1
+ assert feat[0].shape == (1, 10)
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_vision_transformer.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_vision_transformer.py
new file mode 100644
index 0000000000000000000000000000000000000000..26cc73707f43cf42193ddb1fe9c141748a40114a
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/test_vision_transformer.py
@@ -0,0 +1,183 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import math
+import os
+import tempfile
+from copy import deepcopy
+from unittest import TestCase
+
+import torch
+from mmcv.runner import load_checkpoint, save_checkpoint
+
+from mmcls.models.backbones import VisionTransformer
+from .utils import timm_resize_pos_embed
+
+
+class TestVisionTransformer(TestCase):
+
+ def setUp(self):
+ self.cfg = dict(
+ arch='b', img_size=224, patch_size=16, drop_path_rate=0.1)
+
+ def test_structure(self):
+ # Test invalid default arch
+ with self.assertRaisesRegex(AssertionError, 'not in default archs'):
+ cfg = deepcopy(self.cfg)
+ cfg['arch'] = 'unknown'
+ VisionTransformer(**cfg)
+
+ # Test invalid custom arch
+ with self.assertRaisesRegex(AssertionError, 'Custom arch needs'):
+ cfg = deepcopy(self.cfg)
+ cfg['arch'] = {
+ 'num_layers': 24,
+ 'num_heads': 16,
+ 'feedforward_channels': 4096
+ }
+ VisionTransformer(**cfg)
+
+ # Test custom arch
+ cfg = deepcopy(self.cfg)
+ cfg['arch'] = {
+ 'embed_dims': 128,
+ 'num_layers': 24,
+ 'num_heads': 16,
+ 'feedforward_channels': 1024
+ }
+ model = VisionTransformer(**cfg)
+ self.assertEqual(model.embed_dims, 128)
+ self.assertEqual(model.num_layers, 24)
+ for layer in model.layers:
+ self.assertEqual(layer.attn.num_heads, 16)
+ self.assertEqual(layer.ffn.feedforward_channels, 1024)
+
+ # Test out_indices
+ cfg = deepcopy(self.cfg)
+ cfg['out_indices'] = {1: 1}
+ with self.assertRaisesRegex(AssertionError, "get "):
+ VisionTransformer(**cfg)
+ cfg['out_indices'] = [0, 13]
+ with self.assertRaisesRegex(AssertionError, 'Invalid out_indices 13'):
+ VisionTransformer(**cfg)
+
+ # Test model structure
+ cfg = deepcopy(self.cfg)
+ model = VisionTransformer(**cfg)
+ self.assertEqual(len(model.layers), 12)
+ dpr_inc = 0.1 / (12 - 1)
+ dpr = 0
+ for layer in model.layers:
+ self.assertEqual(layer.attn.embed_dims, 768)
+ self.assertEqual(layer.attn.num_heads, 12)
+ self.assertEqual(layer.ffn.feedforward_channels, 3072)
+ self.assertAlmostEqual(layer.attn.out_drop.drop_prob, dpr)
+ self.assertAlmostEqual(layer.ffn.dropout_layer.drop_prob, dpr)
+ dpr += dpr_inc
+
+ def test_init_weights(self):
+ # test weight init cfg
+ cfg = deepcopy(self.cfg)
+ cfg['init_cfg'] = [
+ dict(
+ type='Kaiming',
+ layer='Conv2d',
+ mode='fan_in',
+ nonlinearity='linear')
+ ]
+ model = VisionTransformer(**cfg)
+ ori_weight = model.patch_embed.projection.weight.clone().detach()
+ # The pos_embed is all zero before initialize
+ self.assertTrue(torch.allclose(model.pos_embed, torch.tensor(0.)))
+
+ model.init_weights()
+ initialized_weight = model.patch_embed.projection.weight
+ self.assertFalse(torch.allclose(ori_weight, initialized_weight))
+ self.assertFalse(torch.allclose(model.pos_embed, torch.tensor(0.)))
+
+ # test load checkpoint
+ pretrain_pos_embed = model.pos_embed.clone().detach()
+ tmpdir = tempfile.gettempdir()
+ checkpoint = os.path.join(tmpdir, 'test.pth')
+ save_checkpoint(model, checkpoint)
+ cfg = deepcopy(self.cfg)
+ model = VisionTransformer(**cfg)
+ load_checkpoint(model, checkpoint, strict=True)
+ self.assertTrue(torch.allclose(model.pos_embed, pretrain_pos_embed))
+
+ # test load checkpoint with different img_size
+ cfg = deepcopy(self.cfg)
+ cfg['img_size'] = 384
+ model = VisionTransformer(**cfg)
+ load_checkpoint(model, checkpoint, strict=True)
+ resized_pos_embed = timm_resize_pos_embed(pretrain_pos_embed,
+ model.pos_embed)
+ self.assertTrue(torch.allclose(model.pos_embed, resized_pos_embed))
+
+ os.remove(checkpoint)
+
+ def test_forward(self):
+ imgs = torch.randn(3, 3, 224, 224)
+
+ # test with_cls_token=False
+ cfg = deepcopy(self.cfg)
+ cfg['with_cls_token'] = False
+ cfg['output_cls_token'] = True
+ with self.assertRaisesRegex(AssertionError, 'but got False'):
+ VisionTransformer(**cfg)
+
+ cfg = deepcopy(self.cfg)
+ cfg['with_cls_token'] = False
+ cfg['output_cls_token'] = False
+ model = VisionTransformer(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ patch_token = outs[-1]
+ self.assertEqual(patch_token.shape, (3, 768, 14, 14))
+
+ # test with output_cls_token
+ cfg = deepcopy(self.cfg)
+ model = VisionTransformer(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ patch_token, cls_token = outs[-1]
+ self.assertEqual(patch_token.shape, (3, 768, 14, 14))
+ self.assertEqual(cls_token.shape, (3, 768))
+
+ # test without output_cls_token
+ cfg = deepcopy(self.cfg)
+ cfg['output_cls_token'] = False
+ model = VisionTransformer(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ patch_token = outs[-1]
+ self.assertEqual(patch_token.shape, (3, 768, 14, 14))
+
+ # Test forward with multi out indices
+ cfg = deepcopy(self.cfg)
+ cfg['out_indices'] = [-3, -2, -1]
+ model = VisionTransformer(**cfg)
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 3)
+ for out in outs:
+ patch_token, cls_token = out
+ self.assertEqual(patch_token.shape, (3, 768, 14, 14))
+ self.assertEqual(cls_token.shape, (3, 768))
+
+ # Test forward with dynamic input size
+ imgs1 = torch.randn(3, 3, 224, 224)
+ imgs2 = torch.randn(3, 3, 256, 256)
+ imgs3 = torch.randn(3, 3, 256, 309)
+ cfg = deepcopy(self.cfg)
+ model = VisionTransformer(**cfg)
+ for imgs in [imgs1, imgs2, imgs3]:
+ outs = model(imgs)
+ self.assertIsInstance(outs, tuple)
+ self.assertEqual(len(outs), 1)
+ patch_token, cls_token = outs[-1]
+ expect_feat_shape = (math.ceil(imgs.shape[2] / 16),
+ math.ceil(imgs.shape[3] / 16))
+ self.assertEqual(patch_token.shape, (3, 768, *expect_feat_shape))
+ self.assertEqual(cls_token.shape, (3, 768))
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/utils.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..aba9cafbf8c092ed743d267078937fa992ca05fe
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_backbones/utils.py
@@ -0,0 +1,31 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import math
+
+import torch
+import torch.nn.functional as F
+
+
+def timm_resize_pos_embed(posemb, posemb_new, num_tokens=1, gs_new=()):
+ """Timm version pos embed resize function.
+
+ copied from https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/vision_transformer.py
+ """ # noqa:E501
+ ntok_new = posemb_new.shape[1]
+ if num_tokens:
+ posemb_tok, posemb_grid = posemb[:, :num_tokens], posemb[0,
+ num_tokens:]
+ ntok_new -= num_tokens
+ else:
+ posemb_tok, posemb_grid = posemb[:, :0], posemb[0]
+ gs_old = int(math.sqrt(len(posemb_grid)))
+ if not len(gs_new): # backwards compatibility
+ gs_new = [int(math.sqrt(ntok_new))] * 2
+ assert len(gs_new) >= 2
+ posemb_grid = posemb_grid.reshape(1, gs_old, gs_old,
+ -1).permute(0, 3, 1, 2)
+ posemb_grid = F.interpolate(
+ posemb_grid, size=gs_new, mode='bicubic', align_corners=False)
+ posemb_grid = posemb_grid.permute(0, 2, 3,
+ 1).reshape(1, gs_new[0] * gs_new[1], -1)
+ posemb = torch.cat([posemb_tok, posemb_grid], dim=1)
+ return posemb
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_classifiers.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_classifiers.py
new file mode 100644
index 0000000000000000000000000000000000000000..d021b2fa6fa39911383c36058c9ae094c0262c95
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_classifiers.py
@@ -0,0 +1,326 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import os.path as osp
+import tempfile
+from copy import deepcopy
+
+import numpy as np
+import torch
+from mmcv import ConfigDict
+
+from mmcls.models import CLASSIFIERS
+from mmcls.models.classifiers import ImageClassifier
+
+
+def test_image_classifier():
+ model_cfg = dict(
+ type='ImageClassifier',
+ backbone=dict(
+ type='ResNet_CIFAR',
+ depth=50,
+ num_stages=4,
+ out_indices=(3, ),
+ style='pytorch'),
+ neck=dict(type='GlobalAveragePooling'),
+ head=dict(
+ type='LinearClsHead',
+ num_classes=10,
+ in_channels=2048,
+ loss=dict(type='CrossEntropyLoss')))
+
+ imgs = torch.randn(16, 3, 32, 32)
+ label = torch.randint(0, 10, (16, ))
+
+ model_cfg_ = deepcopy(model_cfg)
+ model = CLASSIFIERS.build(model_cfg_)
+
+ # test property
+ assert model.with_neck
+ assert model.with_head
+
+ # test train_step
+ outputs = model.train_step({'img': imgs, 'gt_label': label}, None)
+ assert outputs['loss'].item() > 0
+ assert outputs['num_samples'] == 16
+
+ # test train_step without optimizer
+ outputs = model.train_step({'img': imgs, 'gt_label': label})
+ assert outputs['loss'].item() > 0
+ assert outputs['num_samples'] == 16
+
+ # test val_step
+ outputs = model.val_step({'img': imgs, 'gt_label': label}, None)
+ assert outputs['loss'].item() > 0
+ assert outputs['num_samples'] == 16
+
+ # test val_step without optimizer
+ outputs = model.val_step({'img': imgs, 'gt_label': label})
+ assert outputs['loss'].item() > 0
+ assert outputs['num_samples'] == 16
+
+ # test forward
+ losses = model(imgs, return_loss=True, gt_label=label)
+ assert losses['loss'].item() > 0
+
+ # test forward_test
+ model_cfg_ = deepcopy(model_cfg)
+ model = CLASSIFIERS.build(model_cfg_)
+ pred = model(imgs, return_loss=False, img_metas=None)
+ assert isinstance(pred, list) and len(pred) == 16
+
+ single_img = torch.randn(1, 3, 32, 32)
+ pred = model(single_img, return_loss=False, img_metas=None)
+ assert isinstance(pred, list) and len(pred) == 1
+
+ pred = model.simple_test(imgs, softmax=False)
+ assert isinstance(pred, list) and len(pred) == 16
+ assert len(pred[0] == 10)
+
+ pred = model.simple_test(imgs, softmax=False, post_process=False)
+ assert isinstance(pred, torch.Tensor)
+ assert pred.shape == (16, 10)
+
+ soft_pred = model.simple_test(imgs, softmax=True, post_process=False)
+ assert isinstance(soft_pred, torch.Tensor)
+ assert soft_pred.shape == (16, 10)
+ torch.testing.assert_allclose(soft_pred, torch.softmax(pred, dim=1))
+
+ # test pretrained
+ model_cfg_ = deepcopy(model_cfg)
+ model_cfg_['pretrained'] = 'checkpoint'
+ model = CLASSIFIERS.build(model_cfg_)
+ assert model.init_cfg == dict(type='Pretrained', checkpoint='checkpoint')
+
+ # test show_result
+ img = np.random.randint(0, 256, (224, 224, 3)).astype(np.uint8)
+ result = dict(pred_class='cat', pred_label=0, pred_score=0.9)
+
+ with tempfile.TemporaryDirectory() as tmpdir:
+ out_file = osp.join(tmpdir, 'out.png')
+ model.show_result(img, result, out_file=out_file)
+ assert osp.exists(out_file)
+
+ with tempfile.TemporaryDirectory() as tmpdir:
+ out_file = osp.join(tmpdir, 'out.png')
+ model.show_result(img, result, out_file=out_file)
+ assert osp.exists(out_file)
+
+
+def test_image_classifier_with_mixup():
+ # Test mixup in ImageClassifier
+ model_cfg = dict(
+ backbone=dict(
+ type='ResNet_CIFAR',
+ depth=50,
+ num_stages=4,
+ out_indices=(3, ),
+ style='pytorch'),
+ neck=dict(type='GlobalAveragePooling'),
+ head=dict(
+ type='MultiLabelLinearClsHead',
+ num_classes=10,
+ in_channels=2048,
+ loss=dict(type='CrossEntropyLoss', loss_weight=1.0,
+ use_soft=True)),
+ train_cfg=dict(
+ augments=dict(
+ type='BatchMixup', alpha=1., num_classes=10, prob=1.)))
+ img_classifier = ImageClassifier(**model_cfg)
+ img_classifier.init_weights()
+ imgs = torch.randn(16, 3, 32, 32)
+ label = torch.randint(0, 10, (16, ))
+
+ losses = img_classifier.forward_train(imgs, label)
+ assert losses['loss'].item() > 0
+
+
+def test_image_classifier_with_cutmix():
+
+ # Test cutmix in ImageClassifier
+ model_cfg = dict(
+ backbone=dict(
+ type='ResNet_CIFAR',
+ depth=50,
+ num_stages=4,
+ out_indices=(3, ),
+ style='pytorch'),
+ neck=dict(type='GlobalAveragePooling'),
+ head=dict(
+ type='MultiLabelLinearClsHead',
+ num_classes=10,
+ in_channels=2048,
+ loss=dict(type='CrossEntropyLoss', loss_weight=1.0,
+ use_soft=True)),
+ train_cfg=dict(
+ augments=dict(
+ type='BatchCutMix', alpha=1., num_classes=10, prob=1.)))
+ img_classifier = ImageClassifier(**model_cfg)
+ img_classifier.init_weights()
+ imgs = torch.randn(16, 3, 32, 32)
+ label = torch.randint(0, 10, (16, ))
+
+ losses = img_classifier.forward_train(imgs, label)
+ assert losses['loss'].item() > 0
+
+
+def test_image_classifier_with_augments():
+
+ imgs = torch.randn(16, 3, 32, 32)
+ label = torch.randint(0, 10, (16, ))
+
+ # Test cutmix and mixup in ImageClassifier
+ model_cfg = dict(
+ backbone=dict(
+ type='ResNet_CIFAR',
+ depth=50,
+ num_stages=4,
+ out_indices=(3, ),
+ style='pytorch'),
+ neck=dict(type='GlobalAveragePooling'),
+ head=dict(
+ type='MultiLabelLinearClsHead',
+ num_classes=10,
+ in_channels=2048,
+ loss=dict(type='CrossEntropyLoss', loss_weight=1.0,
+ use_soft=True)),
+ train_cfg=dict(augments=[
+ dict(type='BatchCutMix', alpha=1., num_classes=10, prob=0.5),
+ dict(type='BatchMixup', alpha=1., num_classes=10, prob=0.3),
+ dict(type='Identity', num_classes=10, prob=0.2)
+ ]))
+ img_classifier = ImageClassifier(**model_cfg)
+ img_classifier.init_weights()
+
+ losses = img_classifier.forward_train(imgs, label)
+ assert losses['loss'].item() > 0
+
+ # Test cutmix with cutmix_minmax in ImageClassifier
+ model_cfg['train_cfg'] = dict(
+ augments=dict(
+ type='BatchCutMix',
+ alpha=1.,
+ num_classes=10,
+ prob=1.,
+ cutmix_minmax=[0.2, 0.8]))
+ img_classifier = ImageClassifier(**model_cfg)
+ img_classifier.init_weights()
+
+ losses = img_classifier.forward_train(imgs, label)
+ assert losses['loss'].item() > 0
+
+ # Test not using train_cfg
+ model_cfg = dict(
+ backbone=dict(
+ type='ResNet_CIFAR',
+ depth=50,
+ num_stages=4,
+ out_indices=(3, ),
+ style='pytorch'),
+ neck=dict(type='GlobalAveragePooling'),
+ head=dict(
+ type='LinearClsHead',
+ num_classes=10,
+ in_channels=2048,
+ loss=dict(type='CrossEntropyLoss', loss_weight=1.0)))
+ img_classifier = ImageClassifier(**model_cfg)
+ img_classifier.init_weights()
+ imgs = torch.randn(16, 3, 32, 32)
+ label = torch.randint(0, 10, (16, ))
+
+ losses = img_classifier.forward_train(imgs, label)
+ assert losses['loss'].item() > 0
+
+ # Test not using cutmix and mixup in ImageClassifier
+ model_cfg['train_cfg'] = dict(augments=None)
+ img_classifier = ImageClassifier(**model_cfg)
+ img_classifier.init_weights()
+
+ losses = img_classifier.forward_train(imgs, label)
+ assert losses['loss'].item() > 0
+
+
+def test_classifier_extract_feat():
+ model_cfg = ConfigDict(
+ type='ImageClassifier',
+ backbone=dict(
+ type='ResNet',
+ depth=18,
+ num_stages=4,
+ out_indices=(0, 1, 2, 3),
+ style='pytorch'),
+ neck=dict(type='GlobalAveragePooling'),
+ head=dict(
+ type='LinearClsHead',
+ num_classes=1000,
+ in_channels=512,
+ loss=dict(type='CrossEntropyLoss'),
+ topk=(1, 5),
+ ))
+
+ model = CLASSIFIERS.build(model_cfg)
+
+ # test backbone output
+ outs = model.extract_feat(torch.rand(1, 3, 224, 224), stage='backbone')
+ assert outs[0].shape == (1, 64, 56, 56)
+ assert outs[1].shape == (1, 128, 28, 28)
+ assert outs[2].shape == (1, 256, 14, 14)
+ assert outs[3].shape == (1, 512, 7, 7)
+
+ # test neck output
+ outs = model.extract_feat(torch.rand(1, 3, 224, 224), stage='neck')
+ assert outs[0].shape == (1, 64)
+ assert outs[1].shape == (1, 128)
+ assert outs[2].shape == (1, 256)
+ assert outs[3].shape == (1, 512)
+
+ # test pre_logits output
+ out = model.extract_feat(torch.rand(1, 3, 224, 224), stage='pre_logits')
+ assert out.shape == (1, 512)
+
+ # test transformer style feature extraction
+ model_cfg = dict(
+ type='ImageClassifier',
+ backbone=dict(
+ type='VisionTransformer', arch='b', out_indices=[-3, -2, -1]),
+ neck=None,
+ head=dict(
+ type='VisionTransformerClsHead',
+ num_classes=1000,
+ in_channels=768,
+ hidden_dim=1024,
+ loss=dict(type='CrossEntropyLoss'),
+ ))
+ model = CLASSIFIERS.build(model_cfg)
+
+ # test backbone output
+ outs = model.extract_feat(torch.rand(1, 3, 224, 224), stage='backbone')
+ for out in outs:
+ patch_token, cls_token = out
+ assert patch_token.shape == (1, 768, 14, 14)
+ assert cls_token.shape == (1, 768)
+
+ # test neck output (the same with backbone)
+ outs = model.extract_feat(torch.rand(1, 3, 224, 224), stage='neck')
+ for out in outs:
+ patch_token, cls_token = out
+ assert patch_token.shape == (1, 768, 14, 14)
+ assert cls_token.shape == (1, 768)
+
+ # test pre_logits output
+ out = model.extract_feat(torch.rand(1, 3, 224, 224), stage='pre_logits')
+ assert out.shape == (1, 1024)
+
+ # test extract_feats
+ multi_imgs = [torch.rand(1, 3, 224, 224) for _ in range(3)]
+ outs = model.extract_feats(multi_imgs)
+ for outs_per_img in outs:
+ for out in outs_per_img:
+ patch_token, cls_token = out
+ assert patch_token.shape == (1, 768, 14, 14)
+ assert cls_token.shape == (1, 768)
+
+ outs = model.extract_feats(multi_imgs, stage='pre_logits')
+ for out_per_img in outs:
+ assert out_per_img.shape == (1, 1024)
+
+ out = model.forward_dummy(torch.rand(1, 3, 224, 224))
+ assert out.shape == (1, 1024)
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_heads.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_heads.py
new file mode 100644
index 0000000000000000000000000000000000000000..e0ecdb6b5c25a92b19b3559442ad1a0934a88cae
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_heads.py
@@ -0,0 +1,400 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from unittest.mock import patch
+
+import pytest
+import torch
+
+from mmcls.models.heads import (ClsHead, ConformerHead, CSRAClsHead,
+ DeiTClsHead, EfficientFormerClsHead,
+ LinearClsHead, MultiLabelClsHead,
+ MultiLabelLinearClsHead, StackedLinearClsHead,
+ VisionTransformerClsHead)
+
+
+@pytest.mark.parametrize('feat', [torch.rand(4, 10), (torch.rand(4, 10), )])
+def test_cls_head(feat):
+ fake_gt_label = torch.randint(0, 10, (4, ))
+
+ # test forward_train with cal_acc=True
+ head = ClsHead(cal_acc=True)
+ losses = head.forward_train(feat, fake_gt_label)
+ assert losses['loss'].item() > 0
+ assert 'accuracy' in losses
+
+ # test forward_train with cal_acc=False
+ head = ClsHead()
+ losses = head.forward_train(feat, fake_gt_label)
+ assert losses['loss'].item() > 0
+
+ # test forward_train with weight
+ weight = torch.tensor([0.5, 0.5, 0.5, 0.5])
+ losses_ = head.forward_train(feat, fake_gt_label)
+ losses = head.forward_train(feat, fake_gt_label, weight=weight)
+ assert losses['loss'].item() == losses_['loss'].item() * 0.5
+
+ # test simple_test with post_process
+ pred = head.simple_test(feat)
+ assert isinstance(pred, list) and len(pred) == 4
+ with patch('torch.onnx.is_in_onnx_export', return_value=True):
+ pred = head.simple_test(feat)
+ assert pred.shape == (4, 10)
+
+ # test simple_test without post_process
+ pred = head.simple_test(feat, post_process=False)
+ assert isinstance(pred, torch.Tensor) and pred.shape == (4, 10)
+ logits = head.simple_test(feat, softmax=False, post_process=False)
+ torch.testing.assert_allclose(pred, torch.softmax(logits, dim=1))
+
+ # test pre_logits
+ features = head.pre_logits(feat)
+ if isinstance(feat, tuple):
+ torch.testing.assert_allclose(features, feat[0])
+ else:
+ torch.testing.assert_allclose(features, feat)
+
+
+@pytest.mark.parametrize('feat', [torch.rand(4, 3), (torch.rand(4, 3), )])
+def test_linear_head(feat):
+
+ fake_gt_label = torch.randint(0, 10, (4, ))
+
+ # test LinearClsHead forward
+ head = LinearClsHead(10, 3)
+ losses = head.forward_train(feat, fake_gt_label)
+ assert losses['loss'].item() > 0
+
+ # test init weights
+ head = LinearClsHead(10, 3)
+ head.init_weights()
+ assert abs(head.fc.weight).sum() > 0
+
+ # test simple_test with post_process
+ pred = head.simple_test(feat)
+ assert isinstance(pred, list) and len(pred) == 4
+ with patch('torch.onnx.is_in_onnx_export', return_value=True):
+ pred = head.simple_test(feat)
+ assert pred.shape == (4, 10)
+
+ # test simple_test without post_process
+ pred = head.simple_test(feat, post_process=False)
+ assert isinstance(pred, torch.Tensor) and pred.shape == (4, 10)
+ logits = head.simple_test(feat, softmax=False, post_process=False)
+ torch.testing.assert_allclose(pred, torch.softmax(logits, dim=1))
+
+ # test pre_logits
+ features = head.pre_logits(feat)
+ if isinstance(feat, tuple):
+ torch.testing.assert_allclose(features, feat[0])
+ else:
+ torch.testing.assert_allclose(features, feat)
+
+
+@pytest.mark.parametrize('feat', [torch.rand(4, 10), (torch.rand(4, 10), )])
+def test_multilabel_head(feat):
+ head = MultiLabelClsHead()
+ fake_gt_label = torch.randint(0, 2, (4, 10))
+
+ losses = head.forward_train(feat, fake_gt_label)
+ assert losses['loss'].item() > 0
+
+ # test simple_test with post_process
+ pred = head.simple_test(feat)
+ assert isinstance(pred, list) and len(pred) == 4
+ with patch('torch.onnx.is_in_onnx_export', return_value=True):
+ pred = head.simple_test(feat)
+ assert pred.shape == (4, 10)
+
+ # test simple_test without post_process
+ pred = head.simple_test(feat, post_process=False)
+ assert isinstance(pred, torch.Tensor) and pred.shape == (4, 10)
+ logits = head.simple_test(feat, sigmoid=False, post_process=False)
+ torch.testing.assert_allclose(pred, torch.sigmoid(logits))
+
+ # test pre_logits
+ features = head.pre_logits(feat)
+ if isinstance(feat, tuple):
+ torch.testing.assert_allclose(features, feat[0])
+ else:
+ torch.testing.assert_allclose(features, feat)
+
+
+@pytest.mark.parametrize('feat', [torch.rand(4, 5), (torch.rand(4, 5), )])
+def test_multilabel_linear_head(feat):
+ head = MultiLabelLinearClsHead(10, 5)
+ fake_gt_label = torch.randint(0, 2, (4, 10))
+
+ head.init_weights()
+ losses = head.forward_train(feat, fake_gt_label)
+ assert losses['loss'].item() > 0
+
+ # test simple_test with post_process
+ pred = head.simple_test(feat)
+ assert isinstance(pred, list) and len(pred) == 4
+ with patch('torch.onnx.is_in_onnx_export', return_value=True):
+ pred = head.simple_test(feat)
+ assert pred.shape == (4, 10)
+
+ # test simple_test without post_process
+ pred = head.simple_test(feat, post_process=False)
+ assert isinstance(pred, torch.Tensor) and pred.shape == (4, 10)
+ logits = head.simple_test(feat, sigmoid=False, post_process=False)
+ torch.testing.assert_allclose(pred, torch.sigmoid(logits))
+
+ # test pre_logits
+ features = head.pre_logits(feat)
+ if isinstance(feat, tuple):
+ torch.testing.assert_allclose(features, feat[0])
+ else:
+ torch.testing.assert_allclose(features, feat)
+
+
+@pytest.mark.parametrize('feat', [torch.rand(4, 5), (torch.rand(4, 5), )])
+def test_stacked_linear_cls_head(feat):
+ # test assertion
+ with pytest.raises(AssertionError):
+ StackedLinearClsHead(num_classes=3, in_channels=5, mid_channels=10)
+
+ with pytest.raises(AssertionError):
+ StackedLinearClsHead(num_classes=-1, in_channels=5, mid_channels=[10])
+
+ fake_gt_label = torch.randint(0, 2, (4, )) # B, num_classes
+
+ # test forward with default setting
+ head = StackedLinearClsHead(
+ num_classes=10, in_channels=5, mid_channels=[20])
+ head.init_weights()
+
+ losses = head.forward_train(feat, fake_gt_label)
+ assert losses['loss'].item() > 0
+
+ # test simple_test with post_process
+ pred = head.simple_test(feat)
+ assert isinstance(pred, list) and len(pred) == 4
+ with patch('torch.onnx.is_in_onnx_export', return_value=True):
+ pred = head.simple_test(feat)
+ assert pred.shape == (4, 10)
+
+ # test simple_test without post_process
+ pred = head.simple_test(feat, post_process=False)
+ assert isinstance(pred, torch.Tensor) and pred.shape == (4, 10)
+ logits = head.simple_test(feat, softmax=False, post_process=False)
+ torch.testing.assert_allclose(pred, torch.softmax(logits, dim=1))
+
+ # test pre_logits
+ features = head.pre_logits(feat)
+ assert features.shape == (4, 20)
+
+ # test forward with full function
+ head = StackedLinearClsHead(
+ num_classes=3,
+ in_channels=5,
+ mid_channels=[8, 10],
+ dropout_rate=0.2,
+ norm_cfg=dict(type='BN1d'),
+ act_cfg=dict(type='HSwish'))
+ head.init_weights()
+
+ losses = head.forward_train(feat, fake_gt_label)
+ assert losses['loss'].item() > 0
+
+
+def test_vit_head():
+ fake_features = ([torch.rand(4, 7, 7, 16), torch.rand(4, 100)], )
+ fake_gt_label = torch.randint(0, 10, (4, ))
+
+ # test vit head forward
+ head = VisionTransformerClsHead(10, 100)
+ losses = head.forward_train(fake_features, fake_gt_label)
+ assert not hasattr(head.layers, 'pre_logits')
+ assert not hasattr(head.layers, 'act')
+ assert losses['loss'].item() > 0
+
+ # test vit head forward with hidden layer
+ head = VisionTransformerClsHead(10, 100, hidden_dim=20)
+ losses = head.forward_train(fake_features, fake_gt_label)
+ assert hasattr(head.layers, 'pre_logits') and hasattr(head.layers, 'act')
+ assert losses['loss'].item() > 0
+
+ # test vit head init_weights
+ head = VisionTransformerClsHead(10, 100, hidden_dim=20)
+ head.init_weights()
+ assert abs(head.layers.pre_logits.weight).sum() > 0
+
+ head = VisionTransformerClsHead(10, 100, hidden_dim=20)
+ # test simple_test with post_process
+ pred = head.simple_test(fake_features)
+ assert isinstance(pred, list) and len(pred) == 4
+ with patch('torch.onnx.is_in_onnx_export', return_value=True):
+ pred = head.simple_test(fake_features)
+ assert pred.shape == (4, 10)
+
+ # test simple_test without post_process
+ pred = head.simple_test(fake_features, post_process=False)
+ assert isinstance(pred, torch.Tensor) and pred.shape == (4, 10)
+ logits = head.simple_test(fake_features, softmax=False, post_process=False)
+ torch.testing.assert_allclose(pred, torch.softmax(logits, dim=1))
+
+ # test pre_logits
+ features = head.pre_logits(fake_features)
+ assert features.shape == (4, 20)
+
+ # test assertion
+ with pytest.raises(ValueError):
+ VisionTransformerClsHead(-1, 100)
+
+
+def test_conformer_head():
+ fake_features = ([torch.rand(4, 64), torch.rand(4, 96)], )
+ fake_gt_label = torch.randint(0, 10, (4, ))
+
+ # test conformer head forward
+ head = ConformerHead(num_classes=10, in_channels=[64, 96])
+ losses = head.forward_train(fake_features, fake_gt_label)
+ assert losses['loss'].item() > 0
+
+ # test simple_test with post_process
+ pred = head.simple_test(fake_features)
+ assert isinstance(pred, list) and len(pred) == 4
+ with patch('torch.onnx.is_in_onnx_export', return_value=True):
+ pred = head.simple_test(fake_features)
+ assert pred.shape == (4, 10)
+
+ # test simple_test without post_process
+ pred = head.simple_test(fake_features, post_process=False)
+ assert isinstance(pred, torch.Tensor) and pred.shape == (4, 10)
+ logits = head.simple_test(fake_features, softmax=False, post_process=False)
+ torch.testing.assert_allclose(pred, torch.softmax(sum(logits), dim=1))
+
+ # test pre_logits
+ features = head.pre_logits(fake_features)
+ assert features is fake_features[0]
+
+
+def test_deit_head():
+ fake_features = ([
+ torch.rand(4, 7, 7, 16),
+ torch.rand(4, 100),
+ torch.rand(4, 100)
+ ], )
+ fake_gt_label = torch.randint(0, 10, (4, ))
+
+ # test deit head forward
+ head = DeiTClsHead(num_classes=10, in_channels=100)
+ losses = head.forward_train(fake_features, fake_gt_label)
+ assert not hasattr(head.layers, 'pre_logits')
+ assert not hasattr(head.layers, 'act')
+ assert losses['loss'].item() > 0
+
+ # test deit head forward with hidden layer
+ head = DeiTClsHead(num_classes=10, in_channels=100, hidden_dim=20)
+ losses = head.forward_train(fake_features, fake_gt_label)
+ assert hasattr(head.layers, 'pre_logits') and hasattr(head.layers, 'act')
+ assert losses['loss'].item() > 0
+
+ # test deit head init_weights
+ head = DeiTClsHead(10, 100, hidden_dim=20)
+ head.init_weights()
+ assert abs(head.layers.pre_logits.weight).sum() > 0
+
+ head = DeiTClsHead(10, 100, hidden_dim=20)
+ # test simple_test with post_process
+ pred = head.simple_test(fake_features)
+ assert isinstance(pred, list) and len(pred) == 4
+ with patch('torch.onnx.is_in_onnx_export', return_value=True):
+ pred = head.simple_test(fake_features)
+ assert pred.shape == (4, 10)
+
+ # test simple_test without post_process
+ pred = head.simple_test(fake_features, post_process=False)
+ assert isinstance(pred, torch.Tensor) and pred.shape == (4, 10)
+ logits = head.simple_test(fake_features, softmax=False, post_process=False)
+ torch.testing.assert_allclose(pred, torch.softmax(logits, dim=1))
+
+ # test pre_logits
+ cls_token, dist_token = head.pre_logits(fake_features)
+ assert cls_token.shape == (4, 20)
+ assert dist_token.shape == (4, 20)
+
+ # test assertion
+ with pytest.raises(ValueError):
+ DeiTClsHead(-1, 100)
+
+
+def test_efficientformer_head():
+ fake_features = (torch.rand(4, 64), )
+ fake_gt_label = torch.randint(0, 10, (4, ))
+
+ # Test without distillation head
+ head = EfficientFormerClsHead(
+ num_classes=10, in_channels=64, distillation=False)
+
+ # test EfficientFormer head forward
+ losses = head.forward_train(fake_features, fake_gt_label)
+ assert losses['loss'].item() > 0
+
+ # test simple_test with post_process
+ pred = head.simple_test(fake_features)
+ assert isinstance(pred, list) and len(pred) == 4
+ with patch('torch.onnx.is_in_onnx_export', return_value=True):
+ pred = head.simple_test(fake_features)
+ assert pred.shape == (4, 10)
+
+ # test simple_test without post_process
+ pred = head.simple_test(fake_features, post_process=False)
+ assert isinstance(pred, torch.Tensor) and pred.shape == (4, 10)
+ logits = head.simple_test(fake_features, softmax=False, post_process=False)
+ torch.testing.assert_allclose(pred, torch.softmax(logits, dim=1))
+
+ # test pre_logits
+ features = head.pre_logits(fake_features)
+ assert features is fake_features[0]
+
+ # Test without distillation head
+ head = EfficientFormerClsHead(num_classes=10, in_channels=64)
+ assert hasattr(head, 'head')
+ assert hasattr(head, 'dist_head')
+
+ # Test loss
+ with pytest.raises(NotImplementedError):
+ losses = head.forward_train(fake_features, fake_gt_label)
+
+ # test simple_test with post_process
+ pred = head.simple_test(fake_features)
+ assert isinstance(pred, list) and len(pred) == 4
+ with patch('torch.onnx.is_in_onnx_export', return_value=True):
+ pred = head.simple_test(fake_features)
+ assert pred.shape == (4, 10)
+
+ # test simple_test without post_process
+ pred = head.simple_test(fake_features, post_process=False)
+ assert isinstance(pred, torch.Tensor) and pred.shape == (4, 10)
+ logits = head.simple_test(fake_features, softmax=False, post_process=False)
+ torch.testing.assert_allclose(pred, torch.softmax(logits, dim=1))
+
+ # test pre_logits
+ features = head.pre_logits(fake_features)
+ assert features is fake_features[0]
+
+
+@pytest.mark.parametrize(
+ 'feat', [torch.rand(4, 20, 20, 30), (torch.rand(4, 20, 20, 30), )])
+def test_csra_head(feat):
+ head = CSRAClsHead(num_classes=10, in_channels=20, num_heads=1, lam=0.1)
+ fake_gt_label = torch.randint(0, 2, (4, 10))
+
+ losses = head.forward_train(feat, fake_gt_label)
+ assert losses['loss'].item() > 0
+
+ # test simple_test with post_process
+ pred = head.simple_test(feat)
+ assert isinstance(pred, list) and len(pred) == 4
+ with patch('torch.onnx.is_in_onnx_export', return_value=True):
+ pred = head.simple_test(feat)
+ assert pred.shape == (4, 10)
+
+ # test pre_logits
+ features = head.pre_logits(feat)
+ if isinstance(feat, tuple):
+ torch.testing.assert_allclose(features, feat[0])
+ else:
+ torch.testing.assert_allclose(features, feat)
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_neck.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_neck.py
new file mode 100644
index 0000000000000000000000000000000000000000..b554e3dae45c34f441eb2afce2fdf5fc8b8af237
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_neck.py
@@ -0,0 +1,87 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import pytest
+import torch
+
+from mmcls.models.necks import (GeneralizedMeanPooling, GlobalAveragePooling,
+ HRFuseScales)
+
+
+def test_gap_neck():
+
+ # test 1d gap_neck
+ neck = GlobalAveragePooling(dim=1)
+ # batch_size, num_features, feature_size
+ fake_input = torch.rand(1, 16, 24)
+
+ output = neck(fake_input)
+ # batch_size, num_features
+ assert output.shape == (1, 16)
+
+ # test 1d gap_neck
+ neck = GlobalAveragePooling(dim=2)
+ # batch_size, num_features, feature_size(2)
+ fake_input = torch.rand(1, 16, 24, 24)
+
+ output = neck(fake_input)
+ # batch_size, num_features
+ assert output.shape == (1, 16)
+
+ # test 1d gap_neck
+ neck = GlobalAveragePooling(dim=3)
+ # batch_size, num_features, feature_size(3)
+ fake_input = torch.rand(1, 16, 24, 24, 5)
+
+ output = neck(fake_input)
+ # batch_size, num_features
+ assert output.shape == (1, 16)
+
+ with pytest.raises(AssertionError):
+ # dim must in [1, 2, 3]
+ GlobalAveragePooling(dim='other')
+
+
+def test_gem_neck():
+
+ # test gem_neck
+ neck = GeneralizedMeanPooling()
+ # batch_size, num_features, feature_size(2)
+ fake_input = torch.rand(1, 16, 24, 24)
+
+ output = neck(fake_input)
+ # batch_size, num_features
+ assert output.shape == (1, 16)
+
+ # test tuple input gem_neck
+ neck = GeneralizedMeanPooling()
+ # batch_size, num_features, feature_size(2)
+ fake_input = (torch.rand(1, 8, 24, 24), torch.rand(1, 16, 24, 24))
+
+ output = neck(fake_input)
+ # batch_size, num_features
+ assert output[0].shape == (1, 8)
+ assert output[1].shape == (1, 16)
+
+ with pytest.raises(AssertionError):
+ # p must be a value greater then 1
+ GeneralizedMeanPooling(p=0.5)
+
+
+def test_hr_fuse_scales():
+
+ in_channels = (18, 32, 64, 128)
+ neck = HRFuseScales(in_channels=in_channels, out_channels=1024)
+
+ feat_size = 56
+ inputs = []
+ for in_channel in in_channels:
+ input_tensor = torch.rand(3, in_channel, feat_size, feat_size)
+ inputs.append(input_tensor)
+ feat_size = feat_size // 2
+
+ with pytest.raises(AssertionError):
+ neck(inputs)
+
+ outs = neck(tuple(inputs))
+ assert isinstance(outs, tuple)
+ assert len(outs) == 1
+ assert outs[0].shape == (3, 1024, 7, 7)
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_utils/test_attention.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_utils/test_attention.py
new file mode 100644
index 0000000000000000000000000000000000000000..cc37d13415dbcb725dc06360d6ff7b3987493310
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_utils/test_attention.py
@@ -0,0 +1,208 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from functools import partial
+from unittest import TestCase
+from unittest.mock import ANY, MagicMock
+
+import pytest
+import torch
+from mmcv.utils import TORCH_VERSION, digit_version
+
+from mmcls.models.utils.attention import ShiftWindowMSA, WindowMSA
+
+if digit_version(TORCH_VERSION) >= digit_version('1.10.0a0'):
+ torch_meshgrid_ij = partial(torch.meshgrid, indexing='ij')
+else:
+ torch_meshgrid_ij = torch.meshgrid # Uses indexing='ij' by default
+
+
+def get_relative_position_index(window_size):
+ """Method from original code of Swin-Transformer."""
+ coords_h = torch.arange(window_size[0])
+ coords_w = torch.arange(window_size[1])
+ coords = torch.stack(torch_meshgrid_ij([coords_h, coords_w])) # 2, Wh, Ww
+ coords_flatten = torch.flatten(coords, 1) # 2, Wh*Ww
+ # 2, Wh*Ww, Wh*Ww
+ relative_coords = coords_flatten[:, :, None] - coords_flatten[:, None, :]
+ # Wh*Ww, Wh*Ww, 2
+ relative_coords = relative_coords.permute(1, 2, 0).contiguous()
+ relative_coords[:, :, 0] += window_size[0] - 1 # shift to start from 0
+ relative_coords[:, :, 1] += window_size[1] - 1
+ relative_coords[:, :, 0] *= 2 * window_size[1] - 1
+ relative_position_index = relative_coords.sum(-1) # Wh*Ww, Wh*Ww
+ return relative_position_index
+
+
+class TestWindowMSA(TestCase):
+
+ def test_forward(self):
+ attn = WindowMSA(embed_dims=96, window_size=(7, 7), num_heads=4)
+ inputs = torch.rand((16, 7 * 7, 96))
+ output = attn(inputs)
+ self.assertEqual(output.shape, inputs.shape)
+
+ # test non-square window_size
+ attn = WindowMSA(embed_dims=96, window_size=(6, 7), num_heads=4)
+ inputs = torch.rand((16, 6 * 7, 96))
+ output = attn(inputs)
+ self.assertEqual(output.shape, inputs.shape)
+
+ def test_relative_pos_embed(self):
+ attn = WindowMSA(embed_dims=96, window_size=(7, 8), num_heads=4)
+ self.assertEqual(attn.relative_position_bias_table.shape,
+ ((2 * 7 - 1) * (2 * 8 - 1), 4))
+ # test relative_position_index
+ expected_rel_pos_index = get_relative_position_index((7, 8))
+ self.assertTrue(
+ torch.allclose(attn.relative_position_index,
+ expected_rel_pos_index))
+
+ # test default init
+ self.assertTrue(
+ torch.allclose(attn.relative_position_bias_table,
+ torch.tensor(0.)))
+ attn.init_weights()
+ self.assertFalse(
+ torch.allclose(attn.relative_position_bias_table,
+ torch.tensor(0.)))
+
+ def test_qkv_bias(self):
+ # test qkv_bias=True
+ attn = WindowMSA(
+ embed_dims=96, window_size=(7, 7), num_heads=4, qkv_bias=True)
+ self.assertEqual(attn.qkv.bias.shape, (96 * 3, ))
+
+ # test qkv_bias=False
+ attn = WindowMSA(
+ embed_dims=96, window_size=(7, 7), num_heads=4, qkv_bias=False)
+ self.assertIsNone(attn.qkv.bias)
+
+ def tets_qk_scale(self):
+ # test default qk_scale
+ attn = WindowMSA(
+ embed_dims=96, window_size=(7, 7), num_heads=4, qk_scale=None)
+ head_dims = 96 // 4
+ self.assertAlmostEqual(attn.scale, head_dims**-0.5)
+
+ # test specified qk_scale
+ attn = WindowMSA(
+ embed_dims=96, window_size=(7, 7), num_heads=4, qk_scale=0.3)
+ self.assertEqual(attn.scale, 0.3)
+
+ def test_attn_drop(self):
+ inputs = torch.rand(16, 7 * 7, 96)
+ attn = WindowMSA(
+ embed_dims=96, window_size=(7, 7), num_heads=4, attn_drop=1.0)
+ # drop all attn output, output shuold be equal to proj.bias
+ self.assertTrue(torch.allclose(attn(inputs), attn.proj.bias))
+
+ def test_prob_drop(self):
+ inputs = torch.rand(16, 7 * 7, 96)
+ attn = WindowMSA(
+ embed_dims=96, window_size=(7, 7), num_heads=4, proj_drop=1.0)
+ self.assertTrue(torch.allclose(attn(inputs), torch.tensor(0.)))
+
+ def test_mask(self):
+ inputs = torch.rand(16, 7 * 7, 96)
+ attn = WindowMSA(embed_dims=96, window_size=(7, 7), num_heads=4)
+ mask = torch.zeros((4, 49, 49))
+ # Mask the first column
+ mask[:, 0, :] = -100
+ mask[:, :, 0] = -100
+ outs = attn(inputs, mask=mask)
+ inputs[:, 0, :].normal_()
+ outs_with_mask = attn(inputs, mask=mask)
+ torch.testing.assert_allclose(outs[:, 1:, :], outs_with_mask[:, 1:, :])
+
+
+class TestShiftWindowMSA(TestCase):
+
+ def test_forward(self):
+ inputs = torch.rand((1, 14 * 14, 96))
+ attn = ShiftWindowMSA(embed_dims=96, window_size=7, num_heads=4)
+ output = attn(inputs, (14, 14))
+ self.assertEqual(output.shape, inputs.shape)
+ self.assertEqual(attn.w_msa.relative_position_bias_table.shape,
+ ((2 * 7 - 1)**2, 4))
+
+ # test forward with shift_size
+ attn = ShiftWindowMSA(
+ embed_dims=96, window_size=7, num_heads=4, shift_size=3)
+ output = attn(inputs, (14, 14))
+ assert output.shape == (inputs.shape)
+
+ # test irregular input shape
+ input_resolution = (19, 18)
+ attn = ShiftWindowMSA(embed_dims=96, num_heads=4, window_size=7)
+ inputs = torch.rand((1, 19 * 18, 96))
+ output = attn(inputs, input_resolution)
+ assert output.shape == (inputs.shape)
+
+ # test wrong input_resolution
+ input_resolution = (14, 14)
+ attn = ShiftWindowMSA(embed_dims=96, num_heads=4, window_size=7)
+ inputs = torch.rand((1, 14 * 14, 96))
+ with pytest.raises(AssertionError):
+ attn(inputs, (14, 15))
+
+ def test_pad_small_map(self):
+ # test pad_small_map=True
+ inputs = torch.rand((1, 6 * 7, 96))
+ attn = ShiftWindowMSA(
+ embed_dims=96,
+ window_size=7,
+ num_heads=4,
+ shift_size=3,
+ pad_small_map=True)
+ attn.get_attn_mask = MagicMock(wraps=attn.get_attn_mask)
+ output = attn(inputs, (6, 7))
+ self.assertEqual(output.shape, inputs.shape)
+ attn.get_attn_mask.assert_called_once_with((7, 7),
+ window_size=7,
+ shift_size=3,
+ device=ANY)
+
+ # test pad_small_map=False
+ inputs = torch.rand((1, 6 * 7, 96))
+ attn = ShiftWindowMSA(
+ embed_dims=96,
+ window_size=7,
+ num_heads=4,
+ shift_size=3,
+ pad_small_map=False)
+ with self.assertRaisesRegex(AssertionError, r'the window size \(7\)'):
+ attn(inputs, (6, 7))
+
+ # test pad_small_map=False, and the input size equals to window size
+ inputs = torch.rand((1, 7 * 7, 96))
+ attn.get_attn_mask = MagicMock(wraps=attn.get_attn_mask)
+ output = attn(inputs, (7, 7))
+ self.assertEqual(output.shape, inputs.shape)
+ attn.get_attn_mask.assert_called_once_with((7, 7),
+ window_size=7,
+ shift_size=0,
+ device=ANY)
+
+ def test_drop_layer(self):
+ inputs = torch.rand((1, 14 * 14, 96))
+ attn = ShiftWindowMSA(
+ embed_dims=96,
+ window_size=7,
+ num_heads=4,
+ dropout_layer=dict(type='Dropout', drop_prob=1.0))
+ attn.init_weights()
+ # drop all attn output, output shuold be equal to proj.bias
+ self.assertTrue(
+ torch.allclose(attn(inputs, (14, 14)), torch.tensor(0.)))
+
+ def test_deprecation(self):
+ # test deprecated arguments
+ with pytest.warns(DeprecationWarning):
+ ShiftWindowMSA(
+ embed_dims=96,
+ num_heads=4,
+ window_size=7,
+ input_resolution=(14, 14))
+
+ with pytest.warns(DeprecationWarning):
+ ShiftWindowMSA(
+ embed_dims=96, num_heads=4, window_size=7, auto_pad=True)
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_utils/test_augment.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_utils/test_augment.py
new file mode 100644
index 0000000000000000000000000000000000000000..d1987fae545965642a90b6b4462e16b04eb95c56
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_utils/test_augment.py
@@ -0,0 +1,96 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import pytest
+import torch
+
+from mmcls.models.utils import Augments
+
+augment_cfgs = [
+ dict(type='BatchCutMix', alpha=1., prob=1.),
+ dict(type='BatchMixup', alpha=1., prob=1.),
+ dict(type='Identity', prob=1.),
+ dict(type='BatchResizeMix', alpha=1., prob=1.)
+]
+
+
+def test_augments():
+ imgs = torch.randn(4, 3, 32, 32)
+ labels = torch.randint(0, 10, (4, ))
+
+ # Test cutmix
+ augments_cfg = dict(type='BatchCutMix', alpha=1., num_classes=10, prob=1.)
+ augs = Augments(augments_cfg)
+ mixed_imgs, mixed_labels = augs(imgs, labels)
+ assert mixed_imgs.shape == torch.Size((4, 3, 32, 32))
+ assert mixed_labels.shape == torch.Size((4, 10))
+
+ # Test mixup
+ augments_cfg = dict(type='BatchMixup', alpha=1., num_classes=10, prob=1.)
+ augs = Augments(augments_cfg)
+ mixed_imgs, mixed_labels = augs(imgs, labels)
+ assert mixed_imgs.shape == torch.Size((4, 3, 32, 32))
+ assert mixed_labels.shape == torch.Size((4, 10))
+
+ # Test resizemix
+ augments_cfg = dict(
+ type='BatchResizeMix', alpha=1., num_classes=10, prob=1.)
+ augs = Augments(augments_cfg)
+ mixed_imgs, mixed_labels = augs(imgs, labels)
+ assert mixed_imgs.shape == torch.Size((4, 3, 32, 32))
+ assert mixed_labels.shape == torch.Size((4, 10))
+
+ # Test cutmixup
+ augments_cfg = [
+ dict(type='BatchCutMix', alpha=1., num_classes=10, prob=0.5),
+ dict(type='BatchMixup', alpha=1., num_classes=10, prob=0.3)
+ ]
+ augs = Augments(augments_cfg)
+ mixed_imgs, mixed_labels = augs(imgs, labels)
+ assert mixed_imgs.shape == torch.Size((4, 3, 32, 32))
+ assert mixed_labels.shape == torch.Size((4, 10))
+
+ augments_cfg = [
+ dict(type='BatchCutMix', alpha=1., num_classes=10, prob=0.5),
+ dict(type='BatchMixup', alpha=1., num_classes=10, prob=0.5)
+ ]
+ augs = Augments(augments_cfg)
+ mixed_imgs, mixed_labels = augs(imgs, labels)
+ assert mixed_imgs.shape == torch.Size((4, 3, 32, 32))
+ assert mixed_labels.shape == torch.Size((4, 10))
+
+ augments_cfg = [
+ dict(type='BatchCutMix', alpha=1., num_classes=10, prob=0.5),
+ dict(type='BatchMixup', alpha=1., num_classes=10, prob=0.3),
+ dict(type='Identity', num_classes=10, prob=0.2)
+ ]
+ augs = Augments(augments_cfg)
+ mixed_imgs, mixed_labels = augs(imgs, labels)
+ assert mixed_imgs.shape == torch.Size((4, 3, 32, 32))
+ assert mixed_labels.shape == torch.Size((4, 10))
+
+
+@pytest.mark.parametrize('cfg', augment_cfgs)
+def test_binary_augment(cfg):
+
+ cfg_ = dict(num_classes=1, **cfg)
+ augs = Augments(cfg_)
+
+ imgs = torch.randn(4, 3, 32, 32)
+ labels = torch.randint(0, 2, (4, 1)).float()
+
+ mixed_imgs, mixed_labels = augs(imgs, labels)
+ assert mixed_imgs.shape == torch.Size((4, 3, 32, 32))
+ assert mixed_labels.shape == torch.Size((4, 1))
+
+
+@pytest.mark.parametrize('cfg', augment_cfgs)
+def test_multilabel_augment(cfg):
+
+ cfg_ = dict(num_classes=10, **cfg)
+ augs = Augments(cfg_)
+
+ imgs = torch.randn(4, 3, 32, 32)
+ labels = torch.randint(0, 2, (4, 10)).float()
+
+ mixed_imgs, mixed_labels = augs(imgs, labels)
+ assert mixed_imgs.shape == torch.Size((4, 3, 32, 32))
+ assert mixed_labels.shape == torch.Size((4, 10))
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_utils/test_embed.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_utils/test_embed.py
new file mode 100644
index 0000000000000000000000000000000000000000..eb7356b1f09cdd8192102571020f093ccac50347
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_utils/test_embed.py
@@ -0,0 +1,88 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import pytest
+import torch
+
+from mmcls.models.backbones import VGG
+from mmcls.models.utils import HybridEmbed, PatchEmbed, PatchMerging
+
+
+def cal_unfold_dim(dim, kernel_size, stride, padding=0, dilation=1):
+ return (dim + 2 * padding - dilation * (kernel_size - 1) - 1) // stride + 1
+
+
+def test_patch_embed():
+ # Test PatchEmbed
+ patch_embed = PatchEmbed()
+ img = torch.randn(1, 3, 224, 224)
+ img = patch_embed(img)
+ assert img.shape == torch.Size((1, 196, 768))
+
+ # Test PatchEmbed with stride = 8
+ conv_cfg = dict(kernel_size=16, stride=8)
+ patch_embed = PatchEmbed(conv_cfg=conv_cfg)
+ img = torch.randn(1, 3, 224, 224)
+ img = patch_embed(img)
+ assert img.shape == torch.Size((1, 729, 768))
+
+
+def test_hybrid_embed():
+ # Test VGG11 HybridEmbed
+ backbone = VGG(11, norm_eval=True)
+ backbone.init_weights()
+ patch_embed = HybridEmbed(backbone)
+ img = torch.randn(1, 3, 224, 224)
+ img = patch_embed(img)
+ assert img.shape == torch.Size((1, 49, 768))
+
+
+def test_patch_merging():
+ settings = dict(in_channels=16, out_channels=32, padding=0)
+ downsample = PatchMerging(**settings)
+
+ # test forward with wrong dims
+ with pytest.raises(AssertionError):
+ inputs = torch.rand((1, 16, 56 * 56))
+ downsample(inputs, input_size=(56, 56))
+
+ # test patch merging forward
+ inputs = torch.rand((1, 56 * 56, 16))
+ out, output_size = downsample(inputs, input_size=(56, 56))
+ assert output_size == (28, 28)
+ assert out.shape == (1, 28 * 28, 32)
+
+ # test different kernel_size in each direction
+ downsample = PatchMerging(kernel_size=(2, 3), **settings)
+ out, output_size = downsample(inputs, input_size=(56, 56))
+ expected_dim = cal_unfold_dim(56, 2, 2) * cal_unfold_dim(56, 3, 3)
+ assert downsample.sampler.kernel_size == (2, 3)
+ assert output_size == (cal_unfold_dim(56, 2, 2), cal_unfold_dim(56, 3, 3))
+ assert out.shape == (1, expected_dim, 32)
+
+ # test default stride
+ downsample = PatchMerging(kernel_size=6, **settings)
+ assert downsample.sampler.stride == (6, 6)
+
+ # test stride=3
+ downsample = PatchMerging(kernel_size=6, stride=3, **settings)
+ out, output_size = downsample(inputs, input_size=(56, 56))
+ assert downsample.sampler.stride == (3, 3)
+ assert out.shape == (1, cal_unfold_dim(56, 6, stride=3)**2, 32)
+
+ # test padding
+ downsample = PatchMerging(
+ in_channels=16, out_channels=32, kernel_size=6, padding=2)
+ out, output_size = downsample(inputs, input_size=(56, 56))
+ assert downsample.sampler.padding == (2, 2)
+ assert out.shape == (1, cal_unfold_dim(56, 6, 6, padding=2)**2, 32)
+
+ # test str padding
+ downsample = PatchMerging(in_channels=16, out_channels=32, kernel_size=6)
+ out, output_size = downsample(inputs, input_size=(56, 56))
+ assert downsample.sampler.padding == (0, 0)
+ assert out.shape == (1, cal_unfold_dim(56, 6, 6, padding=2)**2, 32)
+
+ # test dilation
+ downsample = PatchMerging(kernel_size=6, dilation=2, **settings)
+ out, output_size = downsample(inputs, input_size=(56, 56))
+ assert downsample.sampler.dilation == (2, 2)
+ assert out.shape == (1, cal_unfold_dim(56, 6, 6, dilation=2)**2, 32)
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_utils/test_inverted_residual.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_utils/test_inverted_residual.py
new file mode 100644
index 0000000000000000000000000000000000000000..8c363279e273d033202fdfd5097796843912b5e0
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_utils/test_inverted_residual.py
@@ -0,0 +1,82 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import pytest
+import torch
+from torch.nn.modules import GroupNorm
+from torch.nn.modules.batchnorm import _BatchNorm
+
+from mmcls.models.utils import InvertedResidual, SELayer
+
+
+def is_norm(modules):
+ """Check if is one of the norms."""
+ if isinstance(modules, (GroupNorm, _BatchNorm)):
+ return True
+ return False
+
+
+def test_inverted_residual():
+
+ with pytest.raises(AssertionError):
+ # stride must be in [1, 2]
+ InvertedResidual(16, 16, 32, stride=3)
+
+ with pytest.raises(AssertionError):
+ # se_cfg must be None or dict
+ InvertedResidual(16, 16, 32, se_cfg=list())
+
+ # Add expand conv if in_channels and mid_channels is not the same
+ assert InvertedResidual(32, 16, 32).with_expand_conv is False
+ assert InvertedResidual(16, 16, 32).with_expand_conv is True
+
+ # Test InvertedResidual forward, stride=1
+ block = InvertedResidual(16, 16, 32, stride=1)
+ x = torch.randn(1, 16, 56, 56)
+ x_out = block(x)
+ assert getattr(block, 'se', None) is None
+ assert block.with_res_shortcut
+ assert x_out.shape == torch.Size((1, 16, 56, 56))
+
+ # Test InvertedResidual forward, stride=2
+ block = InvertedResidual(16, 16, 32, stride=2)
+ x = torch.randn(1, 16, 56, 56)
+ x_out = block(x)
+ assert not block.with_res_shortcut
+ assert x_out.shape == torch.Size((1, 16, 28, 28))
+
+ # Test InvertedResidual forward with se layer
+ se_cfg = dict(channels=32)
+ block = InvertedResidual(16, 16, 32, stride=1, se_cfg=se_cfg)
+ x = torch.randn(1, 16, 56, 56)
+ x_out = block(x)
+ assert isinstance(block.se, SELayer)
+ assert x_out.shape == torch.Size((1, 16, 56, 56))
+
+ # Test InvertedResidual forward without expand conv
+ block = InvertedResidual(32, 16, 32)
+ x = torch.randn(1, 32, 56, 56)
+ x_out = block(x)
+ assert getattr(block, 'expand_conv', None) is None
+ assert x_out.shape == torch.Size((1, 16, 56, 56))
+
+ # Test InvertedResidual forward with GroupNorm
+ block = InvertedResidual(
+ 16, 16, 32, norm_cfg=dict(type='GN', num_groups=2))
+ x = torch.randn(1, 16, 56, 56)
+ x_out = block(x)
+ for m in block.modules():
+ if is_norm(m):
+ assert isinstance(m, GroupNorm)
+ assert x_out.shape == torch.Size((1, 16, 56, 56))
+
+ # Test InvertedResidual forward with HSigmoid
+ block = InvertedResidual(16, 16, 32, act_cfg=dict(type='HSigmoid'))
+ x = torch.randn(1, 16, 56, 56)
+ x_out = block(x)
+ assert x_out.shape == torch.Size((1, 16, 56, 56))
+
+ # Test InvertedResidual forward with checkpoint
+ block = InvertedResidual(16, 16, 32, with_cp=True)
+ x = torch.randn(1, 16, 56, 56)
+ x_out = block(x)
+ assert block.with_cp
+ assert x_out.shape == torch.Size((1, 16, 56, 56))
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_utils/test_layer_scale.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_utils/test_layer_scale.py
new file mode 100644
index 0000000000000000000000000000000000000000..824be998844c67ae629e307a2b9c723794346e69
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_utils/test_layer_scale.py
@@ -0,0 +1,48 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from unittest import TestCase
+
+import torch
+
+from mmcls.models.utils import LayerScale
+
+
+class TestLayerScale(TestCase):
+
+ def test_init(self):
+ with self.assertRaisesRegex(AssertionError, "'data_format' could"):
+ cfg = dict(
+ dim=10,
+ inplace=False,
+ data_format='BNC',
+ )
+ LayerScale(**cfg)
+
+ cfg = dict(dim=10)
+ ls = LayerScale(**cfg)
+ assert torch.equal(ls.weight,
+ torch.ones(10, requires_grad=True) * 1e-5)
+
+ def forward(self):
+ # Test channels_last
+ cfg = dict(dim=256, inplace=False, data_format='channels_last')
+ ls_channels_last = LayerScale(**cfg)
+ x = torch.randn((4, 49, 256))
+ out = ls_channels_last(x)
+ self.assertEqual(tuple(out.size()), (4, 49, 256))
+ assert torch.equal(x * 1e-5, out)
+
+ # Test channels_first
+ cfg = dict(dim=256, inplace=False, data_format='channels_first')
+ ls_channels_first = LayerScale(**cfg)
+ x = torch.randn((4, 256, 7, 7))
+ out = ls_channels_first(x)
+ self.assertEqual(tuple(out.size()), (4, 256, 7, 7))
+ assert torch.equal(x * 1e-5, out)
+
+ # Test inplace True
+ cfg = dict(dim=256, inplace=True, data_format='channels_first')
+ ls_channels_first = LayerScale(**cfg)
+ x = torch.randn((4, 256, 7, 7))
+ out = ls_channels_first(x)
+ self.assertEqual(tuple(out.size()), (4, 256, 7, 7))
+ self.assertIs(x, out)
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_utils/test_misc.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_utils/test_misc.py
new file mode 100644
index 0000000000000000000000000000000000000000..86df85ff436284b142b37bab8de617624e7a3264
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_utils/test_misc.py
@@ -0,0 +1,59 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import pytest
+import torch
+from mmcv.utils import digit_version
+
+from mmcls.models.utils import channel_shuffle, is_tracing, make_divisible
+
+
+def test_make_divisible():
+ # test min_value is None
+ result = make_divisible(34, 8, None)
+ assert result == 32
+
+ # test when new_value > min_ratio * value
+ result = make_divisible(10, 8, min_ratio=0.9)
+ assert result == 16
+
+ # test min_value = 0.8
+ result = make_divisible(33, 8, min_ratio=0.8)
+ assert result == 32
+
+
+def test_channel_shuffle():
+ x = torch.randn(1, 24, 56, 56)
+ with pytest.raises(AssertionError):
+ # num_channels should be divisible by groups
+ channel_shuffle(x, 7)
+
+ groups = 3
+ batch_size, num_channels, height, width = x.size()
+ channels_per_group = num_channels // groups
+ out = channel_shuffle(x, groups)
+ # test the output value when groups = 3
+ for b in range(batch_size):
+ for c in range(num_channels):
+ c_out = c % channels_per_group * groups + c // channels_per_group
+ for i in range(height):
+ for j in range(width):
+ assert x[b, c, i, j] == out[b, c_out, i, j]
+
+
+@pytest.mark.skipif(
+ digit_version(torch.__version__) < digit_version('1.6.0'),
+ reason='torch.jit.is_tracing is not available before 1.6.0')
+def test_is_tracing():
+
+ def foo(x):
+ if is_tracing():
+ return x
+ else:
+ return x.tolist()
+
+ x = torch.rand(3)
+ # test without trace
+ assert isinstance(foo(x), list)
+
+ # test with trace
+ traced_foo = torch.jit.trace(foo, (torch.rand(1), ))
+ assert isinstance(traced_foo(x), torch.Tensor)
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_utils/test_position_encoding.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_utils/test_position_encoding.py
new file mode 100644
index 0000000000000000000000000000000000000000..feb171c24966e63c51b5a49e4f36b5a6754d88a7
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_utils/test_position_encoding.py
@@ -0,0 +1,10 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import torch
+
+from mmcls.models.utils import ConditionalPositionEncoding
+
+
+def test_conditional_position_encoding_module():
+ CPE = ConditionalPositionEncoding(in_channels=32, embed_dims=32, stride=2)
+ outs = CPE(torch.randn(1, 3136, 32), (56, 56))
+ assert outs.shape == torch.Size([1, 784, 32])
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_utils/test_se.py b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_utils/test_se.py
new file mode 100644
index 0000000000000000000000000000000000000000..8cb8c50971a5f176fe652f0665bb979009952c72
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_models/test_utils/test_se.py
@@ -0,0 +1,95 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import pytest
+import torch
+from torch.nn.modules import GroupNorm
+from torch.nn.modules.batchnorm import _BatchNorm
+
+from mmcls.models.utils import SELayer
+
+
+def is_norm(modules):
+ """Check if is one of the norms."""
+ if isinstance(modules, (GroupNorm, _BatchNorm)):
+ return True
+ return False
+
+
+def test_se():
+ with pytest.raises(AssertionError):
+ # base_channels must be a number
+ SELayer(16, squeeze_channels='32')
+
+ with pytest.raises(AssertionError):
+ # base_channels must be None or a number larger than 0
+ SELayer(16, squeeze_channels=-1)
+
+ with pytest.raises(AssertionError):
+ # act_cfg must be two dict tuple
+ SELayer(
+ 16,
+ act_cfg=(dict(type='ReLU'), dict(type='Sigmoid'),
+ dict(type='ReLU')))
+
+ # Test SELayer forward, channels=64
+ input = torch.randn((4, 64, 112, 112))
+ se = SELayer(64)
+ output = se(input)
+ assert se.conv1.out_channels == 8
+ assert se.conv2.in_channels == 8
+ assert output.shape == torch.Size((4, 64, 112, 112))
+
+ # Test SELayer forward, ratio=4
+ input = torch.randn((4, 128, 112, 112))
+ se = SELayer(128, ratio=4)
+ output = se(input)
+ assert se.conv1.out_channels == 32
+ assert se.conv2.in_channels == 32
+ assert output.shape == torch.Size((4, 128, 112, 112))
+
+ # Test SELayer forward, channels=54, ratio=4
+ # channels cannot be divisible by ratio
+ input = torch.randn((1, 54, 76, 103))
+ se = SELayer(54, ratio=4)
+ output = se(input)
+ assert se.conv1.out_channels == 16
+ assert se.conv2.in_channels == 16
+ assert output.shape == torch.Size((1, 54, 76, 103))
+
+ # Test SELayer forward, divisor=2
+ se = SELayer(54, ratio=4, divisor=2)
+ output = se(input)
+ assert se.conv1.out_channels == 14
+ assert se.conv2.in_channels == 14
+ assert output.shape == torch.Size((1, 54, 76, 103))
+
+ # Test SELayer forward, squeeze_channels=25
+ input = torch.randn((1, 128, 56, 56))
+ se = SELayer(128, squeeze_channels=25)
+ output = se(input)
+ assert se.conv1.out_channels == 25
+ assert se.conv2.in_channels == 25
+ assert output.shape == torch.Size((1, 128, 56, 56))
+
+ # Test SELayer forward, not used ratio and divisor
+ input = torch.randn((1, 128, 56, 56))
+ se = SELayer(
+ 128,
+ squeeze_channels=13,
+ ratio=4,
+ divisor=8,
+ )
+ output = se(input)
+ assert se.conv1.out_channels == 13
+ assert se.conv2.in_channels == 13
+ assert output.shape == torch.Size((1, 128, 56, 56))
+
+ # Test SELayer with HSigmoid activate layer
+ input = torch.randn((4, 128, 56, 56))
+ se = SELayer(
+ 128,
+ squeeze_channels=25,
+ act_cfg=(dict(type='ReLU'), dict(type='HSigmoid')))
+ output = se(input)
+ assert se.conv1.out_channels == 25
+ assert se.conv2.in_channels == 25
+ assert output.shape == torch.Size((4, 128, 56, 56))
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tests/test_eval_hook.py b/openmmlab_test/mmclassification-0.24.1/tests/test_runtime/test_eval_hook.py
similarity index 89%
rename from openmmlab_test/mmclassification-speed-benchmark/tests/test_eval_hook.py
rename to openmmlab_test/mmclassification-0.24.1/tests/test_runtime/test_eval_hook.py
index 5ef4285740abca994ec2c886c91c742d1107a6c1..b925bdebcd08d4306921b426d1d60761ee2e8318 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/tests/test_eval_hook.py
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_runtime/test_eval_hook.py
@@ -1,6 +1,6 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import logging
import tempfile
-import warnings
from unittest.mock import MagicMock, patch
import mmcv.runner
@@ -8,21 +8,11 @@ import pytest
import torch
import torch.nn as nn
from mmcv.runner import obj_from_dict
+from mmcv.runner.hooks import DistEvalHook, EvalHook
from torch.utils.data import DataLoader, Dataset
from mmcls.apis import single_gpu_test
-# TODO import eval hooks from mmcv and delete them from mmcls
-try:
- from mmcv.runner.hooks import EvalHook, DistEvalHook
- use_mmcv_hook = True
-except ImportError:
- warnings.warn('DeprecationWarning: EvalHook and DistEvalHook from mmcls '
- 'will be deprecated.'
- 'Please install mmcv through master branch.')
- from mmcls.core import EvalHook, DistEvalHook
- use_mmcv_hook = False
-
class ExampleDataset(Dataset):
@@ -156,9 +146,8 @@ def test_dist_eval_hook():
# test DistEvalHook
with tempfile.TemporaryDirectory() as tmpdir:
- if use_mmcv_hook:
- p = patch('mmcv.engine.multi_gpu_test', multi_gpu_test)
- p.start()
+ p = patch('mmcv.engine.multi_gpu_test', multi_gpu_test)
+ p.start()
eval_hook = DistEvalHook(data_loader, by_epoch=False)
runner = mmcv.runner.IterBasedRunner(
model=model,
@@ -170,8 +159,7 @@ def test_dist_eval_hook():
runner.run([loader], [('train', 1)])
test_dataset.evaluate.assert_called_with([torch.tensor([1])],
logger=runner.logger)
- if use_mmcv_hook:
- p.stop()
+ p.stop()
@patch('mmcls.apis.multi_gpu_test', multi_gpu_test)
@@ -200,9 +188,8 @@ def test_dist_eval_hook_epoch():
# test DistEvalHook
with tempfile.TemporaryDirectory() as tmpdir:
- if use_mmcv_hook:
- p = patch('mmcv.engine.multi_gpu_test', multi_gpu_test)
- p.start()
+ p = patch('mmcv.engine.multi_gpu_test', multi_gpu_test)
+ p.start()
eval_hook = DistEvalHook(data_loader, by_epoch=True, interval=2)
runner = mmcv.runner.EpochBasedRunner(
model=model,
@@ -214,5 +201,4 @@ def test_dist_eval_hook_epoch():
runner.run([loader], [('train', 1)])
test_dataset.evaluate.assert_called_with([torch.tensor([1])],
logger=runner.logger)
- if use_mmcv_hook:
- p.stop()
+ p.stop()
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_runtime/test_hooks.py b/openmmlab_test/mmclassification-0.24.1/tests/test_runtime/test_hooks.py
new file mode 100644
index 0000000000000000000000000000000000000000..70140d9e52bbd0e9d092e428dc497bc57e0b4f09
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_runtime/test_hooks.py
@@ -0,0 +1,158 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import logging
+import shutil
+import tempfile
+
+import numpy as np
+import pytest
+import torch
+import torch.nn as nn
+from mmcv.runner import build_runner
+from mmcv.runner.hooks import Hook, IterTimerHook
+from torch.utils.data import DataLoader
+
+import mmcls.core # noqa: F401
+
+
+def _build_demo_runner_without_hook(runner_type='EpochBasedRunner',
+ max_epochs=1,
+ max_iters=None,
+ multi_optimziers=False):
+
+ class Model(nn.Module):
+
+ def __init__(self):
+ super().__init__()
+ self.linear = nn.Linear(2, 1)
+ self.conv = nn.Conv2d(3, 3, 3)
+
+ def forward(self, x):
+ return self.linear(x)
+
+ def train_step(self, x, optimizer, **kwargs):
+ return dict(loss=self(x))
+
+ def val_step(self, x, optimizer, **kwargs):
+ return dict(loss=self(x))
+
+ model = Model()
+
+ if multi_optimziers:
+ optimizer = {
+ 'model1':
+ torch.optim.SGD(model.linear.parameters(), lr=0.02, momentum=0.95),
+ 'model2':
+ torch.optim.SGD(model.conv.parameters(), lr=0.01, momentum=0.9),
+ }
+ else:
+ optimizer = torch.optim.SGD(model.parameters(), lr=0.02, momentum=0.95)
+
+ tmp_dir = tempfile.mkdtemp()
+ runner = build_runner(
+ dict(type=runner_type),
+ default_args=dict(
+ model=model,
+ work_dir=tmp_dir,
+ optimizer=optimizer,
+ logger=logging.getLogger(),
+ max_epochs=max_epochs,
+ max_iters=max_iters))
+ return runner
+
+
+def _build_demo_runner(runner_type='EpochBasedRunner',
+ max_epochs=1,
+ max_iters=None,
+ multi_optimziers=False):
+
+ log_config = dict(
+ interval=1, hooks=[
+ dict(type='TextLoggerHook'),
+ ])
+
+ runner = _build_demo_runner_without_hook(runner_type, max_epochs,
+ max_iters, multi_optimziers)
+
+ runner.register_checkpoint_hook(dict(interval=1))
+ runner.register_logger_hooks(log_config)
+ return runner
+
+
+class ValueCheckHook(Hook):
+
+ def __init__(self, check_dict, by_epoch=False):
+ super().__init__()
+ self.check_dict = check_dict
+ self.by_epoch = by_epoch
+
+ def after_iter(self, runner):
+ if self.by_epoch:
+ return
+ if runner.iter in self.check_dict:
+ for attr, target in self.check_dict[runner.iter].items():
+ value = eval(f'runner.{attr}')
+ assert np.isclose(value, target), \
+ (f'The value of `runner.{attr}` is {value}, '
+ f'not equals to {target}')
+
+ def after_epoch(self, runner):
+ if not self.by_epoch:
+ return
+ if runner.epoch in self.check_dict:
+ for attr, target in self.check_dict[runner.epoch]:
+ value = eval(f'runner.{attr}')
+ assert np.isclose(value, target), \
+ (f'The value of `runner.{attr}` is {value}, '
+ f'not equals to {target}')
+
+
+@pytest.mark.parametrize('multi_optimziers', (True, False))
+def test_cosine_cooldown_hook(multi_optimziers):
+ """xdoctest -m tests/test_hooks.py test_cosine_runner_hook."""
+ loader = DataLoader(torch.ones((10, 2)))
+ runner = _build_demo_runner(multi_optimziers=multi_optimziers)
+
+ # add momentum LR scheduler
+ hook_cfg = dict(
+ type='CosineAnnealingCooldownLrUpdaterHook',
+ by_epoch=False,
+ cool_down_time=2,
+ cool_down_ratio=0.1,
+ min_lr_ratio=0.1,
+ warmup_iters=2,
+ warmup_ratio=0.9)
+ runner.register_hook_from_cfg(hook_cfg)
+ runner.register_hook_from_cfg(dict(type='IterTimerHook'))
+ runner.register_hook(IterTimerHook())
+
+ if multi_optimziers:
+ check_hook = ValueCheckHook({
+ 0: {
+ 'current_lr()["model1"][0]': 0.02,
+ 'current_lr()["model2"][0]': 0.01,
+ },
+ 5: {
+ 'current_lr()["model1"][0]': 0.0075558491,
+ 'current_lr()["model2"][0]': 0.0037779246,
+ },
+ 9: {
+ 'current_lr()["model1"][0]': 0.0002,
+ 'current_lr()["model2"][0]': 0.0001,
+ }
+ })
+ else:
+ check_hook = ValueCheckHook({
+ 0: {
+ 'current_lr()[0]': 0.02,
+ },
+ 5: {
+ 'current_lr()[0]': 0.0075558491,
+ },
+ 9: {
+ 'current_lr()[0]': 0.0002,
+ }
+ })
+ runner.register_hook(check_hook, priority='LOWEST')
+
+ runner.run([loader], [('train', 1)])
+ shutil.rmtree(runner.work_dir)
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_runtime/test_num_class_hook.py b/openmmlab_test/mmclassification-0.24.1/tests/test_runtime/test_num_class_hook.py
new file mode 100644
index 0000000000000000000000000000000000000000..fe8fb059f594e1259bebdbd383c7579fa23d3c85
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_runtime/test_num_class_hook.py
@@ -0,0 +1,84 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import logging
+import tempfile
+from unittest.mock import MagicMock
+
+import mmcv.runner as mmcv_runner
+import pytest
+import torch
+from mmcv.runner import obj_from_dict
+from torch.utils.data import DataLoader, Dataset
+
+from mmcls.core.hook import ClassNumCheckHook
+from mmcls.models.heads.base_head import BaseHead
+
+
+class ExampleDataset(Dataset):
+
+ def __init__(self, CLASSES):
+ self.CLASSES = CLASSES
+
+ def __getitem__(self, idx):
+ results = dict(img=torch.tensor([1]), img_metas=dict())
+ return results
+
+ def __len__(self):
+ return 1
+
+
+class ExampleHead(BaseHead):
+
+ def __init__(self, init_cfg=None):
+ super(BaseHead, self).__init__(init_cfg)
+ self.num_classes = 4
+
+ def forward_train(self, x, gt_label=None, **kwargs):
+ pass
+
+
+class ExampleModel(torch.nn.Module):
+
+ def __init__(self):
+ super(ExampleModel, self).__init__()
+ self.test_cfg = None
+ self.conv = torch.nn.Conv2d(3, 3, 3)
+ self.head = ExampleHead()
+
+ def forward(self, img, img_metas, test_mode=False, **kwargs):
+ return img
+
+ def train_step(self, data_batch, optimizer):
+ loss = self.forward(**data_batch)
+ return dict(loss=loss)
+
+
+@pytest.mark.parametrize('runner_type',
+ ['EpochBasedRunner', 'IterBasedRunner'])
+@pytest.mark.parametrize(
+ 'CLASSES', [None, ('A', 'B', 'C', 'D', 'E'), ('A', 'B', 'C', 'D')])
+def test_num_class_hook(runner_type, CLASSES):
+ test_dataset = ExampleDataset(CLASSES)
+ loader = DataLoader(test_dataset, batch_size=1)
+ model = ExampleModel()
+ optim_cfg = dict(type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0005)
+ optimizer = obj_from_dict(optim_cfg, torch.optim,
+ dict(params=model.parameters()))
+
+ with tempfile.TemporaryDirectory() as tmpdir:
+ num_class_hook = ClassNumCheckHook()
+ logger_mock = MagicMock(spec=logging.Logger)
+ runner = getattr(mmcv_runner, runner_type)(
+ model=model,
+ optimizer=optimizer,
+ work_dir=tmpdir,
+ logger=logger_mock,
+ max_epochs=1)
+ runner.register_hook(num_class_hook)
+ if CLASSES is None:
+ runner.run([loader], [('train', 1)], 1)
+ logger_mock.warning.assert_called()
+ elif len(CLASSES) != 4:
+ with pytest.raises(AssertionError):
+ runner.run([loader], [('train', 1)], 1)
+ else:
+ runner.run([loader], [('train', 1)], 1)
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_runtime/test_optimizer.py b/openmmlab_test/mmclassification-0.24.1/tests/test_runtime/test_optimizer.py
new file mode 100644
index 0000000000000000000000000000000000000000..2fdaeb08f493f1524318cc503d349ebbf08f1d31
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_runtime/test_optimizer.py
@@ -0,0 +1,309 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import functools
+from collections import OrderedDict
+from copy import deepcopy
+from typing import Iterable
+
+import torch
+import torch.nn as nn
+from mmcv.runner import build_optimizer
+from mmcv.runner.optimizer.builder import OPTIMIZERS
+from mmcv.utils.registry import build_from_cfg
+from torch.autograd import Variable
+from torch.optim.optimizer import Optimizer
+
+import mmcls.core # noqa: F401
+
+base_lr = 0.01
+base_wd = 0.0001
+
+
+def assert_equal(x, y):
+ if isinstance(x, torch.Tensor) and isinstance(y, torch.Tensor):
+ torch.testing.assert_allclose(x, y.to(x.device))
+ elif isinstance(x, OrderedDict) and isinstance(y, OrderedDict):
+ for x_value, y_value in zip(x.values(), y.values()):
+ assert_equal(x_value, y_value)
+ elif isinstance(x, dict) and isinstance(y, dict):
+ assert x.keys() == y.keys()
+ for key in x.keys():
+ assert_equal(x[key], y[key])
+ elif isinstance(x, str) and isinstance(y, str):
+ assert x == y
+ elif isinstance(x, Iterable) and isinstance(y, Iterable):
+ assert len(x) == len(y)
+ for x_item, y_item in zip(x, y):
+ assert_equal(x_item, y_item)
+ else:
+ assert x == y
+
+
+class SubModel(nn.Module):
+
+ def __init__(self):
+ super().__init__()
+ self.conv1 = nn.Conv2d(2, 2, kernel_size=1, groups=2)
+ self.gn = nn.GroupNorm(2, 2)
+ self.fc = nn.Linear(2, 2)
+ self.param1 = nn.Parameter(torch.ones(1))
+
+ def forward(self, x):
+ return x
+
+
+class ExampleModel(nn.Module):
+
+ def __init__(self):
+ super().__init__()
+ self.param1 = nn.Parameter(torch.ones(1))
+ self.conv1 = nn.Conv2d(3, 4, kernel_size=1, bias=False)
+ self.conv2 = nn.Conv2d(4, 2, kernel_size=1)
+ self.bn = nn.BatchNorm2d(2)
+ self.sub = SubModel()
+ self.fc = nn.Linear(2, 1)
+
+ def forward(self, x):
+ return x
+
+
+def check_lamb_optimizer(optimizer,
+ model,
+ bias_lr_mult=1,
+ bias_decay_mult=1,
+ norm_decay_mult=1,
+ dwconv_decay_mult=1):
+ param_groups = optimizer.param_groups
+ assert isinstance(optimizer, Optimizer)
+ assert optimizer.defaults['lr'] == base_lr
+ assert optimizer.defaults['weight_decay'] == base_wd
+ model_parameters = list(model.parameters())
+ assert len(param_groups) == len(model_parameters)
+ for i, param in enumerate(model_parameters):
+ param_group = param_groups[i]
+ assert torch.equal(param_group['params'][0], param)
+ # param1
+ param1 = param_groups[0]
+ assert param1['lr'] == base_lr
+ assert param1['weight_decay'] == base_wd
+ # conv1.weight
+ conv1_weight = param_groups[1]
+ assert conv1_weight['lr'] == base_lr
+ assert conv1_weight['weight_decay'] == base_wd
+ # conv2.weight
+ conv2_weight = param_groups[2]
+ assert conv2_weight['lr'] == base_lr
+ assert conv2_weight['weight_decay'] == base_wd
+ # conv2.bias
+ conv2_bias = param_groups[3]
+ assert conv2_bias['lr'] == base_lr * bias_lr_mult
+ assert conv2_bias['weight_decay'] == base_wd * bias_decay_mult
+ # bn.weight
+ bn_weight = param_groups[4]
+ assert bn_weight['lr'] == base_lr
+ assert bn_weight['weight_decay'] == base_wd * norm_decay_mult
+ # bn.bias
+ bn_bias = param_groups[5]
+ assert bn_bias['lr'] == base_lr
+ assert bn_bias['weight_decay'] == base_wd * norm_decay_mult
+ # sub.param1
+ sub_param1 = param_groups[6]
+ assert sub_param1['lr'] == base_lr
+ assert sub_param1['weight_decay'] == base_wd
+ # sub.conv1.weight
+ sub_conv1_weight = param_groups[7]
+ assert sub_conv1_weight['lr'] == base_lr
+ assert sub_conv1_weight['weight_decay'] == base_wd * dwconv_decay_mult
+ # sub.conv1.bias
+ sub_conv1_bias = param_groups[8]
+ assert sub_conv1_bias['lr'] == base_lr * bias_lr_mult
+ assert sub_conv1_bias['weight_decay'] == base_wd * dwconv_decay_mult
+ # sub.gn.weight
+ sub_gn_weight = param_groups[9]
+ assert sub_gn_weight['lr'] == base_lr
+ assert sub_gn_weight['weight_decay'] == base_wd * norm_decay_mult
+ # sub.gn.bias
+ sub_gn_bias = param_groups[10]
+ assert sub_gn_bias['lr'] == base_lr
+ assert sub_gn_bias['weight_decay'] == base_wd * norm_decay_mult
+ # sub.fc1.weight
+ sub_fc_weight = param_groups[11]
+ assert sub_fc_weight['lr'] == base_lr
+ assert sub_fc_weight['weight_decay'] == base_wd
+ # sub.fc1.bias
+ sub_fc_bias = param_groups[12]
+ assert sub_fc_bias['lr'] == base_lr * bias_lr_mult
+ assert sub_fc_bias['weight_decay'] == base_wd * bias_decay_mult
+ # fc1.weight
+ fc_weight = param_groups[13]
+ assert fc_weight['lr'] == base_lr
+ assert fc_weight['weight_decay'] == base_wd
+ # fc1.bias
+ fc_bias = param_groups[14]
+ assert fc_bias['lr'] == base_lr * bias_lr_mult
+ assert fc_bias['weight_decay'] == base_wd * bias_decay_mult
+
+
+def _test_state_dict(weight, bias, input, constructor):
+ weight = Variable(weight, requires_grad=True)
+ bias = Variable(bias, requires_grad=True)
+ inputs = Variable(input)
+
+ def fn_base(optimizer, weight, bias):
+ optimizer.zero_grad()
+ i = input_cuda if weight.is_cuda else inputs
+ loss = (weight.mv(i) + bias).pow(2).sum()
+ loss.backward()
+ return loss
+
+ optimizer = constructor(weight, bias)
+ fn = functools.partial(fn_base, optimizer, weight, bias)
+
+ # Prime the optimizer
+ for _ in range(20):
+ optimizer.step(fn)
+ # Clone the weights and construct new optimizer for them
+ weight_c = Variable(weight.data.clone(), requires_grad=True)
+ bias_c = Variable(bias.data.clone(), requires_grad=True)
+ optimizer_c = constructor(weight_c, bias_c)
+ fn_c = functools.partial(fn_base, optimizer_c, weight_c, bias_c)
+ # Load state dict
+ state_dict = deepcopy(optimizer.state_dict())
+ state_dict_c = deepcopy(optimizer.state_dict())
+ optimizer_c.load_state_dict(state_dict_c)
+ # Run both optimizations in parallel
+ for _ in range(20):
+ optimizer.step(fn)
+ optimizer_c.step(fn_c)
+ assert_equal(weight, weight_c)
+ assert_equal(bias, bias_c)
+ # Make sure state dict wasn't modified
+ assert_equal(state_dict, state_dict_c)
+ # Make sure state dict is deterministic with equal
+ # but not identical parameters
+ # NOTE: The state_dict of optimizers in PyTorch 1.5 have random keys,
+ state_dict = deepcopy(optimizer.state_dict())
+ state_dict_c = deepcopy(optimizer_c.state_dict())
+ keys = state_dict['param_groups'][-1]['params']
+ keys_c = state_dict_c['param_groups'][-1]['params']
+ for key, key_c in zip(keys, keys_c):
+ assert_equal(optimizer.state_dict()['state'][key],
+ optimizer_c.state_dict()['state'][key_c])
+ # Make sure repeated parameters have identical representation in state dict
+ optimizer_c.param_groups.extend(optimizer_c.param_groups)
+ assert_equal(optimizer_c.state_dict()['param_groups'][0],
+ optimizer_c.state_dict()['param_groups'][1])
+
+ # Check that state dict can be loaded even when we cast parameters
+ # to a different type and move to a different device.
+ if not torch.cuda.is_available():
+ return
+
+ input_cuda = Variable(inputs.data.float().cuda())
+ weight_cuda = Variable(weight.data.float().cuda(), requires_grad=True)
+ bias_cuda = Variable(bias.data.float().cuda(), requires_grad=True)
+ optimizer_cuda = constructor(weight_cuda, bias_cuda)
+ fn_cuda = functools.partial(fn_base, optimizer_cuda, weight_cuda,
+ bias_cuda)
+
+ state_dict = deepcopy(optimizer.state_dict())
+ state_dict_c = deepcopy(optimizer.state_dict())
+ optimizer_cuda.load_state_dict(state_dict_c)
+
+ # Make sure state dict wasn't modified
+ assert_equal(state_dict, state_dict_c)
+
+ for _ in range(20):
+ optimizer.step(fn)
+ optimizer_cuda.step(fn_cuda)
+ assert_equal(weight, weight_cuda)
+ assert_equal(bias, bias_cuda)
+
+ # validate deepcopy() copies all public attributes
+ def getPublicAttr(obj):
+ return set(k for k in obj.__dict__ if not k.startswith('_'))
+
+ assert_equal(getPublicAttr(optimizer), getPublicAttr(deepcopy(optimizer)))
+
+
+def _test_basic_cases_template(weight, bias, inputs, constructor,
+ scheduler_constructors):
+ """Copied from PyTorch."""
+ weight = Variable(weight, requires_grad=True)
+ bias = Variable(bias, requires_grad=True)
+ inputs = Variable(inputs)
+ optimizer = constructor(weight, bias)
+ schedulers = []
+ for scheduler_constructor in scheduler_constructors:
+ schedulers.append(scheduler_constructor(optimizer))
+
+ # to check if the optimizer can be printed as a string
+ optimizer.__repr__()
+
+ def fn():
+ optimizer.zero_grad()
+ y = weight.mv(inputs)
+ if y.is_cuda and bias.is_cuda and y.get_device() != bias.get_device():
+ y = y.cuda(bias.get_device())
+ loss = (y + bias).pow(2).sum()
+ loss.backward()
+ return loss
+
+ initial_value = fn().item()
+ for _ in range(200):
+ for scheduler in schedulers:
+ scheduler.step()
+ optimizer.step(fn)
+
+ assert fn().item() < initial_value
+
+
+def _test_basic_cases(constructor,
+ scheduler_constructors=None,
+ ignore_multidevice=False):
+ """Copied from PyTorch."""
+ if scheduler_constructors is None:
+ scheduler_constructors = []
+ _test_state_dict(
+ torch.randn(10, 5), torch.randn(10), torch.randn(5), constructor)
+ _test_basic_cases_template(
+ torch.randn(10, 5), torch.randn(10), torch.randn(5), constructor,
+ scheduler_constructors)
+ # non-contiguous parameters
+ _test_basic_cases_template(
+ torch.randn(10, 5, 2)[..., 0],
+ torch.randn(10, 2)[..., 0], torch.randn(5), constructor,
+ scheduler_constructors)
+ # CUDA
+ if not torch.cuda.is_available():
+ return
+ _test_basic_cases_template(
+ torch.randn(10, 5).cuda(),
+ torch.randn(10).cuda(),
+ torch.randn(5).cuda(), constructor, scheduler_constructors)
+ # Multi-GPU
+ if not torch.cuda.device_count() > 1 or ignore_multidevice:
+ return
+ _test_basic_cases_template(
+ torch.randn(10, 5).cuda(0),
+ torch.randn(10).cuda(1),
+ torch.randn(5).cuda(0), constructor, scheduler_constructors)
+
+
+def test_lamb_optimizer():
+ model = ExampleModel()
+ optimizer_cfg = dict(
+ type='Lamb',
+ lr=base_lr,
+ betas=(0.9, 0.999),
+ eps=1e-8,
+ weight_decay=base_wd,
+ paramwise_cfg=dict(
+ bias_lr_mult=2,
+ bias_decay_mult=0.5,
+ norm_decay_mult=0,
+ dwconv_decay_mult=0.1))
+ optimizer = build_optimizer(model, optimizer_cfg)
+ check_lamb_optimizer(optimizer, model, **optimizer_cfg['paramwise_cfg'])
+
+ _test_basic_cases(lambda weight, bias: build_from_cfg(
+ dict(type='Lamb', params=[weight, bias], lr=base_lr), OPTIMIZERS))
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_runtime/test_preciseBN_hook.py b/openmmlab_test/mmclassification-0.24.1/tests/test_runtime/test_preciseBN_hook.py
new file mode 100644
index 0000000000000000000000000000000000000000..f9375f94af13500c4ea822d92aa2e279041e99b6
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_runtime/test_preciseBN_hook.py
@@ -0,0 +1,274 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import numpy as np
+import pytest
+import torch
+import torch.nn as nn
+from mmcv.parallel import MMDataParallel, MMDistributedDataParallel
+from mmcv.runner import EpochBasedRunner, IterBasedRunner, build_optimizer
+from mmcv.utils import get_logger
+from mmcv.utils.logging import print_log
+from torch.utils.data import DataLoader, Dataset
+
+from mmcls.core.hook import PreciseBNHook
+from mmcls.models.classifiers import BaseClassifier
+
+
+class ExampleDataset(Dataset):
+
+ def __init__(self):
+ self.index = 0
+
+ def __getitem__(self, idx):
+ results = dict(imgs=torch.tensor([1.0], dtype=torch.float32))
+ return results
+
+ def __len__(self):
+ return 1
+
+
+class BiggerDataset(ExampleDataset):
+
+ def __init__(self, fixed_values=range(0, 12)):
+ assert len(self) == len(fixed_values)
+ self.fixed_values = fixed_values
+
+ def __getitem__(self, idx):
+ results = dict(
+ imgs=torch.tensor([self.fixed_values[idx]], dtype=torch.float32))
+ return results
+
+ def __len__(self):
+ # a bigger dataset
+ return 12
+
+
+class ExampleModel(BaseClassifier):
+
+ def __init__(self):
+ super().__init__()
+ self.conv = nn.Linear(1, 1)
+ self.bn = nn.BatchNorm1d(1)
+ self.test_cfg = None
+
+ def forward(self, imgs, return_loss=False):
+ return self.bn(self.conv(imgs))
+
+ def simple_test(self, img, img_metas=None, **kwargs):
+ return {}
+
+ def extract_feat(self, img, stage='neck'):
+ return ()
+
+ def forward_train(self, img, gt_label, **kwargs):
+ return {'loss': 0.5}
+
+ def train_step(self, data_batch, optimizer=None, **kwargs):
+ self.forward(**data_batch)
+ outputs = {
+ 'loss': 0.5,
+ 'log_vars': {
+ 'accuracy': 0.98
+ },
+ 'num_samples': 1
+ }
+ return outputs
+
+
+class SingleBNModel(ExampleModel):
+
+ def __init__(self):
+ super().__init__()
+ self.bn = nn.BatchNorm1d(1)
+ self.test_cfg = None
+
+ def forward(self, imgs, return_loss=False):
+ return self.bn(imgs)
+
+
+class GNExampleModel(ExampleModel):
+
+ def __init__(self):
+ super().__init__()
+ self.conv = nn.Linear(1, 1)
+ self.bn = nn.GroupNorm(1, 1)
+ self.test_cfg = None
+
+
+class NoBNExampleModel(ExampleModel):
+
+ def __init__(self):
+ super().__init__()
+ self.conv = nn.Linear(1, 1)
+ self.test_cfg = None
+
+ def forward(self, imgs, return_loss=False):
+ return self.conv(imgs)
+
+
+def test_precise_bn():
+ optimizer_cfg = dict(
+ type='SGD', lr=0.01, momentum=0.9, weight_decay=0.0001)
+
+ test_dataset = ExampleDataset()
+ loader = DataLoader(test_dataset, batch_size=2)
+ model = ExampleModel()
+ optimizer = build_optimizer(model, optimizer_cfg)
+ logger = get_logger('precise_bn')
+ runner = EpochBasedRunner(
+ model=model,
+ batch_processor=None,
+ optimizer=optimizer,
+ logger=logger,
+ max_epochs=1)
+
+ with pytest.raises(AssertionError):
+ # num_samples must be larger than 0
+ precise_bn_hook = PreciseBNHook(num_samples=-1)
+ runner.register_hook(precise_bn_hook)
+ runner.run([loader], [('train', 1)])
+
+ with pytest.raises(AssertionError):
+ # interval must be larger than 0
+ precise_bn_hook = PreciseBNHook(interval=0)
+ runner.register_hook(precise_bn_hook)
+ runner.run([loader], [('train', 1)])
+
+ with pytest.raises(AssertionError):
+ # interval must be larger than 0
+ runner = EpochBasedRunner(
+ model=model,
+ batch_processor=None,
+ optimizer=optimizer,
+ logger=logger,
+ max_epochs=1)
+ precise_bn_hook = PreciseBNHook(interval=0)
+ runner.register_hook(precise_bn_hook)
+ runner.run([loader], [('train', 1)])
+
+ with pytest.raises(AssertionError):
+ # only support EpochBaseRunner
+ runner = IterBasedRunner(
+ model=model,
+ batch_processor=None,
+ optimizer=optimizer,
+ logger=logger,
+ max_epochs=1)
+ precise_bn_hook = PreciseBNHook(interval=2)
+ runner.register_hook(precise_bn_hook)
+ print_log(runner)
+ runner.run([loader], [('train', 1)])
+
+ # test non-DDP model
+ test_bigger_dataset = BiggerDataset()
+ loader = DataLoader(test_bigger_dataset, batch_size=2)
+ loaders = [loader]
+ precise_bn_hook = PreciseBNHook(num_samples=4)
+ assert precise_bn_hook.num_samples == 4
+ assert precise_bn_hook.interval == 1
+ runner = EpochBasedRunner(
+ model=model,
+ batch_processor=None,
+ optimizer=optimizer,
+ logger=logger,
+ max_epochs=1)
+ runner.register_hook(precise_bn_hook)
+ runner.run(loaders, [('train', 1)])
+
+ # test DP model
+ test_bigger_dataset = BiggerDataset()
+ loader = DataLoader(test_bigger_dataset, batch_size=2)
+ loaders = [loader]
+ precise_bn_hook = PreciseBNHook(num_samples=4)
+ assert precise_bn_hook.num_samples == 4
+ assert precise_bn_hook.interval == 1
+ model = MMDataParallel(model)
+ runner = EpochBasedRunner(
+ model=model,
+ batch_processor=None,
+ optimizer=optimizer,
+ logger=logger,
+ max_epochs=1)
+ runner.register_hook(precise_bn_hook)
+ runner.run(loaders, [('train', 1)])
+
+ # test model w/ gn layer
+ loader = DataLoader(test_bigger_dataset, batch_size=2)
+ loaders = [loader]
+ precise_bn_hook = PreciseBNHook(num_samples=4)
+ assert precise_bn_hook.num_samples == 4
+ assert precise_bn_hook.interval == 1
+ model = GNExampleModel()
+ runner = EpochBasedRunner(
+ model=model,
+ batch_processor=None,
+ optimizer=optimizer,
+ logger=logger,
+ max_epochs=1)
+ runner.register_hook(precise_bn_hook)
+ runner.run(loaders, [('train', 1)])
+
+ # test model without bn layer
+ loader = DataLoader(test_bigger_dataset, batch_size=2)
+ loaders = [loader]
+ precise_bn_hook = PreciseBNHook(num_samples=4)
+ assert precise_bn_hook.num_samples == 4
+ assert precise_bn_hook.interval == 1
+ model = NoBNExampleModel()
+ runner = EpochBasedRunner(
+ model=model,
+ batch_processor=None,
+ optimizer=optimizer,
+ logger=logger,
+ max_epochs=1)
+ runner.register_hook(precise_bn_hook)
+ runner.run(loaders, [('train', 1)])
+
+ # test how precise it is
+ loader = DataLoader(test_bigger_dataset, batch_size=2)
+ loaders = [loader]
+ precise_bn_hook = PreciseBNHook(num_samples=12)
+ assert precise_bn_hook.num_samples == 12
+ assert precise_bn_hook.interval == 1
+ model = SingleBNModel()
+ runner = EpochBasedRunner(
+ model=model,
+ batch_processor=None,
+ optimizer=optimizer,
+ logger=logger,
+ max_epochs=1)
+ runner.register_hook(precise_bn_hook)
+ runner.run(loaders, [('train', 1)])
+ imgs_list = list()
+ for loader in loaders:
+ for i, data in enumerate(loader):
+ imgs_list.append(np.array(data['imgs']))
+ mean = np.mean([np.mean(batch) for batch in imgs_list])
+ # bassel correction used in Pytorch, therefore ddof=1
+ var = np.mean([np.var(batch, ddof=1) for batch in imgs_list])
+ assert np.equal(mean, model.bn.running_mean)
+ assert np.equal(var, model.bn.running_var)
+
+ @pytest.mark.skipif(
+ not torch.cuda.is_available(), reason='requires CUDA support')
+ def test_ddp_model_precise_bn():
+ # test DDP model
+ test_bigger_dataset = BiggerDataset()
+ loader = DataLoader(test_bigger_dataset, batch_size=2)
+ loaders = [loader]
+ precise_bn_hook = PreciseBNHook(num_samples=5)
+ assert precise_bn_hook.num_samples == 5
+ assert precise_bn_hook.interval == 1
+ model = ExampleModel()
+ model = MMDistributedDataParallel(
+ model.cuda(),
+ device_ids=[torch.cuda.current_device()],
+ broadcast_buffers=False,
+ find_unused_parameters=True)
+ runner = EpochBasedRunner(
+ model=model,
+ batch_processor=None,
+ optimizer=optimizer,
+ logger=logger,
+ max_epochs=1)
+ runner.register_hook(precise_bn_hook)
+ runner.run(loaders, [('train', 1)])
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_utils/test_device.py b/openmmlab_test/mmclassification-0.24.1/tests/test_utils/test_device.py
new file mode 100644
index 0000000000000000000000000000000000000000..eb10bb21a76d73b9c963dbdbd37ae9dfffab3e9a
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_utils/test_device.py
@@ -0,0 +1,28 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from unittest import TestCase
+from unittest.mock import patch
+
+import mmcv
+
+from mmcls.utils import auto_select_device
+
+
+class TestAutoSelectDevice(TestCase):
+
+ @patch.object(mmcv, '__version__', '1.6.0')
+ @patch('mmcv.device.get_device', create=True)
+ def test_mmcv(self, mock):
+ auto_select_device()
+ mock.assert_called_once()
+
+ @patch.object(mmcv, '__version__', '1.5.0')
+ @patch('torch.cuda.is_available', return_value=True)
+ def test_cuda(self, mock):
+ device = auto_select_device()
+ self.assertEqual(device, 'cuda')
+
+ @patch.object(mmcv, '__version__', '1.5.0')
+ @patch('torch.cuda.is_available', return_value=False)
+ def test_cpu(self, mock):
+ device = auto_select_device()
+ self.assertEqual(device, 'cpu')
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_utils/test_logger.py b/openmmlab_test/mmclassification-0.24.1/tests/test_utils/test_logger.py
new file mode 100644
index 0000000000000000000000000000000000000000..97a6fb00cf78ad67a15c34238334c32d2fd7eb2b
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_utils/test_logger.py
@@ -0,0 +1,55 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import os
+import os.path as osp
+import tempfile
+
+import mmcv.utils.logging
+
+from mmcls.utils import get_root_logger, load_json_log
+
+
+def test_get_root_logger():
+ # Reset the initialized log
+ mmcv.utils.logging.logger_initialized = {}
+ with tempfile.TemporaryDirectory() as tmpdirname:
+ log_path = osp.join(tmpdirname, 'test.log')
+
+ logger = get_root_logger(log_file=log_path)
+ message1 = 'adhsuadghj'
+ logger.info(message1)
+
+ logger2 = get_root_logger()
+ message2 = 'm,tkrgmkr'
+ logger2.info(message2)
+
+ with open(log_path, 'r') as f:
+ lines = f.readlines()
+ assert message1 in lines[0]
+ assert message2 in lines[1]
+
+ assert logger is logger2
+
+ handlers = list(logger.handlers)
+ for handler in handlers:
+ handler.close()
+ logger.removeHandler(handler)
+ os.remove(log_path)
+
+
+def test_load_json_log():
+ log_path = 'tests/data/test.logjson'
+ log_dict = load_json_log(log_path)
+
+ # test log_dict
+ assert set(log_dict.keys()) == set([1, 2, 3])
+
+ # test epoch dict in log_dict
+ assert set(log_dict[1].keys()) == set(
+ ['iter', 'lr', 'memory', 'data_time', 'time', 'mode'])
+ assert isinstance(log_dict[1]['lr'], list)
+ assert len(log_dict[1]['iter']) == 4
+ assert len(log_dict[1]['lr']) == 4
+ assert len(log_dict[2]['iter']) == 3
+ assert len(log_dict[2]['lr']) == 3
+ assert log_dict[3]['iter'] == [10, 20]
+ assert log_dict[3]['lr'] == [0.33305, 0.34759]
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_utils/test_setup_env.py b/openmmlab_test/mmclassification-0.24.1/tests/test_utils/test_setup_env.py
new file mode 100644
index 0000000000000000000000000000000000000000..2679dbbf5e2a8960fa756b8a0c1e80495bbd2be5
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_utils/test_setup_env.py
@@ -0,0 +1,68 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import multiprocessing as mp
+import os
+import platform
+
+import cv2
+from mmcv import Config
+
+from mmcls.utils import setup_multi_processes
+
+
+def test_setup_multi_processes():
+ # temp save system setting
+ sys_start_mehod = mp.get_start_method(allow_none=True)
+ sys_cv_threads = cv2.getNumThreads()
+ # pop and temp save system env vars
+ sys_omp_threads = os.environ.pop('OMP_NUM_THREADS', default=None)
+ sys_mkl_threads = os.environ.pop('MKL_NUM_THREADS', default=None)
+
+ # test config without setting env
+ config = dict(data=dict(workers_per_gpu=2))
+ cfg = Config(config)
+ setup_multi_processes(cfg)
+ assert os.getenv('OMP_NUM_THREADS') == '1'
+ assert os.getenv('MKL_NUM_THREADS') == '1'
+ # when set to 0, the num threads will be 1
+ assert cv2.getNumThreads() == 1
+ if platform.system() != 'Windows':
+ assert mp.get_start_method() == 'fork'
+
+ # test num workers <= 1
+ os.environ.pop('OMP_NUM_THREADS')
+ os.environ.pop('MKL_NUM_THREADS')
+ config = dict(data=dict(workers_per_gpu=0))
+ cfg = Config(config)
+ setup_multi_processes(cfg)
+ assert 'OMP_NUM_THREADS' not in os.environ
+ assert 'MKL_NUM_THREADS' not in os.environ
+
+ # test manually set env var
+ os.environ['OMP_NUM_THREADS'] = '4'
+ config = dict(data=dict(workers_per_gpu=2))
+ cfg = Config(config)
+ setup_multi_processes(cfg)
+ assert os.getenv('OMP_NUM_THREADS') == '4'
+
+ # test manually set opencv threads and mp start method
+ config = dict(
+ data=dict(workers_per_gpu=2),
+ opencv_num_threads=4,
+ mp_start_method='spawn')
+ cfg = Config(config)
+ setup_multi_processes(cfg)
+ assert cv2.getNumThreads() == 4
+ assert mp.get_start_method() == 'spawn'
+
+ # revert setting to avoid affecting other programs
+ if sys_start_mehod:
+ mp.set_start_method(sys_start_mehod, force=True)
+ cv2.setNumThreads(sys_cv_threads)
+ if sys_omp_threads:
+ os.environ['OMP_NUM_THREADS'] = sys_omp_threads
+ else:
+ os.environ.pop('OMP_NUM_THREADS')
+ if sys_mkl_threads:
+ os.environ['MKL_NUM_THREADS'] = sys_mkl_threads
+ else:
+ os.environ.pop('MKL_NUM_THREADS')
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_utils/test_version_utils.py b/openmmlab_test/mmclassification-0.24.1/tests/test_utils/test_version_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..f4bb389228ad6be2f70b38ca84c9d8a16f104ed3
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_utils/test_version_utils.py
@@ -0,0 +1,21 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from mmcls import digit_version
+
+
+def test_digit_version():
+ assert digit_version('0.2.16') == (0, 2, 16, 0, 0, 0)
+ assert digit_version('1.2.3') == (1, 2, 3, 0, 0, 0)
+ assert digit_version('1.2.3rc0') == (1, 2, 3, 0, -1, 0)
+ assert digit_version('1.2.3rc1') == (1, 2, 3, 0, -1, 1)
+ assert digit_version('1.0rc0') == (1, 0, 0, 0, -1, 0)
+ assert digit_version('1.0') == digit_version('1.0.0')
+ assert digit_version('1.5.0+cuda90_cudnn7.6.3_lms') == digit_version('1.5')
+ assert digit_version('1.0.0dev') < digit_version('1.0.0a')
+ assert digit_version('1.0.0a') < digit_version('1.0.0a1')
+ assert digit_version('1.0.0a') < digit_version('1.0.0b')
+ assert digit_version('1.0.0b') < digit_version('1.0.0rc')
+ assert digit_version('1.0.0rc1') < digit_version('1.0.0')
+ assert digit_version('1.0.0') < digit_version('1.0.0post')
+ assert digit_version('1.0.0post') < digit_version('1.0.0post1')
+ assert digit_version('v1') == (1, 0, 0, 0, 0, 0)
+ assert digit_version('v1.1.5') == (1, 1, 5, 0, 0, 0)
diff --git a/openmmlab_test/mmclassification-0.24.1/tests/test_utils/test_visualization.py b/openmmlab_test/mmclassification-0.24.1/tests/test_utils/test_visualization.py
new file mode 100644
index 0000000000000000000000000000000000000000..1bc4c2b9e64ba0424bd5290d495168bcac4d46f4
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tests/test_utils/test_visualization.py
@@ -0,0 +1,100 @@
+# Copyright (c) Open-MMLab. All rights reserved.
+import os
+import os.path as osp
+import tempfile
+from unittest.mock import MagicMock
+
+import matplotlib.pyplot as plt
+import mmcv
+import numpy as np
+import pytest
+
+from mmcls.core import visualization as vis
+
+
+def test_color():
+ assert vis.color_val_matplotlib(mmcv.Color.blue) == (0., 0., 1.)
+ assert vis.color_val_matplotlib('green') == (0., 1., 0.)
+ assert vis.color_val_matplotlib((1, 2, 3)) == (3 / 255, 2 / 255, 1 / 255)
+ assert vis.color_val_matplotlib(100) == (100 / 255, 100 / 255, 100 / 255)
+ assert vis.color_val_matplotlib(np.zeros(3, dtype=int)) == (0., 0., 0.)
+ # forbid white color
+ with pytest.raises(TypeError):
+ vis.color_val_matplotlib([255, 255, 255])
+ # forbid float
+ with pytest.raises(TypeError):
+ vis.color_val_matplotlib(1.0)
+ # overflowed
+ with pytest.raises(AssertionError):
+ vis.color_val_matplotlib((0, 0, 500))
+
+
+def test_imshow_infos():
+ tmp_dir = osp.join(tempfile.gettempdir(), 'image_infos')
+ tmp_filename = osp.join(tmp_dir, 'image.jpg')
+
+ image = np.ones((10, 10, 3), np.uint8)
+ result = {'pred_label': 1, 'pred_class': 'bird', 'pred_score': 0.98}
+ out_image = vis.imshow_infos(
+ image, result, out_file=tmp_filename, show=False)
+ assert osp.isfile(tmp_filename)
+ assert image.shape == out_image.shape
+ assert not np.allclose(image, out_image)
+ os.remove(tmp_filename)
+
+ # test grayscale images
+ image = np.ones((10, 10), np.uint8)
+ result = {'pred_label': 1, 'pred_class': 'bird', 'pred_score': 0.98}
+ out_image = vis.imshow_infos(
+ image, result, out_file=tmp_filename, show=False)
+ assert osp.isfile(tmp_filename)
+ assert image.shape == out_image.shape[:2]
+ os.remove(tmp_filename)
+
+
+def test_figure_context_manager():
+ # test show multiple images with the same figure.
+ images = [
+ np.random.randint(0, 255, (100, 100, 3), np.uint8) for _ in range(5)
+ ]
+ result = {'pred_label': 1, 'pred_class': 'bird', 'pred_score': 0.98}
+
+ with vis.ImshowInfosContextManager() as manager:
+ fig_show = manager.fig_show
+ fig_save = manager.fig_save
+
+ # Test time out
+ fig_show.canvas.start_event_loop = MagicMock()
+ fig_show.canvas.end_event_loop = MagicMock()
+ for image in images:
+ ret, out_image = manager.put_img_infos(image, result, show=True)
+ assert ret == 0
+ assert image.shape == out_image.shape
+ assert not np.allclose(image, out_image)
+ assert fig_show is manager.fig_show
+ assert fig_save is manager.fig_save
+
+ # Test continue key
+ fig_show.canvas.start_event_loop = (
+ lambda _: fig_show.canvas.key_press_event(' '))
+ for image in images:
+ ret, out_image = manager.put_img_infos(image, result, show=True)
+ assert ret == 0
+ assert image.shape == out_image.shape
+ assert not np.allclose(image, out_image)
+ assert fig_show is manager.fig_show
+ assert fig_save is manager.fig_save
+
+ # Test close figure manually
+ fig_show = manager.fig_show
+
+ def destroy(*_, **__):
+ fig_show.canvas.close_event()
+ plt.close(fig_show)
+
+ fig_show.canvas.start_event_loop = destroy
+ ret, out_image = manager.put_img_infos(images[0], result, show=True)
+ assert ret == 1
+ assert image.shape == out_image.shape
+ assert not np.allclose(image, out_image)
+ assert fig_save is manager.fig_save
diff --git a/openmmlab_test/mmclassification-0.24.1/tools/analysis_tools/analyze_logs.py b/openmmlab_test/mmclassification-0.24.1/tools/analysis_tools/analyze_logs.py
new file mode 100644
index 0000000000000000000000000000000000000000..b8623aec68bd92ecc7af7ff373dc49006ce806fd
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tools/analysis_tools/analyze_logs.py
@@ -0,0 +1,215 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import argparse
+import os
+import re
+
+import matplotlib.pyplot as plt
+import numpy as np
+
+from mmcls.utils import load_json_log
+
+TEST_METRICS = ('precision', 'recall', 'f1_score', 'support', 'mAP', 'CP',
+ 'CR', 'CF1', 'OP', 'OR', 'OF1', 'accuracy')
+
+
+def cal_train_time(log_dicts, args):
+ """Compute the average time per training iteration."""
+ for i, log_dict in enumerate(log_dicts):
+ print(f'{"-" * 5}Analyze train time of {args.json_logs[i]}{"-" * 5}')
+ all_times = []
+ for epoch in log_dict.keys():
+ if args.include_outliers:
+ all_times.append(log_dict[epoch]['time'])
+ else:
+ all_times.append(log_dict[epoch]['time'][1:])
+ all_times = np.array(all_times)
+ epoch_ave_time = all_times.mean(-1)
+ slowest_epoch = epoch_ave_time.argmax()
+ fastest_epoch = epoch_ave_time.argmin()
+ std_over_epoch = epoch_ave_time.std()
+ print(f'slowest epoch {slowest_epoch + 1}, '
+ f'average time is {epoch_ave_time[slowest_epoch]:.4f}')
+ print(f'fastest epoch {fastest_epoch + 1}, '
+ f'average time is {epoch_ave_time[fastest_epoch]:.4f}')
+ print(f'time std over epochs is {std_over_epoch:.4f}')
+ print(f'average iter time: {np.mean(all_times):.4f} s/iter')
+ print()
+
+
+def get_legends(args):
+ """if legend is None, use {filename}_{key} as legend."""
+ legend = args.legend
+ if legend is None:
+ legend = []
+ for json_log in args.json_logs:
+ for metric in args.keys:
+ # remove '.json' in the end of log names
+ basename = os.path.basename(json_log)[:-5]
+ if basename.endswith('.log'):
+ basename = basename[:-4]
+ legend.append(f'{basename}_{metric}')
+ assert len(legend) == (len(args.json_logs) * len(args.keys))
+ return legend
+
+
+def plot_phase_train(metric, log_dict, epochs, curve_label, json_log):
+ """plot phase of train cruve."""
+ if metric not in log_dict[epochs[0]]:
+ raise KeyError(f'{json_log} does not contain metric {metric}'
+ f' in train mode')
+ xs, ys = [], []
+ for epoch in epochs:
+ iters = log_dict[epoch]['iter']
+ if log_dict[epoch]['mode'][-1] == 'val':
+ iters = iters[:-1]
+ num_iters_per_epoch = iters[-1]
+ assert len(iters) > 0, (
+ 'The training log is empty, please try to reduce the '
+ 'interval of log in config file.')
+ xs.append(np.array(iters) / num_iters_per_epoch + (epoch - 1))
+ ys.append(np.array(log_dict[epoch][metric][:len(iters)]))
+ xs = np.concatenate(xs)
+ ys = np.concatenate(ys)
+ plt.xlabel('Epochs')
+ plt.plot(xs, ys, label=curve_label, linewidth=0.75)
+
+
+def plot_phase_val(metric, log_dict, epochs, curve_label, json_log):
+ """plot phase of val cruves."""
+ # some epoch may not have evaluation. as [(train, 5),(val, 1)]
+ xs = [e for e in epochs if metric in log_dict[e]]
+ ys = [log_dict[e][metric] for e in xs if metric in log_dict[e]]
+ assert len(xs) > 0, (f'{json_log} does not contain metric {metric}')
+ plt.xlabel('Epochs')
+ plt.plot(xs, ys, label=curve_label, linewidth=0.75)
+
+
+def plot_curve_helper(log_dicts, metrics, args, legend):
+ """plot curves from log_dicts by metrics."""
+ num_metrics = len(metrics)
+ for i, log_dict in enumerate(log_dicts):
+ epochs = list(log_dict.keys())
+ for j, metric in enumerate(metrics):
+ json_log = args.json_logs[i]
+ print(f'plot curve of {json_log}, metric is {metric}')
+ curve_label = legend[i * num_metrics + j]
+ if any(m in metric for m in TEST_METRICS):
+ plot_phase_val(metric, log_dict, epochs, curve_label, json_log)
+ else:
+ plot_phase_train(metric, log_dict, epochs, curve_label,
+ json_log)
+ plt.legend()
+
+
+def plot_curve(log_dicts, args):
+ """Plot train metric-iter graph."""
+ # set backend and style
+ if args.backend is not None:
+ plt.switch_backend(args.backend)
+ try:
+ import seaborn as sns
+ sns.set_style(args.style)
+ except ImportError:
+ print("Attention: The plot style won't be applied because 'seaborn' "
+ 'package is not installed, please install it if you want better '
+ 'show style.')
+
+ # set plot window size
+ wind_w, wind_h = args.window_size.split('*')
+ wind_w, wind_h = int(wind_w), int(wind_h)
+ plt.figure(figsize=(wind_w, wind_h))
+
+ # get legends and metrics
+ legends = get_legends(args)
+ metrics = args.keys
+
+ # plot curves from log_dicts by metrics
+ plot_curve_helper(log_dicts, metrics, args, legends)
+
+ # set title and show or save
+ if args.title is not None:
+ plt.title(args.title)
+ if args.out is None:
+ plt.show()
+ else:
+ print(f'save curve to: {args.out}')
+ plt.savefig(args.out)
+ plt.cla()
+
+
+def add_plot_parser(subparsers):
+ parser_plt = subparsers.add_parser(
+ 'plot_curve', help='parser for plotting curves')
+ parser_plt.add_argument(
+ 'json_logs',
+ type=str,
+ nargs='+',
+ help='path of train log in json format')
+ parser_plt.add_argument(
+ '--keys',
+ type=str,
+ nargs='+',
+ default=['loss'],
+ help='the metric that you want to plot')
+ parser_plt.add_argument('--title', type=str, help='title of figure')
+ parser_plt.add_argument(
+ '--legend',
+ type=str,
+ nargs='+',
+ default=None,
+ help='legend of each plot')
+ parser_plt.add_argument(
+ '--backend', type=str, default=None, help='backend of plt')
+ parser_plt.add_argument(
+ '--style', type=str, default='whitegrid', help='style of plt')
+ parser_plt.add_argument('--out', type=str, default=None)
+ parser_plt.add_argument(
+ '--window-size',
+ default='12*7',
+ help='size of the window to display images, in format of "$W*$H".')
+
+
+def add_time_parser(subparsers):
+ parser_time = subparsers.add_parser(
+ 'cal_train_time',
+ help='parser for computing the average time per training iteration')
+ parser_time.add_argument(
+ 'json_logs',
+ type=str,
+ nargs='+',
+ help='path of train log in json format')
+ parser_time.add_argument(
+ '--include-outliers',
+ action='store_true',
+ help='include the first value of every epoch when computing '
+ 'the average time')
+
+
+def parse_args():
+ parser = argparse.ArgumentParser(description='Analyze Json Log')
+ # currently only support plot curve and calculate average train time
+ subparsers = parser.add_subparsers(dest='task', help='task parser')
+ add_plot_parser(subparsers)
+ add_time_parser(subparsers)
+ args = parser.parse_args()
+
+ if hasattr(args, 'window_size') and args.window_size != '':
+ assert re.match(r'\d+\*\d+', args.window_size), \
+ "'window-size' must be in format 'W*H'."
+ return args
+
+
+def main():
+ args = parse_args()
+
+ json_logs = args.json_logs
+ for json_log in json_logs:
+ assert json_log.endswith('.json')
+
+ log_dicts = [load_json_log(json_log) for json_log in json_logs]
+
+ eval(args.task)(log_dicts, args)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tools/analyze_results.py b/openmmlab_test/mmclassification-0.24.1/tools/analysis_tools/analyze_results.py
similarity index 75%
rename from openmmlab_test/mmclassification-speed-benchmark/tools/analyze_results.py
rename to openmmlab_test/mmclassification-0.24.1/tools/analysis_tools/analyze_results.py
index 6392eea8c7663fdb3317d5ed8fe88806453fac5c..82555ad5b1de1b6475cc13a7d50b0ddc55cdac04 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/tools/analyze_results.py
+++ b/openmmlab_test/mmclassification-0.24.1/tools/analysis_tools/analyze_results.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import argparse
import os.path as osp
@@ -20,12 +21,17 @@ def parse_args():
type=int,
help='Number of images to select for success/fail')
parser.add_argument(
- '--options',
+ '--cfg-options',
nargs='+',
action=DictAction,
help='override some settings in the used config, the key-value pair '
- 'in xxx=yyy format will be merged into config file.')
+ 'in xxx=yyy format will be merged into config file. If the value to '
+ 'be overwritten is a list, it should be like key="[a,b]" or key=a,b '
+ 'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" '
+ 'Note that the quotation marks are necessary and that no white space '
+ 'is allowed.')
args = parser.parse_args()
+
return args
@@ -45,9 +51,16 @@ def save_imgs(result_dir, folder_name, results, model):
def main():
args = parse_args()
+ # load test results
+ outputs = mmcv.load(args.result)
+ assert ('pred_score' in outputs and 'pred_class' in outputs
+ and 'pred_label' in outputs), \
+ 'No "pred_label", "pred_score" or "pred_class" in result file, ' \
+ 'please set "--out-items" in test.py'
+
cfg = mmcv.Config.fromfile(args.config)
- if args.options is not None:
- cfg.merge_from_dict(args.options)
+ if args.cfg_options is not None:
+ cfg.merge_from_dict(args.cfg_options)
model = build_classifier(cfg.model)
@@ -64,12 +77,15 @@ def main():
gt_labels = list(dataset.get_gt_labels())
gt_classes = [dataset.CLASSES[x] for x in gt_labels]
- # load test results
- outputs = mmcv.load(args.result)
outputs['filename'] = filenames
outputs['gt_label'] = gt_labels
outputs['gt_class'] = gt_classes
+ need_keys = [
+ 'filename', 'gt_label', 'gt_class', 'pred_score', 'pred_label',
+ 'pred_class'
+ ]
+ outputs = {k: v for k, v in outputs.items() if k in need_keys}
outputs_list = list()
for i in range(len(gt_labels)):
output = dict()
diff --git a/openmmlab_test/mmclassification-0.24.1/tools/analysis_tools/eval_metric.py b/openmmlab_test/mmclassification-0.24.1/tools/analysis_tools/eval_metric.py
new file mode 100644
index 0000000000000000000000000000000000000000..1c95dbc0aa497e39ee1086df8554d138dd376c70
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tools/analysis_tools/eval_metric.py
@@ -0,0 +1,71 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import argparse
+
+import mmcv
+from mmcv import Config, DictAction
+
+from mmcls.datasets import build_dataset
+
+
+def parse_args():
+ parser = argparse.ArgumentParser(description='Evaluate metric of the '
+ 'results saved in pkl format')
+ parser.add_argument('config', help='Config of the model')
+ parser.add_argument('pkl_results', help='Results in pickle format')
+ parser.add_argument(
+ '--metrics',
+ type=str,
+ nargs='+',
+ help='Evaluation metrics, which depends on the dataset, e.g., '
+ '"accuracy", "precision", "recall" and "support".')
+ parser.add_argument(
+ '--cfg-options',
+ nargs='+',
+ action=DictAction,
+ help='override some settings in the used config, the key-value pair '
+ 'in xxx=yyy format will be merged into config file. If the value to '
+ 'be overwritten is a list, it should be like key="[a,b]" or key=a,b '
+ 'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" '
+ 'Note that the quotation marks are necessary and that no white space '
+ 'is allowed.')
+ parser.add_argument(
+ '--metric-options',
+ nargs='+',
+ action=DictAction,
+ help='custom options for evaluation, the key-value pair in xxx=yyy '
+ 'format will be kwargs for dataset.evaluate() function')
+ args = parser.parse_args()
+ return args
+
+
+def main():
+ args = parse_args()
+
+ outputs = mmcv.load(args.pkl_results)
+ assert 'class_scores' in outputs, \
+ 'No "class_scores" in result file, please set "--out-items" in test.py'
+
+ cfg = Config.fromfile(args.config)
+ assert args.metrics, (
+ 'Please specify at least one metric the argument "--metrics".')
+
+ if args.cfg_options is not None:
+ cfg.merge_from_dict(args.cfg_options)
+ cfg.data.test.test_mode = True
+
+ dataset = build_dataset(cfg.data.test)
+ pred_score = outputs['class_scores']
+
+ eval_kwargs = cfg.get('evaluation', {}).copy()
+ # hard-code way to remove EvalHook args
+ for key in [
+ 'interval', 'tmpdir', 'start', 'gpu_collect', 'save_best', 'rule'
+ ]:
+ eval_kwargs.pop(key, None)
+ eval_kwargs.update(
+ dict(metric=args.metrics, metric_options=args.metric_options))
+ print(dataset.evaluate(pred_score, **eval_kwargs))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tools/get_flops.py b/openmmlab_test/mmclassification-0.24.1/tools/analysis_tools/get_flops.py
similarity index 91%
rename from openmmlab_test/mmclassification-speed-benchmark/tools/get_flops.py
rename to openmmlab_test/mmclassification-0.24.1/tools/analysis_tools/get_flops.py
index cce41e05174865ebcb43a42d15e8a3dae8bb27cb..45a87857452e1a1a31fc3328fcbd3a6c86b253a7 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/tools/get_flops.py
+++ b/openmmlab_test/mmclassification-0.24.1/tools/analysis_tools/get_flops.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import argparse
from mmcv import Config
@@ -34,8 +35,8 @@ def main():
model = build_classifier(cfg.model)
model.eval()
- if hasattr(model, 'extract_feat'):
- model.forward = model.extract_feat
+ if hasattr(model, 'forward_dummy'):
+ model.forward = model.forward_dummy
else:
raise NotImplementedError(
'FLOPs counter is currently not currently supported with {}'.
diff --git a/openmmlab_test/mmclassification-0.24.1/tools/convert_models/efficientnet_to_mmcls.py b/openmmlab_test/mmclassification-0.24.1/tools/convert_models/efficientnet_to_mmcls.py
new file mode 100644
index 0000000000000000000000000000000000000000..d1b097bd4ca2c4a28bafd588647b130ffb685499
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tools/convert_models/efficientnet_to_mmcls.py
@@ -0,0 +1,215 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import argparse
+import os
+
+import numpy as np
+import torch
+from mmcv.runner import Sequential
+from tensorflow.python.training import py_checkpoint_reader
+
+from mmcls.models.backbones.efficientnet import EfficientNet
+
+
+def tf2pth(v):
+ if v.ndim == 4:
+ return np.ascontiguousarray(v.transpose(3, 2, 0, 1))
+ elif v.ndim == 2:
+ return np.ascontiguousarray(v.transpose())
+ return v
+
+
+def read_ckpt(ckpt):
+ reader = py_checkpoint_reader.NewCheckpointReader(ckpt)
+ weights = {
+ n: torch.as_tensor(tf2pth(reader.get_tensor(n)))
+ for (n, _) in reader.get_variable_to_shape_map().items()
+ }
+ return weights
+
+
+def map_key(weight):
+ m = dict()
+ has_expand_conv = set()
+ is_MBConv = set()
+ max_idx = 0
+ name = None
+ for k, v in weight.items():
+ seg = k.split('/')
+ if len(seg) == 1:
+ continue
+ if 'edgetpu' in seg[0]:
+ name = 'e' + seg[0][21:].lower()
+ else:
+ name = seg[0][13:]
+ if seg[2] == 'tpu_batch_normalization_2':
+ has_expand_conv.add(seg[1])
+ if seg[1].startswith('blocks_'):
+ idx = int(seg[1][7:]) + 1
+ max_idx = max(max_idx, idx)
+ if 'depthwise' in k:
+ is_MBConv.add(seg[1])
+
+ model = EfficientNet(name)
+ idx2key = []
+ for idx, module in enumerate(model.layers):
+ if isinstance(module, Sequential):
+ for j in range(len(module)):
+ idx2key.append('{}.{}'.format(idx, j))
+ else:
+ idx2key.append('{}'.format(idx))
+
+ for k, v in weight.items():
+
+ if 'Exponential' in k or 'RMS' in k:
+ continue
+
+ seg = k.split('/')
+ if len(seg) == 1:
+ continue
+ if seg[2] == 'depthwise_conv2d':
+ v = v.transpose(1, 0)
+
+ if seg[1] == 'stem':
+ prefix = 'backbone.layers.{}'.format(idx2key[0])
+ mapping = {
+ 'conv2d/kernel': 'conv.weight',
+ 'tpu_batch_normalization/beta': 'bn.bias',
+ 'tpu_batch_normalization/gamma': 'bn.weight',
+ 'tpu_batch_normalization/moving_mean': 'bn.running_mean',
+ 'tpu_batch_normalization/moving_variance': 'bn.running_var',
+ }
+ suffix = mapping['/'.join(seg[2:])]
+ m[prefix + '.' + suffix] = v
+
+ elif seg[1].startswith('blocks_'):
+ idx = int(seg[1][7:]) + 1
+ prefix = '.'.join(['backbone', 'layers', idx2key[idx]])
+ if seg[1] not in is_MBConv:
+ mapping = {
+ 'conv2d/kernel':
+ 'conv1.conv.weight',
+ 'tpu_batch_normalization/gamma':
+ 'conv1.bn.weight',
+ 'tpu_batch_normalization/beta':
+ 'conv1.bn.bias',
+ 'tpu_batch_normalization/moving_mean':
+ 'conv1.bn.running_mean',
+ 'tpu_batch_normalization/moving_variance':
+ 'conv1.bn.running_var',
+ 'conv2d_1/kernel':
+ 'conv2.conv.weight',
+ 'tpu_batch_normalization_1/gamma':
+ 'conv2.bn.weight',
+ 'tpu_batch_normalization_1/beta':
+ 'conv2.bn.bias',
+ 'tpu_batch_normalization_1/moving_mean':
+ 'conv2.bn.running_mean',
+ 'tpu_batch_normalization_1/moving_variance':
+ 'conv2.bn.running_var',
+ }
+ else:
+
+ base_mapping = {
+ 'depthwise_conv2d/depthwise_kernel':
+ 'depthwise_conv.conv.weight',
+ 'se/conv2d/kernel': 'se.conv1.conv.weight',
+ 'se/conv2d/bias': 'se.conv1.conv.bias',
+ 'se/conv2d_1/kernel': 'se.conv2.conv.weight',
+ 'se/conv2d_1/bias': 'se.conv2.conv.bias'
+ }
+
+ if seg[1] not in has_expand_conv:
+ mapping = {
+ 'conv2d/kernel':
+ 'linear_conv.conv.weight',
+ 'tpu_batch_normalization/beta':
+ 'depthwise_conv.bn.bias',
+ 'tpu_batch_normalization/gamma':
+ 'depthwise_conv.bn.weight',
+ 'tpu_batch_normalization/moving_mean':
+ 'depthwise_conv.bn.running_mean',
+ 'tpu_batch_normalization/moving_variance':
+ 'depthwise_conv.bn.running_var',
+ 'tpu_batch_normalization_1/beta':
+ 'linear_conv.bn.bias',
+ 'tpu_batch_normalization_1/gamma':
+ 'linear_conv.bn.weight',
+ 'tpu_batch_normalization_1/moving_mean':
+ 'linear_conv.bn.running_mean',
+ 'tpu_batch_normalization_1/moving_variance':
+ 'linear_conv.bn.running_var',
+ }
+ else:
+ mapping = {
+ 'depthwise_conv2d/depthwise_kernel':
+ 'depthwise_conv.conv.weight',
+ 'conv2d/kernel':
+ 'expand_conv.conv.weight',
+ 'conv2d_1/kernel':
+ 'linear_conv.conv.weight',
+ 'tpu_batch_normalization/beta':
+ 'expand_conv.bn.bias',
+ 'tpu_batch_normalization/gamma':
+ 'expand_conv.bn.weight',
+ 'tpu_batch_normalization/moving_mean':
+ 'expand_conv.bn.running_mean',
+ 'tpu_batch_normalization/moving_variance':
+ 'expand_conv.bn.running_var',
+ 'tpu_batch_normalization_1/beta':
+ 'depthwise_conv.bn.bias',
+ 'tpu_batch_normalization_1/gamma':
+ 'depthwise_conv.bn.weight',
+ 'tpu_batch_normalization_1/moving_mean':
+ 'depthwise_conv.bn.running_mean',
+ 'tpu_batch_normalization_1/moving_variance':
+ 'depthwise_conv.bn.running_var',
+ 'tpu_batch_normalization_2/beta':
+ 'linear_conv.bn.bias',
+ 'tpu_batch_normalization_2/gamma':
+ 'linear_conv.bn.weight',
+ 'tpu_batch_normalization_2/moving_mean':
+ 'linear_conv.bn.running_mean',
+ 'tpu_batch_normalization_2/moving_variance':
+ 'linear_conv.bn.running_var',
+ }
+ mapping.update(base_mapping)
+ suffix = mapping['/'.join(seg[2:])]
+ m[prefix + '.' + suffix] = v
+ elif seg[1] == 'head':
+ seq_key = idx2key[max_idx + 1]
+ mapping = {
+ 'conv2d/kernel':
+ 'backbone.layers.{}.conv.weight'.format(seq_key),
+ 'tpu_batch_normalization/beta':
+ 'backbone.layers.{}.bn.bias'.format(seq_key),
+ 'tpu_batch_normalization/gamma':
+ 'backbone.layers.{}.bn.weight'.format(seq_key),
+ 'tpu_batch_normalization/moving_mean':
+ 'backbone.layers.{}.bn.running_mean'.format(seq_key),
+ 'tpu_batch_normalization/moving_variance':
+ 'backbone.layers.{}.bn.running_var'.format(seq_key),
+ 'dense/kernel':
+ 'head.fc.weight',
+ 'dense/bias':
+ 'head.fc.bias'
+ }
+ key = mapping['/'.join(seg[2:])]
+ if name.startswith('e') and 'fc' in key:
+ v = v[1:]
+ m[key] = v
+ return m
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+ parser.add_argument('infile', type=str, help='Path to the ckpt.')
+ parser.add_argument('outfile', type=str, help='Output file.')
+ args = parser.parse_args()
+ assert args.outfile
+
+ outdir = os.path.dirname(os.path.abspath(args.outfile))
+ if not os.path.exists(outdir):
+ os.makedirs(outdir)
+ weights = read_ckpt(args.infile)
+ weights = map_key(weights)
+ torch.save(weights, args.outfile)
diff --git a/openmmlab_test/mmclassification-0.24.1/tools/convert_models/hornet2mmcls.py b/openmmlab_test/mmclassification-0.24.1/tools/convert_models/hornet2mmcls.py
new file mode 100644
index 0000000000000000000000000000000000000000..6f39ffb2ec09fae3aaedd5d005952fb934f662a3
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tools/convert_models/hornet2mmcls.py
@@ -0,0 +1,61 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import argparse
+import os.path as osp
+from collections import OrderedDict
+
+import mmcv
+import torch
+from mmcv.runner import CheckpointLoader
+
+
+def convert_hornet(ckpt):
+
+ new_ckpt = OrderedDict()
+
+ for k, v in list(ckpt.items()):
+ new_v = v
+ if k.startswith('head'):
+ new_k = k.replace('head.', 'head.fc.')
+ new_ckpt[new_k] = new_v
+ continue
+ elif k.startswith('norm'):
+ new_k = k.replace('norm.', 'norm3.')
+ elif 'gnconv.pws' in k:
+ new_k = k.replace('gnconv.pws', 'gnconv.projs')
+ elif 'gamma1' in k:
+ new_k = k.replace('gamma1', 'gamma1.weight')
+ elif 'gamma2' in k:
+ new_k = k.replace('gamma2', 'gamma2.weight')
+ else:
+ new_k = k
+
+ if not new_k.startswith('head'):
+ new_k = 'backbone.' + new_k
+ new_ckpt[new_k] = new_v
+ return new_ckpt
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description='Convert keys in pretrained van models to mmcls style.')
+ parser.add_argument('src', help='src model path or url')
+ # The dst path must be a full path of the new checkpoint.
+ parser.add_argument('dst', help='save path')
+ args = parser.parse_args()
+
+ checkpoint = CheckpointLoader.load_checkpoint(args.src, map_location='cpu')
+
+ if 'model' in checkpoint:
+ state_dict = checkpoint['model']
+ else:
+ state_dict = checkpoint
+
+ weight = convert_hornet(state_dict)
+ mmcv.mkdir_or_exist(osp.dirname(args.dst))
+ torch.save(weight, args.dst)
+
+ print('Done!!')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/openmmlab_test/mmclassification-0.24.1/tools/convert_models/mlpmixer_to_mmcls.py b/openmmlab_test/mmclassification-0.24.1/tools/convert_models/mlpmixer_to_mmcls.py
new file mode 100644
index 0000000000000000000000000000000000000000..6096c138b798004a81daec9249c72b9c3e8d94e1
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tools/convert_models/mlpmixer_to_mmcls.py
@@ -0,0 +1,58 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import argparse
+from pathlib import Path
+
+import torch
+
+
+def convert_weights(weight):
+ """Weight Converter.
+
+ Converts the weights from timm to mmcls
+
+ Args:
+ weight (dict): weight dict from timm
+
+ Returns: converted weight dict for mmcls
+ """
+ result = dict()
+ result['meta'] = dict()
+ temp = dict()
+ mapping = {
+ 'stem': 'patch_embed',
+ 'proj': 'projection',
+ 'mlp_tokens.fc1': 'token_mix.layers.0.0',
+ 'mlp_tokens.fc2': 'token_mix.layers.1',
+ 'mlp_channels.fc1': 'channel_mix.layers.0.0',
+ 'mlp_channels.fc2': 'channel_mix.layers.1',
+ 'norm1': 'ln1',
+ 'norm2': 'ln2',
+ 'norm.': 'ln1.',
+ 'blocks': 'layers'
+ }
+ for k, v in weight.items():
+ for mk, mv in mapping.items():
+ if mk in k:
+ k = k.replace(mk, mv)
+ if k.startswith('head.'):
+ temp['head.fc.' + k[5:]] = v
+ else:
+ temp['backbone.' + k] = v
+ result['state_dict'] = temp
+ return result
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(description='Convert model keys')
+ parser.add_argument('src', help='src detectron model path')
+ parser.add_argument('dst', help='save path')
+ args = parser.parse_args()
+ dst = Path(args.dst)
+ if dst.suffix != '.pth':
+ print('The path should contain the name of the pth format file.')
+ exit(1)
+ dst.parent.mkdir(parents=True, exist_ok=True)
+
+ original_model = torch.load(args.src, map_location='cpu')
+ converted_model = convert_weights(original_model)
+ torch.save(converted_model, args.dst)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tools/convert_models/mobilenetv2_to_mmcls.py b/openmmlab_test/mmclassification-0.24.1/tools/convert_models/mobilenetv2_to_mmcls.py
similarity index 98%
rename from openmmlab_test/mmclassification-speed-benchmark/tools/convert_models/mobilenetv2_to_mmcls.py
rename to openmmlab_test/mmclassification-0.24.1/tools/convert_models/mobilenetv2_to_mmcls.py
index 9957e0ec3298912953b54df0951d64e4bc0ace9b..7f6654eda735eecc1fc819b8203c3420a8a1e665 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/tools/convert_models/mobilenetv2_to_mmcls.py
+++ b/openmmlab_test/mmclassification-0.24.1/tools/convert_models/mobilenetv2_to_mmcls.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import argparse
from collections import OrderedDict
diff --git a/openmmlab_test/mmclassification-0.24.1/tools/convert_models/publish_model.py b/openmmlab_test/mmclassification-0.24.1/tools/convert_models/publish_model.py
new file mode 100644
index 0000000000000000000000000000000000000000..a80f3e2964ce059393a62820453e5b451c7b95ab
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tools/convert_models/publish_model.py
@@ -0,0 +1,55 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import argparse
+import datetime
+import subprocess
+from pathlib import Path
+
+import torch
+from mmcv import digit_version
+
+
+def parse_args():
+ parser = argparse.ArgumentParser(
+ description='Process a checkpoint to be published')
+ parser.add_argument('in_file', help='input checkpoint filename')
+ parser.add_argument('out_file', help='output checkpoint filename')
+ args = parser.parse_args()
+ return args
+
+
+def process_checkpoint(in_file, out_file):
+ checkpoint = torch.load(in_file, map_location='cpu')
+ # remove optimizer for smaller file size
+ if 'optimizer' in checkpoint:
+ del checkpoint['optimizer']
+ # if it is necessary to remove some sensitive data in checkpoint['meta'],
+ # add the code here.
+ if digit_version(torch.__version__) >= digit_version('1.6'):
+ torch.save(checkpoint, out_file, _use_new_zipfile_serialization=False)
+ else:
+ torch.save(checkpoint, out_file)
+
+ sha = subprocess.check_output(['sha256sum', out_file]).decode()
+ if out_file.endswith('.pth'):
+ out_file_name = out_file[:-4]
+ else:
+ out_file_name = out_file
+
+ current_date = datetime.datetime.now().strftime('%Y%m%d')
+ final_file = out_file_name + f'_{current_date}-{sha[:8]}.pth'
+ subprocess.Popen(['mv', out_file, final_file])
+
+ print(f'Successfully generated the publish-ckpt as {final_file}.')
+
+
+def main():
+ args = parse_args()
+ out_dir = Path(args.out_file).parent
+ if not out_dir.exists():
+ raise ValueError(f'Directory {out_dir} does not exist, '
+ 'please generate it manually.')
+ process_checkpoint(args.in_file, args.out_file)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/openmmlab_test/mmclassification-0.24.1/tools/convert_models/reparameterize_model.py b/openmmlab_test/mmclassification-0.24.1/tools/convert_models/reparameterize_model.py
new file mode 100644
index 0000000000000000000000000000000000000000..5224c356b470347b0b2c6b639d0f0d55a59c77f4
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tools/convert_models/reparameterize_model.py
@@ -0,0 +1,55 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import argparse
+from pathlib import Path
+
+import torch
+
+from mmcls.apis import init_model
+from mmcls.models.classifiers import ImageClassifier
+
+
+def convert_classifier_to_deploy(model, save_path):
+ print('Converting...')
+ assert hasattr(model, 'backbone') and \
+ hasattr(model.backbone, 'switch_to_deploy'), \
+ '`model.backbone` must has method of "switch_to_deploy".' \
+ f' But {model.backbone.__class__} does not have.'
+
+ model.backbone.switch_to_deploy()
+ torch.save(model.state_dict(), save_path)
+
+ print('Done! Save at path "{}"'.format(save_path))
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description='Convert the parameters of the repvgg block '
+ 'from training mode to deployment mode.')
+ parser.add_argument(
+ 'config_path',
+ help='The path to the configuration file of the network '
+ 'containing the repvgg block.')
+ parser.add_argument(
+ 'checkpoint_path',
+ help='The path to the checkpoint file corresponding to the model.')
+ parser.add_argument(
+ 'save_path',
+ help='The path where the converted checkpoint file is stored.')
+ args = parser.parse_args()
+
+ save_path = Path(args.save_path)
+ if save_path.suffix != '.pth':
+ print('The path should contain the name of the pth format file.')
+ exit()
+ save_path.parent.mkdir(parents=True, exist_ok=True)
+
+ model = init_model(
+ args.config_path, checkpoint=args.checkpoint_path, device='cpu')
+ assert isinstance(model, ImageClassifier), \
+ '`model` must be a `mmcls.classifiers.ImageClassifier` instance.'
+
+ convert_classifier_to_deploy(model, args.save_path)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/openmmlab_test/mmclassification-0.24.1/tools/convert_models/reparameterize_repvgg.py b/openmmlab_test/mmclassification-0.24.1/tools/convert_models/reparameterize_repvgg.py
new file mode 100644
index 0000000000000000000000000000000000000000..e075d8377c73be4be30742501eb6ece484db841f
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tools/convert_models/reparameterize_repvgg.py
@@ -0,0 +1,60 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import argparse
+import warnings
+from pathlib import Path
+
+import torch
+
+from mmcls.apis import init_model
+
+bright_style, reset_style = '\x1b[1m', '\x1b[0m'
+red_text, blue_text = '\x1b[31m', '\x1b[34m'
+white_background = '\x1b[107m'
+
+msg = bright_style + red_text
+msg += 'DeprecationWarning: This tool will be deprecated in future. '
+msg += red_text + 'Welcome to use the '
+msg += white_background
+msg += '"tools/convert_models/reparameterize_model.py"'
+msg += reset_style
+warnings.warn(msg)
+
+
+def convert_repvggblock_param(config_path, checkpoint_path, save_path):
+ model = init_model(config_path, checkpoint=checkpoint_path)
+ print('Converting...')
+
+ model.backbone.switch_to_deploy()
+ torch.save(model.state_dict(), save_path)
+
+ print('Done! Save at path "{}"'.format(save_path))
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description='Convert the parameters of the repvgg block '
+ 'from training mode to deployment mode.')
+ parser.add_argument(
+ 'config_path',
+ help='The path to the configuration file of the network '
+ 'containing the repvgg block.')
+ parser.add_argument(
+ 'checkpoint_path',
+ help='The path to the checkpoint file corresponding to the model.')
+ parser.add_argument(
+ 'save_path',
+ help='The path where the converted checkpoint file is stored.')
+ args = parser.parse_args()
+
+ save_path = Path(args.save_path)
+ if save_path.suffix != '.pth':
+ print('The path should contain the name of the pth format file.')
+ exit(1)
+ save_path.parent.mkdir(parents=True, exist_ok=True)
+
+ convert_repvggblock_param(args.config_path, args.checkpoint_path,
+ args.save_path)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/openmmlab_test/mmclassification-0.24.1/tools/convert_models/repvgg_to_mmcls.py b/openmmlab_test/mmclassification-0.24.1/tools/convert_models/repvgg_to_mmcls.py
new file mode 100644
index 0000000000000000000000000000000000000000..b7a1f05301858e15f6c40920cf146f66d1bb5ae4
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tools/convert_models/repvgg_to_mmcls.py
@@ -0,0 +1,60 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import argparse
+from collections import OrderedDict
+from pathlib import Path
+
+import torch
+
+
+def convert(src, dst):
+ print('Converting...')
+ blobs = torch.load(src, map_location='cpu')
+ converted_state_dict = OrderedDict()
+
+ for key in blobs:
+ splited_key = key.split('.')
+ splited_key = ['norm' if i == 'bn' else i for i in splited_key]
+ splited_key = [
+ 'branch_norm' if i == 'rbr_identity' else i for i in splited_key
+ ]
+ splited_key = [
+ 'branch_1x1' if i == 'rbr_1x1' else i for i in splited_key
+ ]
+ splited_key = [
+ 'branch_3x3' if i == 'rbr_dense' else i for i in splited_key
+ ]
+ splited_key = [
+ 'backbone.stem' if i[:6] == 'stage0' else i for i in splited_key
+ ]
+ splited_key = [
+ 'backbone.stage_' + i[5] if i[:5] == 'stage' else i
+ for i in splited_key
+ ]
+ splited_key = ['se_layer' if i == 'se' else i for i in splited_key]
+ splited_key = ['conv1.conv' if i == 'down' else i for i in splited_key]
+ splited_key = ['conv2.conv' if i == 'up' else i for i in splited_key]
+ splited_key = ['head.fc' if i == 'linear' else i for i in splited_key]
+ new_key = '.'.join(splited_key)
+ converted_state_dict[new_key] = blobs[key]
+
+ torch.save(converted_state_dict, dst)
+ print('Done!')
+
+
+def main():
+ parser = argparse.ArgumentParser(description='Convert model keys')
+ parser.add_argument('src', help='src detectron model path')
+ parser.add_argument('dst', help='save path')
+ args = parser.parse_args()
+
+ dst = Path(args.dst)
+ if dst.suffix != '.pth':
+ print('The path should contain the name of the pth format file.')
+ exit(1)
+ dst.parent.mkdir(parents=True, exist_ok=True)
+
+ convert(args.src, args.dst)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tools/convert_models/shufflenetv2_to_mmcls.py b/openmmlab_test/mmclassification-0.24.1/tools/convert_models/shufflenetv2_to_mmcls.py
similarity index 98%
rename from openmmlab_test/mmclassification-speed-benchmark/tools/convert_models/shufflenetv2_to_mmcls.py
rename to openmmlab_test/mmclassification-0.24.1/tools/convert_models/shufflenetv2_to_mmcls.py
index 29b9503f12e9321e27099f40b08f77ca29bae849..69046c364cda31fadaca942862d649d86136bf77 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/tools/convert_models/shufflenetv2_to_mmcls.py
+++ b/openmmlab_test/mmclassification-0.24.1/tools/convert_models/shufflenetv2_to_mmcls.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import argparse
from collections import OrderedDict
diff --git a/openmmlab_test/mmclassification-0.24.1/tools/convert_models/torchvision_to_mmcls.py b/openmmlab_test/mmclassification-0.24.1/tools/convert_models/torchvision_to_mmcls.py
new file mode 100644
index 0000000000000000000000000000000000000000..679b791e3634352b1721072949f5317865281333
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tools/convert_models/torchvision_to_mmcls.py
@@ -0,0 +1,63 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import argparse
+from collections import OrderedDict
+from pathlib import Path
+
+import torch
+
+
+def convert_resnet(src_dict, dst_dict):
+ """convert resnet checkpoints from torchvision."""
+ for key, value in src_dict.items():
+ if not key.startswith('fc'):
+ dst_dict['backbone.' + key] = value
+ else:
+ dst_dict['head.' + key] = value
+
+
+# model name to convert function
+CONVERT_F_DICT = {
+ 'resnet': convert_resnet,
+}
+
+
+def convert(src: str, dst: str, convert_f: callable):
+ print('Converting...')
+ blobs = torch.load(src, map_location='cpu')
+ converted_state_dict = OrderedDict()
+
+ # convert key in weight
+ convert_f(blobs, converted_state_dict)
+
+ torch.save(converted_state_dict, dst)
+ print('Done!')
+
+
+def main():
+ parser = argparse.ArgumentParser(description='Convert model keys')
+ parser.add_argument('src', help='src detectron model path')
+ parser.add_argument('dst', help='save path')
+ parser.add_argument(
+ 'model', type=str, help='The algorithm needs to change the keys.')
+ args = parser.parse_args()
+
+ dst = Path(args.dst)
+ if dst.suffix != '.pth':
+ print('The path should contain the name of the pth format file.')
+ exit(1)
+ dst.parent.mkdir(parents=True, exist_ok=True)
+
+ # this tool only support model in CONVERT_F_DICT
+ support_models = list(CONVERT_F_DICT.keys())
+ if args.model not in CONVERT_F_DICT:
+ print(f'The "{args.model}" has not been supported to convert now.')
+ print(f'This tool only supports {", ".join(support_models)}.')
+ print('If you have done the converting job, PR is welcome!')
+ exit(1)
+
+ convert_f = CONVERT_F_DICT[args.model]
+ convert(args.src, args.dst, convert_f)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/openmmlab_test/mmclassification-0.24.1/tools/convert_models/twins2mmcls.py b/openmmlab_test/mmclassification-0.24.1/tools/convert_models/twins2mmcls.py
new file mode 100644
index 0000000000000000000000000000000000000000..e0ea04c27d46978d97c6debe380b24a5f0e45193
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tools/convert_models/twins2mmcls.py
@@ -0,0 +1,73 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import argparse
+import os.path as osp
+from collections import OrderedDict
+
+import mmcv
+import torch
+from mmcv.runner import CheckpointLoader
+
+
+def convert_twins(args, ckpt):
+
+ new_ckpt = OrderedDict()
+
+ for k, v in list(ckpt.items()):
+ new_v = v
+ if k.startswith('head'):
+ new_k = k.replace('head.', 'head.fc.')
+ new_ckpt[new_k] = new_v
+ continue
+ elif k.startswith('patch_embeds'):
+ if 'proj.' in k:
+ new_k = k.replace('proj.', 'projection.')
+ else:
+ new_k = k
+ elif k.startswith('blocks'):
+ k = k.replace('blocks', 'stages')
+ # Union
+ if 'mlp.fc1' in k:
+ new_k = k.replace('mlp.fc1', 'ffn.layers.0.0')
+ elif 'mlp.fc2' in k:
+ new_k = k.replace('mlp.fc2', 'ffn.layers.1')
+
+ else:
+ new_k = k
+ new_k = new_k.replace('blocks.', 'layers.')
+ elif k.startswith('pos_block'):
+ new_k = k.replace('pos_block', 'position_encodings')
+ if 'proj.0.' in new_k:
+ new_k = new_k.replace('proj.0.', 'proj.')
+ elif k.startswith('norm'):
+ new_k = k.replace('norm', 'norm_after_stage3')
+ else:
+ new_k = k
+ new_k = 'backbone.' + new_k
+ new_ckpt[new_k] = new_v
+ return new_ckpt
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description='Convert keys in timm pretrained vit models to '
+ 'MMClassification style.')
+ parser.add_argument('src', help='src model path or url')
+ # The dst path must be a full path of the new checkpoint.
+ parser.add_argument('dst', help='save path')
+ args = parser.parse_args()
+
+ checkpoint = CheckpointLoader.load_checkpoint(args.src, map_location='cpu')
+
+ if 'state_dict' in checkpoint:
+ # timm checkpoint
+ state_dict = checkpoint['state_dict']
+ else:
+ state_dict = checkpoint
+
+ weight = convert_twins(args, state_dict)
+ mmcv.mkdir_or_exist(osp.dirname(args.dst))
+ torch.save(weight, args.dst)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/openmmlab_test/mmclassification-0.24.1/tools/convert_models/van2mmcls.py b/openmmlab_test/mmclassification-0.24.1/tools/convert_models/van2mmcls.py
new file mode 100644
index 0000000000000000000000000000000000000000..5ea7d9ca75d95a5907ade5a9c505a2353c9fba5b
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tools/convert_models/van2mmcls.py
@@ -0,0 +1,65 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import argparse
+import os.path as osp
+from collections import OrderedDict
+
+import mmcv
+import torch
+from mmcv.runner import CheckpointLoader
+
+
+def convert_van(ckpt):
+
+ new_ckpt = OrderedDict()
+
+ for k, v in list(ckpt.items()):
+ new_v = v
+ if k.startswith('head'):
+ new_k = k.replace('head.', 'head.fc.')
+ new_ckpt[new_k] = new_v
+ continue
+ elif k.startswith('patch_embed'):
+ if 'proj.' in k:
+ new_k = k.replace('proj.', 'projection.')
+ else:
+ new_k = k
+ elif k.startswith('block'):
+ new_k = k.replace('block', 'blocks')
+ if 'attn.spatial_gating_unit' in new_k:
+ new_k = new_k.replace('conv0', 'DW_conv')
+ new_k = new_k.replace('conv_spatial', 'DW_D_conv')
+ if 'dwconv.dwconv' in new_k:
+ new_k = new_k.replace('dwconv.dwconv', 'dwconv')
+ else:
+ new_k = k
+
+ if not new_k.startswith('head'):
+ new_k = 'backbone.' + new_k
+ new_ckpt[new_k] = new_v
+ return new_ckpt
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description='Convert keys in pretrained van models to mmcls style.')
+ parser.add_argument('src', help='src model path or url')
+ # The dst path must be a full path of the new checkpoint.
+ parser.add_argument('dst', help='save path')
+ args = parser.parse_args()
+
+ checkpoint = CheckpointLoader.load_checkpoint(args.src, map_location='cpu')
+
+ if 'state_dict' in checkpoint:
+ state_dict = checkpoint['state_dict']
+ else:
+ state_dict = checkpoint
+
+ weight = convert_van(state_dict)
+ mmcv.mkdir_or_exist(osp.dirname(args.dst))
+ torch.save(weight, args.dst)
+
+ print('Done!!')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tools/convert_models/vgg_to_mmcls.py b/openmmlab_test/mmclassification-0.24.1/tools/convert_models/vgg_to_mmcls.py
similarity index 98%
rename from openmmlab_test/mmclassification-speed-benchmark/tools/convert_models/vgg_to_mmcls.py
rename to openmmlab_test/mmclassification-0.24.1/tools/convert_models/vgg_to_mmcls.py
index f1937c485c97cdde4e721938809de9182ceba112..b5ab87f6604a444ca06bfeebac93d91b1e05689c 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/tools/convert_models/vgg_to_mmcls.py
+++ b/openmmlab_test/mmclassification-0.24.1/tools/convert_models/vgg_to_mmcls.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import argparse
import os
from collections import OrderedDict
@@ -103,7 +104,7 @@ def main():
parser.add_argument(
'--bn', action='store_true', help='whether original vgg has BN')
parser.add_argument(
- '--layer_num',
+ '--layer-num',
type=int,
choices=[11, 13, 16, 19],
default=11,
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tools/deployment/mmcls2torchserve.py b/openmmlab_test/mmclassification-0.24.1/tools/deployment/mmcls2torchserve.py
similarity index 98%
rename from openmmlab_test/mmclassification-speed-benchmark/tools/deployment/mmcls2torchserve.py
rename to openmmlab_test/mmclassification-0.24.1/tools/deployment/mmcls2torchserve.py
index 6124291f36c50f813342db629884bfa3427cc0e5..b4ab14d8e8c2f7165455ab1bc9f4097b39ae0d0b 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/tools/deployment/mmcls2torchserve.py
+++ b/openmmlab_test/mmclassification-0.24.1/tools/deployment/mmcls2torchserve.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
from argparse import ArgumentParser, Namespace
from pathlib import Path
from tempfile import TemporaryDirectory
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tools/deployment/mmcls_handler.py b/openmmlab_test/mmclassification-0.24.1/tools/deployment/mmcls_handler.py
similarity index 97%
rename from openmmlab_test/mmclassification-speed-benchmark/tools/deployment/mmcls_handler.py
rename to openmmlab_test/mmclassification-0.24.1/tools/deployment/mmcls_handler.py
index 8a728f47810d34a026419c15b4c82e3eaff758a5..68815e96de5e5626ecdeac4959d07360a0c96834 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/tools/deployment/mmcls_handler.py
+++ b/openmmlab_test/mmclassification-0.24.1/tools/deployment/mmcls_handler.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import base64
import os
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tools/deployment/onnx2tensorrt.py b/openmmlab_test/mmclassification-0.24.1/tools/deployment/onnx2tensorrt.py
similarity index 86%
rename from openmmlab_test/mmclassification-speed-benchmark/tools/deployment/onnx2tensorrt.py
rename to openmmlab_test/mmclassification-0.24.1/tools/deployment/onnx2tensorrt.py
index 60454066f5f77aaa1d63ccd9e0aeaf3ae351e9c7..8f71b6158d398a99cc35f2e9d3dc3f4f96c9b6c5 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/tools/deployment/onnx2tensorrt.py
+++ b/openmmlab_test/mmclassification-0.24.1/tools/deployment/onnx2tensorrt.py
@@ -1,6 +1,8 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import argparse
import os
import os.path as osp
+import warnings
import numpy as np
@@ -27,14 +29,14 @@ def onnx2tensorrt(onnx_file,
max_batch_size (int): Max batch size of the model.
verify (bool, optional): Whether to verify the converted model.
Defaults to False.
- workspace_size (int, optional): Maximium workspace of GPU.
+ workspace_size (int, optional): Maximum workspace of GPU.
Defaults to 1.
"""
import onnx
from mmcv.tensorrt import TRTWraper, onnx2trt, save_trt_engine
onnx_model = onnx.load(onnx_file)
- # create trt engine and wraper
+ # create trt engine and wrapper
assert max_batch_size >= 1
max_shape = [max_batch_size] + list(input_shape[1:])
opt_shape_dict = {'input': [input_shape, input_shape, max_shape]}
@@ -51,8 +53,8 @@ def onnx2tensorrt(onnx_file,
print(f'Successfully created TensorRT engine: {trt_file}')
if verify:
- import torch
import onnxruntime as ort
+ import torch
input_img = torch.randn(*input_shape)
input_img_cpu = input_img.detach().cpu().numpy()
@@ -139,3 +141,15 @@ if __name__ == '__main__':
fp16_mode=args.fp16,
verify=args.verify,
workspace_size=args.workspace_size)
+
+ # Following strings of text style are from colorama package
+ bright_style, reset_style = '\x1b[1m', '\x1b[0m'
+ red_text, blue_text = '\x1b[31m', '\x1b[34m'
+ white_background = '\x1b[107m'
+
+ msg = white_background + bright_style + red_text
+ msg += 'DeprecationWarning: This tool will be deprecated in future. '
+ msg += blue_text + 'Welcome to use the unified model deployment toolbox '
+ msg += 'MMDeploy: https://github.com/open-mmlab/mmdeploy'
+ msg += reset_style
+ warnings.warn(msg)
diff --git a/openmmlab_test/mmclassification-0.24.1/tools/deployment/pytorch2mlmodel.py b/openmmlab_test/mmclassification-0.24.1/tools/deployment/pytorch2mlmodel.py
new file mode 100644
index 0000000000000000000000000000000000000000..814cbe94e75427fb8a033bfdde4ad82a13ea81a6
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tools/deployment/pytorch2mlmodel.py
@@ -0,0 +1,160 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import argparse
+import os
+import os.path as osp
+import warnings
+from functools import partial
+
+import mmcv
+import numpy as np
+import torch
+from mmcv.runner import load_checkpoint
+from torch import nn
+
+from mmcls.models import build_classifier
+
+torch.manual_seed(3)
+
+try:
+ import coremltools as ct
+except ImportError:
+ raise ImportError('Please install coremltools to enable output file.')
+
+
+def _demo_mm_inputs(input_shape: tuple, num_classes: int):
+ """Create a superset of inputs needed to run test or train batches.
+
+ Args:
+ input_shape (tuple):
+ input batch dimensions
+ num_classes (int):
+ number of semantic classes
+ """
+ (N, C, H, W) = input_shape
+ rng = np.random.RandomState(0)
+ imgs = rng.rand(*input_shape)
+ gt_labels = rng.randint(
+ low=0, high=num_classes, size=(N, 1)).astype(np.uint8)
+ mm_inputs = {
+ 'imgs': torch.FloatTensor(imgs).requires_grad_(False),
+ 'gt_labels': torch.LongTensor(gt_labels),
+ }
+ return mm_inputs
+
+
+def pytorch2mlmodel(model: nn.Module, input_shape: tuple, output_file: str,
+ add_norm: bool, norm: dict):
+ """Export Pytorch model to mlmodel format that can be deployed in apple
+ devices through torch.jit.trace and the coremltools library.
+
+ Optionally, embed the normalization step as a layer to the model.
+
+ Args:
+ model (nn.Module): Pytorch model we want to export.
+ input_shape (tuple): Use this input shape to construct
+ the corresponding dummy input and execute the model.
+ show (bool): Whether print the computation graph. Default: False.
+ output_file (string): The path to where we store the output
+ TorchScript model.
+ add_norm (bool): Whether to embed the normalization layer to the
+ output model.
+ norm (dict): image normalization config for embedding it as a layer
+ to the output model.
+ """
+ model.cpu().eval()
+
+ num_classes = model.head.num_classes
+ mm_inputs = _demo_mm_inputs(input_shape, num_classes)
+
+ imgs = mm_inputs.pop('imgs')
+ img_list = [img[None, :] for img in imgs]
+ model.forward = partial(model.forward, img_metas={}, return_loss=False)
+
+ with torch.no_grad():
+ trace_model = torch.jit.trace(model, img_list[0])
+ save_dir, _ = osp.split(output_file)
+ if save_dir:
+ os.makedirs(save_dir, exist_ok=True)
+
+ if add_norm:
+ means, stds = norm.mean, norm.std
+ if stds.count(stds[0]) != len(stds):
+ warnings.warn(f'Image std from config is {stds}. However, '
+ 'current version of coremltools (5.1) uses a '
+ 'global std rather than the channel-specific '
+ 'values that torchvision uses. A mean will be '
+ 'taken but this might tamper with the resulting '
+ 'model\'s predictions. For more details refer '
+ 'to the coreml docs on ImageType pre-processing')
+ scale = np.mean(stds)
+ else:
+ scale = stds[0]
+
+ bias = [-mean / scale for mean in means]
+ image_input = ct.ImageType(
+ name='input_1',
+ shape=input_shape,
+ scale=1 / scale,
+ bias=bias,
+ color_layout='RGB',
+ channel_first=True)
+
+ coreml_model = ct.convert(trace_model, inputs=[image_input])
+ coreml_model.save(output_file)
+ else:
+ coreml_model = ct.convert(
+ trace_model, inputs=[ct.TensorType(shape=input_shape)])
+ coreml_model.save(output_file)
+
+ print(f'Successfully exported coreml model: {output_file}')
+
+
+def parse_args():
+ parser = argparse.ArgumentParser(
+ description='Convert MMCls to MlModel format for apple devices')
+ parser.add_argument('config', help='test config file path')
+ parser.add_argument('--checkpoint', help='checkpoint file', type=str)
+ parser.add_argument('--output-file', type=str, default='model.mlmodel')
+ parser.add_argument(
+ '--shape',
+ type=int,
+ nargs='+',
+ default=[224, 224],
+ help='input image size')
+ parser.add_argument(
+ '--add-norm-layer',
+ action='store_true',
+ help='embed normalization layer to deployed model')
+ args = parser.parse_args()
+ return args
+
+
+if __name__ == '__main__':
+ args = parse_args()
+
+ if len(args.shape) == 1:
+ input_shape = (1, 3, args.shape[0], args.shape[0])
+ elif len(args.shape) == 2:
+ input_shape = (
+ 1,
+ 3,
+ ) + tuple(args.shape)
+ else:
+ raise ValueError('invalid input shape')
+
+ cfg = mmcv.Config.fromfile(args.config)
+ cfg.model.pretrained = None
+
+ # build the model and load checkpoint
+ classifier = build_classifier(cfg.model)
+
+ if args.checkpoint:
+ load_checkpoint(classifier, args.checkpoint, map_location='cpu')
+
+ # convert model to mlmodel file
+ pytorch2mlmodel(
+ classifier,
+ input_shape,
+ output_file=args.output_file,
+ add_norm=args.add_norm_layer,
+ norm=cfg.img_norm_cfg)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tools/deployment/pytorch2onnx.py b/openmmlab_test/mmclassification-0.24.1/tools/deployment/pytorch2onnx.py
similarity index 81%
rename from openmmlab_test/mmclassification-speed-benchmark/tools/deployment/pytorch2onnx.py
rename to openmmlab_test/mmclassification-0.24.1/tools/deployment/pytorch2onnx.py
index 27aba5ff039a1245ec92f39ec0a2d13d41343891..85d795f1f8417e8463062edf02fd8bc724e3d57d 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/tools/deployment/pytorch2onnx.py
+++ b/openmmlab_test/mmclassification-0.24.1/tools/deployment/pytorch2onnx.py
@@ -1,4 +1,6 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import argparse
+import warnings
from functools import partial
import mmcv
@@ -58,7 +60,15 @@ def pytorch2onnx(model,
"""
model.cpu().eval()
- num_classes = model.head.num_classes
+ if hasattr(model.head, 'num_classes'):
+ num_classes = model.head.num_classes
+ # Some backbones use `num_classes=-1` to disable top classifier.
+ elif getattr(model.backbone, 'num_classes', -1) > 0:
+ num_classes = model.backbone.num_classes
+ else:
+ raise AttributeError('Cannot find "num_classes" in both head and '
+ 'backbone, please check the config file.')
+
mm_inputs = _demo_mm_inputs(input_shape, num_classes)
imgs = mm_inputs.pop('imgs')
@@ -99,29 +109,21 @@ def pytorch2onnx(model,
model.forward = origin_forward
if do_simplify:
- from mmcv import digit_version
+ import onnx
import onnxsim
+ from mmcv import digit_version
- min_required_version = '0.3.0'
- assert digit_version(mmcv.__version__) >= digit_version(
+ min_required_version = '0.4.0'
+ assert digit_version(onnxsim.__version__) >= digit_version(
min_required_version
- ), f'Requires to install onnx-simplify>={min_required_version}'
+ ), f'Requires to install onnxsim>={min_required_version}'
- if dynamic_axes:
- input_shape = (input_shape[0], input_shape[1], input_shape[2] * 2,
- input_shape[3] * 2)
+ model_opt, check_ok = onnxsim.simplify(output_file)
+ if check_ok:
+ onnx.save(model_opt, output_file)
+ print(f'Successfully simplified ONNX model: {output_file}')
else:
- input_shape = (input_shape[0], input_shape[1], input_shape[2],
- input_shape[3])
- imgs = _demo_mm_inputs(input_shape, model.head.num_classes).pop('imgs')
- input_dic = {'input': imgs.detach().cpu().numpy()}
- input_shape_dic = {'input': list(input_shape)}
-
- onnxsim.simplify(
- output_file,
- input_shapes=input_shape_dic,
- input_data=input_dic,
- dynamic_input_shape=dynamic_export)
+ print('Failed to simplify ONNX model.')
if verify:
# check by onnx
import onnx
@@ -206,7 +208,7 @@ if __name__ == '__main__':
if args.checkpoint:
load_checkpoint(classifier, args.checkpoint, map_location='cpu')
- # conver model to onnx file
+ # convert model to onnx file
pytorch2onnx(
classifier,
input_shape,
@@ -216,3 +218,15 @@ if __name__ == '__main__':
output_file=args.output_file,
do_simplify=args.simplify,
verify=args.verify)
+
+ # Following strings of text style are from colorama package
+ bright_style, reset_style = '\x1b[1m', '\x1b[0m'
+ red_text, blue_text = '\x1b[31m', '\x1b[34m'
+ white_background = '\x1b[107m'
+
+ msg = white_background + bright_style + red_text
+ msg += 'DeprecationWarning: This tool will be deprecated in future. '
+ msg += blue_text + 'Welcome to use the unified model deployment toolbox '
+ msg += 'MMDeploy: https://github.com/open-mmlab/mmdeploy'
+ msg += reset_style
+ warnings.warn(msg)
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tools/deployment/pytorch2torchscript.py b/openmmlab_test/mmclassification-0.24.1/tools/deployment/pytorch2torchscript.py
similarity index 97%
rename from openmmlab_test/mmclassification-speed-benchmark/tools/deployment/pytorch2torchscript.py
rename to openmmlab_test/mmclassification-0.24.1/tools/deployment/pytorch2torchscript.py
index edaca2d2df85e20ff02f8acd133b57c2951bfa55..f261b7c952602bc3c48f6f0cfaa8465bfccdb901 100644
--- a/openmmlab_test/mmclassification-speed-benchmark/tools/deployment/pytorch2torchscript.py
+++ b/openmmlab_test/mmclassification-0.24.1/tools/deployment/pytorch2torchscript.py
@@ -1,3 +1,4 @@
+# Copyright (c) OpenMMLab. All rights reserved.
import argparse
import os
import os.path as osp
@@ -130,7 +131,7 @@ if __name__ == '__main__':
if args.checkpoint:
load_checkpoint(classifier, args.checkpoint, map_location='cpu')
- # conver model to TorchScript file
+ # convert model to TorchScript file
pytorch2torchscript(
classifier,
input_shape,
diff --git a/openmmlab_test/mmclassification-0.24.1/tools/deployment/test.py b/openmmlab_test/mmclassification-0.24.1/tools/deployment/test.py
new file mode 100644
index 0000000000000000000000000000000000000000..5977f535a9feda616f6c856e8c43273d1ad123e4
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tools/deployment/test.py
@@ -0,0 +1,128 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import argparse
+import warnings
+
+import mmcv
+import numpy as np
+from mmcv import DictAction
+from mmcv.parallel import MMDataParallel
+
+from mmcls.apis import single_gpu_test
+from mmcls.core.export import ONNXRuntimeClassifier, TensorRTClassifier
+from mmcls.datasets import build_dataloader, build_dataset
+
+
+def parse_args():
+ parser = argparse.ArgumentParser(
+ description='Test (and eval) an ONNX model using ONNXRuntime.')
+ parser.add_argument('config', help='model config file')
+ parser.add_argument('model', help='filename of the input ONNX model')
+ parser.add_argument(
+ '--backend',
+ help='Backend of the model.',
+ choices=['onnxruntime', 'tensorrt'])
+ parser.add_argument(
+ '--out', type=str, help='output result file in pickle format')
+ parser.add_argument(
+ '--cfg-options',
+ nargs='+',
+ action=DictAction,
+ help='override some settings in the used config, the key-value pair '
+ 'in xxx=yyy format will be merged into config file.')
+ parser.add_argument(
+ '--metrics',
+ type=str,
+ nargs='+',
+ help='evaluation metrics, which depends on the dataset, e.g., '
+ '"accuracy", "precision", "recall", "f1_score", "support" for single '
+ 'label dataset, and "mAP", "CP", "CR", "CF1", "OP", "OR", "OF1" for '
+ 'multi-label dataset')
+ parser.add_argument(
+ '--metric-options',
+ nargs='+',
+ action=DictAction,
+ default={},
+ help='custom options for evaluation, the key-value pair in xxx=yyy '
+ 'format will be parsed as a dict metric_options for dataset.evaluate()'
+ ' function.')
+ parser.add_argument('--show', action='store_true', help='show results')
+ parser.add_argument(
+ '--show-dir', help='directory where painted images will be saved')
+ args = parser.parse_args()
+ return args
+
+
+def main():
+ args = parse_args()
+
+ if args.out is not None and not args.out.endswith(('.pkl', '.pickle')):
+ raise ValueError('The output file must be a pkl file.')
+
+ cfg = mmcv.Config.fromfile(args.config)
+ if args.cfg_options is not None:
+ cfg.merge_from_dict(args.cfg_options)
+
+ # build dataset and dataloader
+ dataset = build_dataset(cfg.data.test)
+ data_loader = build_dataloader(
+ dataset,
+ samples_per_gpu=cfg.data.samples_per_gpu,
+ workers_per_gpu=cfg.data.workers_per_gpu,
+ shuffle=False,
+ round_up=False)
+
+ # build onnxruntime model and run inference.
+ if args.backend == 'onnxruntime':
+ model = ONNXRuntimeClassifier(
+ args.model, class_names=dataset.CLASSES, device_id=0)
+ elif args.backend == 'tensorrt':
+ model = TensorRTClassifier(
+ args.model, class_names=dataset.CLASSES, device_id=0)
+ else:
+ print('Unknown backend: {}.'.format(args.model))
+ exit(1)
+
+ model = MMDataParallel(model, device_ids=[0])
+ model.CLASSES = dataset.CLASSES
+ outputs = single_gpu_test(model, data_loader, args.show, args.show_dir)
+
+ if args.metrics:
+ results = dataset.evaluate(outputs, args.metrics, args.metric_options)
+ for k, v in results.items():
+ print(f'\n{k} : {v:.2f}')
+ else:
+ warnings.warn('Evaluation metrics are not specified.')
+ scores = np.vstack(outputs)
+ pred_score = np.max(scores, axis=1)
+ pred_label = np.argmax(scores, axis=1)
+ pred_class = [dataset.CLASSES[lb] for lb in pred_label]
+ results = {
+ 'pred_score': pred_score,
+ 'pred_label': pred_label,
+ 'pred_class': pred_class
+ }
+ if not args.out:
+ print('\nthe predicted result for the first element is '
+ f'pred_score = {pred_score[0]:.2f}, '
+ f'pred_label = {pred_label[0]} '
+ f'and pred_class = {pred_class[0]}. '
+ 'Specify --out to save all results to files.')
+ if args.out:
+ print(f'\nwriting results to {args.out}')
+ mmcv.dump(results, args.out)
+
+
+if __name__ == '__main__':
+ main()
+
+ # Following strings of text style are from colorama package
+ bright_style, reset_style = '\x1b[1m', '\x1b[0m'
+ red_text, blue_text = '\x1b[31m', '\x1b[34m'
+ white_background = '\x1b[107m'
+
+ msg = white_background + bright_style + red_text
+ msg += 'DeprecationWarning: This tool will be deprecated in future. '
+ msg += blue_text + 'Welcome to use the unified model deployment toolbox '
+ msg += 'MMDeploy: https://github.com/open-mmlab/mmdeploy'
+ msg += reset_style
+ warnings.warn(msg)
diff --git a/openmmlab_test/mmclassification-0.24.1/tools/deployment/test_torchserver.py b/openmmlab_test/mmclassification-0.24.1/tools/deployment/test_torchserver.py
new file mode 100644
index 0000000000000000000000000000000000000000..1be611f946693bf654a85646dea58499b4ecaa93
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tools/deployment/test_torchserver.py
@@ -0,0 +1,45 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from argparse import ArgumentParser
+
+import numpy as np
+import requests
+
+from mmcls.apis import inference_model, init_model, show_result_pyplot
+
+
+def parse_args():
+ parser = ArgumentParser()
+ parser.add_argument('img', help='Image file')
+ parser.add_argument('config', help='Config file')
+ parser.add_argument('checkpoint', help='Checkpoint file')
+ parser.add_argument('model_name', help='The model name in the server')
+ parser.add_argument(
+ '--inference-addr',
+ default='127.0.0.1:8080',
+ help='Address and port of the inference server')
+ parser.add_argument(
+ '--device', default='cuda:0', help='Device used for inference')
+ args = parser.parse_args()
+ return args
+
+
+def main(args):
+ # Inference single image by native apis.
+ model = init_model(args.config, args.checkpoint, device=args.device)
+ model_result = inference_model(model, args.img)
+ show_result_pyplot(model, args.img, model_result, title='pytorch_result')
+
+ # Inference single image by torchserve engine.
+ url = 'http://' + args.inference_addr + '/predictions/' + args.model_name
+ with open(args.img, 'rb') as image:
+ response = requests.post(url, image)
+ server_result = response.json()
+ show_result_pyplot(model, args.img, server_result, title='server_result')
+
+ assert np.allclose(model_result['pred_score'], server_result['pred_score'])
+ print('Test complete, the results of PyTorch and TorchServe are the same.')
+
+
+if __name__ == '__main__':
+ args = parse_args()
+ main(args)
diff --git a/openmmlab_test/mmclassification-0.24.1/tools/dist_test.sh b/openmmlab_test/mmclassification-0.24.1/tools/dist_test.sh
new file mode 100644
index 0000000000000000000000000000000000000000..dea131b43ea8f1222661d20603d40c18ea7f28a1
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tools/dist_test.sh
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+CONFIG=$1
+CHECKPOINT=$2
+GPUS=$3
+NNODES=${NNODES:-1}
+NODE_RANK=${NODE_RANK:-0}
+PORT=${PORT:-29500}
+MASTER_ADDR=${MASTER_ADDR:-"127.0.0.1"}
+
+PYTHONPATH="$(dirname $0)/..":$PYTHONPATH \
+python -m torch.distributed.launch \
+ --nnodes=$NNODES \
+ --node_rank=$NODE_RANK \
+ --master_addr=$MASTER_ADDR \
+ --nproc_per_node=$GPUS \
+ --master_port=$PORT \
+ $(dirname "$0")/test.py \
+ $CONFIG \
+ $CHECKPOINT \
+ --launcher pytorch \
+ ${@:4}
diff --git a/openmmlab_test/mmclassification-0.24.1/tools/dist_train.sh b/openmmlab_test/mmclassification-0.24.1/tools/dist_train.sh
new file mode 100644
index 0000000000000000000000000000000000000000..b6aa2db56fa9394f228779c0a7f38fca7a806295
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tools/dist_train.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+export MIOPEN_FIND_MODE=1
+export MIOPEN_USE_APPROXIMATE_PERFORMANCE=0
+export HSA_FORCE_FINE_GRAIN_PCIE=1
+CONFIG=$1
+GPUS=$2
+NNODES=${NNODES:-1}
+NODE_RANK=${NODE_RANK:-0}
+PORT=${PORT:-29500}
+MASTER_ADDR=${MASTER_ADDR:-"127.0.0.1"}
+
+PYTHONPATH="$(dirname $0)/..":$PYTHONPATH \
+python -m torch.distributed.launch \
+ --nnodes=$NNODES \
+ --node_rank=$NODE_RANK \
+ --master_addr=$MASTER_ADDR \
+ --nproc_per_node=$GPUS \
+ --master_port=$PORT \
+ $(dirname "$0")/train.py \
+ $CONFIG \
+ --launcher pytorch ${@:3}
diff --git a/openmmlab_test/mmclassification-0.24.1/tools/kfold-cross-valid.py b/openmmlab_test/mmclassification-0.24.1/tools/kfold-cross-valid.py
new file mode 100644
index 0000000000000000000000000000000000000000..2f3a70e14b684e901753c7ed93cc7cfabc39f7c7
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tools/kfold-cross-valid.py
@@ -0,0 +1,371 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import argparse
+import copy
+import os
+import os.path as osp
+import time
+import warnings
+from datetime import datetime
+from pathlib import Path
+
+import mmcv
+import torch
+from mmcv import Config, DictAction
+from mmcv.runner import get_dist_info, init_dist
+
+from mmcls import __version__
+from mmcls.apis import init_random_seed, set_random_seed, train_model
+from mmcls.datasets import build_dataset
+from mmcls.models import build_classifier
+from mmcls.utils import collect_env, get_root_logger, load_json_log
+
+TEST_METRICS = ('precision', 'recall', 'f1_score', 'support', 'mAP', 'CP',
+ 'CR', 'CF1', 'OP', 'OR', 'OF1', 'accuracy')
+
+prog_description = """K-Fold cross-validation.
+
+To start a 5-fold cross-validation experiment:
+ python tools/kfold-cross-valid.py $CONFIG --num-splits 5
+
+To resume a 5-fold cross-validation from an interrupted experiment:
+ python tools/kfold-cross-valid.py $CONFIG --num-splits 5 --resume-from work_dirs/fold2/latest.pth
+
+To summarize a 5-fold cross-validation:
+ python tools/kfold-cross-valid.py $CONFIG --num-splits 5 --summary
+""" # noqa: E501
+
+
+def parse_args():
+ parser = argparse.ArgumentParser(
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ description=prog_description)
+ parser.add_argument('config', help='train config file path')
+ parser.add_argument(
+ '--num-splits', type=int, help='The number of all folds.')
+ parser.add_argument(
+ '--fold',
+ type=int,
+ help='The fold used to do validation. '
+ 'If specify, only do an experiment of the specified fold.')
+ parser.add_argument(
+ '--summary',
+ action='store_true',
+ help='Summarize the k-fold cross-validation results.')
+ parser.add_argument('--work-dir', help='the dir to save logs and models')
+ parser.add_argument(
+ '--resume-from', help='the checkpoint file to resume from')
+ parser.add_argument(
+ '--no-validate',
+ action='store_true',
+ help='whether not to evaluate the checkpoint during training')
+ group_gpus = parser.add_mutually_exclusive_group()
+ group_gpus.add_argument('--device', help='device used for training')
+ group_gpus.add_argument(
+ '--gpus',
+ type=int,
+ help='(Deprecated, please use --gpu-id) number of gpus to use '
+ '(only applicable to non-distributed training)')
+ group_gpus.add_argument(
+ '--gpu-ids',
+ type=int,
+ nargs='+',
+ help='(Deprecated, please use --gpu-id) ids of gpus to use '
+ '(only applicable to non-distributed training)')
+ group_gpus.add_argument(
+ '--gpu-id',
+ type=int,
+ default=0,
+ help='id of gpu to use '
+ '(only applicable to non-distributed training)')
+ parser.add_argument('--seed', type=int, default=None, help='random seed')
+ parser.add_argument(
+ '--deterministic',
+ action='store_true',
+ help='whether to set deterministic options for CUDNN backend.')
+ parser.add_argument(
+ '--cfg-options',
+ nargs='+',
+ action=DictAction,
+ help='override some settings in the used config, the key-value pair '
+ 'in xxx=yyy format will be merged into config file. If the value to '
+ 'be overwritten is a list, it should be like key="[a,b]" or key=a,b '
+ 'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" '
+ 'Note that the quotation marks are necessary and that no white space '
+ 'is allowed.')
+ parser.add_argument(
+ '--launcher',
+ choices=['none', 'pytorch', 'slurm', 'mpi'],
+ default='none',
+ help='job launcher')
+ parser.add_argument('--local_rank', type=int, default=0)
+ args = parser.parse_args()
+ if 'LOCAL_RANK' not in os.environ:
+ os.environ['LOCAL_RANK'] = str(args.local_rank)
+
+ return args
+
+
+def copy_config(old_cfg):
+ """deepcopy a Config object."""
+ new_cfg = Config()
+ _cfg_dict = copy.deepcopy(old_cfg._cfg_dict)
+ _filename = copy.deepcopy(old_cfg._filename)
+ _text = copy.deepcopy(old_cfg._text)
+ super(Config, new_cfg).__setattr__('_cfg_dict', _cfg_dict)
+ super(Config, new_cfg).__setattr__('_filename', _filename)
+ super(Config, new_cfg).__setattr__('_text', _text)
+ return new_cfg
+
+
+def train_single_fold(args, cfg, fold, distributed, seed):
+ # create the work_dir for the fold
+ work_dir = osp.join(cfg.work_dir, f'fold{fold}')
+ cfg.work_dir = work_dir
+
+ # create work_dir
+ mmcv.mkdir_or_exist(osp.abspath(cfg.work_dir))
+
+ # wrap the dataset cfg
+ train_dataset = dict(
+ type='KFoldDataset',
+ fold=fold,
+ dataset=cfg.data.train,
+ num_splits=args.num_splits,
+ seed=seed,
+ )
+ val_dataset = dict(
+ type='KFoldDataset',
+ fold=fold,
+ # Use the same dataset with training.
+ dataset=copy.deepcopy(cfg.data.train),
+ num_splits=args.num_splits,
+ seed=seed,
+ test_mode=True,
+ )
+ val_dataset['dataset']['pipeline'] = cfg.data.val.pipeline
+ cfg.data.train = train_dataset
+ cfg.data.val = val_dataset
+ cfg.data.test = val_dataset
+
+ # dump config
+ stem, suffix = osp.basename(args.config).rsplit('.', 1)
+ cfg.dump(osp.join(cfg.work_dir, f'{stem}_fold{fold}.{suffix}'))
+ # init the logger before other steps
+ timestamp = time.strftime('%Y%m%d_%H%M%S', time.localtime())
+ log_file = osp.join(cfg.work_dir, f'{timestamp}.log')
+ logger = get_root_logger(log_file=log_file, log_level=cfg.log_level)
+
+ # init the meta dict to record some important information such as
+ # environment info and seed, which will be logged
+ meta = dict()
+ # log env info
+ env_info_dict = collect_env()
+ env_info = '\n'.join([(f'{k}: {v}') for k, v in env_info_dict.items()])
+ dash_line = '-' * 60 + '\n'
+ logger.info('Environment info:\n' + dash_line + env_info + '\n' +
+ dash_line)
+ meta['env_info'] = env_info
+
+ # log some basic info
+ logger.info(f'Distributed training: {distributed}')
+ logger.info(f'Config:\n{cfg.pretty_text}')
+ logger.info(
+ f'-------- Cross-validation: [{fold+1}/{args.num_splits}] -------- ')
+
+ # set random seeds
+ # Use different seed in different folds
+ logger.info(f'Set random seed to {seed + fold}, '
+ f'deterministic: {args.deterministic}')
+ set_random_seed(seed + fold, deterministic=args.deterministic)
+ cfg.seed = seed + fold
+ meta['seed'] = seed + fold
+
+ model = build_classifier(cfg.model)
+ model.init_weights()
+
+ datasets = [build_dataset(cfg.data.train)]
+ if len(cfg.workflow) == 2:
+ val_dataset = copy.deepcopy(cfg.data.val)
+ val_dataset.pipeline = cfg.data.train.pipeline
+ datasets.append(build_dataset(val_dataset))
+ meta.update(
+ dict(
+ mmcls_version=__version__,
+ config=cfg.pretty_text,
+ CLASSES=datasets[0].CLASSES,
+ kfold=dict(fold=fold, num_splits=args.num_splits)))
+ # add an attribute for visualization convenience
+ train_model(
+ model,
+ datasets,
+ cfg,
+ distributed=distributed,
+ validate=(not args.no_validate),
+ timestamp=timestamp,
+ device='cpu' if args.device == 'cpu' else 'cuda',
+ meta=meta)
+
+
+def summary(args, cfg):
+ summary = dict()
+ for fold in range(args.num_splits):
+ work_dir = Path(cfg.work_dir) / f'fold{fold}'
+
+ # Find the latest training log
+ log_files = list(work_dir.glob('*.log.json'))
+ if len(log_files) == 0:
+ continue
+ log_file = sorted(log_files)[-1]
+
+ date = datetime.fromtimestamp(log_file.lstat().st_mtime)
+ summary[fold] = {'date': date.strftime('%Y-%m-%d %H:%M:%S')}
+
+ # Find the latest eval log
+ json_log = load_json_log(log_file)
+ epochs = sorted(list(json_log.keys()))
+ eval_log = {}
+
+ def is_metric_key(key):
+ for metric in TEST_METRICS:
+ if metric in key:
+ return True
+ return False
+
+ for epoch in epochs[::-1]:
+ if any(is_metric_key(k) for k in json_log[epoch].keys()):
+ eval_log = json_log[epoch]
+ break
+
+ summary[fold]['epoch'] = epoch
+ summary[fold]['metric'] = {
+ k: v[0] # the value is a list with only one item.
+ for k, v in eval_log.items() if is_metric_key(k)
+ }
+ show_summary(args, summary)
+
+
+def show_summary(args, summary_data):
+ try:
+ from rich.console import Console
+ from rich.table import Table
+ except ImportError:
+ raise ImportError('Please run `pip install rich` to install '
+ 'package `rich` to draw the table.')
+
+ console = Console()
+ table = Table(title=f'{args.num_splits}-fold Cross-validation Summary')
+ table.add_column('Fold')
+ metrics = summary_data[0]['metric'].keys()
+ for metric in metrics:
+ table.add_column(metric)
+ table.add_column('Epoch')
+ table.add_column('Date')
+
+ for fold in range(args.num_splits):
+ row = [f'{fold+1}']
+ if fold not in summary_data:
+ table.add_row(*row)
+ continue
+ for metric in metrics:
+ metric_value = summary_data[fold]['metric'].get(metric, '')
+
+ def format_value(value):
+ if isinstance(value, float):
+ return f'{value:.2f}'
+ if isinstance(value, (list, tuple)):
+ return str([format_value(i) for i in value])
+ else:
+ return str(value)
+
+ row.append(format_value(metric_value))
+ row.append(str(summary_data[fold]['epoch']))
+ row.append(summary_data[fold]['date'])
+ table.add_row(*row)
+
+ console.print(table)
+
+
+def main():
+ args = parse_args()
+
+ cfg = Config.fromfile(args.config)
+ if args.cfg_options is not None:
+ cfg.merge_from_dict(args.cfg_options)
+ # set cudnn_benchmark
+ if cfg.get('cudnn_benchmark', False):
+ torch.backends.cudnn.benchmark = True
+
+ # work_dir is determined in this priority: CLI > segment in file > filename
+ if args.work_dir is not None:
+ # update configs according to CLI args if args.work_dir is not None
+ cfg.work_dir = args.work_dir
+ elif cfg.get('work_dir', None) is None:
+ # use config filename as default work_dir if cfg.work_dir is None
+ cfg.work_dir = osp.join('./work_dirs',
+ osp.splitext(osp.basename(args.config))[0])
+
+ if args.summary:
+ summary(args, cfg)
+ return
+
+ # resume from the previous experiment
+ if args.resume_from is not None:
+ cfg.resume_from = args.resume_from
+ resume_kfold = torch.load(cfg.resume_from).get('meta',
+ {}).get('kfold', None)
+ if resume_kfold is None:
+ raise RuntimeError(
+ 'No "meta" key in checkpoints or no "kfold" in the meta dict. '
+ 'Please check if the resume checkpoint from a k-fold '
+ 'cross-valid experiment.')
+ resume_fold = resume_kfold['fold']
+ assert args.num_splits == resume_kfold['num_splits']
+ else:
+ resume_fold = 0
+
+ if args.gpus is not None:
+ cfg.gpu_ids = range(1)
+ warnings.warn('`--gpus` is deprecated because we only support '
+ 'single GPU mode in non-distributed training. '
+ 'Use `gpus=1` now.')
+ if args.gpu_ids is not None:
+ cfg.gpu_ids = args.gpu_ids[0:1]
+ warnings.warn('`--gpu-ids` is deprecated, please use `--gpu-id`. '
+ 'Because we only support single GPU mode in '
+ 'non-distributed training. Use the first GPU '
+ 'in `gpu_ids` now.')
+ if args.gpus is None and args.gpu_ids is None:
+ cfg.gpu_ids = [args.gpu_id]
+
+ # init distributed env first, since logger depends on the dist info.
+ if args.launcher == 'none':
+ distributed = False
+ else:
+ distributed = True
+ init_dist(args.launcher, **cfg.dist_params)
+ _, world_size = get_dist_info()
+ cfg.gpu_ids = range(world_size)
+
+ # init a unified random seed
+ seed = init_random_seed(args.seed)
+
+ # create work_dir
+ mmcv.mkdir_or_exist(osp.abspath(cfg.work_dir))
+
+ if args.fold is not None:
+ folds = [args.fold]
+ else:
+ folds = range(resume_fold, args.num_splits)
+
+ for fold in folds:
+ cfg_ = copy_config(cfg)
+ if fold != resume_fold:
+ cfg_.resume_from = None
+ train_single_fold(args, cfg_, fold, distributed, seed)
+
+ if args.fold is None:
+ summary(args, cfg)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/openmmlab_test/mmclassification-0.24.1/tools/misc/print_config.py b/openmmlab_test/mmclassification-0.24.1/tools/misc/print_config.py
new file mode 100644
index 0000000000000000000000000000000000000000..a2781a607ea52ff255950483333b8d35d364aadb
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tools/misc/print_config.py
@@ -0,0 +1,35 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import argparse
+
+from mmcv import Config, DictAction
+
+
+def parse_args():
+ parser = argparse.ArgumentParser(description='Print the whole config')
+ parser.add_argument('config', help='config file path')
+ parser.add_argument(
+ '--cfg-options',
+ nargs='+',
+ action=DictAction,
+ help='override some settings in the used config, the key-value pair '
+ 'in xxx=yyy format will be merged into config file. If the value to '
+ 'be overwritten is a list, it should be like key="[a,b]" or key=a,b '
+ 'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" '
+ 'Note that the quotation marks are necessary and that no white space '
+ 'is allowed.')
+ args = parser.parse_args()
+
+ return args
+
+
+def main():
+ args = parse_args()
+
+ cfg = Config.fromfile(args.config)
+ if args.cfg_options is not None:
+ cfg.merge_from_dict(args.cfg_options)
+ print(f'Config:\n{cfg.pretty_text}')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/openmmlab_test/mmclassification-0.24.1/tools/misc/verify_dataset.py b/openmmlab_test/mmclassification-0.24.1/tools/misc/verify_dataset.py
new file mode 100644
index 0000000000000000000000000000000000000000..6114adb152c717c0d6d2fdc9a25b537fade2266c
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tools/misc/verify_dataset.py
@@ -0,0 +1,131 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import argparse
+import fcntl
+import os
+from pathlib import Path
+
+from mmcv import Config, DictAction, track_parallel_progress, track_progress
+
+from mmcls.datasets import PIPELINES, build_dataset
+
+
+def parse_args():
+ parser = argparse.ArgumentParser(description='Verify Dataset')
+ parser.add_argument('config', help='config file path')
+ parser.add_argument(
+ '--out-path',
+ type=str,
+ default='brokenfiles.log',
+ help='output path of all the broken files. If the specified path '
+ 'already exists, delete the previous file ')
+ parser.add_argument(
+ '--phase',
+ default='train',
+ type=str,
+ choices=['train', 'test', 'val'],
+ help='phase of dataset to visualize, accept "train" "test" and "val".')
+ parser.add_argument(
+ '--num-process', type=int, default=1, help='number of process to use')
+ parser.add_argument(
+ '--cfg-options',
+ nargs='+',
+ action=DictAction,
+ help='override some settings in the used config, the key-value pair '
+ 'in xxx=yyy format will be merged into config file. If the value to '
+ 'be overwritten is a list, it should be like key="[a,b]" or key=a,b '
+ 'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" '
+ 'Note that the quotation marks are necessary and that no white space '
+ 'is allowed.')
+ args = parser.parse_args()
+ assert args.out_path is not None
+ assert args.num_process > 0
+ return args
+
+
+class DatasetValidator():
+ """the dataset tool class to check if all file are broken."""
+
+ def __init__(self, dataset_cfg, log_file_path, phase):
+ super(DatasetValidator, self).__init__()
+ # keep only LoadImageFromFile pipeline
+ assert dataset_cfg.data[phase].pipeline[0][
+ 'type'] == 'LoadImageFromFile', 'This tool is only for dataset ' \
+ 'that needs to load image from files.'
+ self.pipeline = PIPELINES.build(dataset_cfg.data[phase].pipeline[0])
+ dataset_cfg.data[phase].pipeline = []
+ dataset = build_dataset(dataset_cfg.data[phase])
+
+ self.dataset = dataset
+ self.log_file_path = log_file_path
+
+ def valid_idx(self, idx):
+ item = self.dataset[idx]
+ try:
+ item = self.pipeline(item)
+ except Exception:
+ with open(self.log_file_path, 'a') as f:
+ # add file lock to prevent multi-process writing errors
+ fcntl.flock(f.fileno(), fcntl.LOCK_EX)
+ filepath = os.path.join(item['img_prefix'],
+ item['img_info']['filename'])
+ f.write(filepath + '\n')
+ print(f'{filepath} cannot be read correctly, please check it.')
+ # Release files lock automatic using with
+
+ def __len__(self):
+ return len(self.dataset)
+
+
+def print_info(log_file_path):
+ """print some information and do extra action."""
+ print()
+ with open(log_file_path, 'r') as f:
+ context = f.read().strip()
+ if context == '':
+ print('There is no broken file found.')
+ os.remove(log_file_path)
+ else:
+ num_file = len(context.split('\n'))
+ print(f'{num_file} broken files found, name list save in file:'
+ f'{log_file_path}')
+ print()
+
+
+def main():
+ # parse cfg and args
+ args = parse_args()
+ cfg = Config.fromfile(args.config)
+ if args.cfg_options is not None:
+ cfg.merge_from_dict(args.cfg_options)
+
+ # touch output file to save broken files list.
+ output_path = Path(args.out_path)
+ if not output_path.parent.exists():
+ raise Exception('log_file parent directory not found.')
+ if output_path.exists():
+ os.remove(output_path)
+ output_path.touch()
+
+ # do valid
+ validator = DatasetValidator(cfg, output_path, args.phase)
+
+ if args.num_process > 1:
+ # The default chunksize calcuation method of Pool.map
+ chunksize, extra = divmod(len(validator), args.num_process * 8)
+ if extra:
+ chunksize += 1
+
+ track_parallel_progress(
+ validator.valid_idx,
+ list(range(len(validator))),
+ args.num_process,
+ chunksize=chunksize,
+ keep_order=False)
+ else:
+ track_progress(validator.valid_idx, list(range(len(validator))))
+
+ print_info(output_path)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tools/slurm_test.sh b/openmmlab_test/mmclassification-0.24.1/tools/slurm_test.sh
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/tools/slurm_test.sh
rename to openmmlab_test/mmclassification-0.24.1/tools/slurm_test.sh
diff --git a/openmmlab_test/mmclassification-speed-benchmark/tools/slurm_train.sh b/openmmlab_test/mmclassification-0.24.1/tools/slurm_train.sh
similarity index 100%
rename from openmmlab_test/mmclassification-speed-benchmark/tools/slurm_train.sh
rename to openmmlab_test/mmclassification-0.24.1/tools/slurm_train.sh
diff --git a/openmmlab_test/mmclassification-0.24.1/tools/test.py b/openmmlab_test/mmclassification-0.24.1/tools/test.py
new file mode 100644
index 0000000000000000000000000000000000000000..69cb2a810f6d2259d8235d88fd9ab861abbd2b3e
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tools/test.py
@@ -0,0 +1,254 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import argparse
+import os
+import warnings
+from numbers import Number
+
+import mmcv
+import numpy as np
+import torch
+from mmcv import DictAction
+from mmcv.runner import (get_dist_info, init_dist, load_checkpoint,
+ wrap_fp16_model)
+
+from mmcls.apis import multi_gpu_test, single_gpu_test
+from mmcls.datasets import build_dataloader, build_dataset
+from mmcls.models import build_classifier
+from mmcls.utils import (auto_select_device, get_root_logger,
+ setup_multi_processes, wrap_distributed_model,
+ wrap_non_distributed_model)
+
+
+def parse_args():
+ parser = argparse.ArgumentParser(description='mmcls test model')
+ parser.add_argument('config', help='test config file path')
+ parser.add_argument('checkpoint', help='checkpoint file')
+ parser.add_argument('--out', help='output result file')
+ out_options = ['class_scores', 'pred_score', 'pred_label', 'pred_class']
+ parser.add_argument(
+ '--out-items',
+ nargs='+',
+ default=['all'],
+ choices=out_options + ['none', 'all'],
+ help='Besides metrics, what items will be included in the output '
+ f'result file. You can choose some of ({", ".join(out_options)}), '
+ 'or use "all" to include all above, or use "none" to disable all of '
+ 'above. Defaults to output all.',
+ metavar='')
+ parser.add_argument(
+ '--metrics',
+ type=str,
+ nargs='+',
+ help='evaluation metrics, which depends on the dataset, e.g., '
+ '"accuracy", "precision", "recall", "f1_score", "support" for single '
+ 'label dataset, and "mAP", "CP", "CR", "CF1", "OP", "OR", "OF1" for '
+ 'multi-label dataset')
+ parser.add_argument('--show', action='store_true', help='show results')
+ parser.add_argument(
+ '--show-dir', help='directory where painted images will be saved')
+ parser.add_argument(
+ '--gpu-collect',
+ action='store_true',
+ help='whether to use gpu to collect results')
+ parser.add_argument('--tmpdir', help='tmp dir for writing some results')
+ parser.add_argument(
+ '--cfg-options',
+ nargs='+',
+ action=DictAction,
+ help='override some settings in the used config, the key-value pair '
+ 'in xxx=yyy format will be merged into config file. If the value to '
+ 'be overwritten is a list, it should be like key="[a,b]" or key=a,b '
+ 'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" '
+ 'Note that the quotation marks are necessary and that no white space '
+ 'is allowed.')
+ parser.add_argument(
+ '--metric-options',
+ nargs='+',
+ action=DictAction,
+ default={},
+ help='custom options for evaluation, the key-value pair in xxx=yyy '
+ 'format will be parsed as a dict metric_options for dataset.evaluate()'
+ ' function.')
+ parser.add_argument(
+ '--show-options',
+ nargs='+',
+ action=DictAction,
+ help='custom options for show_result. key-value pair in xxx=yyy.'
+ 'Check available options in `model.show_result`.')
+ parser.add_argument(
+ '--gpu-ids',
+ type=int,
+ nargs='+',
+ help='(Deprecated, please use --gpu-id) ids of gpus to use '
+ '(only applicable to non-distributed testing)')
+ parser.add_argument(
+ '--gpu-id',
+ type=int,
+ default=0,
+ help='id of gpu to use '
+ '(only applicable to non-distributed testing)')
+ parser.add_argument(
+ '--launcher',
+ choices=['none', 'pytorch', 'slurm', 'mpi'],
+ default='none',
+ help='job launcher')
+ parser.add_argument('--local_rank', type=int, default=0)
+ parser.add_argument('--device', help='device used for testing')
+ args = parser.parse_args()
+ if 'LOCAL_RANK' not in os.environ:
+ os.environ['LOCAL_RANK'] = str(args.local_rank)
+
+ assert args.metrics or args.out, \
+ 'Please specify at least one of output path and evaluation metrics.'
+
+ return args
+
+
+def main():
+ args = parse_args()
+
+ cfg = mmcv.Config.fromfile(args.config)
+ if args.cfg_options is not None:
+ cfg.merge_from_dict(args.cfg_options)
+
+ # set multi-process settings
+ setup_multi_processes(cfg)
+
+ # set cudnn_benchmark
+ if cfg.get('cudnn_benchmark', False):
+ torch.backends.cudnn.benchmark = True
+ cfg.model.pretrained = None
+
+ if args.gpu_ids is not None:
+ cfg.gpu_ids = args.gpu_ids[0:1]
+ warnings.warn('`--gpu-ids` is deprecated, please use `--gpu-id`. '
+ 'Because we only support single GPU mode in '
+ 'non-distributed testing. Use the first GPU '
+ 'in `gpu_ids` now.')
+ else:
+ cfg.gpu_ids = [args.gpu_id]
+ cfg.device = args.device or auto_select_device()
+
+ # init distributed env first, since logger depends on the dist info.
+ if args.launcher == 'none':
+ distributed = False
+ else:
+ distributed = True
+ init_dist(args.launcher, **cfg.dist_params)
+
+ dataset = build_dataset(cfg.data.test, default_args=dict(test_mode=True))
+
+ # build the dataloader
+ # The default loader config
+ loader_cfg = dict(
+ # cfg.gpus will be ignored if distributed
+ num_gpus=1 if cfg.device == 'ipu' else len(cfg.gpu_ids),
+ dist=distributed,
+ round_up=True,
+ )
+ # The overall dataloader settings
+ loader_cfg.update({
+ k: v
+ for k, v in cfg.data.items() if k not in [
+ 'train', 'val', 'test', 'train_dataloader', 'val_dataloader',
+ 'test_dataloader'
+ ]
+ })
+ test_loader_cfg = {
+ **loader_cfg,
+ 'shuffle': False, # Not shuffle by default
+ 'sampler_cfg': None, # Not use sampler by default
+ **cfg.data.get('test_dataloader', {}),
+ }
+ # the extra round_up data will be removed during gpu/cpu collect
+ data_loader = build_dataloader(dataset, **test_loader_cfg)
+
+ # build the model and load checkpoint
+ model = build_classifier(cfg.model)
+ fp16_cfg = cfg.get('fp16', None)
+ print("fp16_cfg is not None-------:",fp16_cfg is not None)
+ if fp16_cfg is not None:
+ wrap_fp16_model(model)
+ checkpoint = load_checkpoint(model, args.checkpoint, map_location='cpu')
+
+ if 'CLASSES' in checkpoint.get('meta', {}):
+ CLASSES = checkpoint['meta']['CLASSES']
+ else:
+ from mmcls.datasets import ImageNet
+ warnings.simplefilter('once')
+ warnings.warn('Class names are not saved in the checkpoint\'s '
+ 'meta data, use imagenet by default.')
+ CLASSES = ImageNet.CLASSES
+
+ if not distributed:
+ model = wrap_non_distributed_model(
+ model, device=cfg.device, device_ids=cfg.gpu_ids)
+ if cfg.device == 'ipu':
+ from mmcv.device.ipu import cfg2options, ipu_model_wrapper
+ opts = cfg2options(cfg.runner.get('options_cfg', {}))
+ if fp16_cfg is not None:
+ model.half()
+ model = ipu_model_wrapper(model, opts, fp16_cfg=fp16_cfg)
+ data_loader.init(opts['inference'])
+ model.CLASSES = CLASSES
+ show_kwargs = args.show_options or {}
+ outputs = single_gpu_test(model, data_loader, args.show, args.show_dir,
+ **show_kwargs)
+ else:
+ from mmcv.parallel import MMDataParallel, MMDistributedDataParallel
+ model = MMDistributedDataParallel(
+ model.cuda(),
+ device_ids=[torch.cuda.current_device()],
+ broadcast_buffers=False)
+ '''
+ model = wrap_distributed_model(
+ model,
+ device=cfg.device,
+ device_ids=[int(os.environ['LOCAL_RANK'])],
+ broadcast_buffers=False)
+ '''
+ outputs = multi_gpu_test(model, data_loader, args.tmpdir,
+ args.gpu_collect)
+
+ rank, _ = get_dist_info()
+ if rank == 0:
+ results = {}
+ logger = get_root_logger()
+ if args.metrics:
+ eval_results = dataset.evaluate(
+ results=outputs,
+ metric=args.metrics,
+ metric_options=args.metric_options,
+ logger=logger)
+ results.update(eval_results)
+ for k, v in eval_results.items():
+ if isinstance(v, np.ndarray):
+ v = [round(out, 2) for out in v.tolist()]
+ elif isinstance(v, Number):
+ v = round(v, 2)
+ else:
+ raise ValueError(f'Unsupport metric type: {type(v)}')
+ print(f'\n{k} : {v}')
+ if args.out:
+ if 'none' not in args.out_items:
+ scores = np.vstack(outputs)
+ pred_score = np.max(scores, axis=1)
+ pred_label = np.argmax(scores, axis=1)
+ pred_class = [CLASSES[lb] for lb in pred_label]
+ res_items = {
+ 'class_scores': scores,
+ 'pred_score': pred_score,
+ 'pred_label': pred_label,
+ 'pred_class': pred_class
+ }
+ if 'all' in args.out_items:
+ results.update(res_items)
+ else:
+ for key in args.out_items:
+ results[key] = res_items[key]
+ print(f'\ndumping results to {args.out}')
+ mmcv.dump(results, args.out)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/openmmlab_test/mmclassification-0.24.1/tools/train.py b/openmmlab_test/mmclassification-0.24.1/tools/train.py
new file mode 100644
index 0000000000000000000000000000000000000000..d14c4e89a75598f6949fc13020252fbd6f9471e6
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tools/train.py
@@ -0,0 +1,215 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import argparse
+import copy
+import os
+import os.path as osp
+import time
+import warnings
+
+import mmcv
+import torch
+import torch.distributed as dist
+from mmcv import Config, DictAction
+from mmcv.runner import get_dist_info, init_dist
+
+from mmcls import __version__
+from mmcls.apis import init_random_seed, set_random_seed, train_model
+from mmcls.datasets import build_dataset
+from mmcls.models import build_classifier
+from mmcls.utils import (auto_select_device, collect_env, get_root_logger,
+ setup_multi_processes)
+
+
+def parse_args():
+ parser = argparse.ArgumentParser(description='Train a model')
+ parser.add_argument('config', help='train config file path')
+ parser.add_argument('--work-dir', help='the dir to save logs and models')
+ parser.add_argument(
+ '--resume-from', help='the checkpoint file to resume from')
+ parser.add_argument(
+ '--no-validate',
+ action='store_true',
+ help='whether not to evaluate the checkpoint during training')
+ group_gpus = parser.add_mutually_exclusive_group()
+ group_gpus.add_argument(
+ '--device', help='device used for training. (Deprecated)')
+ group_gpus.add_argument(
+ '--gpus',
+ type=int,
+ help='(Deprecated, please use --gpu-id) number of gpus to use '
+ '(only applicable to non-distributed training)')
+ group_gpus.add_argument(
+ '--gpu-ids',
+ type=int,
+ nargs='+',
+ help='(Deprecated, please use --gpu-id) ids of gpus to use '
+ '(only applicable to non-distributed training)')
+ group_gpus.add_argument(
+ '--gpu-id',
+ type=int,
+ default=0,
+ help='id of gpu to use '
+ '(only applicable to non-distributed training)')
+ parser.add_argument(
+ '--ipu-replicas',
+ type=int,
+ default=None,
+ help='num of ipu replicas to use')
+ parser.add_argument('--seed', type=int, default=None, help='random seed')
+ parser.add_argument(
+ '--diff-seed',
+ action='store_true',
+ help='Whether or not set different seeds for different ranks')
+ parser.add_argument(
+ '--deterministic',
+ action='store_true',
+ help='whether to set deterministic options for CUDNN backend.')
+ parser.add_argument(
+ '--cfg-options',
+ nargs='+',
+ action=DictAction,
+ help='override some settings in the used config, the key-value pair '
+ 'in xxx=yyy format will be merged into config file. If the value to '
+ 'be overwritten is a list, it should be like key="[a,b]" or key=a,b '
+ 'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" '
+ 'Note that the quotation marks are necessary and that no white space '
+ 'is allowed.')
+ parser.add_argument(
+ '--launcher',
+ choices=['none', 'pytorch', 'slurm', 'mpi'],
+ default='none',
+ help='job launcher')
+ parser.add_argument(
+ '--world_size',
+ type=int,
+ default='128',
+ help='world_size')
+ parser.add_argument(
+ '--rank',
+ type=int,
+ default='128',
+ help='rank')
+ parser.add_argument('--local_rank', type=int, default=0)
+ args = parser.parse_args()
+ if 'LOCAL_RANK' not in os.environ:
+ os.environ['LOCAL_RANK'] = str(args.local_rank)
+
+ return args
+
+
+def main():
+ args = parse_args()
+
+ cfg = Config.fromfile(args.config)
+ if args.cfg_options is not None:
+ cfg.merge_from_dict(args.cfg_options)
+
+ # set multi-process settings
+ setup_multi_processes(cfg)
+
+ # set cudnn_benchmark
+ if cfg.get('cudnn_benchmark', False):
+ torch.backends.cudnn.benchmark = True
+
+ # work_dir is determined in this priority: CLI > segment in file > filename
+ if args.work_dir is not None:
+ # update configs according to CLI args if args.work_dir is not None
+ cfg.work_dir = args.work_dir
+ elif cfg.get('work_dir', None) is None:
+ # use config filename as default work_dir if cfg.work_dir is None
+ cfg.work_dir = osp.join('./work_dirs',
+ osp.splitext(osp.basename(args.config))[0])
+ if args.resume_from is not None:
+ cfg.resume_from = args.resume_from
+ if args.gpus is not None:
+ cfg.gpu_ids = range(1)
+ warnings.warn('`--gpus` is deprecated because we only support '
+ 'single GPU mode in non-distributed training. '
+ 'Use `gpus=1` now.')
+ if args.gpu_ids is not None:
+ cfg.gpu_ids = args.gpu_ids[0:1]
+ warnings.warn('`--gpu-ids` is deprecated, please use `--gpu-id`. '
+ 'Because we only support single GPU mode in '
+ 'non-distributed training. Use the first GPU '
+ 'in `gpu_ids` now.')
+ if args.gpus is None and args.gpu_ids is None:
+ cfg.gpu_ids = [args.gpu_id]
+
+ if args.ipu_replicas is not None:
+ cfg.ipu_replicas = args.ipu_replicas
+ args.device = 'ipu'
+
+ # init distributed env first, since logger depends on the dist info.
+ if args.launcher == 'none':
+ distributed = False
+ else:
+ distributed = True
+ init_dist(args.launcher, **cfg.dist_params)
+ _, world_size = get_dist_info()
+ cfg.gpu_ids = range(world_size)
+
+ # create work_dir
+ mmcv.mkdir_or_exist(osp.abspath(cfg.work_dir))
+ # dump config
+ cfg.dump(osp.join(cfg.work_dir, osp.basename(args.config)))
+ # init the logger before other steps
+ timestamp = time.strftime('%Y%m%d_%H%M%S', time.localtime())
+ log_file = osp.join(cfg.work_dir, f'{timestamp}.log')
+ logger = get_root_logger(log_file=log_file, log_level=cfg.log_level)
+
+ # init the meta dict to record some important information such as
+ # environment info and seed, which will be logged
+ meta = dict()
+ # log env info
+ env_info_dict = collect_env()
+ env_info = '\n'.join([(f'{k}: {v}') for k, v in env_info_dict.items()])
+ dash_line = '-' * 60 + '\n'
+ logger.info('Environment info:\n' + dash_line + env_info + '\n' +
+ dash_line)
+ meta['env_info'] = env_info
+
+ # log some basic info
+ logger.info(f'Distributed training: {distributed}')
+ logger.info(f'Config:\n{cfg.pretty_text}')
+
+ # set random seeds
+ cfg.device = args.device or auto_select_device()
+ seed = init_random_seed(args.seed, device=cfg.device)
+ seed = seed + dist.get_rank() if args.diff_seed else seed
+ logger.info(f'Set random seed to {seed}, '
+ f'deterministic: {args.deterministic}')
+ set_random_seed(seed, deterministic=args.deterministic)
+ cfg.seed = seed
+ meta['seed'] = seed
+
+ model = build_classifier(cfg.model)
+ model.init_weights()
+
+ datasets = [build_dataset(cfg.data.train)]
+ if len(cfg.workflow) == 2:
+ val_dataset = copy.deepcopy(cfg.data.val)
+ val_dataset.pipeline = cfg.data.train.pipeline
+ datasets.append(build_dataset(val_dataset))
+
+ # save mmcls version, config file content and class names in
+ # runner as meta data
+ meta.update(
+ dict(
+ mmcls_version=__version__,
+ config=cfg.pretty_text,
+ CLASSES=datasets[0].CLASSES))
+
+ # add an attribute for visualization convenience
+ train_model(
+ model,
+ datasets,
+ cfg,
+ distributed=distributed,
+ validate=(not args.no_validate),
+ timestamp=timestamp,
+ device=cfg.device,
+ meta=meta)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/openmmlab_test/mmclassification-0.24.1/tools/visualizations/vis_cam.py b/openmmlab_test/mmclassification-0.24.1/tools/visualizations/vis_cam.py
new file mode 100644
index 0000000000000000000000000000000000000000..a1fcadac2320927b67cc7d2072ce28c6b422e668
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tools/visualizations/vis_cam.py
@@ -0,0 +1,356 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import argparse
+import copy
+import math
+import pkg_resources
+import re
+from pathlib import Path
+
+import mmcv
+import numpy as np
+from mmcv import Config, DictAction
+from mmcv.utils import to_2tuple
+from torch.nn import BatchNorm1d, BatchNorm2d, GroupNorm, LayerNorm
+
+from mmcls import digit_version
+from mmcls.apis import init_model
+from mmcls.datasets.pipelines import Compose
+
+try:
+ from pytorch_grad_cam import (EigenCAM, EigenGradCAM, GradCAM,
+ GradCAMPlusPlus, LayerCAM, XGradCAM)
+ from pytorch_grad_cam.activations_and_gradients import \
+ ActivationsAndGradients
+ from pytorch_grad_cam.utils.image import show_cam_on_image
+except ImportError:
+ raise ImportError('Please run `pip install "grad-cam>=1.3.6"` to install '
+ '3rd party package pytorch_grad_cam.')
+
+# set of transforms, which just change data format, not change the pictures
+FORMAT_TRANSFORMS_SET = {'ToTensor', 'Normalize', 'ImageToTensor', 'Collect'}
+
+# Supported grad-cam type map
+METHOD_MAP = {
+ 'gradcam': GradCAM,
+ 'gradcam++': GradCAMPlusPlus,
+ 'xgradcam': XGradCAM,
+ 'eigencam': EigenCAM,
+ 'eigengradcam': EigenGradCAM,
+ 'layercam': LayerCAM,
+}
+
+
+def parse_args():
+ parser = argparse.ArgumentParser(description='Visualize CAM')
+ parser.add_argument('img', help='Image file')
+ parser.add_argument('config', help='Config file')
+ parser.add_argument('checkpoint', help='Checkpoint file')
+ parser.add_argument(
+ '--target-layers',
+ default=[],
+ nargs='+',
+ type=str,
+ help='The target layers to get CAM, if not set, the tool will '
+ 'specify the norm layer in the last block. Backbones '
+ 'implemented by users are recommended to manually specify'
+ ' target layers in commmad statement.')
+ parser.add_argument(
+ '--preview-model',
+ default=False,
+ action='store_true',
+ help='To preview all the model layers')
+ parser.add_argument(
+ '--method',
+ default='GradCAM',
+ help='Type of method to use, supports '
+ f'{", ".join(list(METHOD_MAP.keys()))}.')
+ parser.add_argument(
+ '--target-category',
+ default=[],
+ nargs='+',
+ type=int,
+ help='The target category to get CAM, default to use result '
+ 'get from given model.')
+ parser.add_argument(
+ '--eigen-smooth',
+ default=False,
+ action='store_true',
+ help='Reduce noise by taking the first principle componenet of '
+ '``cam_weights*activations``')
+ parser.add_argument(
+ '--aug-smooth',
+ default=False,
+ action='store_true',
+ help='Wether to use test time augmentation, default not to use')
+ parser.add_argument(
+ '--save-path',
+ type=Path,
+ help='The path to save visualize cam image, default not to save.')
+ parser.add_argument('--device', default='cpu', help='Device to use cpu')
+ parser.add_argument(
+ '--vit-like',
+ action='store_true',
+ help='Whether the network is a ViT-like network.')
+ parser.add_argument(
+ '--num-extra-tokens',
+ type=int,
+ help='The number of extra tokens in ViT-like backbones. Defaults to'
+ ' use num_extra_tokens of the backbone.')
+ parser.add_argument(
+ '--cfg-options',
+ nargs='+',
+ action=DictAction,
+ help='override some settings in the used config, the key-value pair '
+ 'in xxx=yyy format will be merged into config file. If the value to '
+ 'be overwritten is a list, it should be like key="[a,b]" or key=a,b '
+ 'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" '
+ 'Note that the quotation marks are necessary and that no white space '
+ 'is allowed.')
+ args = parser.parse_args()
+ if args.method.lower() not in METHOD_MAP.keys():
+ raise ValueError(f'invalid CAM type {args.method},'
+ f' supports {", ".join(list(METHOD_MAP.keys()))}.')
+
+ return args
+
+
+def build_reshape_transform(model, args):
+ """Build reshape_transform for `cam.activations_and_grads`, which is
+ necessary for ViT-like networks."""
+ # ViT_based_Transformers have an additional clstoken in features
+ if not args.vit_like:
+
+ def check_shape(tensor):
+ assert len(tensor.size()) != 3, \
+ (f"The input feature's shape is {tensor.size()}, and it seems "
+ 'to have been flattened or from a vit-like network. '
+ "Please use `--vit-like` if it's from a vit-like network.")
+ return tensor
+
+ return check_shape
+
+ if args.num_extra_tokens is not None:
+ num_extra_tokens = args.num_extra_tokens
+ elif hasattr(model.backbone, 'num_extra_tokens'):
+ num_extra_tokens = model.backbone.num_extra_tokens
+ else:
+ num_extra_tokens = 1
+
+ def _reshape_transform(tensor):
+ """reshape_transform helper."""
+ assert len(tensor.size()) == 3, \
+ (f"The input feature's shape is {tensor.size()}, "
+ 'and the feature seems not from a vit-like network?')
+ tensor = tensor[:, num_extra_tokens:, :]
+ # get heat_map_height and heat_map_width, preset input is a square
+ heat_map_area = tensor.size()[1]
+ height, width = to_2tuple(int(math.sqrt(heat_map_area)))
+ assert height * height == heat_map_area, \
+ (f"The input feature's length ({heat_map_area+num_extra_tokens}) "
+ f'minus num-extra-tokens ({num_extra_tokens}) is {heat_map_area},'
+ ' which is not a perfect square number. Please check if you used '
+ 'a wrong num-extra-tokens.')
+ result = tensor.reshape(tensor.size(0), height, width, tensor.size(2))
+
+ # Bring the channels to the first dimension, like in CNNs.
+ result = result.transpose(2, 3).transpose(1, 2)
+ return result
+
+ return _reshape_transform
+
+
+def apply_transforms(img_path, pipeline_cfg):
+ """Apply transforms pipeline and get both formatted data and the image
+ without formatting."""
+ data = dict(img_info=dict(filename=img_path), img_prefix=None)
+
+ def split_pipeline_cfg(pipeline_cfg):
+ """to split the transfoms into image_transforms and
+ format_transforms."""
+ image_transforms_cfg, format_transforms_cfg = [], []
+ if pipeline_cfg[0]['type'] != 'LoadImageFromFile':
+ pipeline_cfg.insert(0, dict(type='LoadImageFromFile'))
+ for transform in pipeline_cfg:
+ if transform['type'] in FORMAT_TRANSFORMS_SET:
+ format_transforms_cfg.append(transform)
+ else:
+ image_transforms_cfg.append(transform)
+ return image_transforms_cfg, format_transforms_cfg
+
+ image_transforms, format_transforms = split_pipeline_cfg(pipeline_cfg)
+ image_transforms = Compose(image_transforms)
+ format_transforms = Compose(format_transforms)
+
+ intermediate_data = image_transforms(data)
+ inference_img = copy.deepcopy(intermediate_data['img'])
+ format_data = format_transforms(intermediate_data)
+
+ return format_data, inference_img
+
+
+class MMActivationsAndGradients(ActivationsAndGradients):
+ """Activations and gradients manager for mmcls models."""
+
+ def __call__(self, x):
+ self.gradients = []
+ self.activations = []
+ return self.model(
+ x, return_loss=False, softmax=False, post_process=False)
+
+
+def init_cam(method, model, target_layers, use_cuda, reshape_transform):
+ """Construct the CAM object once, In order to be compatible with mmcls,
+ here we modify the ActivationsAndGradients object."""
+
+ GradCAM_Class = METHOD_MAP[method.lower()]
+ cam = GradCAM_Class(
+ model=model, target_layers=target_layers, use_cuda=use_cuda)
+ # Release the original hooks in ActivationsAndGradients to use
+ # MMActivationsAndGradients.
+ cam.activations_and_grads.release()
+ cam.activations_and_grads = MMActivationsAndGradients(
+ cam.model, cam.target_layers, reshape_transform)
+
+ return cam
+
+
+def get_layer(layer_str, model):
+ """get model layer from given str."""
+ cur_layer = model
+ layer_names = layer_str.strip().split('.')
+
+ def get_children_by_name(model, name):
+ try:
+ return getattr(model, name)
+ except AttributeError as e:
+ raise AttributeError(
+ e.args[0] +
+ '. Please use `--preview-model` to check keys at first.')
+
+ def get_children_by_eval(model, name):
+ try:
+ return eval(f'model{name}', {}, {'model': model})
+ except (AttributeError, IndexError) as e:
+ raise AttributeError(
+ e.args[0] +
+ '. Please use `--preview-model` to check keys at first.')
+
+ for layer_name in layer_names:
+ match_res = re.match('(?P.+?)(?P(\\[.+\\])+)',
+ layer_name)
+ if match_res:
+ layer_name = match_res.groupdict()['name']
+ indices = match_res.groupdict()['indices']
+ cur_layer = get_children_by_name(cur_layer, layer_name)
+ cur_layer = get_children_by_eval(cur_layer, indices)
+ else:
+ cur_layer = get_children_by_name(cur_layer, layer_name)
+
+ return cur_layer
+
+
+def show_cam_grad(grayscale_cam, src_img, title, out_path=None):
+ """fuse src_img and grayscale_cam and show or save."""
+ grayscale_cam = grayscale_cam[0, :]
+ src_img = np.float32(src_img) / 255
+ visualization_img = show_cam_on_image(
+ src_img, grayscale_cam, use_rgb=False)
+
+ if out_path:
+ mmcv.imwrite(visualization_img, str(out_path))
+ else:
+ mmcv.imshow(visualization_img, win_name=title)
+
+
+def get_default_traget_layers(model, args):
+ """get default target layers from given model, here choose nrom type layer
+ as default target layer."""
+ norm_layers = []
+ for m in model.backbone.modules():
+ if isinstance(m, (BatchNorm2d, LayerNorm, GroupNorm, BatchNorm1d)):
+ norm_layers.append(m)
+ if len(norm_layers) == 0:
+ raise ValueError(
+ '`--target-layers` is empty. Please use `--preview-model`'
+ ' to check keys at first and then specify `target-layers`.')
+ # if the model is CNN model or Swin model, just use the last norm
+ # layer as the target-layer, if the model is ViT model, the final
+ # classification is done on the class token computed in the last
+ # attention block, the output will not be affected by the 14x14
+ # channels in the last layer. The gradient of the output with
+ # respect to them, will be 0! here use the last 3rd norm layer.
+ # means the first norm of the last decoder block.
+ if args.vit_like:
+ if args.num_extra_tokens:
+ num_extra_tokens = args.num_extra_tokens
+ elif hasattr(model.backbone, 'num_extra_tokens'):
+ num_extra_tokens = model.backbone.num_extra_tokens
+ else:
+ raise AttributeError('Please set num_extra_tokens in backbone'
+ " or using 'num-extra-tokens'")
+
+ # if a vit-like backbone's num_extra_tokens bigger than 0, view it
+ # as a VisionTransformer backbone, eg. DeiT, T2T-ViT.
+ if num_extra_tokens >= 1:
+ print('Automatically choose the last norm layer before the '
+ 'final attention block as target_layer..')
+ return [norm_layers[-3]]
+ print('Automatically choose the last norm layer as target_layer.')
+ target_layers = [norm_layers[-1]]
+ return target_layers
+
+
+def main():
+ args = parse_args()
+ cfg = Config.fromfile(args.config)
+ if args.cfg_options is not None:
+ cfg.merge_from_dict(args.cfg_options)
+
+ # build the model from a config file and a checkpoint file
+ model = init_model(cfg, args.checkpoint, device=args.device)
+ if args.preview_model:
+ print(model)
+ print('\n Please remove `--preview-model` to get the CAM.')
+ return
+
+ # apply transform and perpare data
+ data, src_img = apply_transforms(args.img, cfg.data.test.pipeline)
+
+ # build target layers
+ if args.target_layers:
+ target_layers = [
+ get_layer(layer, model) for layer in args.target_layers
+ ]
+ else:
+ target_layers = get_default_traget_layers(model, args)
+
+ # init a cam grad calculator
+ use_cuda = ('cuda' in args.device)
+ reshape_transform = build_reshape_transform(model, args)
+ cam = init_cam(args.method, model, target_layers, use_cuda,
+ reshape_transform)
+
+ # warp the target_category with ClassifierOutputTarget in grad_cam>=1.3.7,
+ # to fix the bug in #654.
+ targets = None
+ if args.target_category:
+ grad_cam_v = pkg_resources.get_distribution('grad_cam').version
+ if digit_version(grad_cam_v) >= digit_version('1.3.7'):
+ from pytorch_grad_cam.utils.model_targets import \
+ ClassifierOutputTarget
+ targets = [ClassifierOutputTarget(c) for c in args.target_category]
+ else:
+ targets = args.target_category
+
+ # calculate cam grads and show|save the visualization image
+ grayscale_cam = cam(
+ data['img'].unsqueeze(0),
+ targets,
+ eigen_smooth=args.eigen_smooth,
+ aug_smooth=args.aug_smooth)
+ show_cam_grad(
+ grayscale_cam, src_img, title=args.method, out_path=args.save_path)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/openmmlab_test/mmclassification-0.24.1/tools/visualizations/vis_lr.py b/openmmlab_test/mmclassification-0.24.1/tools/visualizations/vis_lr.py
new file mode 100644
index 0000000000000000000000000000000000000000..bd34421505bcaaadd821c739118ed46739ff9287
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tools/visualizations/vis_lr.py
@@ -0,0 +1,334 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import argparse
+import os.path as osp
+import re
+import time
+from pathlib import Path
+from pprint import pformat
+
+import matplotlib.pyplot as plt
+import mmcv
+import torch.nn as nn
+from mmcv import Config, DictAction, ProgressBar
+from mmcv.runner import (EpochBasedRunner, IterBasedRunner, IterLoader,
+ build_optimizer)
+from torch.utils.data import DataLoader
+
+from mmcls.utils import get_root_logger
+
+
+class DummyEpochBasedRunner(EpochBasedRunner):
+ """Fake Epoch-based Runner.
+
+ This runner won't train model, and it will only call hooks and return all
+ learning rate in each iteration.
+ """
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.progress_bar = ProgressBar(self._max_epochs, start=False)
+
+ def train(self, data_loader, **kwargs):
+ lr_list = []
+ self.model.train()
+ self.mode = 'train'
+ self.data_loader = data_loader
+ self._max_iters = self._max_epochs * len(self.data_loader)
+ self.call_hook('before_train_epoch')
+ for i in range(len(self.data_loader)):
+ self._inner_iter = i
+ self.call_hook('before_train_iter')
+ lr_list.append(self.current_lr())
+ self.call_hook('after_train_iter')
+ self._iter += 1
+
+ self.call_hook('after_train_epoch')
+ self._epoch += 1
+ self.progress_bar.update(1)
+ return lr_list
+
+ def run(self, data_loaders, workflow, **kwargs):
+ assert isinstance(data_loaders, list)
+ assert mmcv.is_list_of(workflow, tuple)
+ assert len(data_loaders) == len(workflow)
+
+ assert self._max_epochs is not None, (
+ 'max_epochs must be specified during instantiation')
+
+ for i, flow in enumerate(workflow):
+ mode, epochs = flow
+ if mode == 'train':
+ self._max_iters = self._max_epochs * len(data_loaders[i])
+ break
+
+ self.logger.info('workflow: %s, max: %d epochs', workflow,
+ self._max_epochs)
+ self.call_hook('before_run')
+
+ self.progress_bar.start()
+ lr_list = []
+ while self.epoch < self._max_epochs:
+ for i, flow in enumerate(workflow):
+ mode, epochs = flow
+ if isinstance(mode, str): # self.train()
+ if not hasattr(self, mode):
+ raise ValueError(
+ f'runner has no method named "{mode}" to run an '
+ 'epoch')
+ epoch_runner = getattr(self, mode)
+ else:
+ raise TypeError(
+ 'mode in workflow must be a str, but got {}'.format(
+ type(mode)))
+
+ for _ in range(epochs):
+ if mode == 'train' and self.epoch >= self._max_epochs:
+ break
+ lr_list.extend(epoch_runner(data_loaders[i], **kwargs))
+
+ self.progress_bar.file.write('\n')
+ time.sleep(1) # wait for some hooks like loggers to finish
+ self.call_hook('after_run')
+ return lr_list
+
+
+class DummyIterBasedRunner(IterBasedRunner):
+ """Fake Iter-based Runner.
+
+ This runner won't train model, and it will only call hooks and return all
+ learning rate in each iteration.
+ """
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.progress_bar = ProgressBar(self._max_iters, start=False)
+
+ def train(self, data_loader, **kwargs):
+ lr_list = []
+ self.model.train()
+ self.mode = 'train'
+ self.data_loader = data_loader
+ self._epoch = data_loader.epoch
+ next(data_loader)
+ self.call_hook('before_train_iter')
+ lr_list.append(self.current_lr())
+ self.call_hook('after_train_iter')
+ self._inner_iter += 1
+ self._iter += 1
+ self.progress_bar.update(1)
+ return lr_list
+
+ def run(self, data_loaders, workflow, **kwargs):
+ assert isinstance(data_loaders, list)
+ assert mmcv.is_list_of(workflow, tuple)
+ assert len(data_loaders) == len(workflow)
+ assert self._max_iters is not None, (
+ 'max_iters must be specified during instantiation')
+
+ self.logger.info('workflow: %s, max: %d iters', workflow,
+ self._max_iters)
+ self.call_hook('before_run')
+
+ iter_loaders = [IterLoader(x) for x in data_loaders]
+
+ self.call_hook('before_epoch')
+
+ self.progress_bar.start()
+ lr_list = []
+ while self.iter < self._max_iters:
+ for i, flow in enumerate(workflow):
+ self._inner_iter = 0
+ mode, iters = flow
+ if not isinstance(mode, str) or not hasattr(self, mode):
+ raise ValueError(
+ 'runner has no method named "{}" to run a workflow'.
+ format(mode))
+ iter_runner = getattr(self, mode)
+ for _ in range(iters):
+ if mode == 'train' and self.iter >= self._max_iters:
+ break
+ lr_list.extend(iter_runner(iter_loaders[i], **kwargs))
+
+ self.progress_bar.file.write('\n')
+ time.sleep(1) # wait for some hooks like loggers to finish
+ self.call_hook('after_epoch')
+ self.call_hook('after_run')
+ return lr_list
+
+
+class SimpleModel(nn.Module):
+ """simple model that do nothing in train_step."""
+
+ def __init__(self):
+ super(SimpleModel, self).__init__()
+ self.conv = nn.Conv2d(1, 1, 1)
+
+ def train_step(self, *args, **kwargs):
+ pass
+
+
+def parse_args():
+ parser = argparse.ArgumentParser(
+ description='Visualize a Dataset Pipeline')
+ parser.add_argument('config', help='config file path')
+ parser.add_argument(
+ '--dataset-size',
+ type=int,
+ help='The size of the dataset. If specify, `build_dataset` will '
+ 'be skipped and use this size as the dataset size.')
+ parser.add_argument(
+ '--ngpus',
+ type=int,
+ default=1,
+ help='The number of GPUs used in training.')
+ parser.add_argument('--title', type=str, help='title of figure')
+ parser.add_argument(
+ '--style', type=str, default='whitegrid', help='style of plt')
+ parser.add_argument(
+ '--save-path',
+ type=Path,
+ help='The learning rate curve plot save path')
+ parser.add_argument(
+ '--window-size',
+ default='12*7',
+ help='Size of the window to display images, in format of "$W*$H".')
+ parser.add_argument(
+ '--cfg-options',
+ nargs='+',
+ action=DictAction,
+ help='override some settings in the used config, the key-value pair '
+ 'in xxx=yyy format will be merged into config file. If the value to '
+ 'be overwritten is a list, it should be like key="[a,b]" or key=a,b '
+ 'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" '
+ 'Note that the quotation marks are necessary and that no white space '
+ 'is allowed.')
+ args = parser.parse_args()
+ if args.window_size != '':
+ assert re.match(r'\d+\*\d+', args.window_size), \
+ "'window-size' must be in format 'W*H'."
+
+ return args
+
+
+def plot_curve(lr_list, args, iters_per_epoch, by_epoch=True):
+ """Plot learning rate vs iter graph."""
+ try:
+ import seaborn as sns
+ sns.set_style(args.style)
+ except ImportError:
+ print("Attention: The plot style won't be applied because 'seaborn' "
+ 'package is not installed, please install it if you want better '
+ 'show style.')
+ wind_w, wind_h = args.window_size.split('*')
+ wind_w, wind_h = int(wind_w), int(wind_h)
+ plt.figure(figsize=(wind_w, wind_h))
+ # if legend is None, use {filename}_{key} as legend
+
+ ax: plt.Axes = plt.subplot()
+
+ ax.plot(lr_list, linewidth=1)
+ if by_epoch:
+ ax.xaxis.tick_top()
+ ax.set_xlabel('Iters')
+ ax.xaxis.set_label_position('top')
+ sec_ax = ax.secondary_xaxis(
+ 'bottom',
+ functions=(lambda x: x / iters_per_epoch,
+ lambda y: y * iters_per_epoch))
+ sec_ax.set_xlabel('Epochs')
+ # ticks = range(0, len(lr_list), iters_per_epoch)
+ # plt.xticks(ticks=ticks, labels=range(len(ticks)))
+ else:
+ plt.xlabel('Iters')
+ plt.ylabel('Learning Rate')
+
+ if args.title is None:
+ plt.title(f'{osp.basename(args.config)} Learning Rate curve')
+ else:
+ plt.title(args.title)
+
+ if args.save_path:
+ plt.savefig(args.save_path)
+ print(f'The learning rate graph is saved at {args.save_path}')
+ plt.show()
+
+
+def simulate_train(data_loader, cfg, by_epoch=True):
+ # build logger, data_loader, model and optimizer
+ logger = get_root_logger()
+ data_loaders = [data_loader]
+ model = SimpleModel()
+ optimizer = build_optimizer(model, cfg.optimizer)
+
+ # build runner
+ if by_epoch:
+ runner = DummyEpochBasedRunner(
+ max_epochs=cfg.runner.max_epochs,
+ model=model,
+ optimizer=optimizer,
+ logger=logger)
+ else:
+ runner = DummyIterBasedRunner(
+ max_iters=cfg.runner.max_iters,
+ model=model,
+ optimizer=optimizer,
+ logger=logger)
+
+ # register hooks
+ runner.register_training_hooks(
+ lr_config=cfg.lr_config,
+ custom_hooks_config=cfg.get('custom_hooks', None),
+ )
+
+ # only use the first train workflow
+ workflow = cfg.workflow[:1]
+ assert workflow[0][0] == 'train'
+ return runner.run(data_loaders, cfg.workflow)
+
+
+def main():
+ args = parse_args()
+ cfg = Config.fromfile(args.config)
+ if args.cfg_options is not None:
+ cfg.merge_from_dict(args.cfg_options)
+
+ # make sure save_root exists
+ if args.save_path and not args.save_path.parent.exists():
+ raise Exception(f'The save path is {args.save_path}, and directory '
+ f"'{args.save_path.parent}' do not exist.")
+
+ # init logger
+ logger = get_root_logger(log_level=cfg.log_level)
+ logger.info('Lr config : \n\n' + pformat(cfg.lr_config, sort_dicts=False) +
+ '\n')
+
+ by_epoch = True if cfg.runner.type == 'EpochBasedRunner' else False
+
+ # prepare data loader
+ batch_size = cfg.data.samples_per_gpu * args.ngpus
+
+ if args.dataset_size is None and by_epoch:
+ from mmcls.datasets.builder import build_dataset
+ dataset_size = len(build_dataset(cfg.data.train))
+ else:
+ dataset_size = args.dataset_size or batch_size
+
+ fake_dataset = list(range(dataset_size))
+ data_loader = DataLoader(fake_dataset, batch_size=batch_size)
+ dataset_info = (f'\nDataset infos:'
+ f'\n - Dataset size: {dataset_size}'
+ f'\n - Samples per GPU: {cfg.data.samples_per_gpu}'
+ f'\n - Number of GPUs: {args.ngpus}'
+ f'\n - Total batch size: {batch_size}')
+ if by_epoch:
+ dataset_info += f'\n - Iterations per epoch: {len(data_loader)}'
+ logger.info(dataset_info)
+
+ # simulation training process
+ lr_list = simulate_train(data_loader, cfg, by_epoch)
+
+ plot_curve(lr_list, args, len(data_loader), by_epoch)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/openmmlab_test/mmclassification-0.24.1/tools/visualizations/vis_pipeline.py b/openmmlab_test/mmclassification-0.24.1/tools/visualizations/vis_pipeline.py
new file mode 100644
index 0000000000000000000000000000000000000000..ffb9b183c5cbdc35bfed70c14573a929a889f3ff
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/tools/visualizations/vis_pipeline.py
@@ -0,0 +1,337 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+import argparse
+import copy
+import itertools
+import os
+import re
+import sys
+import warnings
+from pathlib import Path
+from typing import List
+
+import cv2
+import mmcv
+import numpy as np
+from mmcv import Config, DictAction, ProgressBar
+
+from mmcls.core import visualization as vis
+from mmcls.datasets.builder import PIPELINES, build_dataset, build_from_cfg
+from mmcls.models.utils import to_2tuple
+
+# text style
+bright_style, reset_style = '\x1b[1m', '\x1b[0m'
+red_text, blue_text = '\x1b[31m', '\x1b[34m'
+white_background = '\x1b[107m'
+
+
+def parse_args():
+ parser = argparse.ArgumentParser(
+ description='Visualize a Dataset Pipeline')
+ parser.add_argument('config', help='config file path')
+ parser.add_argument(
+ '--skip-type',
+ type=str,
+ nargs='*',
+ default=['ToTensor', 'Normalize', 'ImageToTensor', 'Collect'],
+ help='the pipelines to skip when visualizing')
+ parser.add_argument(
+ '--output-dir',
+ default='',
+ type=str,
+ help='folder to save output pictures, if not set, do not save.')
+ parser.add_argument(
+ '--phase',
+ default='train',
+ type=str,
+ choices=['train', 'test', 'val'],
+ help='phase of dataset to visualize, accept "train" "test" and "val".'
+ ' Default train.')
+ parser.add_argument(
+ '--number',
+ type=int,
+ default=sys.maxsize,
+ help='number of images selected to visualize, must bigger than 0. if '
+ 'the number is bigger than length of dataset, show all the images in '
+ 'dataset; default "sys.maxsize", show all images in dataset')
+ parser.add_argument(
+ '--mode',
+ default='concat',
+ type=str,
+ choices=['original', 'transformed', 'concat', 'pipeline'],
+ help='display mode; display original pictures or transformed pictures'
+ ' or comparison pictures. "original" means show images load from disk'
+ '; "transformed" means to show images after transformed; "concat" '
+ 'means show images stitched by "original" and "output" images. '
+ '"pipeline" means show all the intermediate images. Default concat.')
+ parser.add_argument(
+ '--show',
+ default=False,
+ action='store_true',
+ help='whether to display images in pop-up window. Default False.')
+ parser.add_argument(
+ '--adaptive',
+ default=False,
+ action='store_true',
+ help='whether to automatically adjust the visualization image size')
+ parser.add_argument(
+ '--min-edge-length',
+ default=200,
+ type=int,
+ help='the min edge length when visualizing images, used when '
+ '"--adaptive" is true. Default 200.')
+ parser.add_argument(
+ '--max-edge-length',
+ default=800,
+ type=int,
+ help='the max edge length when visualizing images, used when '
+ '"--adaptive" is true. Default 1000.')
+ parser.add_argument(
+ '--bgr2rgb',
+ default=False,
+ action='store_true',
+ help='flip the color channel order of images')
+ parser.add_argument(
+ '--window-size',
+ default='12*7',
+ help='size of the window to display images, in format of "$W*$H".')
+ parser.add_argument(
+ '--cfg-options',
+ nargs='+',
+ action=DictAction,
+ help='override some settings in the used config, the key-value pair '
+ 'in xxx=yyy format will be merged into config file. If the value to '
+ 'be overwritten is a list, it should be like key="[a,b]" or key=a,b '
+ 'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" '
+ 'Note that the quotation marks are necessary and that no white space '
+ 'is allowed.')
+ parser.add_argument(
+ '--show-options',
+ nargs='+',
+ action=DictAction,
+ help='custom options for display. key-value pair in xxx=yyy. options '
+ 'in `mmcls.core.visualization.ImshowInfosContextManager.put_img_infos`'
+ )
+ args = parser.parse_args()
+
+ assert args.number > 0, "'args.number' must be larger than zero."
+ if args.window_size != '':
+ assert re.match(r'\d+\*\d+', args.window_size), \
+ "'window-size' must be in format 'W*H'."
+ if args.output_dir == '' and not args.show:
+ raise ValueError("if '--output-dir' and '--show' are not set, "
+ 'nothing will happen when the program running.')
+
+ if args.show_options is None:
+ args.show_options = {}
+ return args
+
+
+def retrieve_data_cfg(config_path, skip_type, cfg_options, phase):
+ cfg = Config.fromfile(config_path)
+ if cfg_options is not None:
+ cfg.merge_from_dict(cfg_options)
+ data_cfg = cfg.data[phase]
+ while 'dataset' in data_cfg:
+ data_cfg = data_cfg['dataset']
+ data_cfg['pipeline'] = [
+ x for x in data_cfg.pipeline if x['type'] not in skip_type
+ ]
+
+ return cfg
+
+
+def build_dataset_pipelines(cfg, phase):
+ """build dataset and pipeline from config.
+
+ Separate the pipeline except 'LoadImageFromFile' step if
+ 'LoadImageFromFile' in the pipeline.
+ """
+ data_cfg = cfg.data[phase]
+ loadimage_pipeline = []
+ if len(data_cfg.pipeline
+ ) != 0 and data_cfg.pipeline[0]['type'] == 'LoadImageFromFile':
+ loadimage_pipeline.append(data_cfg.pipeline.pop(0))
+ origin_pipeline = data_cfg.pipeline
+ data_cfg.pipeline = loadimage_pipeline
+ dataset = build_dataset(data_cfg)
+ pipelines = {
+ pipeline_cfg['type']: build_from_cfg(pipeline_cfg, PIPELINES)
+ for pipeline_cfg in origin_pipeline
+ }
+
+ return dataset, pipelines
+
+
+def prepare_imgs(args, imgs: List[np.ndarray], steps=None):
+ """prepare the showing picture."""
+ ori_shapes = [img.shape for img in imgs]
+ # adaptive adjustment to rescale pictures
+ if args.adaptive:
+ for i, img in enumerate(imgs):
+ imgs[i] = adaptive_size(img, args.min_edge_length,
+ args.max_edge_length)
+ else:
+ # if src image is too large or too small,
+ # warning a "--adaptive" message.
+ for ori_h, ori_w, _ in ori_shapes:
+ if (args.min_edge_length > ori_h or args.min_edge_length > ori_w
+ or args.max_edge_length < ori_h
+ or args.max_edge_length < ori_w):
+ msg = red_text
+ msg += 'The visualization picture is too small or too large to'
+ msg += ' put text information on it, please add '
+ msg += bright_style + red_text + white_background
+ msg += '"--adaptive"'
+ msg += reset_style + red_text
+ msg += ' to adaptively rescale the showing pictures'
+ msg += reset_style
+ warnings.warn(msg)
+
+ if len(imgs) == 1:
+ return imgs[0]
+ else:
+ return concat_imgs(imgs, steps, ori_shapes)
+
+
+def concat_imgs(imgs, steps, ori_shapes):
+ """Concat list of pictures into a single big picture, align height here."""
+ show_shapes = [img.shape for img in imgs]
+ show_heights = [shape[0] for shape in show_shapes]
+ show_widths = [shape[1] for shape in show_shapes]
+
+ max_height = max(show_heights)
+ text_height = 20
+ font_size = 0.5
+ pic_horizontal_gap = min(show_widths) // 10
+ for i, img in enumerate(imgs):
+ cur_height = show_heights[i]
+ pad_height = max_height - cur_height
+ pad_top, pad_bottom = to_2tuple(pad_height // 2)
+ # handle instance that the pad_height is an odd number
+ if pad_height % 2 == 1:
+ pad_top = pad_top + 1
+ pad_bottom += text_height * 3 # keep pxs to put step information text
+ pad_left, pad_right = to_2tuple(pic_horizontal_gap)
+ # make border
+ img = cv2.copyMakeBorder(
+ img,
+ pad_top,
+ pad_bottom,
+ pad_left,
+ pad_right,
+ cv2.BORDER_CONSTANT,
+ value=(255, 255, 255))
+ # put transform phase information in the bottom
+ imgs[i] = cv2.putText(
+ img=img,
+ text=steps[i],
+ org=(pic_horizontal_gap, max_height + text_height // 2),
+ fontFace=cv2.FONT_HERSHEY_TRIPLEX,
+ fontScale=font_size,
+ color=(255, 0, 0),
+ lineType=1)
+ # put image size information in the bottom
+ imgs[i] = cv2.putText(
+ img=img,
+ text=str(ori_shapes[i]),
+ org=(pic_horizontal_gap, max_height + int(text_height * 1.5)),
+ fontFace=cv2.FONT_HERSHEY_TRIPLEX,
+ fontScale=font_size,
+ color=(255, 0, 0),
+ lineType=1)
+
+ # Height alignment for concatenating
+ board = np.concatenate(imgs, axis=1)
+ return board
+
+
+def adaptive_size(image, min_edge_length, max_edge_length, src_shape=None):
+ """rescale image if image is too small to put text like cifar."""
+ assert min_edge_length >= 0 and max_edge_length >= 0
+ assert max_edge_length >= min_edge_length
+ src_shape = image.shape if src_shape is None else src_shape
+ image_h, image_w, _ = src_shape
+
+ if image_h < min_edge_length or image_w < min_edge_length:
+ image = mmcv.imrescale(
+ image, min(min_edge_length / image_h, min_edge_length / image_h))
+ if image_h > max_edge_length or image_w > max_edge_length:
+ image = mmcv.imrescale(
+ image, max(max_edge_length / image_h, max_edge_length / image_w))
+ return image
+
+
+def get_display_img(args, item, pipelines):
+ """get image to display."""
+ # srcs picture could be in RGB or BGR order due to different backends.
+ if args.bgr2rgb:
+ item['img'] = mmcv.bgr2rgb(item['img'])
+ src_image = item['img'].copy()
+ pipeline_images = [src_image]
+
+ # get intermediate images through pipelines
+ if args.mode in ['transformed', 'concat', 'pipeline']:
+ for pipeline in pipelines.values():
+ item = pipeline(item)
+ trans_image = copy.deepcopy(item['img'])
+ trans_image = np.ascontiguousarray(trans_image, dtype=np.uint8)
+ pipeline_images.append(trans_image)
+
+ # concatenate images to be showed according to mode
+ if args.mode == 'original':
+ image = prepare_imgs(args, [src_image], ['src'])
+ elif args.mode == 'transformed':
+ image = prepare_imgs(args, [pipeline_images[-1]], ['transformed'])
+ elif args.mode == 'concat':
+ steps = ['src', 'transformed']
+ image = prepare_imgs(args, [pipeline_images[0], pipeline_images[-1]],
+ steps)
+ elif args.mode == 'pipeline':
+ steps = ['src'] + list(pipelines.keys())
+ image = prepare_imgs(args, pipeline_images, steps)
+
+ return image
+
+
+def main():
+ args = parse_args()
+ wind_w, wind_h = args.window_size.split('*')
+ wind_w, wind_h = int(wind_w), int(wind_h) # showing windows size
+ cfg = retrieve_data_cfg(args.config, args.skip_type, args.cfg_options,
+ args.phase)
+
+ dataset, pipelines = build_dataset_pipelines(cfg, args.phase)
+ CLASSES = dataset.CLASSES
+ display_number = min(args.number, len(dataset))
+ progressBar = ProgressBar(display_number)
+
+ with vis.ImshowInfosContextManager(fig_size=(wind_w, wind_h)) as manager:
+ for i, item in enumerate(itertools.islice(dataset, display_number)):
+ image = get_display_img(args, item, pipelines)
+
+ # dist_path is None as default, means not saving pictures
+ dist_path = None
+ if args.output_dir:
+ # some datasets don't have filenames, such as cifar
+ src_path = item.get('filename', '{}.jpg'.format(i))
+ dist_path = os.path.join(args.output_dir, Path(src_path).name)
+
+ infos = dict(label=CLASSES[item['gt_label']])
+
+ ret, _ = manager.put_img_infos(
+ image,
+ infos,
+ font_size=20,
+ out_file=dist_path,
+ show=args.show,
+ **args.show_options)
+
+ progressBar.update()
+
+ if ret == 1:
+ print('\nMannualy interrupted.')
+ break
+
+
+if __name__ == '__main__':
+ main()
diff --git a/openmmlab_test/mmclassification-0.24.1/train.md b/openmmlab_test/mmclassification-0.24.1/train.md
new file mode 100644
index 0000000000000000000000000000000000000000..d9e451e4c4ba43441431a514defbf44d6217e037
--- /dev/null
+++ b/openmmlab_test/mmclassification-0.24.1/train.md
@@ -0,0 +1,101 @@
+# MMClassification算例测试
+
+## 测试前准备
+
+### 数据集准备
+
+使用ImageNet-pytorch数据集。
+
+### 环境部署
+
+```python
+yum install python3
+yum install libquadmath
+yum install numactl
+yum install openmpi3
+yum install glog
+yum install lmdb-libs
+yum install opencv-core
+yum install opencv
+yum install openblas-serial
+pip3 install --upgrade pip
+pip3 install opencv-python
+```
+
+### 安装python依赖包
+
+```python
+pip3 install torch-1.10.0a0+git2040069.dtk2210-cp37-cp37m-manylinux2014_x86_64.whl -i https://pypi.tuna.tsinghua.edu.cn/simple
+pip3 install torchvision-0.10.0a0+e04d001.dtk2210-cp37-cp37m-manylinux2014_x86_64.whl -i https://pypi.tuna.tsinghua.edu.cn/simple
+pip3 install mmcv_full-1.6.1+gitdebbc80.dtk2210-cp37-cp37m-manylinux2014_x86_64.whl -i https://pypi.tuna.tsinghua.edu.cn/simple
+mmcls 安装:
+cd mmclassification-0.24.1
+pip3 install -e .
+```
+
+注:测试不同版本的dtk,需安装对应版本的库whl包
+
+## ResNet18测试
+### 单精度测试
+
+### 单卡测试(单精度)
+
+```python
+./sing_test.sh configs/resnet/resnet18_b32x8_imagenet.py
+```
+#### 参数说明
+
+configs/_base_/datasets/imagenet_bs32.py 中batch_size=samples_per_gpu*卡数,性能计算方法:batch_size/time
+
+#### 性能关注:time
+
+### 多卡测试(单精度)
+#### 单机多卡训练
+
+1.pytorch单机多卡训练
+
+```python
+./tools/dist_train.sh configs/resnet/resnet18_b32x8_imagenet.py
+```
+2.mpirun单机多卡训练
+mpirun --allow-run-as-root --bind-to none -np 4 single_process.sh a03r3n15
+a03r3n15为master节点ip
+
+#### 多机多卡训练
+
+1.pytorch多机多卡训练
+在第一台机器上:
+NODES=2 NODE_RANK=0 PORT=12345 MASTER_ADDR=10.1.3.56 sh tools/dist_train.sh configs/resnet/resnet18_b32x8_imagenet.py 4
+在第二台机器上:
+NODES=2 NODE_RANK=1 PORT=12345 MASTER_ADDR=10.1.3.56 sh tools/dist_train.sh configs/resnet/resnet18_b32x8_imagenet.py 4
+
+2.mpirun多机多卡训练
+mpirun --allow-run-as-root --hostfile hostfile --bind-to none -np 4 single_process.sh a03r3n15
+a03r3n15为master节点ip
+
+hostfile 文件
+
+a03r3n15 slots=4
+
+e10r4n04 slots=4
+
+### 半精度测试
+修改configs文件,添加fp16 = dict(loss_scale=512.),单机多卡和多机多卡测试与单精度测试方法相同。
+
+### 其他模型测试
+
+其他模型的测试步骤和ResNet18相同,只需修改对应的config文件即可,下面列出相关模型对应的config文件列表:
+
+| 模型 | configs |
+| ------------- | ------------------------------------------------------------ |
+| ResNet34 | configs/resnet/resnet34_b32x8_imagenet.py |
+| ResNet50 | configs/resnet/resnet50_b32x8_imagenet.py |
+| ResNet152 | configs/resnet/resnet152_b32x8_imagenet.py |
+| Vgg11 | configs/vgg/vgg11_b32x8_imagenet.py |
+| Vgg16 | configs/vgg/vgg16_b32x8_imagenet.py |
+| SeresNet50 | configs/seresnet/seresnet50_b32x8_imagenet.py |
+| ResNext50 | configs/resnext/resnext50_32x4d_b32x8_imagenet.py |
+| MobileNet-v2 | configs/mobilenet_v2/mobilenet_v2_b32x8_imagenet.py |
+| ShuffleNet-v1 | configs/shufflenet_v1/shufflenet_v1_1x_b64x16_linearlr_bn_nowd_imagenet.py |
+| ShuffleNet-v2 | configs/shufflenet_v2/shufflenet_v2_1x_b64x16_linearlr_bn_nowd_imagenet.py |
+
diff --git a/openmmlab_test/mmclassification-speed-benchmark/.github/CONTRIBUTING.md b/openmmlab_test/mmclassification-speed-benchmark/.github/CONTRIBUTING.md
deleted file mode 100644
index 6f8399b8b65abadb5cbc76ed797c32eab3b2b394..0000000000000000000000000000000000000000
--- a/openmmlab_test/mmclassification-speed-benchmark/.github/CONTRIBUTING.md
+++ /dev/null
@@ -1,69 +0,0 @@
-# Contributing to OpenMMLab
-
-All kinds of contributions are welcome, including but not limited to the following.
-
-- Fixes (typo, bugs)
-- New features and components
-
-## Workflow
-
-1. fork and pull the latest OpenMMLab repository (mmclassification)
-3. checkout a new branch (do not use master branch for PRs)
-4. commit your changes
-5. create a PR
-
-Note: If you plan to add some new features that involve large changes, it is encouraged to open an issue for discussion first.
-
-## Code style
-
-### Python
-
-We adopt [PEP8](https://www.python.org/dev/peps/pep-0008/) as the preferred code style.
-
-We use the following tools for linting and formatting:
-
-- [flake8](http://flake8.pycqa.org/en/latest/): A wrapper around some linter tools.
-- [yapf](https://github.com/google/yapf): A formatter for Python files.
-- [isort](https://github.com/timothycrosley/isort): A Python utility to sort imports.
-- [markdownlint](https://github.com/markdownlint/markdownlint): A linter to check markdown files and flag style issues.
-- [docformatter](https://github.com/myint/docformatter): A formatter to format docstring.
-
-Style configurations of yapf and isort can be found in [setup.cfg](./setup.cfg).
-
-We use [pre-commit hook](https://pre-commit.com/) that checks and formats for `flake8`, `yapf`, `isort`, `trailing whitespaces`, `markdown files`,
-fixes `end-of-files`, `double-quoted-strings`, `python-encoding-pragma`, `mixed-line-ending`, sorts `requirments.txt` automatically on every commit.
-The config for a pre-commit hook is stored in [.pre-commit-config](./.pre-commit-config.yaml).
-
-After you clone the repository, you will need to install initialize pre-commit hook.
-
-```shell
-pip install -U pre-commit
-```
-
-From the repository folder
-
-```shell
-pre-commit install
-```
-
-Try the following steps to install ruby when you encounter an issue on installing markdownlint
-
-```shell
-# install rvm
-curl -L https://get.rvm.io | bash -s -- --autolibs=read-fail
-[[ -s "$HOME/.rvm/scripts/rvm" ]] && source "$HOME/.rvm/scripts/rvm"
-rvm autolibs disable
-
-# install ruby
-rvm install 2.7.1
-```
-
-Or refer to [this repo](https://github.com/innerlee/setup) and take [`zzruby.sh`](https://github.com/innerlee/setup/blob/master/zzruby.sh) according its instruction.
-
-After this on every commit check code linters and formatter will be enforced.
-
->Before you create a PR, make sure that your code lints and is formatted by yapf.
-
-### C++ and CUDA
-
-We follow the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html).
diff --git a/openmmlab_test/mmclassification-speed-benchmark/.github/workflows/build.yml b/openmmlab_test/mmclassification-speed-benchmark/.github/workflows/build.yml
deleted file mode 100644
index 2f70db08370f90433fddf947f7f24ed1d1f22aec..0000000000000000000000000000000000000000
--- a/openmmlab_test/mmclassification-speed-benchmark/.github/workflows/build.yml
+++ /dev/null
@@ -1,98 +0,0 @@
-# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
-# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
-
-name: build
-
-on: [push, pull_request]
-
-jobs:
- lint:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - name: Set up Python 3.7
- uses: actions/setup-python@v1
- with:
- python-version: 3.7
- - name: Install pre-commit hook
- run: |
- pip install pre-commit
- pre-commit install
- - name: Linting
- run: pre-commit run --all-files
-
- build:
- runs-on: ubuntu-latest
- env:
- UBUNTU_VERSION: ubuntu1804
- strategy:
- matrix:
- python-version: [3.7]
- torch: [1.3.0, 1.5.0, 1.6.0, 1.7.0, 1.8.0]
- include:
- - torch: 1.3.0
- torchvision: 0.4.2
- - torch: 1.5.0
- torchvision: 0.6.0
- - torch: 1.6.0
- torchvision: 0.7.0
- - torch: 1.6.0
- torchvision: 0.7.0
- python-version: 3.6
- - torch: 1.6.0
- torchvision: 0.7.0
- python-version: 3.8
- - torch: 1.7.0
- torchvision: 0.8.1
- - torch: 1.7.0
- torchvision: 0.8.1
- python-version: 3.6
- - torch: 1.7.0
- torchvision: 0.8.1
- python-version: 3.8
- - torch: 1.8.0
- torchvision: 0.9.0
- - torch: 1.8.0
- torchvision: 0.9.0
- python-version: 3.6
- - torch: 1.8.0
- torchvision: 0.9.0
- python-version: 3.8
-
- steps:
- - uses: actions/checkout@v2
- - name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v2
- with:
- python-version: ${{ matrix.python-version }}
- - name: Install Pillow
- run: pip install Pillow==6.2.2
- if: ${{matrix.torchvision < 0.5}}
- - name: Install PyTorch
- run: pip install --use-deprecated=legacy-resolver torch==${{matrix.torch}}+cpu torchvision==${{matrix.torchvision}}+cpu -f https://download.pytorch.org/whl/torch_stable.html
- - name: Install MMCV
- run: |
- pip install --use-deprecated=legacy-resolver mmcv-full -f https://download.openmmlab.com/mmcv/dist/cpu/torch${{matrix.torch}}/index.html
- python -c 'import mmcv; print(mmcv.__version__)'
- - name: Install mmcls dependencies
- run: |
- pip install -r requirements.txt
- - name: Build and install
- run: |
- rm -rf .eggs
- pip install -e . -U
- - name: Run unittests and generate coverage report
- run: |
- coverage run --branch --source mmcls -m pytest tests/
- coverage xml
- coverage report -m --omit="mmcls/utils/*","mmcls/apis/*"
- # Only upload coverage report for python3.7 && pytorch1.5
- - name: Upload coverage to Codecov
- if: ${{matrix.torch == '1.5.0' && matrix.python-version == '3.7'}}
- uses: codecov/codecov-action@v1.0.10
- with:
- file: ./coverage.xml
- flags: unittests
- env_vars: OS,PYTHON
- name: codecov-umbrella
- fail_ci_if_error: false
diff --git a/openmmlab_test/mmclassification-speed-benchmark/.github/workflows/deploy.yml b/openmmlab_test/mmclassification-speed-benchmark/.github/workflows/deploy.yml
deleted file mode 100644
index 08936cb2cd314d65f9fecef77a724d617ee1420c..0000000000000000000000000000000000000000
--- a/openmmlab_test/mmclassification-speed-benchmark/.github/workflows/deploy.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-name: deploy
-
-on: push
-
-jobs:
- build-n-publish:
- runs-on: ubuntu-latest
- if: startsWith(github.event.ref, 'refs/tags')
- steps:
- - uses: actions/checkout@v2
- - name: Set up Python 3.7
- uses: actions/setup-python@v2
- with:
- python-version: 3.7
- - name: Build MMClassification
- run: |
- pip install wheel
- python setup.py sdist bdist_wheel
- - name: Publish distribution to PyPI
- run: |
- pip install twine
- twine upload dist/* -u __token__ -p ${{ secrets.pypi_password }}
diff --git a/openmmlab_test/mmclassification-speed-benchmark/.gitignore b/openmmlab_test/mmclassification-speed-benchmark/.gitignore
deleted file mode 100644
index fb6da3608a523cb652306c5773a54beb32d45a39..0000000000000000000000000000000000000000
--- a/openmmlab_test/mmclassification-speed-benchmark/.gitignore
+++ /dev/null
@@ -1,117 +0,0 @@
-# Byte-compiled / optimized / DLL files
-__pycache__/
-*.py[cod]
-*$py.class
-**/*.pyc
-
-# C extensions
-*.so
-
-# Distribution / packaging
-.Python
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-wheels/
-*.egg-info/
-.installed.cfg
-*.egg
-MANIFEST
-
-# PyInstaller
-# Usually these files are written by a python script from a template
-# before PyInstaller builds the exe, so as to inject date/other infos into it.
-*.manifest
-*.spec
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.coverage
-.coverage.*
-.cache
-nosetests.xml
-coverage.xml
-*.cover
-.hypothesis/
-.pytest_cache/
-
-# Translations
-*.mo
-*.pot
-
-# Django stuff:
-*.log
-local_settings.py
-db.sqlite3
-
-# Flask stuff:
-instance/
-.webassets-cache
-
-# Scrapy stuff:
-.scrapy
-
-# Sphinx documentation
-docs/_build/
-
-# PyBuilder
-target/
-
-# Jupyter Notebook
-.ipynb_checkpoints
-
-# pyenv
-.python-version
-
-# celery beat schedule file
-celerybeat-schedule
-
-# SageMath parsed files
-*.sage.py
-
-# Environments
-.env
-.venv
-env/
-venv/
-ENV/
-env.bak/
-venv.bak/
-
-# Spyder project settings
-.spyderproject
-.spyproject
-
-# Rope project settings
-.ropeproject
-
-# mkdocs documentation
-/site
-
-# mypy
-.mypy_cache/
-
-# custom
-data
-.vscode
-.idea
-*.pkl
-*.pkl.json
-*.log.json
-work_dirs/
-
-# Pytorch
-*.pth
diff --git a/openmmlab_test/mmclassification-speed-benchmark/.idea/.gitignore b/openmmlab_test/mmclassification-speed-benchmark/.idea/.gitignore
deleted file mode 100644
index 26d33521af10bcc7fd8cea344038eaaeb78d0ef5..0000000000000000000000000000000000000000
--- a/openmmlab_test/mmclassification-speed-benchmark/.idea/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
diff --git a/openmmlab_test/mmclassification-speed-benchmark/.idea/.name b/openmmlab_test/mmclassification-speed-benchmark/.idea/.name
deleted file mode 100644
index 29a9395e9634df149d8e2a0ecdfc4755d9152769..0000000000000000000000000000000000000000
--- a/openmmlab_test/mmclassification-speed-benchmark/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-resnet34_b32x8_fp16_imagenet.py
\ No newline at end of file
diff --git a/openmmlab_test/mmclassification-speed-benchmark/.idea/inspectionProfiles/profiles_settings.xml b/openmmlab_test/mmclassification-speed-benchmark/.idea/inspectionProfiles/profiles_settings.xml
deleted file mode 100644
index 105ce2da2d6447d11dfe32bfb846c3d5b199fc99..0000000000000000000000000000000000000000
--- a/openmmlab_test/mmclassification-speed-benchmark/.idea/inspectionProfiles/profiles_settings.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/openmmlab_test/mmclassification-speed-benchmark/.idea/mmclassification-speed-benchmark.iml b/openmmlab_test/mmclassification-speed-benchmark/.idea/mmclassification-speed-benchmark.iml
deleted file mode 100644
index 3ed51aeda3d5eea1dc7ceb0c422ace8fb3cb70f4..0000000000000000000000000000000000000000
--- a/openmmlab_test/mmclassification-speed-benchmark/.idea/mmclassification-speed-benchmark.iml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/openmmlab_test/mmclassification-speed-benchmark/.idea/modules.xml b/openmmlab_test/mmclassification-speed-benchmark/.idea/modules.xml
deleted file mode 100644
index c40979dc103db944dcd4b8fa81fd723a5d3a49de..0000000000000000000000000000000000000000
--- a/openmmlab_test/mmclassification-speed-benchmark/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/openmmlab_test/mmclassification-speed-benchmark/.idea/vcs.xml b/openmmlab_test/mmclassification-speed-benchmark/.idea/vcs.xml
deleted file mode 100644
index b2bdec2d71b6a5ce4ae49efc37516809c50e4d5e..0000000000000000000000000000000000000000
--- a/openmmlab_test/mmclassification-speed-benchmark/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/openmmlab_test/mmclassification-speed-benchmark/.pre-commit-config.yaml b/openmmlab_test/mmclassification-speed-benchmark/.pre-commit-config.yaml
deleted file mode 100644
index efa84b8cfaac8bdbb4699487975385167ed17e76..0000000000000000000000000000000000000000
--- a/openmmlab_test/mmclassification-speed-benchmark/.pre-commit-config.yaml
+++ /dev/null
@@ -1,50 +0,0 @@
-exclude: ^tests/data/
-repos:
- - repo: https://gitlab.com/pycqa/flake8.git
- rev: 3.8.3
- hooks:
- - id: flake8
- - repo: https://github.com/asottile/seed-isort-config
- rev: v2.2.0
- hooks:
- - id: seed-isort-config
- - repo: https://github.com/timothycrosley/isort
- rev: 4.3.21
- hooks:
- - id: isort
- - repo: https://github.com/pre-commit/mirrors-yapf
- rev: v0.30.0
- hooks:
- - id: yapf
- - repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v3.1.0
- hooks:
- - id: trailing-whitespace
- - id: check-yaml
- - id: end-of-file-fixer
- - id: requirements-txt-fixer
- - id: double-quote-string-fixer
- - id: check-merge-conflict
- - id: fix-encoding-pragma
- args: ["--remove"]
- - id: mixed-line-ending
- args: ["--fix=lf"]
- - repo: https://github.com/jumanjihouse/pre-commit-hooks
- rev: 2.1.4
- hooks:
- - id: markdownlint
- args: ["-r", "~MD002,~MD013,~MD029,~MD033,~MD034",
- "-t", "allow_different_nesting"]
- - repo: https://github.com/myint/docformatter
- rev: v1.3.1
- hooks:
- - id: docformatter
- args: ["--in-place", "--wrap-descriptions", "79"]
- # - repo: local
- # hooks:
- # - id: clang-format
- # name: clang-format
- # description: Format files with ClangFormat
- # entry: clang-format -style=google -i
- # language: system
- # files: \.(c|cc|cxx|cpp|cu|h|hpp|hxx|cuh|proto)$
diff --git a/openmmlab_test/mmclassification-speed-benchmark/.readthedocs.yml b/openmmlab_test/mmclassification-speed-benchmark/.readthedocs.yml
deleted file mode 100644
index 73ea4cb7e95530cd18ed94895ca38edd531f0d94..0000000000000000000000000000000000000000
--- a/openmmlab_test/mmclassification-speed-benchmark/.readthedocs.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-version: 2
-
-python:
- version: 3.7
- install:
- - requirements: requirements/docs.txt
- - requirements: requirements/readthedocs.txt
diff --git a/openmmlab_test/mmclassification-speed-benchmark/MANIFEST.in b/openmmlab_test/mmclassification-speed-benchmark/MANIFEST.in
deleted file mode 100644
index bf5a59ff64e4e2afb3f4017d2fcb228f3a41f462..0000000000000000000000000000000000000000
--- a/openmmlab_test/mmclassification-speed-benchmark/MANIFEST.in
+++ /dev/null
@@ -1,3 +0,0 @@
-include mmcls/model_zoo.yml
-recursive-include mmcls/configs *.py *.yml
-recursive-include mmcls/tools *.sh *.py
diff --git a/openmmlab_test/mmclassification-speed-benchmark/README.md b/openmmlab_test/mmclassification-speed-benchmark/README.md
deleted file mode 100644
index a0e5598c29934c75c870a8ed1d0ed40a8325a179..0000000000000000000000000000000000000000
--- a/openmmlab_test/mmclassification-speed-benchmark/README.md
+++ /dev/null
@@ -1,98 +0,0 @@
-
-
-
-
-[](https://github.com/open-mmlab/mmclassification/actions)
-[](https://mmclassification.readthedocs.io/en/latest/?badge=latest)
-[](https://codecov.io/gh/open-mmlab/mmclassification)
-[](https://github.com/open-mmlab/mmclassification/blob/master/LICENSE)
-
-## Introduction
-
-English | [简体中文](/README_zh-CN.md) | [模型的测试方法及测试步骤](train.md)
-
-MMClassification is an open source image classification toolbox based on PyTorch. It is
-a part of the [OpenMMLab](https://openmmlab.com/) project.
-
-Documentation: https://mmclassification.readthedocs.io/en/latest/
-
-
-
-### Major features
-
-- Various backbones and pretrained models
-- Bag of training tricks
-- Large-scale training configs
-- High efficiency and extensibility
-
-## License
-
-This project is released under the [Apache 2.0 license](LICENSE).
-
-## Changelog
-
-v0.12.0 was released in 3/6/2021.
-Please refer to [changelog.md](docs/changelog.md) for details and release history.
-
-## Benchmark and model zoo
-
-Results and models are available in the [model zoo](docs/model_zoo.md).
-
-Supported backbones:
-
-- [x] ResNet
-- [x] ResNeXt
-- [x] SE-ResNet
-- [x] SE-ResNeXt
-- [x] RegNet
-- [x] ShuffleNetV1
-- [x] ShuffleNetV2
-- [x] MobileNetV2
-- [x] MobileNetV3
-
-## Installation
-
-Please refer to [install.md](docs/install.md) for installation and dataset preparation.
-
-## Getting Started
-
-Please see [getting_started.md](docs/getting_started.md) for the basic usage of MMClassification. There are also tutorials for [finetuning models](docs/tutorials/finetune.md), [adding new dataset](docs/tutorials/new_dataset.md), [designing data pipeline](docs/tutorials/data_pipeline.md), and [adding new modules](docs/tutorials/new_modules.md).
-
-## Citation
-
-If you find this project useful in your research, please consider cite:
-
-```BibTeX
-@misc{2020mmclassification,
- title={OpenMMLab's Image Classification Toolbox and Benchmark},
- author={MMClassification Contributors},
- howpublished = {\url{https://github.com/open-mmlab/mmclassification}},
- year={2020}
-}
-```
-
-## Contributing
-
-We appreciate all contributions to improve MMClassification.
-Please refer to [CONTRUBUTING.md](.github/CONTRIBUTING.md) for the contributing guideline.
-
-## Acknowledgement
-
-MMClassification is an open source project that is contributed by researchers and engineers from various colleges and companies. We appreciate all the contributors who implement their methods or add new features, as well as users who give valuable feedbacks.
-We wish that the toolbox and benchmark could serve the growing research community by providing a flexible toolkit to reimplement existing methods and develop their own new classifiers.
-
-## Projects in OpenMMLab
-
-- [MMCV](https://github.com/open-mmlab/mmcv): OpenMMLab foundational library for computer vision.
-- [MMClassification](https://github.com/open-mmlab/mmclassification): OpenMMLab image classification toolbox and benchmark.
-- [MMDetection](https://github.com/open-mmlab/mmdetection): OpenMMLab detection toolbox and benchmark.
-- [MMDetection3D](https://github.com/open-mmlab/mmdetection3d): OpenMMLab's next-generation platform for general 3D object detection.
-- [MMSegmentation](https://github.com/open-mmlab/mmsegmentation): OpenMMLab semantic segmentation toolbox and benchmark.
-- [MMAction2](https://github.com/open-mmlab/mmaction2): OpenMMLab's next-generation action understanding toolbox and benchmark.
-- [MMTracking](https://github.com/open-mmlab/mmtracking): OpenMMLab video perception toolbox and benchmark.
-- [MMPose](https://github.com/open-mmlab/mmpose): OpenMMLab pose estimation toolbox and benchmark.
-- [MMEditing](https://github.com/open-mmlab/mmediting): OpenMMLab image and video editing toolbox.
-- [MMOCR](https://github.com/open-mmlab/mmocr): OpenMMLab toolbox for text detection, recognition and understanding.
-- [MMGeneration](https://github.com/open-mmlab/mmgeneration): OpenMMlab toolkit for generative models.
-
-
diff --git a/openmmlab_test/mmclassification-speed-benchmark/README_zh-CN.md b/openmmlab_test/mmclassification-speed-benchmark/README_zh-CN.md
deleted file mode 100644
index 2a080641f55f2a7c474b1150e1cc67a2d77d7b11..0000000000000000000000000000000000000000
--- a/openmmlab_test/mmclassification-speed-benchmark/README_zh-CN.md
+++ /dev/null
@@ -1,101 +0,0 @@
-